Replace StatusManager

This commit is contained in:
hensm
2021-04-28 06:21:50 +01:00
parent c1172410f9
commit f44d142631
22 changed files with 295 additions and 286 deletions

View File

@@ -12,7 +12,7 @@ import { ReceiverSelectionActionType
import ReceiverSelectorManager
from "./receiverSelector/ReceiverSelectorManager";
import StatusManager from "./StatusManager";
import receiverDevices from "./receiverDevices";
type AnyPort = Port | MessagePort;
@@ -37,20 +37,20 @@ export default new class ShimManager {
}
});
StatusManager.addEventListener("serviceUp", ev => {
receiverDevices.addEventListener("receiverDeviceUp", ev => {
for (const shim of this.activeShims) {
shim.contentPort.postMessage({
subject: "shim:serviceUp"
, data: { id: ev.detail.id }
, data: { id: ev.detail.receiverDevice.id }
});
}
});
StatusManager.addEventListener("serviceDown", ev => {
receiverDevices.addEventListener("receiverDeviceDown", ev => {
for (const shim of this.activeShims) {
shim.contentPort.postMessage({
subject: "shim:serviceDown"
, data: { id: ev.detail.id }
, data: { id: ev.detail.receiverDeviceId }
});
}
});
@@ -170,10 +170,10 @@ export default new class ShimManager {
case "main:shimReady": {
shim.appId = message.data.appId;
for (const receiver of StatusManager.getReceivers()) {
for (const receiverDevice of receiverDevices.getDevices()) {
shim.contentPort.postMessage({
subject: "shim:serviceUp"
, data: { id: receiver.id }
, data: { id: receiverDevice.id }
});
}

View File

@@ -1,163 +0,0 @@
"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 };
}
export default new class StatusManager
extends TypedEventTarget<EventMap> {
private bridgePort: (Port | null) = null;
private receivers = new Map<string, Receiver>();
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:updateReceiverStatus": {
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);
}
};

View File

@@ -11,7 +11,8 @@ import ReceiverSelectorManager
from "./receiverSelector/ReceiverSelectorManager";
import ShimManager from "./ShimManager";
import StatusManager from "./StatusManager";
import receiverDevices from "./receiverDevices";
import { initMenus } from "./menus";
import { initWhitelist } from "./whitelist";
@@ -155,7 +156,7 @@ async function init() {
await notifyBridgeCompat();
await StatusManager.init();
await receiverDevices.init();
await ShimManager.init();
await initMenus();

View File

@@ -0,0 +1,167 @@
"use strict";
import bridge from "../lib/bridge";
import logger from "../lib/logger";
import { TypedEventTarget } from "../lib/TypedEventTarget";
import messaging, { Message, Port } from "../messaging";
import { ReceiverDevice, ReceiverStatus } from "../types";
interface EventMap {
"receiverDeviceUp": { receiverDevice: ReceiverDevice }
, "receiverDeviceDown": { receiverDeviceId: string }
, "receiverDeviceUpdated": {
receiverDeviceId: string
, status: ReceiverStatus
}
}
export default new class extends TypedEventTarget<EventMap> {
/**
* Map of receiver device IDs to devices. Updated as
* receiverDevice messages are received from the bridge.
*/
private receiverDevices = new Map<string, ReceiverDevice>();
private bridgePort?: Port;
async init() {
if (!this.bridgePort) {
await this.refresh();
}
}
/**
* Initialize (or re-initialize) a bridge connection to
* start dispatching events.
*/
async refresh() {
this.bridgePort?.disconnect();
const port = await bridge.connect();
port.onMessage.addListener(this.onBridgeMessage);
port.onDisconnect.addListener(this.onBridgeDisconnect);
port.postMessage({
subject: "bridge:initialize"
, data: {
// Also send back status messages
shouldWatchStatus: true
}
});
this.bridgePort = port;
}
/**
* Get a list of receiver devices
*/
getDevices() {
return Array.from(this.receiverDevices.values());
}
/**
* Stops a receiver app running on a given device.
*/
stopReceiverApp(receiverDeviceId: string) {
if (!this.bridgePort) {
logger.error("Failed to stop receiver device, no bridge connection");
return;
}
const receiverDevice = this.receiverDevices.get(receiverDeviceId);
if (receiverDevice) {
this.bridgePort.postMessage({
subject: "bridge:stopReceiverApp"
, data: { receiverDevice }
});
}
}
private onBridgeMessage = (message: Message) => {
switch (message.subject) {
case "main:receiverDeviceUp": {
const { receiverDevice } = message.data;
this.receiverDevices.set(receiverDevice.id, receiverDevice);
this.dispatchEvent(
new CustomEvent("receiverDeviceUp"
, {
detail: { receiverDevice }
}));
break;
}
case "main:receiverDeviceDown": {
const { receiverDeviceId } = message.data;
if (this.receiverDevices.has(receiverDeviceId)) {
this.receiverDevices.delete(receiverDeviceId);
}
this.dispatchEvent(
new CustomEvent("receiverDeviceDown"
, {
detail: { receiverDeviceId }
}));
break;
}
case "main:receiverDeviceUpdated": {
const { receiverDeviceId, status } = message.data;
const receiverDevice =
this.receiverDevices.get(receiverDeviceId);
if (!receiverDevice) {
logger.error(`Receiver ID \`${receiverDeviceId}\` not found!`);
break;
}
if (receiverDevice.status) {
receiverDevice.status.isActiveInput = status.isActiveInput;
receiverDevice.status.isStandBy = status.isStandBy;
receiverDevice.status.userEq = status.userEq;
receiverDevice.status.volume = status.volume;
if (status.applications) {
receiverDevice.status.applications =
status.applications;
}
} else {
receiverDevice.status = status;
}
this.dispatchEvent(
new CustomEvent("receiverDeviceUpdated"
, {
detail: {
receiverDeviceId
, status: receiverDevice.status
}
}));
}
}
}
private onBridgeDisconnect = () => {
// Notify listeners of device availablility
for (const [ , receiverDevice ] of this.receiverDevices) {
const event = new CustomEvent("receiverDeviceDown", {
detail: { receiverDeviceId: receiverDevice.id }
});
this.dispatchEvent(event);
}
this.receiverDevices.clear();
// Re-initialize after 10 seconds
window.setTimeout(() => {
this.refresh();
}, 10000);
}
};

View File

@@ -8,7 +8,7 @@ import options from "../../lib/options";
import { TypedEventTarget } from "../../lib/TypedEventTarget";
import { getWindowCenteredProps } from "../../lib/utils";
import { Receiver } from "../../types";
import { ReceiverDevice } from "../../types";
import ReceiverSelector, {
ReceiverSelection
@@ -34,7 +34,7 @@ export default class NativeReceiverSelector extends ReceiverSelector {
}
public async open(
receivers: Receiver[]
receivers: ReceiverDevice[]
, defaultMediaType: ReceiverSelectorMediaType
, availableMediaTypes: ReceiverSelectorMediaType
, appId?: string): Promise<void> {

View File

@@ -9,7 +9,7 @@ import options from "../../lib/options";
import { TypedEventTarget } from "../../lib/TypedEventTarget";
import { getWindowCenteredProps, WindowCenteredProps } from "../../lib/utils";
import { Receiver } from "../../types";
import { ReceiverDevice } from "../../types";
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
@@ -20,7 +20,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
private messagePort?: Port;
private messagePortDisconnected?: boolean;
private receivers?: Receiver[];
private receivers?: ReceiverDevice[];
private defaultMediaType?: ReceiverSelectorMediaType;
private availableMediaTypes?: ReceiverSelectorMediaType;
@@ -53,7 +53,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
}
public async open(
receivers: Receiver[]
receivers: ReceiverDevice[]
, defaultMediaType: ReceiverSelectorMediaType
, availableMediaTypes: ReceiverSelectorMediaType
, appId?: string): Promise<void> {
@@ -115,7 +115,7 @@ export default class PopupReceiverSelector extends ReceiverSelector {
}
}
public update(receivers: Receiver[]) {
public update(receivers: ReceiverDevice[]) {
this.receivers = receivers;
this.messagePort?.postMessage({
subject: "popup:update"

View File

@@ -1,7 +1,7 @@
"use strict";
import { TypedEventTarget } from "../../lib/TypedEventTarget";
import { Receiver } from "../../types";
import { ReceiverDevice } from "../../types";
export enum ReceiverSelectorMediaType {
@@ -18,13 +18,13 @@ export enum ReceiverSelectionActionType {
export interface ReceiverSelectionCast {
actionType: ReceiverSelectionActionType.Cast;
receiver: Receiver;
receiver: ReceiverDevice;
mediaType: ReceiverSelectorMediaType;
filePath?: string;
}
export interface ReceiverSelectionStop {
actionType: ReceiverSelectionActionType.Stop;
receiver: Receiver;
receiver: ReceiverDevice;
}
export type ReceiverSelection = ReceiverSelectionCast | ReceiverSelectionStop;
@@ -43,12 +43,12 @@ export default abstract class ReceiverSelector
abstract readonly isOpen: boolean;
abstract open (
receivers: Receiver[]
receivers: ReceiverDevice[]
, defaultMediaType: ReceiverSelectorMediaType
, availableMediaTypes: ReceiverSelectorMediaType
, appId?: string): void;
abstract update (receivers: Receiver[]): void;
abstract update (receivers: ReceiverDevice[]): void;
abstract close (): void;
}

View File

@@ -4,7 +4,7 @@ import options from "../../lib/options";
import logger from "../../lib/logger";
import ShimManager from "../ShimManager";
import StatusManager from "../StatusManager";
import receiverDevices from "../receiverDevices";
import { getMediaTypesForPageUrl } from "../../lib/utils";
@@ -120,12 +120,15 @@ async function getSelection(
function onReceiverChange() {
sharedSelector.update(Array.from(StatusManager.getReceivers()));
sharedSelector.update(receiverDevices.getDevices());
}
StatusManager.addEventListener("serviceUp", onReceiverChange);
StatusManager.addEventListener("serviceDown", onReceiverChange);
StatusManager.addEventListener("statusUpdate", onReceiverChange);
receiverDevices.addEventListener(
"receiverDeviceUp", onReceiverChange);
receiverDevices.addEventListener(
"receiverDeviceDown", onReceiverChange);
receiverDevices.addEventListener(
"receiverDeviceUpdated", onReceiverChange);
let onSelected: any;
@@ -156,9 +159,12 @@ async function getSelection(
sharedSelector.removeEventListener("error", onError);
sharedSelector.removeEventListener("stop", onStop);
StatusManager.removeEventListener("serviceUp", onReceiverChange);
StatusManager.removeEventListener("serviceDown", onReceiverChange);
StatusManager.removeEventListener("statusUpdate", onReceiverChange);
receiverDevices.removeEventListener(
"receiverDeviceUp", onReceiverChange);
receiverDevices.removeEventListener(
"receiverDeviceDown", onReceiverChange);
receiverDevices.removeEventListener(
"receiverDeviceUpdated", onReceiverChange);
}
sharedSelector.addEventListener("selected"
@@ -191,10 +197,9 @@ async function getSelection(
sharedSelector.addEventListener("stop"
, storeListener("stop", async ev => {
logger.info("Stopped receiver app", ev.detail);
logger.info("Stopping receiver app...", ev.detail);
await StatusManager.init();
await StatusManager.stopReceiverApp(ev.detail.receiver);
receiverDevices.stopReceiverApp(ev.detail.receiver.id);
resolve({
actionType: ReceiverSelectionActionType.Stop
@@ -205,10 +210,10 @@ async function getSelection(
// Ensure status manager is initialized
await StatusManager.init();
await receiverDevices.init();
sharedSelector.open(
Array.from(StatusManager.getReceivers())
receiverDevices.getDevices()
, defaultMediaType
, availableMediaTypes
, currentShim?.appId);