"use strict"; import bridge from "../lib/bridge"; import logger from "../lib/logger"; import { Message, Port } from "../messaging"; import { TypedEventTarget } from "../lib/TypedEventTarget"; import { Receiver, ReceiverStatus } from "../types"; interface EventMap { "serviceUp": Receiver; "serviceDown": { id: string }; "statusUpdate": { id: string, status: ReceiverStatus }; } // tslint:disable-next-line:new-parens export default new class StatusManager extends TypedEventTarget { private bridgePort: (Port | null) = null; private receivers = new Map(); constructor () { super(); // Bind listeners this.onBridgePortMessage = this.onBridgePortMessage.bind(this); this.onBridgePortDisconnect = this.onBridgePortDisconnect.bind(this); } public async init () { if (!this.bridgePort) { this.bridgePort = await this.createBridgePort(); } } public *getReceivers () { for (const [, receiver ] of this.receivers) { if (receiver.status && receiver.status.application && receiver.status.volume) { yield receiver; } } } public async stopReceiverApp (receiver: Receiver) { if (!this.bridgePort) { return; } this.bridgePort.postMessage({ subject: "bridge:/stopReceiverApp" , data: { receiver } }); } private async createBridgePort () { const bridgePort = await bridge.connect(); bridgePort.onMessage.addListener(this.onBridgePortMessage); bridgePort.onDisconnect.addListener(this.onBridgePortDisconnect); bridgePort.postMessage({ subject: "bridge:/initialize" , data: { shouldWatchStatus: true } }); return bridgePort; } /** * Handles incoming bridge status messages, manages the * receiver list, and dispatches events. */ private onBridgePortMessage (message: Message) { switch (message.subject) { case "main:/serviceUp": { const { data: receiver } = message; this.receivers.set(receiver.id, receiver); this.dispatchEvent(new CustomEvent("serviceUp", { detail: receiver })); break; } case "main:/serviceDown": { const { data: { id }} = message; if (this.receivers.has(id)) { this.receivers.delete(id); } this.dispatchEvent(new CustomEvent("serviceDown", { detail: { id } })); break; } case "main:/receiverStatus": { const { data: { id, status }} = message; const receiver = this.receivers.get(id); if (!receiver) { throw logger.error(`Could not find receiver (${id}) specified in status message.`); } // Merge with existing this.receivers.set(id, { ...receiver , status: { ...receiver.status , ...status } }); this.dispatchEvent(new CustomEvent("statusUpdate", { detail: { id, status } })); } } } /** * Runs once the status bridge has disconnected. Sends * serviceDown messages for all receivers to all shims to * update receiver availability, then clears the receiver * list. * * Attempts to reinitialize the status bridge after 10 * seconds. If it fails immediately, this handler will be * triggered again and the timer is reset for another 10 * seconds. */ private onBridgePortDisconnect () { for (const [, receiver] of this.receivers) { const serviceDownEvent = new CustomEvent("serviceDown", { detail: { id: receiver.id } }); this.dispatchEvent(serviceDownEvent); } // Cleanup this.receivers.clear(); if (this.bridgePort) { this.bridgePort.onDisconnect.removeListener( this.onBridgePortDisconnect); this.bridgePort.onMessage.removeListener( this.onBridgePortMessage); this.bridgePort = null; } window.setTimeout(async () => { this.bridgePort = await this.createBridgePort(); }, 10000); } };