Add typed messaging

This commit is contained in:
hensm
2020-02-18 07:37:20 +00:00
parent 652fd21f77
commit 2eeaff4c15
19 changed files with 431 additions and 227 deletions

View File

@@ -1,6 +1,6 @@
"use strict";
export interface TypedEvents {
interface TypedEvents {
[key: string]: any;
}

59
ext/src/lib/TypedPort.ts Normal file
View File

@@ -0,0 +1,59 @@
"use strict";
const portMap = new WeakMap<any, browser.runtime.Port>();
/**
* Allows typed access to a runtime.Port object.
*/
export class TypedPort<T extends any[]> {
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<T>) => void) => {
portMap.get(this)?.onDisconnect.addListener(cb as any);
}
, removeListener: (cb: (port: TypedPort<T>) => void) => {
portMap.get(this)?.onDisconnect.addListener(cb as any);
}
, hasListener: (cb: (port: TypedPort<T>) => 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);
}
}

View File

@@ -13,14 +13,6 @@ export class TypedStorageArea<Schema extends { [key: string]: any }> {
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<SchemaKey extends keyof Schema
, SchemaPartial extends Partial<Schema>> (
keys?: SchemaKey
@@ -33,47 +25,22 @@ export class TypedStorageArea<Schema extends { [key: string]: any }> {
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<SchemaKey extends keyof Schema> (
keys?: Schema | SchemaKey[]): Promise<number> {
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<Schema>): Promise<void> {
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<SchemaKey extends keyof Schema> (
keys: SchemaKey | SchemaKey[]): Promise<void> {
await this.storageArea.remove(keys);
}
/**
* Removes all items from the storage area.
*/
public async clear (): Promise<void> {
await this.storageArea.clear();
}

View File

@@ -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<browser.runtime.Port> {
async function connect (): Promise<Port> {
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) {

213
ext/src/lib/messaging.ts Normal file
View File

@@ -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<Messages>;
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<Messages>;
}
, connectTab (tabId: number, connectInfo: TabConnectInfo) {
return browser.tabs.connect(tabId, connectInfo) as
unknown as TypedPort<Messages>;
}
, onConnect: {
addListener (cb: (port: TypedPort<Messages>) => void) {
browser.runtime.onConnect.addListener(cb as any);
}
, removeListener (cb: (port: TypedPort<Messages>) => void) {
browser.runtime.onConnect.removeListener(cb as any);
}
, hasListener (cb: (port: TypedPort<Messages>) => void) {
return browser.runtime.onConnect.hasListener(cb as any);
}
}
};

View File

@@ -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<{