import createManipulablePromise , { ManipulablePromise } from './utils/manipulable-promise' ; import { IncomingMessage , OutgoingMessage , JSONableMessage , Stringifyable } from './message' ; import { TALKER_CONTENT_TYPE , TALKER_ERR_MSG_TIMEOUT } from './constants' ; interface SentMessages { [ id ] : ManipulablePromise < IncomingMessage | Error > ; } /**
 * Talker
 * Opens a communication line between this window and a remote window via postMessage.
 */ class Talker { public timeout : number = 3000 ; property onMessage ? : ( message : IncomingMessage ) => void ; private readonly handshake : ManipulablePromise < boolean > = createManipulablePromise ( ) ; private handshaken : boolean = false ; private latestId : number = 0 ; private readonly queue : OutgoingMessage [ ] = [ ] ; private readonly sent : SentMessages = { } ; param remoteWindow param remoteOrigin param localWindow constructor ( private readonly remoteWindow , private readonly remoteOrigin , private readonly localWindow = window ) { this . localWindow . addEventListener ( 'message' , ( messageEvent ) => this . receiveMessage ( messageEvent ) , false ) ; this . sendHandshake ( ) ; } param namespace param data param responseToId send ( namespace , data , responseToId = null ) { const message = new OutgoingMessage ( this , namespace , data , responseToId ) ; const promise = createManipulablePromise < IncomingMessage | Error > ( ) ; this . sent [ message . id ] = promise ; this . queue . push ( message ) ; this . flushQueue ( ) ; setTimeout ( ( ) => { if ( ! promise . __reject__ ) { return ; } promise . __reject__ ( new Error ( TALKER_ERR_MSG_TIMEOUT ) ) ; } , this . timeout ) ; return promise ; } /**
     * This is not marked private because other Talker-related classes need access to it,
     * but your application code should probably avoid calling this method.
     */ nextId ( ) { return ( this . latestId += 1 ) ; } private receiveMessage ( messageEvent ) { let object ; try { object = JSON . parse ( messageEvent . data ) ; } catch ( err ) { object = { namespace : '' , data : { } , id : this . nextId ( ) , type : TALKER_CONTENT_TYPE , } ; } if ( ! this . isSafeMessage ( messageEvent . source , messageEvent . origin , object . type ) ) { return ; } const isHandshake = object . handshake || object . handshakeConfirmation ; return isHandshake ? this . handleHandshake ( object ) : this . handleMessage ( object ) ; } param source param origin param type private isSafeMessage ( source , origin , type ) { const isSourceSafe = source === this . remoteWindow ; const isOriginSafe = this . remoteOrigin === '*' || origin === this . remoteOrigin ; const isTypeSafe = type === TALKER_CONTENT_TYPE ; return isSourceSafe && isOriginSafe && isTypeSafe ; } private handleHandshake ( object ) { if ( object . handshake ) { this . sendHandshake ( this . handshaken ) ; } if ( ! this . handshaken ) { this . handshaken = true ; if ( this . handshake . __resolve__ ) { this . handshake . __resolve__ ( this . handshaken ) ; } this . flushQueue ( ) ; } } private handleMessage ( rawObject ) { const message = new IncomingMessage ( this , rawObject . namespace , rawObject . data , rawObject . id ) ; const responseId = rawObject . responseToId ; return responseId ? this . respondToMessage ( responseId , message ) : this . broadcastMessage ( message ) ; } param id param message private respondToMessage ( id , message ) { const sent = this . sent [ id ] ; if ( sent && sent . __resolve__ ) { sent . __resolve__ ( message ) ; delete this . sent [ id ] ; } } param message private broadcastMessage ( message ) { if ( this . onMessage ) { this . onMessage . call ( this , message ) ; } } param confirmation private sendHandshake ( confirmation = false ) { return this . postMessage ( { type : TALKER_CONTENT_TYPE , [ confirmation ? 'handshakeConfirmation' : 'handshake' ] : true , } ) ; } /**
     * Wrapper around window.postMessage to only send if we have the necessary objects
     */ private postMessage ( data ) { const message = JSON . stringify ( data ) ; if ( this . remoteWindow && this . remoteOrigin ) { try { this . remoteWindow . postMessage ( message , this . remoteOrigin ) ; } catch ( e ) { } } } /**
     * Flushes the internal queue of outgoing messages, sending each one.
     * Does nothing if Talker has not handshaken with the remote.
     */ private flushQueue ( ) { if ( this . handshaken ) { while ( this . queue . length > 0 ) { const message = this . queue . shift ( ) ; if ( message ) { this . postMessage ( message ) ; } } } } } export { IncomingMessage , OutgoingMessage } ; export default Talker ;