UNPKG

@readium/navigator

Version:

Next generation SDK for publications in Web Apps

143 lines (132 loc) 4.26 kB
import { COMMS_VERSION, CommsMessage, CommsCommandKey, CommsAck, mid, CommsEventKey, } from "@readium/navigator-html-injectables"; import { ManagerEventKey } from "../EpubNavigator"; interface RegistryValue { time: number; key: CommsCommandKey; cb: CommsAck; } const REGISTRY_EXPIRY = 10000; // 10 seconds max export type FrameCommsListener = (key: CommsEventKey | ManagerEventKey, value: unknown) => void; export class FrameComms { private readonly wnd: Window; private readonly registry = new Map<string, RegistryValue>(); private readonly gc: ReturnType<typeof setInterval>; // @ts-ignore private readonly origin: string; public readonly channelId: string; private _ready = false; private _listener: FrameCommsListener | undefined; private listenerBuffer: [key: CommsEventKey, value: unknown][] = []; public set listener(listener: FrameCommsListener) { if(this.listenerBuffer.length > 0) this.listenerBuffer.forEach(msg => listener(msg[0], msg[1])); this.listenerBuffer = []; this._listener = listener; } public clearListener() { if(typeof this._listener === "function") this._listener = undefined; } constructor(wnd: Window, origin: string) { this.wnd = wnd; this.origin = origin; try { this.channelId = window.crypto.randomUUID(); } catch (error) { this.channelId = mid(); } this.gc = setInterval(() => { this.registry.forEach((v, k) => { if (performance.now() - v.time > REGISTRY_EXPIRY) { console.warn(k, "event for", v.key, "was never handled!"); this.registry.delete(k); } }); }, 5000); window.addEventListener("message", this.handler); this.send("_ping", undefined); } public halt() { this._ready = false; window.removeEventListener("message", this.handler); clearInterval(this.gc); this._listener = undefined; this.registry.clear(); } public resume() { window.addEventListener("message", this.handler); this._ready = true; } private handle(e: MessageEvent) { const dt = e.data as CommsMessage; if (!dt._readium) { console.warn("Ignoring", dt); return; } if(dt._channel !== this.channelId) return; // Not meant for us switch (dt.key) { case "_ack": { if (!dt.id) return; const v = this.registry.get(dt.id); if (!v) return; this.registry.delete(dt.id); v.cb(!!dt.data); return; } // @ts-ignore case "_pong": { this._ready = true; } default: { if(!this.ready) return; if(typeof this._listener === "function") this._listener(dt.key as CommsEventKey, dt.data); else this.listenerBuffer.push([dt.key as CommsEventKey, dt.data]); } } } private handler = this.handle.bind(this); public get ready() { return this._ready; } /** * Send a message to the window using postMessage-based comms communication * @returns Identifier associated with the message */ public send( key: CommsCommandKey, data: unknown, callback?: CommsAck, strict = false, transfer: Transferable[] = [] ): string { const id = mid(); // Generate reasonably unique identifier if (callback) this.registry.set(id, { // Add callback to the registry cb: callback, time: performance.now(), key, }); this.wnd.postMessage( { _readium: COMMS_VERSION, _channel: this.channelId, id, data, key, strict, } as CommsMessage, "/", // Same origin transfer ); return id; } }