prettier: Re-format .ts files

This commit is contained in:
hensm
2021-08-31 08:28:23 +01:00
parent d6ca1325dc
commit 41d8edcab4
82 changed files with 2683 additions and 2532 deletions

View File

@@ -6,15 +6,15 @@ import logger from "../lib/logger";
import messaging, { Message, Port } from "../messaging";
import options from "../lib/options";
import { ReceiverSelectionActionType
, ReceiverSelectorMediaType } from "./receiverSelector";
import {
ReceiverSelectionActionType,
ReceiverSelectorMediaType
} from "./receiverSelector";
import ReceiverSelectorManager
from "./receiverSelector/ReceiverSelectorManager";
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager";
import receiverDevices from "./receiverDevices";
type AnyPort = Port | MessagePort;
export interface Shim {
@@ -25,8 +25,7 @@ export interface Shim {
appId?: string;
}
export default new class ShimManager {
export default new (class ShimManager {
private activeShims = new Set<Shim>();
public async init() {
@@ -40,8 +39,8 @@ export default new class ShimManager {
receiverDevices.addEventListener("receiverDeviceUp", ev => {
for (const shim of this.activeShims) {
shim.contentPort.postMessage({
subject: "shim:serviceUp"
, data: { receiverDevice: ev.detail.receiverDevice }
subject: "shim:serviceUp",
data: { receiverDevice: ev.detail.receiverDevice }
});
}
});
@@ -49,8 +48,8 @@ export default new class ShimManager {
receiverDevices.addEventListener("receiverDeviceDown", ev => {
for (const shim of this.activeShims) {
shim.contentPort.postMessage({
subject: "shim:serviceDown"
, data: { receiverDeviceId: ev.detail.receiverDeviceId }
subject: "shim:serviceDown",
data: { receiverDeviceId: ev.detail.receiverDeviceId }
});
}
});
@@ -74,19 +73,19 @@ export default new class ShimManager {
: this.createShimFromContent(port));
shim.contentPort.postMessage({
subject: "shim:initialized"
, data: await bridge.getInfo()
subject: "shim:initialized",
data: await bridge.getInfo()
});
this.activeShims.add(shim);
}
private async createShimFromBackground(
contentPort: MessagePort): Promise<Shim> {
contentPort: MessagePort
): Promise<Shim> {
const shim: Shim = {
bridgePort: await bridge.connect()
, contentPort
bridgePort: await bridge.connect(),
contentPort
};
shim.bridgePort.onDisconnect.addListener(() => {
@@ -105,12 +104,14 @@ export default new class ShimManager {
return shim;
}
private async createShimFromContent(
contentPort: Port): Promise<Shim> {
if (contentPort.sender?.tab?.id === undefined
|| contentPort.sender?.frameId === undefined) {
throw logger.error("Content shim created with an invalid port context.");
private async createShimFromContent(contentPort: Port): Promise<Shim> {
if (
contentPort.sender?.tab?.id === undefined ||
contentPort.sender?.frameId === undefined
) {
throw logger.error(
"Content shim created with an invalid port context."
);
}
/**
@@ -118,20 +119,21 @@ export default new class ShimManager {
* tab/frame ID, disconnect it.
*/
for (const activeShim of this.activeShims) {
if (activeShim.contentTabId === contentPort.sender.tab.id
&& activeShim.contentFrameId === contentPort.sender.frameId) {
if (
activeShim.contentTabId === contentPort.sender.tab.id &&
activeShim.contentFrameId === contentPort.sender.frameId
) {
activeShim.bridgePort.disconnect();
}
}
const shim: Shim = {
bridgePort: await bridge.connect()
, contentPort
, contentTabId: contentPort.sender.tab.id
, contentFrameId: contentPort.sender.frameId
bridgePort: await bridge.connect(),
contentPort,
contentTabId: contentPort.sender.tab.id,
contentFrameId: contentPort.sender.frameId
};
const onContentPortMessage = (message: Message) => {
this.handleContentMessage(shim, message);
};
@@ -150,7 +152,6 @@ export default new class ShimManager {
this.activeShims.delete(shim);
};
shim.bridgePort.onDisconnect.addListener(onDisconnect);
shim.bridgePort.onMessage.addListener(onBridgePortMessage);
@@ -161,7 +162,7 @@ export default new class ShimManager {
}
private async handleContentMessage(shim: Shim, message: Message) {
const [ destination ] = message.subject.split(":");
const [destination] = message.subject.split(":");
if (destination === "bridge") {
shim.bridgePort.postMessage(message);
}
@@ -172,8 +173,8 @@ export default new class ShimManager {
for (const receiverDevice of receiverDevices.getDevices()) {
shim.contentPort.postMessage({
subject: "shim:serviceUp"
, data: { receiverDevice }
subject: "shim:serviceUp",
data: { receiverDevice }
});
}
@@ -181,15 +182,21 @@ export default new class ShimManager {
}
case "main:selectReceiver": {
if (shim.contentTabId === undefined
|| shim.contentFrameId === undefined) {
throw logger.error("Shim associated with content sender missing tab/frame ID");
if (
shim.contentTabId === undefined ||
shim.contentFrameId === undefined
) {
throw logger.error(
"Shim associated with content sender missing tab/frame ID"
);
}
try {
const selection =
await ReceiverSelectorManager.getSelection(
shim.contentTabId, shim.contentFrameId);
await ReceiverSelectorManager.getSelection(
shim.contentTabId,
shim.contentFrameId
);
// Handle cancellation
if (!selection) {
@@ -207,25 +214,26 @@ export default new class ShimManager {
* been changed, we need to cancel the current
* sender and switch it out for the right one.
*/
if (selection.mediaType !==
ReceiverSelectorMediaType.App) {
if (
selection.mediaType !==
ReceiverSelectorMediaType.App
) {
shim.contentPort.postMessage({
subject: "shim:selectReceiver/cancelled"
});
loadSender({
tabId: shim.contentTabId
, frameId: shim.contentFrameId
, selection
tabId: shim.contentTabId,
frameId: shim.contentFrameId,
selection
});
break;
}
shim.contentPort.postMessage({
subject: "shim:selectReceiver/selected"
, data: selection
subject: "shim:selectReceiver/selected",
data: selection
});
break;
@@ -233,8 +241,8 @@ export default new class ShimManager {
case ReceiverSelectionActionType.Stop: {
shim.contentPort.postMessage({
subject: "shim:selectReceiver/stopped"
, data: selection
subject: "shim:selectReceiver/stopped",
data: selection
});
break;
@@ -257,7 +265,8 @@ export default new class ShimManager {
case "main:sessionCreated": {
const selector = await ReceiverSelectorManager.getSelector();
const shouldClose = await options.get(
"receiverSelectorWaitForConnection");
"receiverSelectorWaitForConnection"
);
if (selector.isOpen && shouldClose) {
selector.close();
@@ -267,4 +276,4 @@ export default new class ShimManager {
}
}
}
};
})();

View File

@@ -7,8 +7,7 @@ import messaging from "../messaging";
import options from "../lib/options";
import bridge, { BridgeInfo } from "../lib/bridge";
import ReceiverSelectorManager
from "./receiverSelector/ReceiverSelectorManager";
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager";
import ShimManager from "./ShimManager";
@@ -17,10 +16,8 @@ import receiverDevices from "./receiverDevices";
import { initMenus } from "./menus";
import { initWhitelist } from "./whitelist";
const _ = browser.i18n.getMessage;
/**
* On install, set the default options before initializing the
* extension. On update, handle any unset values and set to
@@ -36,7 +33,7 @@ browser.runtime.onInstalled.addListener(async details => {
init();
break;
}
case "update": {
// Set new defaults
await options.update(defaultOptions);
@@ -45,7 +42,6 @@ browser.runtime.onInstalled.addListener(async details => {
}
});
/**
* Sets up media overlay content script and handles toggling
* on options change.
@@ -62,10 +58,10 @@ async function initMediaOverlay() {
try {
contentScript = await browser.contentScripts.register({
allFrames: true
, js: [{ file: "senders/media/overlay/overlayContentLoader.js" }]
, matches: [ "<all_urls>" ]
, runAt: "document_start"
allFrames: true,
js: [{ file: "senders/media/overlay/overlayContentLoader.js" }],
matches: ["<all_urls>"],
runAt: "document_start"
});
} catch (err) {
logger.error("Failed to register media overlay");
@@ -76,7 +72,6 @@ async function initMediaOverlay() {
await contentScript?.unregister();
}
registerMediaOverlayContentScript();
// Update if toggled
@@ -90,7 +85,6 @@ async function initMediaOverlay() {
});
}
/**
* Checks whether the bridge can be reached and is compatible
* with the current version of the extension. If not, triggers
@@ -113,10 +107,11 @@ async function notifyBridgeCompat() {
logger.info("... bridge incompatible!");
const updateNotificationId = await browser.notifications.create({
type: "basic"
, title: `${
_("extensionName")}${_("optionsBridgeIssueStatusTitle")}`
, message: info.isVersionOlder
type: "basic",
title: `${_("extensionName")}${_(
"optionsBridgeIssueStatusTitle"
)}`,
message: info.isVersionOlder
? _("optionsBridgeOlderAction")
: _("optionsBridgeNewerAction")
});
@@ -127,14 +122,12 @@ async function notifyBridgeCompat() {
}
browser.tabs.create({
url: `https://github.com/hensm/fx_cast/releases/tag/v${
info.expectedVersion}`
url: `https://github.com/hensm/fx_cast/releases/tag/v${info.expectedVersion}`
});
});
}
}
let isInitialized = false;
async function init() {
@@ -163,14 +156,13 @@ async function init() {
await initWhitelist();
await initMediaOverlay();
/**
* When the browser action is clicked, open a receiver
* selector and load a sender for the response. The
* mirroring sender is loaded into the current tab at the
* top-level frame.
*/
browser.browserAction.onClicked.addListener(async tab => {
browser.browserAction.onClicked.addListener(async tab => {
if (tab.id === undefined) {
throw logger.error("Tab ID not found in browser action handler.");
}
@@ -178,9 +170,9 @@ async function init() {
const selection = await ReceiverSelectorManager.getSelection(tab.id);
if (selection) {
loadSender({
tabId: tab.id
, frameId: 0
, selection
tabId: tab.id,
frameId: 0,
selection
});
}
});

View File

@@ -6,22 +6,21 @@ import options from "../lib/options";
import { stringify } from "../lib/utils";
import { ReceiverSelectionActionType
, ReceiverSelectorMediaType } from "./receiverSelector";
import {
ReceiverSelectionActionType,
ReceiverSelectorMediaType
} from "./receiverSelector";
import ReceiverSelectorManager
from "./receiverSelector/ReceiverSelectorManager";
import ReceiverSelectorManager from "./receiverSelector/ReceiverSelectorManager";
const _ = browser.i18n.getMessage;
const URL_PATTERN_HTTP = "http://*/*";
const URL_PATTERN_HTTPS = "https://*/*";
const URL_PATTERN_FILE = "file://*/*";
const URL_PATTERNS_REMOTE = [ URL_PATTERN_HTTP, URL_PATTERN_HTTPS ];
const URL_PATTERNS_ALL = [ ...URL_PATTERNS_REMOTE, URL_PATTERN_FILE ];
const URL_PATTERNS_REMOTE = [URL_PATTERN_HTTP, URL_PATTERN_HTTPS];
const URL_PATTERNS_ALL = [...URL_PATTERNS_REMOTE, URL_PATTERN_FILE];
type MenuId = string | number;
@@ -39,44 +38,44 @@ export async function initMenus() {
// Global "Cast..." menu item
menuIdCast = browser.menus.create({
contexts: [ "browser_action", "page", "tools_menu" ]
, title: _("contextCast")
contexts: ["browser_action", "page", "tools_menu"],
title: _("contextCast")
});
// <video>/<audio> "Cast..." context menu item
menuIdMediaCast = browser.menus.create({
contexts: [ "audio", "video", "image" ]
, title: _("contextCast")
, visible: opts.mediaEnabled
, targetUrlPatterns: opts.localMediaEnabled
contexts: ["audio", "video", "image"],
title: _("contextCast"),
visible: opts.mediaEnabled,
targetUrlPatterns: opts.localMediaEnabled
? URL_PATTERNS_ALL
: URL_PATTERNS_REMOTE
});
menuIdWhitelist = browser.menus.create({
contexts: [ "browser_action" ]
, title: _("contextAddToWhitelist")
, enabled: false
contexts: ["browser_action"],
title: _("contextAddToWhitelist"),
enabled: false
});
menuIdWhitelistRecommended = browser.menus.create({
title: _("contextAddToWhitelistRecommended")
, parentId: menuIdWhitelist
title: _("contextAddToWhitelistRecommended"),
parentId: menuIdWhitelist
});
browser.menus.create({
type: "separator"
, parentId: menuIdWhitelist
type: "separator",
parentId: menuIdWhitelist
});
}
browser.menus.onClicked.addListener(async (info, tab) => {
if (info.parentMenuItemId === menuIdWhitelist) {
const pattern = whitelistChildMenuPatterns.get(info.menuItemId);
if (!pattern) {
throw logger.error(`Whitelist pattern not found for menu item ID ${info.menuItemId}.`);
throw logger.error(
`Whitelist pattern not found for menu item ID ${info.menuItemId}.`
);
}
const whitelist = await options.get("userAgentWhitelist");
@@ -99,7 +98,9 @@ browser.menus.onClicked.addListener(async (info, tab) => {
switch (info.menuItemId) {
case menuIdCast: {
const selection = await ReceiverSelectorManager.getSelection(
tab.id, info.frameId);
tab.id,
info.frameId
);
// Selection cancelled
if (!selection) {
@@ -107,9 +108,9 @@ browser.menus.onClicked.addListener(async (info, tab) => {
}
loadSender({
tabId: tab.id
, frameId: info.frameId
, selection
tabId: tab.id,
frameId: info.frameId,
selection
});
break;
@@ -117,7 +118,10 @@ browser.menus.onClicked.addListener(async (info, tab) => {
case menuIdMediaCast: {
const selection = await ReceiverSelectorManager.getSelection(
tab.id, info.frameId, true);
tab.id,
info.frameId,
true
);
// Selection cancelled
if (!selection) {
@@ -130,30 +134,26 @@ browser.menus.onClicked.addListener(async (info, tab) => {
* If the selected media type is App, that refers to the
* media sender in this context, so load media sender.
*/
if (selection.mediaType ===
ReceiverSelectorMediaType.App) {
if (selection.mediaType === ReceiverSelectorMediaType.App) {
await browser.tabs.executeScript(tab.id, {
code: stringify`
window.receiver = ${selection.receiver};
window.mediaUrl = ${info.srcUrl};
window.targetElementId = ${
info.targetElementId};
`
, frameId: info.frameId
window.targetElementId = ${info.targetElementId};
`,
frameId: info.frameId
});
await browser.tabs.executeScript(tab.id, {
file: "senders/media/index.js"
, frameId: info.frameId
file: "senders/media/index.js",
frameId: info.frameId
});
} else {
// Handle other responses
loadSender({
tabId: tab.id
, frameId: info.frameId
, selection
tabId: tab.id,
frameId: info.frameId,
selection
});
}
@@ -225,7 +225,7 @@ browser.menus.onShown.addListener(async info => {
enabled: true
});
for (const [ menuId ] of whitelistChildMenuPatterns) {
for (const [menuId] of whitelistChildMenuPatterns) {
// Clear all page-specific temporary menus
if (menuId !== menuIdWhitelistRecommended) {
browser.menus.remove(menuId);
@@ -235,9 +235,10 @@ browser.menus.onShown.addListener(async info => {
}
// If there is more than one subdomain, get the base domain
const baseDomain = (url.hostname.match(/\./g) || []).length > 1
? url.hostname.substring(url.hostname.indexOf(".") + 1)
: url.hostname;
const baseDomain =
(url.hostname.match(/\./g) || []).length > 1
? url.hostname.substring(url.hostname.indexOf(".") + 1)
: url.hostname;
const portlessOrigin = `${url.protocol}//${url.hostname}`;
@@ -253,17 +254,17 @@ browser.menus.onShown.addListener(async info => {
});
whitelistChildMenuPatterns.set(
menuIdWhitelistRecommended, patternRecommended);
menuIdWhitelistRecommended,
patternRecommended
);
if (url.search) {
const whitelistSearchMenuId = browser.menus.create({
title: _("contextAddToWhitelistAdvancedAdd", patternSearch)
, parentId: menuIdWhitelist
title: _("contextAddToWhitelistAdvancedAdd", patternSearch),
parentId: menuIdWhitelist
});
whitelistChildMenuPatterns.set(
whitelistSearchMenuId, patternSearch);
whitelistChildMenuPatterns.set(whitelistSearchMenuId, patternSearch);
}
/**
@@ -275,65 +276,63 @@ browser.menus.onShown.addListener(async info => {
? url.pathname.substring(0, url.pathname.length - 1)
: url.pathname;
const pathSegments = pathTrimmed.split("/")
.filter(segment => segment)
.reverse();
const pathSegments = pathTrimmed
.split("/")
.filter(segment => segment)
.reverse();
if (pathSegments.length) {
for (let i = 0; i < pathSegments.length; i++) {
const partialPath = pathSegments
.slice(i)
.reverse()
.join("/");
const partialPath = pathSegments.slice(i).reverse().join("/");
const pattern = `${portlessOrigin}/${partialPath}/*`;
const partialPathMenuId = browser.menus.create({
title: _("contextAddToWhitelistAdvancedAdd", pattern)
, parentId: menuIdWhitelist
title: _("contextAddToWhitelistAdvancedAdd", pattern),
parentId: menuIdWhitelist
});
whitelistChildMenuPatterns.set(
partialPathMenuId, pattern);
whitelistChildMenuPatterns.set(partialPathMenuId, pattern);
}
}
}
const wildcardProtocolMenuId = browser.menus.create({
title: _("contextAddToWhitelistAdvancedAdd"
, patternWildcardProtocol)
, parentId: menuIdWhitelist
title: _("contextAddToWhitelistAdvancedAdd", patternWildcardProtocol),
parentId: menuIdWhitelist
});
whitelistChildMenuPatterns.set(
wildcardProtocolMenuId, patternWildcardProtocol);
wildcardProtocolMenuId,
patternWildcardProtocol
);
const wildcardSubdomainMenuId = browser.menus.create({
title: _("contextAddToWhitelistAdvancedAdd"
, patternWildcardSubdomain)
, parentId: menuIdWhitelist
title: _("contextAddToWhitelistAdvancedAdd", patternWildcardSubdomain),
parentId: menuIdWhitelist
});
whitelistChildMenuPatterns.set(
wildcardSubdomainMenuId, patternWildcardSubdomain);
wildcardSubdomainMenuId,
patternWildcardSubdomain
);
const wildcardProtocolAndSubdomainMenuId = browser.menus.create({
title: _("contextAddToWhitelistAdvancedAdd"
, patternWildcardProtocolAndSubdomain)
, parentId: menuIdWhitelist
title: _(
"contextAddToWhitelistAdvancedAdd",
patternWildcardProtocolAndSubdomain
),
parentId: menuIdWhitelist
});
whitelistChildMenuPatterns.set(
wildcardProtocolAndSubdomainMenuId
, patternWildcardProtocolAndSubdomain);
wildcardProtocolAndSubdomainMenuId,
patternWildcardProtocolAndSubdomain
);
await browser.menus.refresh();
});
options.addEventListener("changed", async ev => {
const alteredOpts = ev.detail;
const newOpts = await options.getAll();

View File

@@ -8,17 +8,16 @@ import { Message, Port } from "../messaging";
import { ReceiverDevice } from "../types";
import { ReceiverStatus } from "../shim/cast/types";
interface EventMap {
"receiverDeviceUp": { receiverDevice: ReceiverDevice }
, "receiverDeviceDown": { receiverDeviceId: string }
, "receiverDeviceUpdated": {
receiverDeviceId: string
, status: ReceiverStatus
}
receiverDeviceUp: { receiverDevice: ReceiverDevice };
receiverDeviceDown: { receiverDeviceId: string };
receiverDeviceUpdated: {
receiverDeviceId: string;
status: ReceiverStatus;
};
}
export default new class extends TypedEventTarget<EventMap> {
export default new (class extends TypedEventTarget<EventMap> {
/**
* Map of receiver device IDs to devices. Updated as
* receiverDevice messages are received from the bridge.
@@ -27,7 +26,6 @@ export default new class extends TypedEventTarget<EventMap> {
private bridgePort?: Port;
async init() {
if (!this.bridgePort) {
await this.refresh();
@@ -47,8 +45,8 @@ export default new class extends TypedEventTarget<EventMap> {
port.onDisconnect.addListener(this.onBridgeDisconnect);
port.postMessage({
subject: "bridge:startDiscovery"
, data: {
subject: "bridge:startDiscovery",
data: {
// Also send back status messages
shouldWatchStatus: true
}
@@ -69,15 +67,17 @@ export default new class extends TypedEventTarget<EventMap> {
*/
stopReceiverApp(receiverDeviceId: string) {
if (!this.bridgePort) {
logger.error("Failed to stop receiver device, no bridge connection");
logger.error(
"Failed to stop receiver device, no bridge connection"
);
return;
}
const receiverDevice = this.receiverDevices.get(receiverDeviceId);
if (receiverDevice) {
this.bridgePort.postMessage({
subject: "bridge:stopCastApp"
, data: { receiverDevice }
subject: "bridge:stopCastApp",
data: { receiverDevice }
});
}
}
@@ -89,10 +89,10 @@ export default new class extends TypedEventTarget<EventMap> {
this.receiverDevices.set(receiverDevice.id, receiverDevice);
this.dispatchEvent(
new CustomEvent("receiverDeviceUp"
, {
detail: { receiverDevice }
}));
new CustomEvent("receiverDeviceUp", {
detail: { receiverDevice }
})
);
break;
}
@@ -104,10 +104,10 @@ export default new class extends TypedEventTarget<EventMap> {
this.receiverDevices.delete(receiverDeviceId);
}
this.dispatchEvent(
new CustomEvent("receiverDeviceDown"
, {
detail: { receiverDeviceId }
}));
new CustomEvent("receiverDeviceDown", {
detail: { receiverDeviceId }
})
);
break;
}
@@ -115,10 +115,12 @@ export default new class extends TypedEventTarget<EventMap> {
case "main:receiverDeviceUpdated": {
const { receiverDeviceId, status } = message.data;
const receiverDevice =
this.receiverDevices.get(receiverDeviceId);
this.receiverDevices.get(receiverDeviceId);
if (!receiverDevice) {
logger.error(`Receiver ID \`${receiverDeviceId}\` not found!`);
logger.error(
`Receiver ID \`${receiverDeviceId}\` not found!`
);
break;
}
@@ -129,27 +131,27 @@ export default new class extends TypedEventTarget<EventMap> {
if (status.applications) {
receiverDevice.status.applications =
status.applications;
status.applications;
}
} else {
receiverDevice.status = status;
}
this.dispatchEvent(
new CustomEvent("receiverDeviceUpdated"
, {
detail: {
receiverDeviceId
, status: receiverDevice.status
}
}));
new CustomEvent("receiverDeviceUpdated", {
detail: {
receiverDeviceId,
status: receiverDevice.status
}
})
);
}
}
}
};
private onBridgeDisconnect = () => {
// Notify listeners of device availablility
for (const [ , receiverDevice ] of this.receiverDevices) {
for (const [, receiverDevice] of this.receiverDevices) {
const event = new CustomEvent("receiverDeviceDown", {
detail: { receiverDeviceId: receiverDevice.id }
});
@@ -163,5 +165,5 @@ export default new class extends TypedEventTarget<EventMap> {
window.setTimeout(() => {
this.refresh();
}, 10000);
}
};
};
})();

View File

@@ -8,25 +8,22 @@ import { TypedEventTarget } from "../../lib/TypedEventTarget";
import { getWindowCenteredProps, WindowCenteredProps } from "../../lib/utils";
import { ReceiverDevice } from "../../types";
import { ReceiverSelectionCast
, ReceiverSelectionStop
, ReceiverSelectorMediaType } from "./index";
import {
ReceiverSelectionCast,
ReceiverSelectionStop,
ReceiverSelectorMediaType
} from "./index";
const POPUP_URL = browser.runtime.getURL("ui/popup/index.html");
interface ReceiverSelectorEvents {
"selected": ReceiverSelectionCast;
"error": string;
"cancelled": void;
"stop": ReceiverSelectionStop;
selected: ReceiverSelectionCast;
error: string;
cancelled: void;
stop: ReceiverSelectionStop;
}
export default class ReceiverSelector
extends TypedEventTarget<ReceiverSelectorEvents> {
export default class ReceiverSelector extends TypedEventTarget<ReceiverSelectorEvents> {
private windowId?: number;
private messagePort?: Port;
@@ -65,11 +62,11 @@ export default class ReceiverSelector
}
public async open(
receivers: ReceiverDevice[]
, defaultMediaType: ReceiverSelectorMediaType
, availableMediaTypes: ReceiverSelectorMediaType
, appId?: string): Promise<void> {
receivers: ReceiverDevice[],
defaultMediaType: ReceiverSelectorMediaType,
availableMediaTypes: ReceiverSelectorMediaType,
appId?: string
): Promise<void> {
this.appId = appId;
// If popup already exists, close it
@@ -81,27 +78,28 @@ export default class ReceiverSelector
this.defaultMediaType = defaultMediaType;
this.availableMediaTypes = availableMediaTypes;
let centeredProps: WindowCenteredProps = {
left: 100
, top: 100
, width: 350
, height: 200
left: 100,
top: 100,
width: 350,
height: 200
};
try {
// Calculate centered size/position based on current window
centeredProps = getWindowCenteredProps(
await browser.windows.getCurrent()
, centeredProps.width, centeredProps.height);
await browser.windows.getCurrent(),
centeredProps.width,
centeredProps.height
);
} catch {
// Shouldn't ever hit this, but defaults are provided in case
}
const popup = await browser.windows.create({
url: POPUP_URL
, type: "popup"
, ...centeredProps
url: POPUP_URL,
type: "popup",
...centeredProps
});
if (popup?.id === undefined) {
@@ -116,22 +114,23 @@ export default class ReceiverSelector
...centeredProps
});
const closeIfFocusLost = await options.get(
"receiverSelectorCloseIfFocusLost");
"receiverSelectorCloseIfFocusLost"
);
if (closeIfFocusLost) {
// Add focus listener
browser.windows.onFocusChanged.addListener(
this.onWindowsFocusChanged);
this.onWindowsFocusChanged
);
}
}
public update(receivers: ReceiverDevice[]) {
this.receivers = receivers;
this.messagePort?.postMessage({
subject: "popup:update"
, data: {
subject: "popup:update",
data: {
receivers: this.receivers
}
});
@@ -167,23 +166,25 @@ export default class ReceiverSelector
this.messagePortDisconnected = true;
});
if (!this.receivers
|| !this.defaultMediaType
|| !this.availableMediaTypes) {
if (
!this.receivers ||
!this.defaultMediaType ||
!this.availableMediaTypes
) {
throw logger.error("Popup receiver data not found.");
}
this.messagePort.postMessage({
subject: "popup:init"
, data: { appId: this.appId }
subject: "popup:init",
data: { appId: this.appId }
});
this.messagePort.postMessage({
subject: "popup:update"
, data: {
receivers: this.receivers
, defaultMediaType: this.defaultMediaType
, availableMediaTypes: this.availableMediaTypes
subject: "popup:update",
data: {
receivers: this.receivers,
defaultMediaType: this.defaultMediaType,
availableMediaTypes: this.availableMediaTypes
}
});
@@ -197,17 +198,21 @@ export default class ReceiverSelector
switch (message.subject) {
case "receiverSelector:selected": {
this.wasReceiverSelected = true;
this.dispatchEvent(new CustomEvent("selected", {
detail: message.data
}));
this.dispatchEvent(
new CustomEvent("selected", {
detail: message.data
})
);
break;
}
case "receiverSelector:stop": {
this.dispatchEvent(new CustomEvent("stop", {
detail: message.data
}));
this.dispatchEvent(
new CustomEvent("stop", {
detail: message.data
})
);
break;
}
@@ -226,7 +231,8 @@ export default class ReceiverSelector
browser.windows.onRemoved.removeListener(this.onWindowsRemoved);
browser.windows.onFocusChanged.removeListener(
this.onWindowsFocusChanged);
this.onWindowsFocusChanged
);
if (!this.wasReceiverSelected) {
this.dispatchEvent(new CustomEvent("cancelled"));
@@ -247,12 +253,14 @@ export default class ReceiverSelector
* `WINDOW_ID_NONE` or if the popup window is re-focused.
*/
private onWindowsFocusChanged(windowId: number) {
if (windowId !== browser.windows.WINDOW_ID_NONE
&& windowId !== this.windowId) {
if (
windowId !== browser.windows.WINDOW_ID_NONE &&
windowId !== this.windowId
) {
// Only run once
browser.windows.onFocusChanged.removeListener(
this.onWindowsFocusChanged);
this.onWindowsFocusChanged
);
if (this.windowId) {
browser.windows.remove(this.windowId);

View File

@@ -8,18 +8,18 @@ import receiverDevices from "../receiverDevices";
import { getMediaTypesForPageUrl } from "../../lib/utils";
import { ReceiverSelection
, ReceiverSelectionActionType
, ReceiverSelectorMediaType } from "./index";
import {
ReceiverSelection,
ReceiverSelectionActionType,
ReceiverSelectorMediaType
} from "./index";
import ReceiverSelector from "./ReceiverSelector";
async function createSelector() {
return new ReceiverSelector();
}
let sharedSelector: ReceiverSelector;
async function getSelector() {
@@ -34,7 +34,6 @@ async function getSelector() {
return sharedSelector;
}
/**
* Opens a receiver selector with the specified
* default/available media types.
@@ -46,21 +45,18 @@ async function getSelector() {
* - Rejects if the selection fails.
*/
async function getSelection(
contextTabId: number
, contextFrameId = 0
, withMediaSender = false)
: Promise<ReceiverSelection | null> {
contextTabId: number,
contextFrameId = 0,
withMediaSender = false
): Promise<ReceiverSelection | null> {
return new Promise(async (resolve, reject) => {
let currentShim = ShimManager.getShim(
contextTabId, contextFrameId);
let currentShim = ShimManager.getShim(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 (currentShim?.appId ===
await options.get("mirroringAppId")) {
if (currentShim?.appId === (await options.get("mirroringAppId"))) {
currentShim = undefined;
}
@@ -69,13 +65,15 @@ async function getSelection(
try {
const { url } = await browser.webNavigation.getFrame({
tabId: contextTabId
, frameId: contextFrameId
tabId: contextTabId,
frameId: contextFrameId
});
availableMediaTypes = getMediaTypesForPageUrl(url);
} catch {
logger.error("Failed to locate frame, falling back to default available media types.");
logger.error(
"Failed to locate frame, falling back to default available media types."
);
availableMediaTypes = ReceiverSelectorMediaType.File;
}
@@ -90,8 +88,8 @@ async function getSelection(
// Remove mirroring media types if mirroring is not enabled
if (!opts.mirroringEnabled) {
availableMediaTypes &= ~(
ReceiverSelectorMediaType.Tab
| ReceiverSelectorMediaType.Screen);
ReceiverSelectorMediaType.Tab | ReceiverSelectorMediaType.Screen
);
}
// Remove file media type if local media is not enabled
@@ -107,26 +105,28 @@ async function getSelection(
// Get a new selector for each selection
sharedSelector = await createSelector();
function onReceiverChange() {
sharedSelector.update(receiverDevices.getDevices());
}
receiverDevices.addEventListener("receiverDeviceUp", onReceiverChange);
receiverDevices.addEventListener(
"receiverDeviceUp", onReceiverChange);
"receiverDeviceDown",
onReceiverChange
);
receiverDevices.addEventListener(
"receiverDeviceDown", onReceiverChange);
receiverDevices.addEventListener(
"receiverDeviceUpdated", onReceiverChange);
"receiverDeviceUpdated",
onReceiverChange
);
let onSelected: any;
let onCancelled: any;
let onError: any;
let onStop: any;
type EvParamsType =
Parameters<typeof sharedSelector.addEventListener>[0];
type EvParamsType = Parameters<
typeof sharedSelector.addEventListener
>[0];
function storeListener<T>(type: EvParamsType, fn: T) {
if (type === "selected") {
@@ -149,67 +149,78 @@ async function getSelection(
sharedSelector.removeEventListener("stop", onStop);
receiverDevices.removeEventListener(
"receiverDeviceUp", onReceiverChange);
"receiverDeviceUp",
onReceiverChange
);
receiverDevices.removeEventListener(
"receiverDeviceDown", onReceiverChange);
"receiverDeviceDown",
onReceiverChange
);
receiverDevices.removeEventListener(
"receiverDeviceUpdated", onReceiverChange);
"receiverDeviceUpdated",
onReceiverChange
);
}
sharedSelector.addEventListener("selected"
, storeListener("selected", ev => {
sharedSelector.addEventListener(
"selected",
storeListener("selected", ev => {
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast,
receiver: ev.detail.receiver,
mediaType: ev.detail.mediaType,
filePath: ev.detail.filePath
});
removeListeners();
})
);
logger.info("Selected receiver", ev.detail);
resolve({
actionType: ReceiverSelectionActionType.Cast
, receiver: ev.detail.receiver
, mediaType: ev.detail.mediaType
, filePath: ev.detail.filePath
});
removeListeners();
}));
sharedSelector.addEventListener(
"cancelled",
storeListener("cancelled", () => {
logger.info("Cancelled receiver selection");
resolve(null);
removeListeners();
})
);
sharedSelector.addEventListener("cancelled"
, storeListener("cancelled", () => {
sharedSelector.addEventListener(
"error",
storeListener("error", () => {
reject();
removeListeners();
})
);
logger.info("Cancelled receiver selection");
resolve(null);
removeListeners();
}));
sharedSelector.addEventListener(
"stop",
storeListener("stop", async ev => {
logger.info("Stopping receiver app...", ev.detail);
sharedSelector.addEventListener("error"
, storeListener("error", () => {
reject();
removeListeners();
}));
sharedSelector.addEventListener("stop"
, storeListener("stop", async ev => {
logger.info("Stopping receiver app...", ev.detail);
receiverDevices.stopReceiverApp(ev.detail.receiver.id);
resolve({
actionType: ReceiverSelectionActionType.Stop
, receiver: ev.detail.receiver
});
removeListeners();
}));
receiverDevices.stopReceiverApp(ev.detail.receiver.id);
resolve({
actionType: ReceiverSelectionActionType.Stop,
receiver: ev.detail.receiver
});
removeListeners();
})
);
// Ensure status manager is initialized
await receiverDevices.init();
sharedSelector.open(
receiverDevices.getDevices()
, defaultMediaType
, availableMediaTypes
, currentShim?.appId);
receiverDevices.getDevices(),
defaultMediaType,
availableMediaTypes,
currentShim?.appId
);
});
}
export default {
getSelection
, getSelector
getSelection,
getSelector
};

View File

@@ -1,20 +1,20 @@
"use strict";
export enum ReceiverSelectorType {
Popup
, Native
Popup,
Native
}
export enum ReceiverSelectorMediaType {
App = 1
, Tab = 2
, Screen = 4
, File = 8
App = 1,
Tab = 2,
Screen = 4,
File = 8
}
export enum ReceiverSelectionActionType {
Cast = 1
, Stop = 2
Cast = 1,
Stop = 2
}
export interface ReceiverSelectionCast {

View File

@@ -5,21 +5,23 @@ import options from "../lib/options";
import { getChromeUserAgent } from "../lib/userAgents";
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
, CAST_LOADER_SCRIPT_URL } from "../lib/endpoints";
import {
CAST_FRAMEWORK_LOADER_SCRIPT_URL,
CAST_LOADER_SCRIPT_URL
} from "../lib/endpoints";
// Missing on @types/firefox-webext-browser
type OnBeforeSendHeadersDetails = Parameters<Parameters<
typeof browser.webRequest.onBeforeSendHeaders.addListener>[0]>[0] & {
frameAncestors?: Array<{ url: string, frameId: number }>
type OnBeforeSendHeadersDetails = Parameters<
Parameters<typeof browser.webRequest.onBeforeSendHeaders.addListener>[0]
>[0] & {
frameAncestors?: Array<{ url: string; frameId: number }>;
};
type OnBeforeRequestDetails = Parameters<Parameters<
typeof browser.webRequest.onBeforeRequest.addListener>[0]>[0] & {
frameAncestors?: Array<{ url: string, frameId: number }>
type OnBeforeRequestDetails = Parameters<
Parameters<typeof browser.webRequest.onBeforeRequest.addListener>[0]
>[0] & {
frameAncestors?: Array<{ url: string; frameId: number }>;
};
const originUrlCache: string[] = [];
let platform: string;
@@ -51,40 +53,43 @@ export async function initWhitelist() {
options.addEventListener("changed", ev => {
const alteredOpts = ev.detail;
if (alteredOpts.includes("userAgentWhitelist")
|| alteredOpts.includes("userAgentWhitelistEnabled")) {
if (
alteredOpts.includes("userAgentWhitelist") ||
alteredOpts.includes("userAgentWhitelistEnabled")
) {
unregisterUserAgentWhitelist();
registerUserAgentWhitelist();
}
});
}
/**
* 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 onWhitelistedBeforeSendHeaders(
details: OnBeforeSendHeadersDetails) {
async function onWhitelistedBeforeSendHeaders(
details: OnBeforeSendHeadersDetails
) {
if (!details.requestHeaders) {
throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders.");
throw logger.error(
"OnBeforeSendHeaders handler details missing requestHeaders."
);
}
if (details.originUrl && !originUrlCache.includes(details.originUrl)) {
originUrlCache.push(details.originUrl);
}
const host = details.requestHeaders.find(
header => header.name === "Host");
const host = details.requestHeaders.find(header => header.name === "Host");
for (const header of details.requestHeaders) {
if (header.name === "User-Agent") {
header.value = host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent;
header.value =
host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent;
break;
}
}
@@ -101,8 +106,8 @@ export async function initWhitelist() {
* main site is whitelisted.
*/
function onWhitelistedChildBeforeSendHeaders(
details: OnBeforeSendHeadersDetails) {
details: OnBeforeSendHeadersDetails
) {
if (!details.requestHeaders || !details.frameAncestors) {
return;
}
@@ -110,13 +115,15 @@ function onWhitelistedChildBeforeSendHeaders(
for (const ancestor of details.frameAncestors) {
if (originUrlCache.includes(ancestor.url)) {
const host = details.requestHeaders.find(
header => header.name === "Host");
header => header.name === "Host"
);
for (const header of details.requestHeaders) {
if (header.name === "User-Agent") {
header.value = host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent;
header.value =
host?.value === "www.youtube.com"
? chromeUserAgentHybrid
: chromeUserAgent;
break;
}
}
@@ -128,7 +135,6 @@ function onWhitelistedChildBeforeSendHeaders(
}
}
/**
* Sender applications load a cast_sender.js script that
* functions as a loader for the internal chrome-extension:
@@ -165,16 +171,17 @@ async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
await browser.tabs.executeScript(details.tabId, {
code: `
window.isFramework = ${
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL};
`
, frameId: details.frameId
, runAt: "document_start"
details.url === CAST_FRAMEWORK_LOADER_SCRIPT_URL
};
`,
frameId: details.frameId,
runAt: "document_start"
});
await browser.tabs.executeScript(details.tabId, {
file: "shim/contentBridge.js"
, frameId: details.frameId
, runAt: "document_start"
file: "shim/contentBridge.js",
frameId: details.frameId,
runAt: "document_start"
});
return {
@@ -182,40 +189,41 @@ async function onBeforeCastSDKRequest(details: OnBeforeRequestDetails) {
};
}
async function registerUserAgentWhitelist() {
const { userAgentWhitelist
, userAgentWhitelistEnabled } = await options.getAll();
const { userAgentWhitelist, userAgentWhitelistEnabled } =
await options.getAll();
browser.webRequest.onBeforeRequest.addListener(
onBeforeCastSDKRequest
, { urls: [
CAST_LOADER_SCRIPT_URL
, CAST_FRAMEWORK_LOADER_SCRIPT_URL ]}
, [ "blocking" ]);
onBeforeCastSDKRequest,
{ urls: [CAST_LOADER_SCRIPT_URL, CAST_FRAMEWORK_LOADER_SCRIPT_URL] },
["blocking"]
);
if (!userAgentWhitelistEnabled || !userAgentWhitelist.length) {
return;
}
browser.webRequest.onBeforeSendHeaders.addListener(
onWhitelistedBeforeSendHeaders
, { urls: userAgentWhitelist }
, [ "blocking", "requestHeaders" ]);
onWhitelistedBeforeSendHeaders,
{ urls: userAgentWhitelist },
["blocking", "requestHeaders"]
);
browser.webRequest.onBeforeSendHeaders.addListener(
onWhitelistedChildBeforeSendHeaders
, { urls: [ "<all_urls>" ]}
, [ "blocking", "requestHeaders" ]);
onWhitelistedChildBeforeSendHeaders,
{ urls: ["<all_urls>"] },
["blocking", "requestHeaders"]
);
}
function unregisterUserAgentWhitelist() {
originUrlCache.length = 0;
browser.webRequest.onBeforeSendHeaders
.removeListener(onWhitelistedBeforeSendHeaders);
browser.webRequest.onBeforeSendHeaders
.removeListener(onWhitelistedChildBeforeSendHeaders);
browser.webRequest.onBeforeRequest
.removeListener(onBeforeCastSDKRequest);
browser.webRequest.onBeforeSendHeaders.removeListener(
onWhitelistedBeforeSendHeaders
);
browser.webRequest.onBeforeSendHeaders.removeListener(
onWhitelistedChildBeforeSendHeaders
);
browser.webRequest.onBeforeRequest.removeListener(onBeforeCastSDKRequest);
}