|
| 1 | +/** |
| 2 | + * This is a utility class for interacting with a framed document using `postMessage`. |
| 3 | + */ |
| 4 | + |
| 5 | +import { AlBehaviorPromise } from "../promises"; |
| 6 | +import { AlStopwatch } from './al-stopwatch'; |
| 7 | + |
| 8 | +export class AlFrameMessageStream |
| 9 | +{ |
| 10 | + protected isReady = new AlBehaviorPromise( false ); |
| 11 | + protected container:HTMLElement; |
| 12 | + protected handlers:{[messageType:string]:{(data:any,rawEvent?:any,stream?:AlFrameMessageStream):void}} = {}; |
| 13 | + |
| 14 | + /** |
| 15 | + * Constructor |
| 16 | + * |
| 17 | + * @param frameURI: where the iframe should be pointed to |
| 18 | + * @param waitForMessage: if truthy, the `start()` method will not resolve until the first message is received from the frame. |
| 19 | + * @param messagePrefix: if provided, only messages whose bodies include a `type` property starting with this value will be handled. |
| 20 | + */ |
| 21 | + constructor( public frameURI:string, |
| 22 | + public waitForMessage?:boolean, |
| 23 | + public messagePrefix?:string ) { |
| 24 | + } |
| 25 | + |
| 26 | + public async start() { |
| 27 | + if ( document && document.body && typeof( document.body.appendChild ) === 'function' ) { |
| 28 | + document.body.appendChild( this.render() ); |
| 29 | + } else { |
| 30 | + throw new Error(`Cannot start frame message stream to [${this.frameURI}] without access to DOM` ); |
| 31 | + } |
| 32 | + window.addEventListener( "message", this.onReceiveMessage, false ); |
| 33 | + if ( this.waitForMessage ) { |
| 34 | + await this.ready(); |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + public async ready() { |
| 39 | + await this.isReady; |
| 40 | + } |
| 41 | + |
| 42 | + public stop() { |
| 43 | + document.body.removeChild( this.container ); |
| 44 | + window.removeEventListener( "message", this.onReceiveMessage); |
| 45 | + } |
| 46 | + |
| 47 | + public on( messageType:string, handler:{(data:any,rawEvent?:any,stream?:AlFrameMessageStream):void} ):AlFrameMessageStream { |
| 48 | + if ( this.messagePrefix ) { |
| 49 | + messageType = `${this.messagePrefix}.${messageType}`; |
| 50 | + } |
| 51 | + this.handlers[messageType] = handler; |
| 52 | + return this; |
| 53 | + } |
| 54 | + |
| 55 | + private onReceiveMessage = ( event:any ):void => { |
| 56 | + if ( ! this.frameURI.startsWith( event.origin ) ) { |
| 57 | + return; |
| 58 | + } |
| 59 | + |
| 60 | + if ( ! event.data || typeof( event.data.type ) !== 'string' ) { |
| 61 | + return; |
| 62 | + } |
| 63 | + |
| 64 | + if ( this.messagePrefix && ! event.data.type.startsWith( this.messagePrefix ) ) { |
| 65 | + return; |
| 66 | + } |
| 67 | + |
| 68 | + this.isReady.resolve( true ); |
| 69 | + |
| 70 | + if ( event.data.type in this.handlers ) { |
| 71 | + this.handlers[event.data.type]( event.data, event, this ); |
| 72 | + } else { |
| 73 | + console.log(`Notice: received unhandled message of type '${event.data.type}' from ${event.origin}` ); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + private render():DocumentFragment { |
| 78 | + const fragment = document.createDocumentFragment(); |
| 79 | + this.container = document.createElement( "div" ); |
| 80 | + this.container.setAttribute("class", "message-frame" ); |
| 81 | + fragment.appendChild( this.container ); |
| 82 | + this.container.innerHTML = `<iframe frameborder="0" src="${this.frameURI}" style="width:1px;height:1px;position:absolute;left:-1px;top:-1px;"></iframe>`; |
| 83 | + return fragment; |
| 84 | + } |
| 85 | + |
| 86 | + |
| 87 | +} |
0 commit comments