declare global { interface Window { msCrypto : Crypto ; } } interface IDeferred { resolve : < T > ( value ? : T | Promise < T > ) => void ; reject : < T > ( error : T ) => void ; promise : Promise < any > ; } interface IDeferredCache { [ messageId ] : IDeferred ; } export interface ITrackingProperties { id : string ; } export interface IAddTrackingProperties { < T > ( message , trackingProperties ) : T ; } export interface IGetTrackingProperties { ( message ) : ITrackingProperties ; } export interface IProcessTrackingProperties { addTrackingProperties : IAddTrackingProperties ; getTrackingProperties : IGetTrackingProperties ; } export interface IIsErrorMessage { ( message ) : boolean ; } export interface IMessageHandler { test ( message ) : boolean ; handle ( message ) : any ; } export interface IWindowPostMessageProxyOptions { receiveWindow ? : Window ; processTrackingProperties ? : IProcessTrackingProperties ; isErrorMessage ? : IIsErrorMessage ; name ? : string ; logMessages ? : boolean ; eventSourceOverrideWindow ? : Window ; suppressWarnings ? : boolean ; } export class WindowPostMessageProxy { static defaultAddTrackingProperties < T > ( message , trackingProperties ) { ( < any > message ) [ WindowPostMessageProxy . messagePropertyName ] = trackingProperties ; return message ; } static defaultGetTrackingProperties ( message ) { return message [ WindowPostMessageProxy . messagePropertyName ] ; } static defaultIsErrorMessage ( message ) { return ! ! message . error ; } private static messagePropertyName = "windowPostMessageProxy" ; /**
     * Utility to create a deferred object.
     */ private static createDeferred ( ) { const deferred = { resolve : null , reject : null , promise : null } ; const promise = new Promise ( ( resolve , reject ) => { deferred . resolve = resolve ; deferred . reject = reject ; } ) ; deferred . promise = promise ; return deferred ; } /**
     * Utility to generate random sequence of characters used as tracking id for promises.
     */ private static createRandomString ( ) { let cryptoObj = window . crypto || window . msCrypto ; let randomValueArray = new Uint32Array ( 1 ) ; cryptoObj . getRandomValues ( randomValueArray ) ; return randomValueArray [ 0 ] . toString ( 36 ) . substring ( 1 ) ; } private logMessages : boolean ; private name : string ; private addTrackingProperties : IAddTrackingProperties ; private getTrackingProperties : IGetTrackingProperties ; private isErrorMessage : IIsErrorMessage ; private receiveWindow : Window ; private pendingRequestPromises : IDeferredCache = { } ; private handlers : IMessageHandler [ ] ; private windowMessageHandler : ( e : MessageEvent ) => any ; private eventSourceOverrideWindow : Window ; private suppressWarnings : boolean ; constructor ( options = { processTrackingProperties : { addTrackingProperties : WindowPostMessageProxy . defaultAddTrackingProperties , getTrackingProperties : WindowPostMessageProxy . defaultGetTrackingProperties } , isErrorMessage : WindowPostMessageProxy . defaultIsErrorMessage , receiveWindow : window , name : WindowPostMessageProxy . createRandomString ( ) } ) { this . addTrackingProperties = ( options . processTrackingProperties && options . processTrackingProperties . addTrackingProperties ) || WindowPostMessageProxy . defaultAddTrackingProperties ; this . getTrackingProperties = ( options . processTrackingProperties && options . processTrackingProperties . getTrackingProperties ) || WindowPostMessageProxy . defaultGetTrackingProperties ; this . isErrorMessage = options . isErrorMessage || WindowPostMessageProxy . defaultIsErrorMessage ; this . receiveWindow = options . receiveWindow || window ; this . name = options . name || WindowPostMessageProxy . createRandomString ( ) ; this . logMessages = options . logMessages || false ; this . eventSourceOverrideWindow = options . eventSourceOverrideWindow ; this . suppressWarnings = options . suppressWarnings || false ; if ( this . logMessages ) { console . log ( `new WindowPostMessageProxy created with name: ${ this . name } receiving on window: ${ this . receiveWindow . document . title }` ) ; } this . handlers = [ ] ; this . windowMessageHandler = ( event ) => this . onMessageReceived ( event ) ; this . start ( ) ; } /**
     * Adds handler.
     * If the first handler whose test method returns true will handle the message and provide a response.
     */ addHandler ( handler ) { this . handlers . push ( handler ) ; } /**
     * Removes handler.
     * The reference must match the original object that was provided when adding the handler.
     */ removeHandler ( handler ) { const handlerIndex = this . handlers . indexOf ( handler ) ; if ( handlerIndex === - 1 ) { throw new Error ( `You attempted to remove a handler but no matching handler was found.` ) ; } this . handlers . splice ( handlerIndex , 1 ) ; } /**
     * Start listening to message events.
     */ start ( ) { this . receiveWindow . addEventListener ( 'message' , this . windowMessageHandler ) ; } /**
     * Stops listening to message events.
     */ stop ( ) { this . receiveWindow . removeEventListener ( 'message' , this . windowMessageHandler ) ; } /**
     * Post message to target window with tracking properties added and save deferred object referenced by tracking id.
     */ postMessage < T > ( targetWindow , message ) { const trackingProperties = { id : WindowPostMessageProxy . createRandomString ( ) } ; this . addTrackingProperties ( message , trackingProperties ) ; if ( this . logMessages ) { console . log ( `${ this . name } Posting message:` ) ; console . log ( JSON . stringify ( message , null , '  ' ) ) ; } targetWindow . postMessage ( message , "*" ) ; const deferred = WindowPostMessageProxy . createDeferred ( ) ; this . pendingRequestPromises [ trackingProperties . id ] = deferred ; return deferred . promise ; } /**
     * Send response message to target window.
     * Response messages re-use tracking properties from a previous request message.
     */ private sendResponse ( targetWindow , message , trackingProperties ) { this . addTrackingProperties ( message , trackingProperties ) ; if ( this . logMessages ) { console . log ( `${ this . name } Sending response:` ) ; console . log ( JSON . stringify ( message , null , '  ' ) ) ; } targetWindow . postMessage ( message , "*" ) ; } /**
     * Message handler.
     */ private onMessageReceived ( event ) { if ( this . logMessages ) { console . log ( `${ this . name } Received message:` ) ; console . log ( `type: ${ event . type }` ) ; console . log ( JSON . stringify ( event . data , null , '  ' ) ) ; } let sendingWindow = this . eventSourceOverrideWindow || event . source ; let message = event . data ; if ( typeof message !== "object" ) { if ( ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }): Received message that was not an object. Discarding message` ) ; } return ; } let trackingProperties ; try { trackingProperties = this . getTrackingProperties ( message ) ; } catch ( e ) { if ( ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }): Error occurred when attempting to get tracking properties from incoming message:` , JSON . stringify ( message , null , '  ' ) , "Error: " , e ) ; } } let deferred ; if ( trackingProperties ) { deferred = this . pendingRequestPromises [ trackingProperties . id ] ; } if ( ! deferred ) { const handled = this . handlers . some ( handler => { let canMessageBeHandled = false ; try { canMessageBeHandled = handler . test ( message ) ; } catch ( e ) { if ( ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }): Error occurred when handler was testing incoming message:` , JSON . stringify ( message , null , '  ' ) , "Error: " , e ) ; } } if ( canMessageBeHandled ) { let responseMessagePromise ; try { responseMessagePromise = Promise . resolve ( handler . handle ( message ) ) ; } catch ( e ) { if ( ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }): Error occurred when handler was processing incoming message:` , JSON . stringify ( message , null , '  ' ) , "Error: " , e ) ; } responseMessagePromise = Promise . resolve ( ) ; } responseMessagePromise . then ( responseMessage => { if ( ! responseMessage ) { const warningMessage = `Handler for message: ${ JSON . stringify ( message , null , '  ' ) } did not return a response message. The default response message will be returned instead.` ; if ( ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }): ${ warningMessage }` ) ; } responseMessage = { warning : warningMessage } ; } this . sendResponse ( sendingWindow , responseMessage , trackingProperties ) ; } ) ; return true ; } } ) ; /**
             * TODO: Consider returning an error message if nothing handled the message.
             * In the case of the Report receiving messages all of them should be handled,
             * however, in the case of the SDK receiving messages it's likely it won't register handlers
             * for all events. Perhaps make this an option at construction time.
             */ if ( ! handled && ! this . suppressWarnings ) { console . warn ( `Proxy(${ this . name }) did not handle message. Handlers: ${ this . handlers . length }  Message: ${ JSON . stringify ( message , null , '' ) }.` ) ; } } else { /**
             * If error message reject promise,
             * Otherwise, resolve promise
             */ let isErrorMessage = true ; try { isErrorMessage = this . isErrorMessage ( message ) ; } catch ( e ) { console . warn ( `Proxy(${ this . name }) Error occurred when trying to determine if message is consider an error response. Message: ` , JSON . stringify ( message , null , '' ) , 'Error: ' , e ) ; } if ( isErrorMessage ) { deferred . reject ( message ) ; } else { deferred . resolve ( message ) ; } delete this . pendingRequestPromises [ trackingProperties . id ] ; } } }