Fix longstanding architectural issues

This commit is contained in:
hensm
2022-09-01 02:14:00 +01:00
committed by Matt Hensman
parent 83c81219d7
commit 7a35da2ba1
30 changed files with 1274 additions and 1282 deletions

View File

@@ -4,23 +4,15 @@ import logger from "../lib/logger";
import messaging, { Port, Message } from "../messaging";
import options from "../lib/options";
import { TypedEventTarget } from "../lib/TypedEventTarget";
import { getMediaTypesForPageUrl } from "../lib/utils";
import {
import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
import type {
ReceiverDevice,
ReceiverSelectorAppInfo,
ReceiverSelectorMediaType,
ReceiverSelectorPageInfo
} from "../types";
import deviceManager from "./deviceManager";
import castManager from "./castManager";
import { BaseConfig, baseConfigStorage, getAppTag } from "../cast/googleApi";
import type { SenderMediaMessage, SenderMessage } from "../cast/sdk/types";
import type { SessionRequest } from "../cast/sdk/classes";
import { ReceiverAction } from "../cast/sdk/enums";
import { createReceiver } from "../cast/utils";
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
export interface ReceiverSelection {
@@ -47,8 +39,6 @@ interface ReceiverSelectorEvents {
mediaMessage: ReceiverSelectorMediaMessage;
}
let baseConfig: BaseConfig;
/**
* Manages the receiver selector popup window and communication with the
* extension page hosted within.
@@ -68,8 +58,7 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
private wasReceiverSelected = false;
private appId?: string;
appInfo?: ReceiverSelectorAppInfo;
pageInfo?: ReceiverSelectorPageInfo;
constructor() {
@@ -102,10 +91,10 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
receiverDevices: ReceiverDevice[];
defaultMediaType: ReceiverSelectorMediaType;
availableMediaTypes: ReceiverSelectorMediaType;
appId?: string;
appInfo?: ReceiverSelectorAppInfo;
pageInfo?: ReceiverSelectorPageInfo;
}) {
this.appId = opts.appId;
this.appInfo = opts.appInfo;
this.pageInfo = opts.pageInfo;
// If popup already exists, close it
@@ -178,8 +167,6 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
await browser.windows.remove(this.windowId);
}
this.appId = undefined;
if (this.messagePort && !this.messagePortDisconnected) {
this.messagePort.disconnect();
}
@@ -220,7 +207,10 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
this.messagePort.postMessage({
subject: "popup:init",
data: { appId: this.appId, pageInfo: this.pageInfo }
data: {
appInfo: this.appInfo,
pageInfo: this.pageInfo
}
});
this.messagePort.postMessage({
@@ -303,226 +293,4 @@ export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorE
}
}
}
static sharedInstance = new ReceiverSelector();
/**
* Opens a receiver selector with the specified default/available media
* types.
*
* Returns a promise that:
* - Resolves to a ReceiverSelection object if selection is
* successful.
* - Resolves to null if the selection is cancelled.
* - Rejects if the selection fails.
*/
static async getSelection(
contextTabId: number,
contextFrameId = 0,
selectionOpts?: {
sessionRequest?: SessionRequest;
withMediaSender?: boolean;
}
): Promise<ReceiverSelection | null> {
let castInstance = castManager.getInstance(
contextTabId,
contextFrameId
);
/**
* If the current context is running the mirroring app, pretend
* it doesn't exist because it shouldn't be launched like this.
*/
if (
castInstance?.apiConfig?.sessionRequest.appId ===
(await options.get("mirroringAppId"))
) {
castInstance = undefined;
}
let defaultMediaType = ReceiverSelectorMediaType.Tab;
let availableMediaTypes = ReceiverSelectorMediaType.None;
let pageUrl: string | undefined;
try {
pageUrl = (
await browser.webNavigation.getFrame({
tabId: contextTabId,
frameId: contextFrameId
})
).url;
availableMediaTypes = getMediaTypesForPageUrl(pageUrl);
} catch {
logger.error(
"Failed to locate frame, falling back to default available media types."
);
}
// Enable app media type if sender application is present
if (castInstance || selectionOpts?.withMediaSender) {
defaultMediaType = ReceiverSelectorMediaType.App;
availableMediaTypes |= ReceiverSelectorMediaType.App;
}
const opts = await options.getAll();
// Disable mirroring media types if mirroring is not enabled
if (!opts.mirroringEnabled) {
availableMediaTypes &= ~(
ReceiverSelectorMediaType.Tab | ReceiverSelectorMediaType.Screen
);
}
// Remove file media type if local media is not enabled
if (!opts.mediaEnabled || !opts.localMediaEnabled) {
availableMediaTypes &= ~ReceiverSelectorMediaType.File;
}
// Ensure status manager is initialized
await deviceManager.init();
let isRequestAppAudioCompatible: Optional<boolean>;
if (castInstance?.apiConfig?.sessionRequest.appId) {
if (!baseConfig) {
try {
baseConfig = (await baseConfigStorage.get("baseConfig"))
.baseConfig;
} catch (err) {
throw logger.error("Failed to get Chromecast base config!");
}
}
isRequestAppAudioCompatible = getAppTag(
baseConfig,
castInstance.apiConfig?.sessionRequest.appId
)?.supports_audio_only;
}
return new Promise(async (resolve, reject) => {
// Close an existing open selector
if (ReceiverSelector.sharedInstance.isOpen) {
await ReceiverSelector.sharedInstance.close();
}
const selector = createSelector();
ReceiverSelector.sharedInstance = selector;
// Handle selected return value
const onSelected = (ev: CustomEvent<ReceiverSelection>) =>
resolve(ev.detail);
selector.addEventListener("selected", onSelected);
// Handle cancelled return value
const onCancelled = () => resolve(null);
selector.addEventListener("cancelled", onCancelled);
const onError = (ev: CustomEvent<string>) => reject(ev.detail);
selector.addEventListener("error", onError);
// Cleanup listeners
selector.addEventListener(
"close",
() => {
selector.removeEventListener("selected", onSelected);
selector.removeEventListener("cancelled", onCancelled);
selector.removeEventListener("error", onError);
},
{ once: true }
);
selector.open({
receiverDevices: deviceManager.getDevices(),
defaultMediaType,
availableMediaTypes,
appId: castInstance?.apiConfig?.sessionRequest.appId,
// Create page info
pageInfo: pageUrl
? {
url: pageUrl,
tabId: contextTabId,
frameId: contextFrameId,
sessionRequest: selectionOpts?.sessionRequest,
isRequestAppAudioCompatible
}
: undefined
});
});
}
}
/**
* Creates new ReceiverSelector object and adds listeners for
* updates/messages.
*/
function createSelector() {
// Get a new selector for each selection
const selector = new ReceiverSelector();
ReceiverSelector.sharedInstance = selector;
/**
* Sends message to cast instance to trigger stopped receiver action
* (if applicable).
*/
const onStop = (ev: CustomEvent<{ deviceId: string }>) => {
const castInstance = castManager.getInstanceByDeviceId(
ev.detail.deviceId
);
if (!castInstance) return;
const device = deviceManager.getDeviceById(ev.detail.deviceId);
if (!device) return;
castInstance.contentPort.postMessage({
subject: "cast:sendReceiverAction",
data: {
receiver: createReceiver(device),
action: ReceiverAction.STOP
}
});
};
selector.addEventListener("stop", onStop);
// Forward receiver messages
const onReceiverMessage = (
ev: CustomEvent<ReceiverSelectorReceiverMessage>
) =>
deviceManager.sendReceiverMessage(
ev.detail.deviceId,
ev.detail.message
);
selector.addEventListener("receiverMessage", onReceiverMessage);
// Forward media messages
const onMediaMessage = (ev: CustomEvent<ReceiverSelectorMediaMessage>) =>
deviceManager.sendMediaMessage(ev.detail.deviceId, ev.detail.message);
selector.addEventListener("mediaMessage", onMediaMessage);
// Update selector data whenever devices change/update
const onDeviceChange = () => selector.update(deviceManager.getDevices());
deviceManager.addEventListener("deviceUp", onDeviceChange);
deviceManager.addEventListener("deviceDown", onDeviceChange);
deviceManager.addEventListener("deviceUpdated", onDeviceChange);
deviceManager.addEventListener("deviceMediaUpdated", onDeviceChange);
// Cleanup listeners
selector.addEventListener(
"close",
() => {
deviceManager.removeEventListener("deviceUp", onDeviceChange);
deviceManager.removeEventListener("deviceDown", onDeviceChange);
deviceManager.removeEventListener("deviceUpdated", onDeviceChange);
deviceManager.removeEventListener(
"deviceMediaUpdated",
onDeviceChange
);
selector.removeEventListener("stop", onStop);
selector.removeEventListener("receiverMessage", onReceiverMessage);
selector.removeEventListener("mediaMessage", onMediaMessage);
},
{ once: true }
);
return selector;
}