mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-09 09:09:58 +00:00
Move CastManager to cast/
This commit is contained in:
@@ -1,318 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import bridge from "../lib/bridge";
|
||||
import loadSender from "../lib/loadSender";
|
||||
import logger from "../lib/logger";
|
||||
import messaging, { Message, Port } from "../messaging";
|
||||
import options from "../lib/options";
|
||||
|
||||
import {
|
||||
ReceiverSelectionActionType,
|
||||
ReceiverSelectorMediaType
|
||||
} from "./receiverSelector";
|
||||
|
||||
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager";
|
||||
|
||||
import receiverDevices from "./receiverDevices";
|
||||
|
||||
type AnyPort = Port | MessagePort;
|
||||
|
||||
export interface CastInstance {
|
||||
bridgePort: Port;
|
||||
contentPort: AnyPort;
|
||||
contentTabId?: number;
|
||||
contentFrameId?: number;
|
||||
appId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of cast API instances and provides bridge
|
||||
* messaging.
|
||||
*/
|
||||
export default new (class CastManager {
|
||||
private activeInstances = new Set<CastInstance>();
|
||||
|
||||
public async init() {
|
||||
// Handle incoming instance connections
|
||||
messaging.onConnect.addListener(async port => {
|
||||
if (port.name === "cast") {
|
||||
this.createInstance(port);
|
||||
}
|
||||
});
|
||||
|
||||
// Forward receiver eventes to cast instances
|
||||
receiverDevices.addEventListener("receiverDeviceUp", ev => {
|
||||
for (const instance of this.activeInstances) {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:receiverDeviceUp",
|
||||
data: { receiverDevice: ev.detail.deviceInfo }
|
||||
});
|
||||
}
|
||||
});
|
||||
receiverDevices.addEventListener("receiverDeviceDown", ev => {
|
||||
for (const instance of this.activeInstances) {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:receiverDeviceDown",
|
||||
data: { receiverDeviceId: ev.detail.deviceId }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a cast instance at the given tab (and optionally
|
||||
* frame) ID.
|
||||
*/
|
||||
public getInstance(tabId: number, frameId?: number) {
|
||||
for (const instance of this.activeInstances) {
|
||||
if (instance.contentTabId === tabId) {
|
||||
// If frame ID doesn't match go to next instance
|
||||
if (frameId && instance.contentFrameId !== frameId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cast instance with a given port and connects
|
||||
* messaging correctly depending on the type of port.
|
||||
*/
|
||||
public async createInstance(port: AnyPort) {
|
||||
const instance = await (port instanceof MessagePort
|
||||
? this.createInstanceFromBackground(port)
|
||||
: this.createInstanceFromContent(port));
|
||||
|
||||
this.activeInstances.add(instance);
|
||||
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:initialized",
|
||||
data: await bridge.getInfo()
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cast instance with a `MessagePort` content port.
|
||||
*/
|
||||
private async createInstanceFromBackground(
|
||||
contentPort: MessagePort
|
||||
): Promise<CastInstance> {
|
||||
const instance: CastInstance = {
|
||||
bridgePort: await bridge.connect(),
|
||||
contentPort
|
||||
};
|
||||
|
||||
instance.bridgePort.onDisconnect.addListener(() => {
|
||||
contentPort.close();
|
||||
this.activeInstances.delete(instance);
|
||||
});
|
||||
|
||||
// bridge -> content
|
||||
instance.bridgePort.onMessage.addListener(message => {
|
||||
contentPort.postMessage(message);
|
||||
});
|
||||
|
||||
// content -> (any)
|
||||
contentPort.addEventListener("message", ev => {
|
||||
this.handleContentMessage(instance, ev.data);
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cast instance with a WebExtension `Port` content
|
||||
* port.
|
||||
*/
|
||||
private async createInstanceFromContent(
|
||||
contentPort: Port
|
||||
): Promise<CastInstance> {
|
||||
if (
|
||||
contentPort.sender?.tab?.id === undefined ||
|
||||
contentPort.sender?.frameId === undefined
|
||||
) {
|
||||
throw logger.error(
|
||||
"Cast instance created from content with an invalid port context."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's already an active instance for the sender
|
||||
* tab/frame ID, disconnect it.
|
||||
*
|
||||
* TODO: Fix this behaviour!
|
||||
*/
|
||||
for (const instance of this.activeInstances) {
|
||||
if (
|
||||
instance.contentTabId === contentPort.sender.tab.id &&
|
||||
instance.contentFrameId === contentPort.sender.frameId
|
||||
) {
|
||||
instance.bridgePort.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
const instance: CastInstance = {
|
||||
bridgePort: await bridge.connect(),
|
||||
contentPort,
|
||||
contentTabId: contentPort.sender.tab.id,
|
||||
contentFrameId: contentPort.sender.frameId
|
||||
};
|
||||
|
||||
// content -> (any)
|
||||
const onContentPortMessage = (message: Message) => {
|
||||
this.handleContentMessage(instance, message);
|
||||
};
|
||||
// bridge -> content
|
||||
const onBridgePortMessage = (message: Message) => {
|
||||
contentPort.postMessage(message);
|
||||
};
|
||||
|
||||
const onDisconnect = () => {
|
||||
instance.bridgePort.onMessage.removeListener(onBridgePortMessage);
|
||||
contentPort.onMessage.removeListener(onContentPortMessage);
|
||||
|
||||
instance.bridgePort.disconnect();
|
||||
contentPort.disconnect();
|
||||
|
||||
this.activeInstances.delete(instance);
|
||||
};
|
||||
|
||||
instance.bridgePort.onDisconnect.addListener(onDisconnect);
|
||||
instance.bridgePort.onMessage.addListener(onBridgePortMessage);
|
||||
|
||||
contentPort.onDisconnect.addListener(onDisconnect);
|
||||
contentPort.onMessage.addListener(onContentPortMessage);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle content messages from the cast instance. These will
|
||||
* either be handled here in the background script or forwarded
|
||||
* to the bridge associated with the cast instance.
|
||||
*/
|
||||
private async handleContentMessage(
|
||||
instance: CastInstance,
|
||||
message: Message
|
||||
) {
|
||||
const [destination] = message.subject.split(":");
|
||||
if (destination === "bridge") {
|
||||
instance.bridgePort.postMessage(message);
|
||||
}
|
||||
|
||||
switch (message.subject) {
|
||||
// Cast API has been initialized
|
||||
case "main:initializeCast": {
|
||||
instance.appId = message.data.appId;
|
||||
|
||||
for (const receiverDevice of receiverDevices.getDevices()) {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:receiverDeviceUp",
|
||||
data: { receiverDevice }
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// User has triggered receiver selection via the cast API
|
||||
case "main:selectReceiver": {
|
||||
if (
|
||||
instance.contentTabId === undefined ||
|
||||
instance.contentFrameId === undefined
|
||||
) {
|
||||
throw logger.error(
|
||||
"Cast instance associated with content sender missing tab/frame ID"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const selection =
|
||||
await ReceiverSelectorManager.getSelection(
|
||||
instance.contentTabId,
|
||||
instance.contentFrameId
|
||||
);
|
||||
|
||||
// Handle cancellation
|
||||
if (!selection) {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:selectReceiver/cancelled"
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
switch (selection.actionType) {
|
||||
case ReceiverSelectionActionType.Cast: {
|
||||
/**
|
||||
* If the media type returned from the selector has
|
||||
* been changed, we need to cancel the current
|
||||
* sender and switch it out for the right one.
|
||||
*/
|
||||
if (
|
||||
selection.mediaType !==
|
||||
ReceiverSelectorMediaType.App
|
||||
) {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:selectReceiver/cancelled"
|
||||
});
|
||||
|
||||
loadSender({
|
||||
tabId: instance.contentTabId,
|
||||
frameId: instance.contentFrameId,
|
||||
selection
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:selectReceiver/selected",
|
||||
data: selection
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ReceiverSelectionActionType.Stop: {
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:selectReceiver/stopped",
|
||||
data: selection
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// TODO: Report errors properly
|
||||
instance.contentPort.postMessage({
|
||||
subject: "cast:selectReceiver/cancelled"
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: If we're closing a selector, make sure it's the same
|
||||
* one that caused the session creation.
|
||||
*/
|
||||
case "main:closeReceiverSelector": {
|
||||
const selector = await ReceiverSelectorManager.getSelector();
|
||||
const shouldClose = await options.get(
|
||||
"receiverSelectorWaitForConnection"
|
||||
);
|
||||
|
||||
if (selector.isOpen && shouldClose) {
|
||||
selector.close();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -8,7 +8,7 @@ import bridge, { BridgeInfo } from "../lib/bridge";
|
||||
|
||||
import { RemoteMatchPattern } from "../lib/matchPattern";
|
||||
|
||||
import CastManager from "./CastManager";
|
||||
import CastManager from "../cast/CastManager";
|
||||
import receiverDevices from "./receiverDevices";
|
||||
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import options from "../../lib/options";
|
||||
import logger from "../../lib/logger";
|
||||
|
||||
import CastManager from "../CastManager";
|
||||
import CastManager from "../../cast/CastManager";
|
||||
import receiverDevices from "../receiverDevices";
|
||||
|
||||
import { getMediaTypesForPageUrl } from "../../lib/utils";
|
||||
|
||||
Reference in New Issue
Block a user