From 2eeaff4c15c01f44db78e668cefed3f766e8391e Mon Sep 17 00:00:00 2001 From: hensm Date: Tue, 18 Feb 2020 07:37:20 +0000 Subject: [PATCH] Add typed messaging --- app/src/bridge/index.ts | 7 +- ext/src/background/ShimManager.ts | 23 +- ext/src/background/StatusManager.ts | 49 ++-- ext/src/background/background.ts | 5 +- .../NativeReceiverSelector.ts | 116 ++++------ .../receiverSelector/PopupReceiverSelector.ts | 82 ++++--- .../receiverSelector/ReceiverSelector.ts | 2 +- .../{typedEvents.ts => TypedEventTarget.ts} | 2 +- ext/src/lib/TypedPort.ts | 59 +++++ .../{typedStorage.ts => TypedStorageArea.ts} | 33 --- ext/src/lib/bridge.ts | 12 +- ext/src/lib/messaging.ts | 213 ++++++++++++++++++ ext/src/lib/options.ts | 4 +- ext/src/shim/cast/classes/Session.ts | 27 +-- ext/src/shim/cast/index.ts | 2 +- ext/src/shim/cast/media/classes/Media.ts | 13 +- ext/src/shim/contentBridge.ts | 5 +- ext/src/shim/eventMessageChannel.ts | 2 +- ext/src/shim/export.ts | 2 +- 19 files changed, 431 insertions(+), 227 deletions(-) rename ext/src/lib/{typedEvents.ts => TypedEventTarget.ts} (95%) create mode 100644 ext/src/lib/TypedPort.ts rename ext/src/lib/{typedStorage.ts => TypedStorageArea.ts} (58%) create mode 100644 ext/src/lib/messaging.ts diff --git a/app/src/bridge/index.ts b/app/src/bridge/index.ts index c75db25..8bebef7 100755 --- a/app/src/bridge/index.ts +++ b/app/src/bridge/index.ts @@ -16,7 +16,6 @@ import { DecodeTransform , EncodeTransform } from "../transforms"; import { ReceiverStatus } from "./castTypes"; - import { Message, Receiver } from "./types"; import { __applicationName @@ -501,7 +500,7 @@ function initialize (options: InitializeOptions) { function onBrowserServiceUp (service: dnssd.Service) { sendMessage({ - subject: "shim:/serviceUp" + subject: "main:/serviceUp" , data: { host: service.addresses[0] , port: service.port @@ -513,7 +512,7 @@ function initialize (options: InitializeOptions) { function onBrowserServiceDown (service: dnssd.Service) { sendMessage({ - subject: "shim:/serviceDown" + subject: "main:/serviceDown" , data: { id: service.txt.id } @@ -533,7 +532,7 @@ function initialize (options: InitializeOptions) { listener.on("receiverStatus", (status: ReceiverStatus) => { const receiverStatusMessage: any = { - subject: "receiverStatus" + subject: "main:/receiverStatus" , data: { id , status: { diff --git a/ext/src/background/ShimManager.ts b/ext/src/background/ShimManager.ts index 597e1fa..4de2a61 100644 --- a/ext/src/background/ShimManager.ts +++ b/ext/src/background/ShimManager.ts @@ -3,11 +3,9 @@ import bridge from "../lib/bridge"; import loadSender from "../lib/loadSender"; import logger from "../lib/logger"; +import { Message, Port } from "../lib/messaging"; import options from "../lib/options"; -import { Message } from "../types"; - -import { getMediaTypesForPageUrl } from "../lib/utils"; import { ReceiverSelectionActionType , ReceiverSelectorMediaType } from "./receiverSelector"; @@ -17,11 +15,11 @@ import ReceiverSelectorManager import StatusManager from "./StatusManager"; -type Port = browser.runtime.Port | MessagePort; +type AnyPort = Port | MessagePort; export interface Shim { - bridgePort: browser.runtime.Port; - contentPort: Port; + bridgePort: Port; + contentPort: AnyPort; contentTabId?: number; contentFrameId?: number; requestedAppId?: string; @@ -48,7 +46,7 @@ export default new class ShimManager { } } - public async createShim (port: Port) { + public async createShim (port: AnyPort) { const shim = await (port instanceof MessagePort ? this.createShimFromBackground(port) : this.createShimFromContent(port)); @@ -74,20 +72,19 @@ export default new class ShimManager { this.activeShims.delete(shim); }); - shim.bridgePort.onMessage.addListener((message: Message) => { + shim.bridgePort.onMessage.addListener(message => { contentPort.postMessage(message); }); - contentPort.onmessage = ev => { - const message = ev.data as Message; - this.handleContentMessage(shim, message); - }; + contentPort.addEventListener("message", ev => { + this.handleContentMessage(shim, ev.data); + }); return shim; } private async createShimFromContent ( - contentPort: browser.runtime.Port): Promise { + contentPort: Port): Promise { if (contentPort.sender?.tab?.id === undefined || contentPort.sender?.frameId === undefined) { diff --git a/ext/src/background/StatusManager.ts b/ext/src/background/StatusManager.ts index 5658777..6282f24 100644 --- a/ext/src/background/StatusManager.ts +++ b/ext/src/background/StatusManager.ts @@ -2,44 +2,23 @@ import bridge from "../lib/bridge"; import logger from "../lib/logger"; +import { Message, Port } from "../lib/messaging"; -import { TypedEventTarget } from "../lib/typedEvents"; -import { Message, Receiver, ReceiverStatus } from "../types"; - - -interface ReceiverStatusMessage extends Message { - subject: "receiverStatus"; - data: { - id: string; - status: ReceiverStatus; - }; -} - -interface ServiceDownMessage extends Message { - subject: "shim:/serviceDown"; - data: { - id: string; - }; -} - -interface ServiceUpMessage extends Message { - subject: "shim:/serviceUp"; - data: Receiver; -} - +import { TypedEventTarget } from "../lib/TypedEventTarget"; +import { Receiver, ReceiverStatus } from "../types"; interface EventMap { - "serviceUp": ServiceUpMessage["data"]; - "serviceDown": ServiceDownMessage["data"]; - "statusUpdate": ReceiverStatusMessage["data"]; + "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: (browser.runtime.Port | null) = null; + private bridgePort: (Port | null) = null; private receivers = new Map(); constructor () { @@ -97,8 +76,8 @@ export default new class StatusManager */ private onBridgePortMessage (message: Message) { switch (message.subject) { - case "shim:/serviceUp": { - const { data: receiver } = (message as ServiceUpMessage); + case "main:/serviceUp": { + const { data: receiver } = message; this.receivers.set(receiver.id, receiver); const serviceUpEvent = new CustomEvent("serviceUp", { @@ -110,8 +89,8 @@ export default new class StatusManager break; } - case "shim:/serviceDown": { - const { data: { id }} = (message as ServiceDownMessage); + case "main:/serviceDown": { + const { data: { id }} = message; if (this.receivers.has(id)) { this.receivers.delete(id); @@ -126,10 +105,8 @@ export default new class StatusManager break; } - case "receiverStatus": { - const { data: { id, status }} - = (message as ReceiverStatusMessage); - + case "main:/receiverStatus": { + const { data: { id, status }} = message; const receiver = this.receivers.get(id); if (!receiver) { diff --git a/ext/src/background/background.ts b/ext/src/background/background.ts index d568094..3f05936 100755 --- a/ext/src/background/background.ts +++ b/ext/src/background/background.ts @@ -3,6 +3,7 @@ import defaultOptions from "../defaultOptions"; import loadSender from "../lib/loadSender"; import logger from "../lib/logger"; +import messaging from "../lib/messaging"; import options from "../lib/options"; import { getChromeUserAgent } from "../lib/userAgents"; @@ -617,9 +618,9 @@ async function init () { * established, pass it to createShim to handle the setup * and maintenance. */ - browser.runtime.onConnect.addListener(async port => { + messaging.onConnect.addListener(async port => { if (port.name === "shim") { - ShimManager.createShim(port); + ShimManager.createShim(port as any); } }); } diff --git a/ext/src/background/receiverSelector/NativeReceiverSelector.ts b/ext/src/background/receiverSelector/NativeReceiverSelector.ts index 55606e1..40f8692 100644 --- a/ext/src/background/receiverSelector/NativeReceiverSelector.ts +++ b/ext/src/background/receiverSelector/NativeReceiverSelector.ts @@ -3,11 +3,12 @@ import bridge from "../../lib/bridge"; import knownApps from "../../lib/knownApps"; import logger from "../../lib/logger"; +import { Message, Port } from "../../lib/messaging"; import options from "../../lib/options"; -import { TypedEventTarget } from "../../lib/typedEvents"; +import { TypedEventTarget } from "../../lib/TypedEventTarget"; import { getWindowCenteredProps } from "../../lib/utils"; -import { Message, Receiver } from "../../types"; +import { Receiver } from "../../types"; import ReceiverSelector, { ReceiverSelection @@ -17,27 +18,20 @@ import ReceiverSelector, { const _ = browser.i18n.getMessage; - -interface NativeReceiverSelectorSelectedMessage extends Message { - subject: "main:/receiverSelector/selected"; - data: ReceiverSelection; -} - -interface NativeReceiverSelectorErrorMessage extends Message { - subject: "main:/receiverSelector/error"; - data: string; -} - - // TODO: Figure out lifetime properly export default class NativeReceiverSelector extends TypedEventTarget implements ReceiverSelector { - private bridgePort: (browser.runtime.Port | null) = null; + private bridgePort: (Port | null) = null; private wasReceiverSelected: boolean = false; private _isOpen: boolean = false; + constructor () { + super(); + this.onBridgePortMessage = this.onBridgePortMessage.bind(this); + } + get isOpen () { return this._isOpen; } @@ -50,31 +44,7 @@ export default class NativeReceiverSelector this.bridgePort = await bridge.connect(); - this.bridgePort.onMessage.addListener((message: Message) => { - switch (message.subject) { - case "main:/receiverSelector/selected": { - this.onBridgePortMessageSelected( - message as NativeReceiverSelectorSelectedMessage); - break; - } - case "main:/receiverSelector/error": { - this.onBridgePortMessageError( - message as NativeReceiverSelectorErrorMessage); - break; - } - case "main:/receiverSelector/close": { - this.onBridgePortMessageClose(); - break; - } - case "main:/receiverSelector/stop": { - this.dispatchEvent(new CustomEvent("stop", { - detail: message.data - })); - break; - } - } - }); - + this.bridgePort.onMessage.addListener(this.onBridgePortMessage); this.bridgePort.onDisconnect.addListener(() => { this.bridgePort = null; this.wasReceiverSelected = false; @@ -128,40 +98,46 @@ export default class NativeReceiverSelector this._isOpen = false; } + private async onBridgePortMessage (message: Message) { + switch (message.subject) { + case "main:/receiverSelector/selected": { + this.wasReceiverSelected = true; + this.dispatchEvent(new CustomEvent("selected", { + detail: message.data + })); - private async onBridgePortMessageSelected ( - message: NativeReceiverSelectorSelectedMessage) { + if (!(await options.get("receiverSelectorWaitForConnection"))) { + this.close(); + } - this.wasReceiverSelected = true; + break; + } + case "main:/receiverSelector/error": { + logger.error("Native receiver selector error", message.data); + this.dispatchEvent(new CustomEvent("error")); + break; + } + case "main:/receiverSelector/close": { + if (!this.wasReceiverSelected) { + this.dispatchEvent(new CustomEvent("cancelled")); + } - this.dispatchEvent(new CustomEvent("selected", { - detail: message.data - })); + if (this.bridgePort) { + this.bridgePort.disconnect(); + } - if (!(await options.get("receiverSelectorWaitForConnection"))) { - this.close(); + this.bridgePort = null; + this.wasReceiverSelected = false; + this._isOpen = false; + + break; + } + case "main:/receiverSelector/stop": { + this.dispatchEvent(new CustomEvent("stop", { + detail: message.data + })); + break; + } } } - - private async onBridgePortMessageError ( - message: NativeReceiverSelectorErrorMessage) { - - logger.error("Native receiver selector error", message.data); - - this.dispatchEvent(new CustomEvent("error")); - } - - private async onBridgePortMessageClose () { - if (!this.wasReceiverSelected) { - this.dispatchEvent(new CustomEvent("cancelled")); - } - - if (this.bridgePort) { - this.bridgePort.disconnect(); - } - - this.bridgePort = null; - this.wasReceiverSelected = false; - this._isOpen = false; - } } diff --git a/ext/src/background/receiverSelector/PopupReceiverSelector.ts b/ext/src/background/receiverSelector/PopupReceiverSelector.ts index 80445f0..0e89d26 100644 --- a/ext/src/background/receiverSelector/PopupReceiverSelector.ts +++ b/ext/src/background/receiverSelector/PopupReceiverSelector.ts @@ -5,11 +5,12 @@ import ReceiverSelector, { , ReceiverSelectorMediaType } from "./ReceiverSelector"; import logger from "../../lib/logger"; +import messaging, { Port, Message } from "../../lib/messaging"; import options from "../../lib/options"; -import { TypedEventTarget } from "../../lib/typedEvents"; +import { TypedEventTarget } from "../../lib/TypedEventTarget"; import { getWindowCenteredProps, WindowCenteredProps } from "../../lib/utils"; -import { Message, Receiver } from "../../types"; +import { Receiver } from "../../types"; const POPUP_URL = browser.runtime.getURL("ui/popup/index.html"); @@ -20,7 +21,7 @@ export default class PopupReceiverSelector private windowId?: number; - private messagePort?: browser.runtime.Port; + private messagePort?: Port; private messagePortDisconnected?: boolean; private receivers?: Receiver[]; @@ -37,6 +38,7 @@ export default class PopupReceiverSelector super(); // Bind methods to pass to addListener + this.onConnect = this.onConnect.bind(this); this.onPopupMessage = this.onPopupMessage.bind(this); this.onWindowsRemoved = this.onWindowsRemoved.bind(this); this.onWindowsFocusChanged = this.onWindowsFocusChanged.bind(this); @@ -47,39 +49,7 @@ export default class PopupReceiverSelector * Handle incoming message channel connection from popup * window script. */ - browser.runtime.onConnect.addListener(port => { - // Don't pollute history - browser.history.deleteUrl({ url: POPUP_URL }); - - if (port.name !== "popup") { - return; - } - - // Disconnect existing port - if (this.messagePort) { - this.messagePort.disconnect(); - } - - this.messagePort = port; - this.messagePort.onMessage.addListener(this.onPopupMessage); - this.messagePort.onDisconnect.addListener(() => { - this.messagePortDisconnected = true; - }); - - this.messagePort.postMessage({ - subject: "popup:/sendRequestedAppId" - , data: { requestedAppId: this.requestedAppId } - }); - - this.messagePort.postMessage({ - subject: "popup:/populateReceiverList" - , data: { - receivers: this.receivers - , defaultMediaType: this.defaultMediaType - , availableMediaTypes: this.availableMediaTypes - } - }); - }); + messaging.onConnect.addListener(this.onConnect); } get isOpen () { @@ -162,6 +132,46 @@ export default class PopupReceiverSelector } } + private onConnect (port: Port) { + browser.history.deleteUrl({ url: POPUP_URL }); + + if (port.name !== "popup") { + return; + } + + if (this.messagePort) { + this.messagePort.disconnect(); + } + + this.messagePort = port; + this.messagePort.onMessage.addListener(this.onPopupMessage); + this.messagePort.onDisconnect.addListener(() => { + this.messagePortDisconnected = true; + }); + + if (!this.requestedAppId + || !this.receivers + || !this.defaultMediaType + || !this.availableMediaTypes) { + throw logger.error("Popup receiver data not found."); + } + + this.messagePort.postMessage({ + subject: "popup:/sendRequestedAppId" + , data: { requestedAppId: this.requestedAppId } + }); + + this.messagePort.postMessage({ + subject: "popup:/populateReceiverList" + , data: { + receivers: this.receivers + , defaultMediaType: this.defaultMediaType + , availableMediaTypes: this.availableMediaTypes + } + }); + + messaging.onConnect.removeListener(this.onConnect); + } /** * Handles popup messages. diff --git a/ext/src/background/receiverSelector/ReceiverSelector.ts b/ext/src/background/receiverSelector/ReceiverSelector.ts index 8f38839..9570073 100644 --- a/ext/src/background/receiverSelector/ReceiverSelector.ts +++ b/ext/src/background/receiverSelector/ReceiverSelector.ts @@ -1,6 +1,6 @@ "use strict"; -import { TypedEventTarget } from "../../lib/typedEvents"; +import { TypedEventTarget } from "../../lib/TypedEventTarget"; import { Receiver } from "../../types"; diff --git a/ext/src/lib/typedEvents.ts b/ext/src/lib/TypedEventTarget.ts similarity index 95% rename from ext/src/lib/typedEvents.ts rename to ext/src/lib/TypedEventTarget.ts index ef2f9b4..6773c94 100644 --- a/ext/src/lib/typedEvents.ts +++ b/ext/src/lib/TypedEventTarget.ts @@ -1,6 +1,6 @@ "use strict"; -export interface TypedEvents { +interface TypedEvents { [key: string]: any; } diff --git a/ext/src/lib/TypedPort.ts b/ext/src/lib/TypedPort.ts new file mode 100644 index 0000000..830cfe0 --- /dev/null +++ b/ext/src/lib/TypedPort.ts @@ -0,0 +1,59 @@ +"use strict"; + +const portMap = new WeakMap(); + +/** + * Allows typed access to a runtime.Port object. + */ +export class TypedPort { + public name: string; + public error: { message: string }; + public sender?: browser.runtime.MessageSender; + + constructor (port: browser.runtime.Port) { + portMap.set(this, port); + this.name = port.name; + + // @ts-ignore + this.error = null; + } + + public disconnect () { + portMap.get(this)?.disconnect(); + } + + public onDisconnect = { + addListener: (cb: (port: TypedPort) => void) => { + portMap.get(this)?.onDisconnect.addListener(cb as any); + } + , removeListener: (cb: (port: TypedPort) => void) => { + portMap.get(this)?.onDisconnect.addListener(cb as any); + } + , hasListener: (cb: (port: TypedPort) => void) => { + return portMap.get(this)?.onDisconnect.hasListener(cb as any) + ?? false; + } + , hasListeners: () => { + return portMap.get(this)?.onMessage.hasListeners() ?? false; + } + }; + + public onMessage = { + addListener: (cb: (message: T[number]) => void) => { + portMap.get(this)?.onMessage.addListener(cb); + } + , removeListener: (cb: (message: T[number]) => void) => { + portMap.get(this)?.onMessage.removeListener(cb); + } + , hasListener: (cb: (message: T[number]) => void) => { + return portMap.get(this)?.onMessage.hasListener(cb as any) ?? false; + } + , hasListeners: () => { + return portMap.get(this)?.onMessage.hasListeners() ?? false; + } + }; + + public postMessage (message: T[number]) { + portMap.get(this)?.postMessage(message); + } +} diff --git a/ext/src/lib/typedStorage.ts b/ext/src/lib/TypedStorageArea.ts similarity index 58% rename from ext/src/lib/typedStorage.ts rename to ext/src/lib/TypedStorageArea.ts index b224777..0b8768b 100644 --- a/ext/src/lib/typedStorage.ts +++ b/ext/src/lib/TypedStorageArea.ts @@ -13,14 +13,6 @@ export class TypedStorageArea { this.storageArea = storageArea; } - /** - * Retrieves one or more items from the storage area. - * - * @param keys - - * A string, array of strings or partial schema object - * (with default values) indicating which keys to retrieve - * from storage. - */ public async get> ( keys?: SchemaKey @@ -33,47 +25,22 @@ export class TypedStorageArea { return await this.storageArea.get(keys); } - /** - * Gets the amount of storage space — in bytes — used by one - * or more items in the storage area. - * - * @param keys - - * A string or array of strings indicating the keys of - * which to get the storage space. - */ public async getBytesInUse ( keys?: Schema | SchemaKey[]): Promise { return await this.storageArea.getBytesInUse(keys); } - /** - * Stores one or more items in the storage area. - * - * @param keys - - * A partial schema object containing the items to be - * stored or updated. - */ public async set (keys: Partial): Promise { await this.storageArea.set(keys); } - /** - * Removes one or more items from the storage area. - * - * @param keys - - * A string or array of strings indicating which keys to - * remove from storage. - */ public async remove ( keys: SchemaKey | SchemaKey[]): Promise { await this.storageArea.remove(keys); } - /** - * Removes all items from the storage area. - */ public async clear (): Promise { await this.storageArea.clear(); } diff --git a/ext/src/lib/bridge.ts b/ext/src/lib/bridge.ts index 58086af..c8fced0 100644 --- a/ext/src/lib/bridge.ts +++ b/ext/src/lib/bridge.ts @@ -2,14 +2,22 @@ import semver from "semver"; +import { TypedPort } from "./TypedPort"; + import logger from "./logger"; +import { Messages, Message, Port } from "./messaging"; import nativeMessaging from "./nativeMessaging"; import options from "./options"; +import { Receiver } from "../types"; +import { ReceiverSelectionCast + , ReceiverSelectionStop } from "../background/receiverSelector/ReceiverSelector"; -async function connect (): Promise { + +async function connect (): Promise { const applicationName = await options.get("bridgeApplicationName"); - const bridgePort = nativeMessaging.connectNative(applicationName); + const bridgePort = nativeMessaging.connectNative(applicationName) as + unknown as Port; bridgePort.onDisconnect.addListener(() => { if (bridgePort.error) { diff --git a/ext/src/lib/messaging.ts b/ext/src/lib/messaging.ts new file mode 100644 index 0000000..15f63a0 --- /dev/null +++ b/ext/src/lib/messaging.ts @@ -0,0 +1,213 @@ +"use strict"; + +import { TypedPort } from "./TypedPort"; + +import { BridgeInfo } from "./bridge"; +import { Receiver, ReceiverStatus } from "../types"; +import { ReceiverSelectorMediaType } from "../background/receiverSelector"; +import { ReceiverSelection + , ReceiverSelectionCast + , ReceiverSelectionStop } from "../background/receiverSelector/ReceiverSelector"; + +import Volume from "../shim/cast/classes/Volume"; +import { MediaInfo } from "../shim/cast/media"; + + +// TODO: Document messages properly +export type Messages = [ + { + subject: "popup:/sendRequestedAppId" + , data: { + requestedAppId: string; + } + } + , { + subject: "popup:/populateReceiverList" + , data: { + receivers: Receiver[] + , defaultMediaType: ReceiverSelectorMediaType + , availableMediaTypes: ReceiverSelectorMediaType + } + } + , { subject: "receiverSelector:/selected", data: ReceiverSelection } + , { subject: "receiverSelector:/stop", data: ReceiverSelection } + , { subject: "main:/shimInitialized", data: { appId: string; }} + , { subject: "main:/selectReceiverBegin" } + , { subject: "shim:/selectReceiverEnd", data: ReceiverSelectionCast } + , { subject: "shim:/selectReceiverStop", data: ReceiverSelectionStop } + , { subject: "shim:/selectReceiverCancelled" } + , { subject: "main:/sessionCreated" } + , { subject: "shim:/serviceUp", data: { id: Receiver["id"] }} + , { subject: "shim:/serviceDown", data: { id: Receiver["id"] }} + , { subject: "shim:/initialized", data: BridgeInfo } + , { subject: "shim:/launchApp", data: { receiver: Receiver }} + + // Session messages + , { subject: "shim:/session/stopped" } + , { + subject: "shim:/session/connected" + , data: { + sessionId: string; + namespaces: Array<{ name: string }>; + displayName: string; + statusText: string; + } + } + , { + subject: "shim:/session/updateStatus" + , data: { volume: Volume } + } + , { + subject: "shim:/session/impl_addMessageListener" + , data: { namespace: string, data: string } + } + , { + subject: "shim:/session/impl_sendMessage" + , data: { messageId: string, error: boolean } + } + , { + subject: "shim:/session/impl_setReceiverMuted" + , data: { volumeId: string, error: boolean } + } + , { + subject: "shim:/session/impl_setReceiverVolumeLevel" + , data: { volumeId: string, error: boolean } + } + , { + subject: "shim:/session/impl_stop" + , data: { stopId: string, error: boolean } + } + + // Bridge session messages + , { + subject: "bridge:/session/impl_leave" + , data: { id: string } + , _id: string + } + , { + subject: "bridge:/session/impl_sendMessage" + , data: { namespace: string, message: any, messageId: string } + , _id: string + } + , { + subject: "bridge:/session/impl_setReceiverMuted" + , data: { muted: boolean, volumeId: string } + , _id: string + } + , { + subject: "bridge:/session/impl_setReceiverVolumeLevel" + , data: { newLevel: number, volumeId: string } + , _id: string + } + , { + subject: "bridge:/session/impl_stop" + , data: { stopId: string } + , _id: string + } + , { + subject: "bridge:/session/impl_addMessageListener" + , data: { namespace: string } + , _id: string + } + + // Media messages + , { + subject: "shim:/media/update" + , data: { + currentTime: number + , _lastCurrentTime: number + , customData: any + , playbackRate: number + , playerState: string + , repeatMode: string + , _volumeLevel: number + , _volumeMuted: boolean + , media: MediaInfo + , mediaSessionId: number + } + } + , { + subject: "shim:/media/sendMediaMessageResponse" + , data: { messageId: string, error: boolean } + } + + // Bridge media messages + , { + subject: "bridge:/media/initialize" + , data: { + sessionId: string + , mediaSessionId: number + , _internalSessionId: string + } + , _id: string; + } + , { + subject: "bridge:/media/sendMediaMessage" + , data: { message: any, messageId: string } + , _id: string; + } + + // Bridge messages + , { subject: "main:/receiverSelector/selected", data: ReceiverSelectionCast } + , { subject: "main:/receiverSelector/error", data: string } + , { subject: "main:/receiverSelector/close" } + , { subject: "main:/receiverSelector/stop", data: ReceiverSelectionStop } + + , { subject: "bridge:/initialize", data: { shouldWatchStatus: boolean }} + , { subject: "bridge:/receiverSelector/open", data: any } + , { subject: "bridge:/receiverSelector/close" } + , { subject: "bridge:/stopReceiverApp", data: { receiver: Receiver }} + , { + subject: "bridge:/session/initialize" + , data: { + address: string + , port: number + , appId: string + , sessionId: string + } + , _id: string; + } + + , { subject: "main:/serviceUp", data: Receiver } + , { subject: "main:/serviceDown", data: { id: string }} + , { + subject: "main:/receiverStatus" + , data: { id: string, status: ReceiverStatus } + } +]; + +export type Port = TypedPort; +export type Message = Messages[number]; + + +interface RuntimeConnectInfo { + name: string; +} +interface TabConnectInfo { + name: string; + frameId: number; +} + +export default { + connect (connectInfo: RuntimeConnectInfo) { + return browser.runtime.connect(connectInfo) as + unknown as TypedPort; + } + + , connectTab (tabId: number, connectInfo: TabConnectInfo) { + return browser.tabs.connect(tabId, connectInfo) as + unknown as TypedPort; + } + + , onConnect: { + addListener (cb: (port: TypedPort) => void) { + browser.runtime.onConnect.addListener(cb as any); + } + , removeListener (cb: (port: TypedPort) => void) { + browser.runtime.onConnect.removeListener(cb as any); + } + , hasListener (cb: (port: TypedPort) => void) { + return browser.runtime.onConnect.hasListener(cb as any); + } + } +}; diff --git a/ext/src/lib/options.ts b/ext/src/lib/options.ts index e8d076f..af1cb39 100644 --- a/ext/src/lib/options.ts +++ b/ext/src/lib/options.ts @@ -5,8 +5,8 @@ import defaultOptions from "../defaultOptions"; import logger from "./logger"; import { ReceiverSelectorType } from "../background/receiverSelector"; -import { TypedEventTarget } from "./typedEvents"; -import { TypedStorageArea } from "./typedStorage"; +import { TypedEventTarget } from "./TypedEventTarget"; +import { TypedStorageArea } from "./TypedStorageArea"; const storageArea = new TypedStorageArea<{ diff --git a/ext/src/shim/cast/classes/Session.ts b/ext/src/shim/cast/classes/Session.ts index 3ec6256..20b30e5 100755 --- a/ext/src/shim/cast/classes/Session.ts +++ b/ext/src/shim/cast/classes/Session.ts @@ -49,7 +49,7 @@ const _stopCallbacks = new WeakMap(); export default class Session { public media: Media[]; - public namespaces: Array<{ name: "string" }>; + public namespaces: Array<{ name: string }>; public senderApps: SenderApplication[]; public status: SessionStatus; public statusText: string | null; @@ -91,13 +91,14 @@ export default class Session { , appId , sessionId } - , _id: _id.get(this) + , _id: _id.get(this)! }); } const listenerObject = onMessage(message => { // Filter other session messages - if (message._id && message._id !== _id.get(this)) { + if ((message as any)._id + && (message as any)._id !== _id.get(this)) { return; } @@ -164,9 +165,7 @@ export default class Session { case "shim:/session/impl_addMessageListener": { - const { namespace, data } - : { namespace: string, data: string } = message.data; - + const { namespace, data } = message.data; const messageListeners = _messageListeners .get(this)?.get(namespace); @@ -180,9 +179,7 @@ export default class Session { } case "shim:/session/impl_sendMessage": { - const { messageId, error } - : { messageId: string, error: boolean } = message.data; - + const { messageId, error } = message.data; const [ successCallback, errorCallback ] = _sendMessageCallbacks .get(this)?.get(messageId) ?? []; @@ -286,7 +283,7 @@ export default class Session { sendMessageResponse({ subject: "bridge:/session/impl_addMessageListener" , data: { namespace } - , _id: _id.get(this) + , _id: _id.get(this)! }); } @@ -303,7 +300,7 @@ export default class Session { sendMessageResponse({ subject: "bridge:/session/impl_leave" , data: { id } - , _id: _id.get(this) + , _id: _id.get(this)! }); _leaveCallbacks.get(this)?.set(id, [ @@ -411,7 +408,7 @@ export default class Session { , message , messageId } - , _id: _id.get(this) + , _id: _id.get(this)! }); _sendMessageCallbacks.get(this)?.set(messageId, [ @@ -430,7 +427,7 @@ export default class Session { sendMessageResponse({ subject: "bridge:/session/impl_setReceiverMuted" , data: { muted, volumeId } - , _id: _id.get(this) + , _id: _id.get(this)! }); _setReceiverMutedCallbacks.get(this)?.set(volumeId, [ @@ -449,7 +446,7 @@ export default class Session { sendMessageResponse({ subject: "bridge:/session/impl_setReceiverVolumeLevel" , data: { newLevel, volumeId } - , _id: _id.get(this) + , _id: _id.get(this)! }); _setReceiverVolumeLevelCallbacks.get(this)?.set(volumeId, [ @@ -467,7 +464,7 @@ export default class Session { sendMessageResponse({ subject: "bridge:/session/impl_stop" , data: { stopId } - , _id: _id.get(this) + , _id: _id.get(this)! }); _stopCallbacks.get(this)?.set(stopId, [ diff --git a/ext/src/shim/cast/index.ts b/ext/src/shim/cast/index.ts index 2e372a4..4b74797 100755 --- a/ext/src/shim/cast/index.ts +++ b/ext/src/shim/cast/index.ts @@ -44,7 +44,7 @@ type ErrorCallback = (err: Error_) => void; let apiConfig: ApiConfig; -const receiverList: Receiver[] = []; +const receiverList: Array<{ id: string }> = []; const sessionList: Session[] = []; const receiverActionListeners = new Set(); diff --git a/ext/src/shim/cast/media/classes/Media.ts b/ext/src/shim/cast/media/classes/Media.ts index c29bbe3..83d6fb1 100644 --- a/ext/src/shim/cast/media/classes/Media.ts +++ b/ext/src/shim/cast/media/classes/Media.ts @@ -76,11 +76,12 @@ export default class Media { , mediaSessionId , _internalSessionId } - , _id: _id.get(this) + , _id: _id.get(this)! }); onMessage(message => { - if (!message._id || message._id !== _id.get(this)) { + if ((message as any)._id + && (message as any)._id !== _id.get(this)) { return; } @@ -95,7 +96,7 @@ export default class Media { this.playerState = status.playerState; this.repeatMode = status.repeatMode; - if (status.volume) { + if (status._volumeLevel && status._volumeMuted) { this.volume = new Volume( status._volumeLevel , status._volumeMuted); @@ -120,9 +121,7 @@ export default class Media { } case "shim:/media/sendMediaMessageResponse": { - const { messageId, error } - : { messageId: string, error: any } = message.data; - + const { messageId, error } = message.data; const [ successCallback, errorCallback ] = _sendMediaMessageCallbacks .get(this)?.get(messageId) ?? []; @@ -323,7 +322,7 @@ export default class Media { message , messageId } - , _id: _id.get(this) + , _id: _id.get(this)! }); } } diff --git a/ext/src/shim/contentBridge.ts b/ext/src/shim/contentBridge.ts index 36232ec..8645d79 100644 --- a/ext/src/shim/contentBridge.ts +++ b/ext/src/shim/contentBridge.ts @@ -1,9 +1,10 @@ "use strict"; import { loadScript } from "../lib/utils"; -import { Message } from "../types"; import { onMessageResponse, sendMessage } from "./eventMessageChannel"; +import messaging, { Message } from "../lib/messaging"; + const { isFramework } : { isFramework: boolean } = (window as any); @@ -18,7 +19,7 @@ if (isFramework) { // Message port to background script -export const backgroundPort = browser.runtime.connect({ name: "shim" }); +export const backgroundPort = messaging.connect({ name: "shim" }); const forwardToShim = (message: Message) => sendMessage(message); const forwardToMain = (message: Message) => backgroundPort.postMessage(message); diff --git a/ext/src/shim/eventMessageChannel.ts b/ext/src/shim/eventMessageChannel.ts index 8603bbd..b95f093 100644 --- a/ext/src/shim/eventMessageChannel.ts +++ b/ext/src/shim/eventMessageChannel.ts @@ -1,6 +1,6 @@ "use strict"; -import { Message } from "../types"; +import { Message } from "../lib/messaging"; type ListenerFunc = (message: Message) => void; diff --git a/ext/src/shim/export.ts b/ext/src/shim/export.ts index 548153d..ef1d174 100644 --- a/ext/src/shim/export.ts +++ b/ext/src/shim/export.ts @@ -1,9 +1,9 @@ "use strict"; import * as cast from "./cast"; +import { Message } from "../lib/messaging"; import { BridgeInfo } from "../lib/bridge"; -import { Message } from "../types"; import { onMessage , onMessageResponse