import { ConnectIframe } from './ConnectIframe'; import { logger } from '../../adaptors/logger/LoggerAdaptor'; import { analytics } from '../../adaptors/analytics/AnalyticsAdaptor'; import { IframeExtension } from '../../adaptors/framework/ACJSFrameworkAdaptor'; import { allowedStrings, AnalyticsCategories, AnalyticsActions } from '../../adaptors/analytics/AnalyticsConstants'; /* * Same as in ACJS. See loading_indicator.js. */ const DEFAULT_APP_LOADING_TIMEOUT:number = 12000; let connectIframes:ConnectIframe[] = []; let registerHostCallbacks:boolean = true; const hostIframeEstablishedCallback = (data: IframeExtension) => { const connectIframe:ConnectIframe = connectIframes.find(connectIframe => data.extension.id === connectIframe.getId()) as ConnectIframe; if (connectIframe) { const manager:IFrameLifecycleEventManager|undefined = connectIframe.getIFrameLifecycleEventManager(); if (manager) { manager.iframeEstablishedCallback(data); } } } const hostIframeUnloadCallback = (data: IframeExtension) => { const connectIframe:ConnectIframe = connectIframes.find(connectIframe => data.extension.id === connectIframe.getId()) as ConnectIframe; if (connectIframe) { const manager:IFrameLifecycleEventManager|undefined = connectIframe.getIFrameLifecycleEventManager(); if (manager) { manager.iframeUnloadCallback(data); } } } export default class IFrameLifecycleEventManager { timeoutHandle: number; creationTimeMs: number; connectIframe: ConnectIframe; iframeEstablishedCallbacks: { (data: IframeExtension): void }[]; constructor(connectIframe: ConnectIframe) { this.creationTimeMs = (new Date()).getTime(); this.connectIframe = connectIframe; connectIframes.push(connectIframe); if (registerHostCallbacks) { this.connectIframe.props.connectHost.onIframeEstablished(hostIframeEstablishedCallback); this.connectIframe.props.connectHost.onIframeUnload(hostIframeUnloadCallback); registerHostCallbacks = false; } this.startTimeoutDetectionProcessing(); if (this.connectIframe.props.connectIframeProvider.handleIframeLoadingStarted) { this.connectIframe.props.connectIframeProvider.handleIframeLoadingStarted(this.connectIframe.props.appKey); } } reset = () => { connectIframes = []; registerHostCallbacks = true; } unregister = (connectIframe:ConnectIframe) => { var index = connectIframes.indexOf(connectIframe); if (index > -1) { connectIframes.splice(index, 1); } this.clearTimeoutDetectionProcessing(); } iframeEstablishedCallback = (data:IframeExtension) => { logger.debug('Established iframe for add-on ', data.extension.addon_key, data); if (this.connectIframe.props.connectIframeProvider.handleIframeLoadingComplete) { this.connectIframe.props.connectIframeProvider.handleIframeLoadingComplete(data.extension.addon_key); } const loadTime = (new Date()).getTime(); const durationToLoadMs = loadTime - this.creationTimeMs; const payload = { hostFrameOffset: analytics.dangerouslyCreateSafeString( (this.connectIframe.props.options.hostFrameOffset || 1).toString()), durationToLoadMs: analytics.dangerouslyCreateSafeString( durationToLoadMs.toString()) }; analytics.trigger( AnalyticsCategories.iframe, analytics.markAsSafe(...allowedStrings)(AnalyticsActions.established), analytics.dangerouslyCreateSafeString(data.extension.addon_key), payload ); this.connectIframe.iframeEstablishedCallback(); this.clearTimeoutDetectionProcessing(); } iframeUnloadCallback = (data:IframeExtension) => { logger.debug('Unloaded iframe for add-on ', data.extension.id, data); if (this.connectIframe.props.connectIframeProvider.handleIframeUnload) { this.connectIframe.props.connectIframeProvider.handleIframeUnload(data.extension.id); } const unloadTime = (new Date()).getTime(); const sessionDurationMs = unloadTime - this.creationTimeMs; const payload = { hostFrameOffset: analytics.dangerouslyCreateSafeString( (this.connectIframe.props.options.hostFrameOffset || 1).toString()), sessionDurationMs: analytics.dangerouslyCreateSafeString( sessionDurationMs.toString()) }; analytics.trigger( AnalyticsCategories.iframe, analytics.markAsSafe(...allowedStrings)(AnalyticsActions.close), analytics.dangerouslyCreateSafeString(data.extension.id), payload ); } startTimeoutDetectionProcessing = () => { logger.debug('Starting iframe timed out processing for add-on ', this.connectIframe.props.appKey); const timeoutMilliseconds = this.getLoadingTimeout(); this.timeoutHandle = window.setTimeout(() => { logger.warn('Add-on iframe timed out for add-on ', this.connectIframe.props.appKey); this.timeoutHandle = 0; if (this.connectIframe.props.connectIframeProvider.handleIframeLoadTimeout) { this.connectIframe.props.connectIframeProvider.handleIframeLoadTimeout( this.connectIframe.props.appKey, this.connectIframe.iframeFailedToLoadCallback.bind(this) ); } const payload = { hostFrameOffset: analytics.dangerouslyCreateSafeString( (this.connectIframe.props.options.hostFrameOffset || 1).toString()) }; analytics.trigger( AnalyticsCategories.iframe, analytics.markAsSafe(...allowedStrings)(AnalyticsActions.timeout), analytics.dangerouslyCreateSafeString(this.connectIframe.props.appKey), payload ); this.connectIframe.iframeTimeoutCallback(); }, timeoutMilliseconds); } getLoadingTimeout = () => { const providedTimeoutMilliseconds = this.connectIframe.props.connectIframeProvider.getLoadingTimeoutMilliseconds ? this.connectIframe.props.connectIframeProvider.getLoadingTimeoutMilliseconds(this.connectIframe.props.appKey) : DEFAULT_APP_LOADING_TIMEOUT; const timeoutMilliseconds = providedTimeoutMilliseconds && providedTimeoutMilliseconds > 0 ? providedTimeoutMilliseconds : DEFAULT_APP_LOADING_TIMEOUT; return timeoutMilliseconds; } clearTimeoutDetectionProcessing = () => { if (this.timeoutHandle) { logger.debug('Clearing iframe timed out timer for add-on ', this.connectIframe.props.appKey); window.clearTimeout(this.timeoutHandle); this.timeoutHandle = 0; } } }