mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-09 00:59:59 +00:00
806 lines
21 KiB
TypeScript
Executable File
806 lines
21 KiB
TypeScript
Executable File
"use strict";
|
|
|
|
import semver from "semver";
|
|
|
|
import defaultOptions, { Options } from "./defaultOptions";
|
|
import getBridgeInfo from "./lib/getBridgeInfo";
|
|
import messageRouter from "./lib/messageRouter";
|
|
import nativeMessaging from "./lib/nativeMessaging";
|
|
import options from "./lib/options";
|
|
|
|
import { getChromeUserAgent } from "./lib/userAgents";
|
|
import { getWindowCenteredProps } from "./lib/utils";
|
|
|
|
import { getReceiverSelector
|
|
, ReceiverSelectorMediaType
|
|
, ReceiverSelectorSelectedEvent
|
|
, ReceiverSelectorType } from "./receiver_selectors";
|
|
|
|
import { Message, Receiver } from "./types";
|
|
|
|
import { ReceiverStatusMessage
|
|
, ServiceDownMessage
|
|
, ServiceUpMessage } from "./messageTypes";
|
|
|
|
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
|
, CAST_LOADER_SCRIPT_URL } from "./endpoints";
|
|
|
|
|
|
const _ = browser.i18n.getMessage;
|
|
|
|
|
|
browser.runtime.onInstalled.addListener(async details => {
|
|
switch (details.reason) {
|
|
// Set default options
|
|
case "install": {
|
|
await options.setAll(defaultOptions);
|
|
break;
|
|
}
|
|
// Set newly added options
|
|
case "update": {
|
|
await options.update(defaultOptions);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Call after default options have been set
|
|
init();
|
|
});
|
|
|
|
|
|
type MenuId = string | number;
|
|
|
|
// Menu IDs
|
|
let mirrorCastMenuId: MenuId;
|
|
let mediaCastMenuId: MenuId;
|
|
|
|
|
|
let whitelistMenuId: MenuId;
|
|
let whitelistRecommendedMenuId: MenuId;
|
|
|
|
const whitelistMenuMap = new Map<MenuId, string>();
|
|
|
|
|
|
const mediaCastTargetUrlPatterns = new Set([
|
|
"http://*/*"
|
|
, "https://*/*"
|
|
]);
|
|
|
|
const LOCAL_MEDIA_URL_PATTERN = "file://*/*";
|
|
|
|
|
|
async function initCreateMenus (opts: Options) {
|
|
|
|
// If menus have already been created, return.
|
|
if (mirrorCastMenuId
|
|
|| mediaCastMenuId
|
|
|| whitelistMenuId
|
|
|| whitelistRecommendedMenuId) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* If local media casting is enabled, allow the media cast
|
|
* menu item to appear on file URIs.
|
|
*/
|
|
if (opts.localMediaEnabled) {
|
|
mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN);
|
|
}
|
|
|
|
// <video>/<audio> "Cast..." context menu item
|
|
mediaCastMenuId = await browser.menus.create({
|
|
contexts: [ "audio", "video" ]
|
|
, id: "contextCastMedia"
|
|
, targetUrlPatterns: Array.from(mediaCastTargetUrlPatterns)
|
|
, title: _("contextCast")
|
|
, visible: opts.mediaEnabled
|
|
});
|
|
|
|
// Screen/Tab mirroring "Cast..." context menu item
|
|
mirrorCastMenuId = await browser.menus.create({
|
|
contexts: [ "browser_action", "page", "tools_menu" ]
|
|
, id: "contextCast"
|
|
, title: _("contextCast")
|
|
, visible: opts.mirroringEnabled
|
|
|
|
// Mirroring doesn't work from local files
|
|
, documentUrlPatterns: [
|
|
"http://*/*"
|
|
, "https://*/*"
|
|
]
|
|
});
|
|
|
|
whitelistMenuId = await browser.menus.create({
|
|
contexts: [ "browser_action" ]
|
|
, title: _("contextAddToWhitelist")
|
|
, enabled: false
|
|
});
|
|
|
|
whitelistRecommendedMenuId = await browser.menus.create({
|
|
title: _("contextAddToWhitelistRecommended")
|
|
, parentId: whitelistMenuId
|
|
});
|
|
|
|
await browser.menus.create({
|
|
type: "separator"
|
|
, parentId: whitelistMenuId
|
|
});
|
|
}
|
|
|
|
|
|
browser.menus.onShown.addListener(info => {
|
|
// Only rebuild menus if whitelist menu present
|
|
// Workaround type issues
|
|
const menuIds = info.menuIds as unknown as number[];
|
|
if (!menuIds.includes(whitelistMenuId as number)) {
|
|
return;
|
|
}
|
|
|
|
if (!info.pageUrl) {
|
|
browser.menus.update(whitelistMenuId, {
|
|
enabled: false
|
|
});
|
|
|
|
browser.menus.refresh();
|
|
return;
|
|
}
|
|
|
|
const url = new URL(info.pageUrl);
|
|
const hasOrigin = url.origin !== "null";
|
|
|
|
|
|
/**
|
|
* If .origin is "null", hide top-level menu and don't
|
|
* bother re-building submenus, since we're probably not on
|
|
* a remote page.
|
|
*/
|
|
browser.menus.update(whitelistMenuId, {
|
|
enabled: hasOrigin
|
|
});
|
|
|
|
if (!hasOrigin) {
|
|
browser.menus.refresh();
|
|
return;
|
|
}
|
|
|
|
|
|
function addWhitelistMenuItem (pattern: string) {
|
|
const menuId = browser.menus.create({
|
|
title: _("contextAddToWhitelistAdvancedAdd", pattern)
|
|
, parentId: whitelistMenuId
|
|
});
|
|
|
|
whitelistMenuMap.set(menuId, pattern);
|
|
}
|
|
|
|
|
|
for (const [ menuId ] of whitelistMenuMap) {
|
|
// Remove all temporary menus
|
|
if (menuId !== whitelistRecommendedMenuId) {
|
|
browser.menus.remove(menuId);
|
|
}
|
|
|
|
// Clear map
|
|
whitelistMenuMap.delete(menuId);
|
|
}
|
|
|
|
|
|
const baseHost = (url.host.match(/\./g) || []).length > 1
|
|
? url.host.substring(url.host.indexOf(".") + 1)
|
|
: url.host;
|
|
|
|
|
|
const patternRecommended = `${url.origin}/*`;
|
|
const patternSearch = `${url.origin}${url.pathname}${url.search}`;
|
|
const patternWildcardProtocol = `*://${url.host}/*`;
|
|
const patternWildcardSubdomain = `${url.protocol}//*.${baseHost}/*`;
|
|
const patternWildcardProtocolAndSubdomain = `*://*.${baseHost}/*`;
|
|
|
|
|
|
// Update recommended menu item
|
|
browser.menus.update(whitelistRecommendedMenuId, {
|
|
title: _("contextAddToWhitelistRecommended", patternRecommended)
|
|
});
|
|
whitelistMenuMap.set(whitelistRecommendedMenuId, patternRecommended);
|
|
|
|
|
|
if (url.search) {
|
|
addWhitelistMenuItem(patternSearch);
|
|
}
|
|
|
|
|
|
/**
|
|
* Split url path into segments and add menu items for each
|
|
* partial path as the segments are removed.
|
|
*/
|
|
{
|
|
const pathTrimmed = url.pathname.endsWith("/")
|
|
? url.pathname.substring(0, url.pathname.length - 1)
|
|
: url.pathname;
|
|
|
|
const pathSegments = pathTrimmed.split("/")
|
|
.filter(segment => segment)
|
|
.reverse();
|
|
|
|
if (pathSegments.length) {
|
|
let index = 0;
|
|
|
|
for (const pathSegment of pathSegments) {
|
|
const partialPath = pathSegments
|
|
.slice(index)
|
|
.reverse()
|
|
.join("/");
|
|
|
|
addWhitelistMenuItem(`${url.origin}/${partialPath}/*`);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Add remaining menu items
|
|
addWhitelistMenuItem(patternWildcardProtocol);
|
|
addWhitelistMenuItem(patternWildcardSubdomain);
|
|
addWhitelistMenuItem(patternWildcardProtocolAndSubdomain);
|
|
|
|
browser.menus.refresh();
|
|
});
|
|
|
|
|
|
/**
|
|
* Sender applications load a cast_sender.js script that
|
|
* functions as a loader for the internal chrome-extension:
|
|
* hosted script.
|
|
*
|
|
* We can redirect this and inject our own script to setup
|
|
* the API shim.
|
|
*/
|
|
browser.webRequest.onBeforeRequest.addListener(
|
|
async details => {
|
|
await browser.tabs.executeScript(details.tabId, {
|
|
code: `window._isFramework = ${
|
|
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL}`
|
|
, frameId: details.frameId
|
|
, runAt: "document_start"
|
|
});
|
|
|
|
await browser.tabs.executeScript(details.tabId, {
|
|
file: "shim/content.js"
|
|
, frameId: details.frameId
|
|
, runAt: "document_start"
|
|
});
|
|
|
|
return {
|
|
redirectUrl: browser.runtime.getURL("shim/bundle.js")
|
|
};
|
|
}
|
|
, { urls: [
|
|
CAST_LOADER_SCRIPT_URL
|
|
, CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
|
]}
|
|
, [ "blocking" ]);
|
|
|
|
|
|
// Current user agent string for all whitelisted requests
|
|
let currentUAString: string;
|
|
|
|
/**
|
|
* Web apps usually only load the sender library and
|
|
* provide cast functionality if the browser is detected
|
|
* as Chrome, so we should rewrite the User-Agent header
|
|
* to reflect this on whitelisted sites.
|
|
*/
|
|
async function onBeforeSendHeaders (
|
|
details: { requestHeaders?: browser.webRequest.HttpHeaders }) {
|
|
|
|
const { os } = await browser.runtime.getPlatformInfo();
|
|
|
|
// Create Chrome UA from platform info on first run
|
|
if (!currentUAString) {
|
|
currentUAString = getChromeUserAgent(os);
|
|
}
|
|
|
|
const host = details.requestHeaders.find(
|
|
(header: any) => header.name === "Host");
|
|
|
|
// Find and rewrite the User-Agent header
|
|
for (const header of details.requestHeaders) {
|
|
if (header.name.toLowerCase() === "user-agent") {
|
|
|
|
// TODO: Remove need for this
|
|
if (host.value === "www.youtube.com") {
|
|
header.value = getChromeUserAgent(os, true);
|
|
break;
|
|
}
|
|
|
|
header.value = currentUAString;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {
|
|
requestHeaders: details.requestHeaders
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initializes any functionality based on options state.
|
|
*/
|
|
async function initRegisterOptionalFeatures (
|
|
opts: Options
|
|
, alteredOptions?: Array<(keyof Options)>) {
|
|
|
|
/**
|
|
* Adds a webRequest listener that intercepts and modifies user
|
|
* agent.
|
|
*/
|
|
function register_userAgentWhitelist () {
|
|
browser.webRequest.onBeforeSendHeaders.addListener(
|
|
onBeforeSendHeaders
|
|
, { urls: opts.userAgentWhitelistEnabled
|
|
? opts.userAgentWhitelist
|
|
: [] }
|
|
, [ "blocking", "requestHeaders" ]);
|
|
}
|
|
|
|
function unregister_userAgentWhitelist () {
|
|
browser.webRequest.onBeforeSendHeaders.removeListener(
|
|
onBeforeSendHeaders);
|
|
}
|
|
|
|
|
|
|
|
if (!alteredOptions) {
|
|
// If no altered properties specified, register all listeners
|
|
register_userAgentWhitelist();
|
|
} else {
|
|
|
|
if (alteredOptions.includes("userAgentWhitelist")
|
|
|| alteredOptions.includes("userAgentWhitelistEnabled")) {
|
|
|
|
unregister_userAgentWhitelist();
|
|
register_userAgentWhitelist();
|
|
}
|
|
|
|
if (alteredOptions.includes("mirroringEnabled")) {
|
|
browser.menus.update(mirrorCastMenuId, {
|
|
visible: opts.mirroringEnabled
|
|
});
|
|
}
|
|
|
|
if (alteredOptions.includes("mediaEnabled")) {
|
|
browser.menus.update(mediaCastMenuId, {
|
|
visible: opts.mediaEnabled
|
|
});
|
|
}
|
|
|
|
if (alteredOptions.includes("localMediaEnabled")) {
|
|
if (opts.localMediaEnabled) {
|
|
mediaCastTargetUrlPatterns.add(LOCAL_MEDIA_URL_PATTERN);
|
|
} else {
|
|
mediaCastTargetUrlPatterns.delete(LOCAL_MEDIA_URL_PATTERN);
|
|
}
|
|
|
|
browser.menus.update(mediaCastMenuId, {
|
|
targetUrlPatterns: Array.from(mediaCastTargetUrlPatterns)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
browser.runtime.onMessage.addListener(async message => {
|
|
switch (message.subject) {
|
|
case "optionsUpdated": {
|
|
const opts = await options.getAll();
|
|
initRegisterOptionalFeatures(opts, message.data.alteredOptions);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Defines window.chrome for site compatibility
|
|
browser.contentScripts.register({
|
|
allFrames: true
|
|
, js: [{ file: "shim/contentSetup.js" }]
|
|
, matches: [ "<all_urls>" ]
|
|
, runAt: "document_start"
|
|
});
|
|
|
|
|
|
let mediaCastTabId: number;
|
|
let mediaCastFrameId: number;
|
|
|
|
let mirrorCastTabId: number;
|
|
let mirrorCastFrameId: number;
|
|
|
|
|
|
browser.menus.onClicked.addListener(async (info, tab) => {
|
|
if (info.menuItemId === mirrorCastMenuId
|
|
|| info.menuItemId === mediaCastMenuId) {
|
|
|
|
const { frameId } = info;
|
|
|
|
switch (info.menuItemId) {
|
|
case mirrorCastMenuId: {
|
|
mirrorCastTabId = tab.id;
|
|
mirrorCastFrameId = frameId;
|
|
|
|
await browser.tabs.executeScript(tab.id, {
|
|
code: `
|
|
window.selectedMedia = ${info.pageUrl
|
|
? ReceiverSelectorMediaType.Tab
|
|
: ReceiverSelectorMediaType.Screen};
|
|
`
|
|
, frameId
|
|
});
|
|
|
|
// Load mirroring sender app
|
|
await browser.tabs.executeScript(tab.id, {
|
|
file: "senders/mirroringCast.js"
|
|
, frameId
|
|
});
|
|
|
|
break;
|
|
}
|
|
|
|
case mediaCastMenuId: {
|
|
mediaCastTabId = tab.id;
|
|
mediaCastFrameId = frameId;
|
|
|
|
// Pass media URL to media sender app
|
|
await browser.tabs.executeScript(tab.id, {
|
|
code: `
|
|
window.srcUrl = "${info.srcUrl}";
|
|
window.targetElementId = ${info.targetElementId};
|
|
`
|
|
, frameId
|
|
});
|
|
|
|
// Load media sender app
|
|
await browser.tabs.executeScript(tab.id, {
|
|
file: "senders/mediaCast.js"
|
|
, frameId
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (info.parentMenuItemId === whitelistMenuId) {
|
|
const matchPattern = whitelistMenuMap.get(info.menuItemId);
|
|
const userAgentWhitelist = await options.get("userAgentWhitelist");
|
|
|
|
// Add to whitelist
|
|
userAgentWhitelist.push(matchPattern);
|
|
|
|
// Update options
|
|
await options.set("userAgentWhitelist", userAgentWhitelist);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
interface Shim {
|
|
port: browser.runtime.Port;
|
|
bridgePort: browser.runtime.Port;
|
|
tabId: number;
|
|
frameId: number;
|
|
}
|
|
|
|
const shimMap = new Map<string, Shim>();
|
|
|
|
let statusBridge: browser.runtime.Port;
|
|
const statusBridgeReceivers = new Map<string, Receiver>();
|
|
|
|
|
|
/**
|
|
* Create status bridge, set event handlers and initialize.
|
|
*/
|
|
async function initCreateStatusBridge (opts: Options) {
|
|
// Status bridge already initialized
|
|
if (statusBridge) {
|
|
return;
|
|
}
|
|
|
|
statusBridge = nativeMessaging.connectNative(opts.bridgeApplicationName);
|
|
statusBridge.onDisconnect.addListener(onStatusBridgeDisconnect);
|
|
statusBridge.onMessage.addListener(onStatusBridgeMessage);
|
|
|
|
statusBridge.postMessage({
|
|
subject: "bridge:/initialize"
|
|
, data: {
|
|
mode: "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.
|
|
*/
|
|
function onStatusBridgeDisconnect () {
|
|
// Notify shims for receiver availability
|
|
for (const [ , receiver ] of statusBridgeReceivers) {
|
|
for (const [, shim ] of shimMap) {
|
|
shim.port.postMessage({
|
|
subject: "shim:/serviceDown"
|
|
, data: { id: receiver.id }
|
|
});
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
statusBridgeReceivers.clear();
|
|
statusBridge.onDisconnect.removeListener(onStatusBridgeDisconnect);
|
|
statusBridge.onMessage.removeListener(onStatusBridgeMessage);
|
|
statusBridge = null;
|
|
|
|
// After 10 seconds, attempt to reinitialize
|
|
window.setTimeout(async () => {
|
|
const opts = await options.getAll();
|
|
initCreateStatusBridge(opts);
|
|
}, 10000);
|
|
}
|
|
|
|
/**
|
|
* Handles incoming status bridge messages.
|
|
*/
|
|
async function onStatusBridgeMessage (message: Message) {
|
|
switch (message.subject) {
|
|
|
|
case "shim:/serviceUp": {
|
|
const receiver = (message as ServiceUpMessage).data;
|
|
statusBridgeReceivers.set(receiver.id, receiver);
|
|
|
|
// Forward update to shims
|
|
for (const shim of shimMap.values()) {
|
|
shim.port.postMessage({
|
|
subject: "shim:/serviceUp"
|
|
, data: { id: receiver.id }
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "shim:/serviceDown": {
|
|
const { id } = (message as ServiceDownMessage).data;
|
|
|
|
if (statusBridgeReceivers.has(id)) {
|
|
statusBridgeReceivers.delete(id);
|
|
}
|
|
|
|
// Forward update to shims
|
|
for (const shim of shimMap.values()) {
|
|
shim.port.postMessage({
|
|
subject: "shim:/serviceDown"
|
|
, data: { id }
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "receiverStatus": {
|
|
const { id, status } = (message as ReceiverStatusMessage).data;
|
|
|
|
const receiver = statusBridgeReceivers.get(id);
|
|
|
|
// Merge new status with old status
|
|
statusBridgeReceivers.set(id, {
|
|
...receiver
|
|
, status: {
|
|
...receiver.status
|
|
, ...status
|
|
}
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
async function onConnectShim (port: browser.runtime.Port) {
|
|
const bridgeInfo = await getBridgeInfo();
|
|
if (bridgeInfo && !bridgeInfo.isVersionCompatible) {
|
|
return;
|
|
}
|
|
|
|
|
|
const { os } = await browser.runtime.getPlatformInfo();
|
|
|
|
const receiverSelector = getReceiverSelector(os === "mac"
|
|
? ReceiverSelectorType.NativeMac
|
|
: ReceiverSelectorType.Popup);
|
|
|
|
function onReceiverSelectorSelected (
|
|
ev: ReceiverSelectorSelectedEvent) {
|
|
|
|
port.postMessage({
|
|
subject: "shim:/selectReceiverEnd"
|
|
, data: ev.detail
|
|
});
|
|
}
|
|
|
|
function onReceiverSelectorCancelled () {
|
|
port.postMessage({
|
|
subject: "shim:/selectReceiverCancelled"
|
|
});
|
|
}
|
|
|
|
function onReceiverSelectorError () {
|
|
// TODO: Report errors properly
|
|
port.postMessage({
|
|
subject: "shim:/selectReceiverCancelled"
|
|
});
|
|
}
|
|
|
|
receiverSelector.addEventListener("selected"
|
|
, onReceiverSelectorSelected);
|
|
receiverSelector.addEventListener("cancelled"
|
|
, onReceiverSelectorCancelled);
|
|
receiverSelector.addEventListener("error"
|
|
, onReceiverSelectorError);
|
|
|
|
port.onDisconnect.addListener(() => {
|
|
receiverSelector.removeEventListener("selected"
|
|
, onReceiverSelectorSelected);
|
|
receiverSelector.removeEventListener("cancelled"
|
|
, onReceiverSelectorCancelled);
|
|
receiverSelector.removeEventListener("error"
|
|
, onReceiverSelectorError);
|
|
});
|
|
|
|
|
|
const tabId = port.sender.tab.id;
|
|
const frameId = port.sender.frameId;
|
|
const shimId = `${tabId}:${frameId}`;
|
|
|
|
// Disconnect existing shim
|
|
if (shimMap.has(shimId)) {
|
|
shimMap.get(shimId).port.disconnect();
|
|
shimMap.delete(shimId);
|
|
}
|
|
|
|
|
|
const applicationName = await options.get("bridgeApplicationName");
|
|
|
|
// Spawn bridge app instance
|
|
const bridgePort = nativeMessaging.connectNative(applicationName);
|
|
|
|
if (bridgePort.error) {
|
|
console.error(`Failed connect to ${applicationName}:`
|
|
, bridgePort.error.message);
|
|
}
|
|
|
|
shimMap.set(shimId, {
|
|
port
|
|
, bridgePort
|
|
, tabId
|
|
, frameId
|
|
});
|
|
|
|
bridgePort.onDisconnect.addListener(() => {
|
|
if (bridgePort.error) {
|
|
console.error(`${applicationName} disconnected:`
|
|
, bridgePort.error.message);
|
|
} else {
|
|
console.info(`${applicationName} disconnected`);
|
|
}
|
|
});
|
|
|
|
// Handle disconnect
|
|
port.onDisconnect.addListener(() => {
|
|
bridgePort.disconnect();
|
|
shimMap.delete(shimId);
|
|
});
|
|
|
|
|
|
bridgePort.onMessage.addListener((message: Message) => {
|
|
port.postMessage(message);
|
|
});
|
|
|
|
port.onMessage.addListener(async (message: Message) => {
|
|
const [ destination ] = message.subject.split(":/");
|
|
switch (destination) {
|
|
case "bridge": {
|
|
bridgePort.postMessage(message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (message.subject) {
|
|
case "main:/shimInitialized": {
|
|
|
|
// Send existing receivers as serviceUp messages
|
|
for (const receiver of statusBridgeReceivers.values()) {
|
|
port.postMessage({
|
|
subject: "shim:/serviceUp"
|
|
, data: { id: receiver.id }
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case "main:/sessionCreated": {
|
|
receiverSelector.close();
|
|
break;
|
|
}
|
|
|
|
case "main:/selectReceiverBegin": {
|
|
receiverSelector.open(
|
|
Array.from(statusBridgeReceivers.values())
|
|
, message.data.defaultMediaType);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
// TODO: Remove need for this
|
|
messageRouter.handleMessage(message);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
port.postMessage({
|
|
subject: "shim:/initialized"
|
|
, data: bridgeInfo
|
|
});
|
|
}
|
|
|
|
browser.runtime.onConnect.addListener(port => {
|
|
switch (port.name) {
|
|
case "shim":
|
|
onConnectShim(port);
|
|
break;
|
|
}
|
|
});
|
|
|
|
|
|
messageRouter.register("mirrorCast", message => {
|
|
browser.tabs.sendMessage(mirrorCastTabId, message
|
|
, { frameId: mirrorCastFrameId });
|
|
});
|
|
messageRouter.register("mediaCast", message => {
|
|
browser.tabs.sendMessage(mediaCastTabId, message
|
|
, { frameId: mediaCastFrameId });
|
|
});
|
|
|
|
|
|
// Forward messages into messageRouter
|
|
browser.runtime.onMessage.addListener((message, sender) => {
|
|
messageRouter.handleMessage(message, {
|
|
tabId: sender.tab.id
|
|
, frameId: sender.frameId
|
|
});
|
|
});
|
|
|
|
|
|
// Misc init
|
|
async function init () {
|
|
const opts = await options.getAll();
|
|
if (!opts) {
|
|
return;
|
|
}
|
|
|
|
initCreateMenus(opts);
|
|
initRegisterOptionalFeatures(opts);
|
|
initCreateStatusBridge(opts);
|
|
}
|
|
|
|
init();
|