mirror of
https://github.com/hensm/fx_cast.git
synced 2026-06-12 10:39:57 +00:00
Move some background modules to a separate folder and fix init order
This commit is contained in:
237
ext/src/background/ShimManager.ts
Normal file
237
ext/src/background/ShimManager.ts
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import bridge from "../lib/bridge";
|
||||||
|
import loadSender from "../lib/loadSender";
|
||||||
|
import options from "../lib/options";
|
||||||
|
|
||||||
|
import { TypedEventTarget } from "../lib/typedEvents";
|
||||||
|
import { Message } from "../types";
|
||||||
|
|
||||||
|
import { ReceiverSelectorMediaType } from "./receiverSelector";
|
||||||
|
|
||||||
|
import ReceiverSelectorManager
|
||||||
|
from "./receiverSelector/ReceiverSelectorManager";
|
||||||
|
|
||||||
|
import StatusManager from "./StatusManager";
|
||||||
|
|
||||||
|
|
||||||
|
type Port = browser.runtime.Port | MessagePort;
|
||||||
|
|
||||||
|
export interface Shim {
|
||||||
|
bridgePort: browser.runtime.Port;
|
||||||
|
contentPort: Port;
|
||||||
|
contentTabId?: number;
|
||||||
|
contentFrameId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// tslint:disable-next-line:new-parens
|
||||||
|
export default new class ShimManager {
|
||||||
|
private activeShims = new Set<Shim>();
|
||||||
|
|
||||||
|
public async init () {
|
||||||
|
await StatusManager.init();
|
||||||
|
await this.initStatusListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createShim (port: Port) {
|
||||||
|
const shim = await (port instanceof MessagePort
|
||||||
|
? this.createShimFromBackground(port)
|
||||||
|
: this.createShimFromContent(port));
|
||||||
|
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/initialized"
|
||||||
|
, data: await bridge.getInfo()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activeShims.add(shim);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createShimFromBackground (
|
||||||
|
contentPort: MessagePort): Promise<Shim> {
|
||||||
|
|
||||||
|
const shim: Shim = {
|
||||||
|
bridgePort: await bridge.connect()
|
||||||
|
, contentPort
|
||||||
|
};
|
||||||
|
|
||||||
|
shim.bridgePort.onDisconnect.addListener(() => {
|
||||||
|
contentPort.close();
|
||||||
|
this.activeShims.delete(shim);
|
||||||
|
});
|
||||||
|
|
||||||
|
shim.bridgePort.onMessage.addListener((message: Message) => {
|
||||||
|
contentPort.postMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
contentPort.onmessage = ev => {
|
||||||
|
const message = ev.data as Message;
|
||||||
|
this.handleContentMessage(shim, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
return shim;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createShimFromContent (
|
||||||
|
contentPort: browser.runtime.Port): Promise<Shim> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there's already an active shim for the sender
|
||||||
|
* tab/frame ID, disconnect it.
|
||||||
|
*/
|
||||||
|
for (const activeShim of this.activeShims) {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const onContentPortMessage = (message: Message) => {
|
||||||
|
this.handleContentMessage(shim, message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBridgePortMessage = (message: Message) => {
|
||||||
|
contentPort.postMessage(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDisconnect = () => {
|
||||||
|
shim.bridgePort.onMessage.removeListener(onBridgePortMessage);
|
||||||
|
contentPort.onMessage.removeListener(onContentPortMessage);
|
||||||
|
|
||||||
|
shim.bridgePort.disconnect();
|
||||||
|
contentPort.disconnect();
|
||||||
|
|
||||||
|
this.activeShims.delete(shim);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
shim.bridgePort.onDisconnect.addListener(onDisconnect);
|
||||||
|
shim.bridgePort.onMessage.addListener(onBridgePortMessage);
|
||||||
|
|
||||||
|
contentPort.onDisconnect.addListener(onDisconnect);
|
||||||
|
contentPort.onMessage.addListener(onContentPortMessage);
|
||||||
|
|
||||||
|
return shim;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleContentMessage (shim: Shim, message: Message) {
|
||||||
|
const [ destination ] = message.subject.split(":/");
|
||||||
|
if (destination === "bridge") {
|
||||||
|
shim.bridgePort.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message.subject) {
|
||||||
|
case "main:/shimInitialized": {
|
||||||
|
for (const receiver of StatusManager.getReceivers()) {
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/serviceUp"
|
||||||
|
, data: { id: receiver.id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "main:/selectReceiverBegin": {
|
||||||
|
const allMediaTypes =
|
||||||
|
ReceiverSelectorMediaType.App
|
||||||
|
| ReceiverSelectorMediaType.Tab
|
||||||
|
| ReceiverSelectorMediaType.Screen
|
||||||
|
| ReceiverSelectorMediaType.File;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selection = await ReceiverSelectorManager
|
||||||
|
.getSelection(
|
||||||
|
ReceiverSelectorMediaType.App
|
||||||
|
, allMediaTypes);
|
||||||
|
|
||||||
|
// Handle cancellation
|
||||||
|
if (!selection) {
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/selectReceiverCancelled"
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/selectReceiverCancelled"
|
||||||
|
});
|
||||||
|
|
||||||
|
loadSender({
|
||||||
|
tabId: shim.contentTabId
|
||||||
|
, frameId: shim.contentFrameId
|
||||||
|
, selection
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass selection back to shim
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/selectReceiverEnd"
|
||||||
|
, data: selection
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
// TODO: Report errors properly
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/selectReceiverCancelled"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: If we're closing a selector, make sure it's the
|
||||||
|
* same one that caused the session creation.
|
||||||
|
*/
|
||||||
|
case "main:/sessionCreated": {
|
||||||
|
const selector = await ReceiverSelectorManager.getSelector();
|
||||||
|
const shouldClose = await options.get(
|
||||||
|
"receiverSelectorWaitForConnection");
|
||||||
|
|
||||||
|
if (selector.isOpen && shouldClose) {
|
||||||
|
selector.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initStatusListeners () {
|
||||||
|
StatusManager.addEventListener("serviceUp", ev => {
|
||||||
|
for (const shim of this.activeShims) {
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/serviceUp"
|
||||||
|
, data: { id: ev.detail.id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
StatusManager.addEventListener("serviceDown", ev => {
|
||||||
|
for (const shim of this.activeShims) {
|
||||||
|
shim.contentPort.postMessage({
|
||||||
|
subject: "shim:/serviceDown"
|
||||||
|
, data: { id: ev.detail.id }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import bridge from "./lib/bridge";
|
import bridge from "../lib/bridge";
|
||||||
import options from "./lib/options";
|
import options from "../lib/options";
|
||||||
|
|
||||||
import { TypedEventTarget } from "./lib/typedEvents";
|
import { TypedEventTarget } from "../lib/typedEvents";
|
||||||
import { Message, Receiver, ReceiverStatus } from "./types";
|
import { Message, Receiver, ReceiverStatus } from "../types";
|
||||||
|
|
||||||
|
|
||||||
interface ReceiverStatusMessage extends Message {
|
interface ReceiverStatusMessage extends Message {
|
||||||
@@ -36,7 +36,9 @@ interface EventMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line:new-parens
|
// tslint:disable-next-line:new-parens
|
||||||
export default new class extends TypedEventTarget<EventMap> {
|
export default new class StatusManager
|
||||||
|
extends TypedEventTarget<EventMap> {
|
||||||
|
|
||||||
private bridgePort: browser.runtime.Port;
|
private bridgePort: browser.runtime.Port;
|
||||||
private receivers = new Map<string, Receiver>();
|
private receivers = new Map<string, Receiver>();
|
||||||
|
|
||||||
@@ -46,25 +48,31 @@ export default new class extends TypedEventTarget<EventMap> {
|
|||||||
// Bind listeners
|
// Bind listeners
|
||||||
this.onBridgePortMessage = this.onBridgePortMessage.bind(this);
|
this.onBridgePortMessage = this.onBridgePortMessage.bind(this);
|
||||||
this.onBridgePortDisconnect = this.onBridgePortDisconnect.bind(this);
|
this.onBridgePortDisconnect = this.onBridgePortDisconnect.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
this.initBridgePort();
|
public async init () {
|
||||||
|
if (!this.bridgePort) {
|
||||||
|
await this.createBridgePort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getReceivers () {
|
public getReceivers () {
|
||||||
return Array.from(this.receivers.values());
|
return Array.from(this.receivers.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initBridgePort () {
|
private async createBridgePort () {
|
||||||
this.bridgePort = await bridge.connect();
|
const bridgePort = await bridge.connect();
|
||||||
this.bridgePort.onMessage.addListener(this.onBridgePortMessage);
|
bridgePort.onMessage.addListener(this.onBridgePortMessage);
|
||||||
this.bridgePort.onDisconnect.addListener(this.onBridgePortDisconnect);
|
bridgePort.onDisconnect.addListener(this.onBridgePortDisconnect);
|
||||||
|
|
||||||
this.bridgePort.postMessage({
|
bridgePort.postMessage({
|
||||||
subject: "bridge:/initialize"
|
subject: "bridge:/initialize"
|
||||||
, data: {
|
, data: {
|
||||||
shouldWatchStatus: true
|
shouldWatchStatus: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return bridgePort;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,7 +156,7 @@ export default new class extends TypedEventTarget<EventMap> {
|
|||||||
this.bridgePort = null;
|
this.bridgePort = null;
|
||||||
|
|
||||||
window.setTimeout(async () => {
|
window.setTimeout(async () => {
|
||||||
this.initBridgePort();
|
this.bridgePort = await this.createBridgePort();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import options from "./lib/options";
|
import options from "../lib/options";
|
||||||
import { TypedEventTarget } from "./lib/typedEvents";
|
import { TypedEventTarget } from "../lib/typedEvents";
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import bridge from "../lib/bridge";
|
import bridge from "../../lib/bridge";
|
||||||
import options from "../lib/options";
|
import options from "../../lib/options";
|
||||||
|
|
||||||
|
import { TypedEventTarget } from "../../lib/typedEvents";
|
||||||
|
import { getWindowCenteredProps } from "../../lib/utils";
|
||||||
|
import { Message, Receiver } from "../../types";
|
||||||
|
|
||||||
import ReceiverSelector, {
|
import ReceiverSelector, {
|
||||||
ReceiverSelection
|
ReceiverSelection
|
||||||
, ReceiverSelectorEvents
|
, ReceiverSelectorEvents
|
||||||
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
||||||
|
|
||||||
import { TypedEventTarget } from "../lib/typedEvents";
|
|
||||||
import { getWindowCenteredProps } from "../lib/utils";
|
|
||||||
import { Message, Receiver } from "../types";
|
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ interface NativeReceiverSelectorErrorMessage extends Message {
|
|||||||
|
|
||||||
|
|
||||||
// TODO: Figure out lifetime properly
|
// TODO: Figure out lifetime properly
|
||||||
export default class NativeMacReceiverSelector
|
export default class NativeReceiverSelector
|
||||||
extends TypedEventTarget<ReceiverSelectorEvents>
|
extends TypedEventTarget<ReceiverSelectorEvents>
|
||||||
implements ReceiverSelector {
|
implements ReceiverSelector {
|
||||||
|
|
||||||
@@ -4,11 +4,11 @@ import ReceiverSelector, {
|
|||||||
ReceiverSelectorEvents
|
ReceiverSelectorEvents
|
||||||
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
||||||
|
|
||||||
import options from "../lib/options";
|
import options from "../../lib/options";
|
||||||
|
|
||||||
import { TypedEventTarget } from "../lib/typedEvents";
|
import { TypedEventTarget } from "../../lib/typedEvents";
|
||||||
import { getWindowCenteredProps } from "../lib/utils";
|
import { getWindowCenteredProps } from "../../lib/utils";
|
||||||
import { Message, Receiver } from "../types";
|
import { Message, Receiver } from "../../types";
|
||||||
|
|
||||||
|
|
||||||
export default class PopupReceiverSelector
|
export default class PopupReceiverSelector
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import { TypedEventTarget } from "../lib/typedEvents";
|
import { TypedEventTarget } from "../../lib/typedEvents";
|
||||||
import { Receiver } from "../types";
|
import { Receiver } from "../../types";
|
||||||
|
|
||||||
|
|
||||||
export enum ReceiverSelectorMediaType {
|
export enum ReceiverSelectorMediaType {
|
||||||
@@ -1,31 +1,43 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import options from "./lib/options";
|
import options from "../../lib/options";
|
||||||
|
|
||||||
import { getReceiverSelector
|
import StatusManager from "../StatusManager";
|
||||||
, ReceiverSelection
|
|
||||||
, ReceiverSelector
|
|
||||||
, ReceiverSelectorMediaType
|
|
||||||
, ReceiverSelectorType } from "./receiver_selectors";
|
|
||||||
|
|
||||||
import StatusManager from "./StatusManager";
|
import { ReceiverSelector
|
||||||
|
, ReceiverSelectorType } from "./";
|
||||||
|
import { ReceiverSelection
|
||||||
|
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
||||||
|
|
||||||
|
import NativeReceiverSelector from "./NativeReceiverSelector";
|
||||||
|
import PopupReceiverSelector from "./PopupReceiverSelector";
|
||||||
|
|
||||||
|
|
||||||
|
async function createSelector () {
|
||||||
|
const type = await options.get("receiverSelectorType");
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case ReceiverSelectorType.Native: {
|
||||||
|
return new NativeReceiverSelector();
|
||||||
|
}
|
||||||
|
case ReceiverSelectorType.Popup: {
|
||||||
|
return new PopupReceiverSelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let sharedSelector: ReceiverSelector;
|
let sharedSelector: ReceiverSelector;
|
||||||
|
|
||||||
async function getSelector () {
|
async function getSelector () {
|
||||||
return getReceiverSelector(
|
|
||||||
await options.get("receiverSelectorType"));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSharedSelector () {
|
|
||||||
if (!sharedSelector) {
|
if (!sharedSelector) {
|
||||||
sharedSelector = await getSelector();
|
sharedSelector = await createSelector();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sharedSelector;
|
return sharedSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a receiver selector with the specified
|
* Opens a receiver selector with the specified
|
||||||
* default/available media types.
|
* default/available media types.
|
||||||
@@ -46,16 +58,14 @@ async function getSelection (
|
|||||||
: Promise<ReceiverSelection> {
|
: Promise<ReceiverSelection> {
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
/**
|
|
||||||
* Close any existing selector, and renew to minimize issues
|
// Close an existing open selector
|
||||||
* with bridge failing.
|
if (sharedSelector && sharedSelector.isOpen) {
|
||||||
*/
|
|
||||||
await getSharedSelector();
|
|
||||||
if (sharedSelector.isOpen) {
|
|
||||||
sharedSelector.close();
|
sharedSelector.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedSelector = await getSelector();
|
// Get a new selector for each selection
|
||||||
|
sharedSelector = await createSelector();
|
||||||
|
|
||||||
sharedSelector.addEventListener("selected", ev => {
|
sharedSelector.addEventListener("selected", ev => {
|
||||||
console.info("fx_cast (Debug): Selected receiver", ev.detail);
|
console.info("fx_cast (Debug): Selected receiver", ev.detail);
|
||||||
@@ -72,6 +82,10 @@ async function getSelection (
|
|||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure status manager is initialized
|
||||||
|
await StatusManager.init();
|
||||||
|
|
||||||
sharedSelector.open(
|
sharedSelector.open(
|
||||||
StatusManager.getReceivers()
|
StatusManager.getReceivers()
|
||||||
, defaultMediaType
|
, defaultMediaType
|
||||||
@@ -81,5 +95,5 @@ async function getSelection (
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
getSelection
|
getSelection
|
||||||
, getSharedSelector
|
, getSelector
|
||||||
};
|
};
|
||||||
17
ext/src/background/receiverSelector/index.ts
Normal file
17
ext/src/background/receiverSelector/index.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import NativeReceiverSelector from "./NativeReceiverSelector";
|
||||||
|
import PopupReceiverSelector from "./PopupReceiverSelector";
|
||||||
|
|
||||||
|
|
||||||
|
export type ReceiverSelector =
|
||||||
|
NativeReceiverSelector
|
||||||
|
| PopupReceiverSelector;
|
||||||
|
|
||||||
|
export enum ReceiverSelectorType {
|
||||||
|
Popup
|
||||||
|
, Native
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ReceiverSelection
|
||||||
|
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import bridge from "./lib/bridge";
|
|
||||||
import loadSender from "./lib/loadSender";
|
|
||||||
import options from "./lib/options";
|
|
||||||
|
|
||||||
import { TypedEventTarget } from "./lib/typedEvents";
|
|
||||||
import { Message } from "./types";
|
|
||||||
|
|
||||||
import { ReceiverSelectorMediaType } from "./receiver_selectors";
|
|
||||||
|
|
||||||
import SelectorManager from "./SelectorManager";
|
|
||||||
import StatusManager from "./StatusManager";
|
|
||||||
|
|
||||||
|
|
||||||
type Port = browser.runtime.Port | MessagePort;
|
|
||||||
|
|
||||||
export interface Shim {
|
|
||||||
bridgePort: browser.runtime.Port;
|
|
||||||
contentPort: Port;
|
|
||||||
contentTabId?: number;
|
|
||||||
contentFrameId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const activeShims = new Set<Shim>();
|
|
||||||
|
|
||||||
StatusManager.addEventListener("serviceUp", ev => {
|
|
||||||
for (const shim of activeShims) {
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/serviceUp"
|
|
||||||
, data: { id: ev.detail.id }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
StatusManager.addEventListener("serviceDown", ev => {
|
|
||||||
for (const shim of activeShims) {
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/serviceDown"
|
|
||||||
, data: { id: ev.detail.id }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
async function createShim (port: Port): Promise<void> {
|
|
||||||
const shim = await (port instanceof MessagePort
|
|
||||||
? createShimFromBackground(port)
|
|
||||||
: createShimFromContent(port));
|
|
||||||
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/initialized"
|
|
||||||
, data: await bridge.getInfo()
|
|
||||||
});
|
|
||||||
|
|
||||||
activeShims.add(shim);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function createShimFromBackground (
|
|
||||||
contentPort: MessagePort): Promise<Shim> {
|
|
||||||
|
|
||||||
const shim: Shim = {
|
|
||||||
bridgePort: await bridge.connect()
|
|
||||||
, contentPort
|
|
||||||
};
|
|
||||||
|
|
||||||
shim.bridgePort.onDisconnect.addListener(() => {
|
|
||||||
contentPort.close();
|
|
||||||
activeShims.delete(shim);
|
|
||||||
});
|
|
||||||
|
|
||||||
shim.bridgePort.onMessage.addListener((message: Message) => {
|
|
||||||
contentPort.postMessage(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
contentPort.onmessage = ev => {
|
|
||||||
const message = ev.data as Message;
|
|
||||||
handleContentMessage(shim, message);
|
|
||||||
};
|
|
||||||
|
|
||||||
return shim;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function createShimFromContent (
|
|
||||||
contentPort: browser.runtime.Port): Promise<Shim> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there's already an active shim for the sender
|
|
||||||
* tab/frame ID, disconnect it.
|
|
||||||
*/
|
|
||||||
for (const activeShim of activeShims) {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
function onContentPortMessage (message: Message) {
|
|
||||||
handleContentMessage(shim, message);
|
|
||||||
}
|
|
||||||
function onBridgePortMessage (message: Message) {
|
|
||||||
contentPort.postMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDisconnect () {
|
|
||||||
shim.bridgePort.onMessage.removeListener(onBridgePortMessage);
|
|
||||||
contentPort.onMessage.removeListener(onContentPortMessage);
|
|
||||||
|
|
||||||
shim.bridgePort.disconnect();
|
|
||||||
contentPort.disconnect();
|
|
||||||
|
|
||||||
activeShims.delete(shim);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
shim.bridgePort.onDisconnect.addListener(onDisconnect);
|
|
||||||
shim.bridgePort.onMessage.addListener(onBridgePortMessage);
|
|
||||||
|
|
||||||
contentPort.onDisconnect.addListener(onDisconnect);
|
|
||||||
contentPort.onMessage.addListener(onContentPortMessage);
|
|
||||||
|
|
||||||
|
|
||||||
return shim;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function handleContentMessage (shim: Shim, message: Message) {
|
|
||||||
const [ destination ] = message.subject.split(":/");
|
|
||||||
if (destination === "bridge") {
|
|
||||||
shim.bridgePort.postMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (message.subject) {
|
|
||||||
case "main:/shimInitialized": {
|
|
||||||
for (const receiver of StatusManager.getReceivers()) {
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/serviceUp"
|
|
||||||
, data: { id: receiver.id }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "main:/selectReceiverBegin": {
|
|
||||||
const allMediaTypes =
|
|
||||||
ReceiverSelectorMediaType.App
|
|
||||||
| ReceiverSelectorMediaType.Tab
|
|
||||||
| ReceiverSelectorMediaType.Screen
|
|
||||||
| ReceiverSelectorMediaType.File;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const selection = await SelectorManager.getSelection(
|
|
||||||
ReceiverSelectorMediaType.App
|
|
||||||
, allMediaTypes);
|
|
||||||
|
|
||||||
// Handle cancellation
|
|
||||||
if (!selection) {
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/selectReceiverCancelled"
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/selectReceiverCancelled"
|
|
||||||
});
|
|
||||||
|
|
||||||
loadSender({
|
|
||||||
tabId: shim.contentTabId
|
|
||||||
, frameId: shim.contentFrameId
|
|
||||||
, selection
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass selection back to shim
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/selectReceiverEnd"
|
|
||||||
, data: selection
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
// TODO: Report errors properly
|
|
||||||
shim.contentPort.postMessage({
|
|
||||||
subject: "shim:/selectReceiverCancelled"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: If we're closing a selector, make sure it's the
|
|
||||||
* same one that caused the session creation.
|
|
||||||
*/
|
|
||||||
case "main:/sessionCreated": {
|
|
||||||
const selector = await SelectorManager.getSharedSelector();
|
|
||||||
|
|
||||||
const shouldClose = await options.get(
|
|
||||||
"receiverSelectorWaitForConnection");
|
|
||||||
|
|
||||||
if (selector.isOpen && shouldClose) {
|
|
||||||
selector.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default createShim;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import { ReceiverSelectorType } from "./background/receiverSelector";
|
||||||
import { Options } from "./lib/options";
|
import { Options } from "./lib/options";
|
||||||
import { ReceiverSelectorType } from "./receiver_selectors";
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { stringify } from "./utils";
|
import { stringify } from "./utils";
|
||||||
|
|
||||||
import { ReceiverSelection
|
import { ReceiverSelection
|
||||||
, ReceiverSelectorMediaType } from "../receiver_selectors";
|
, ReceiverSelectorMediaType } from "../background/receiverSelector";
|
||||||
|
|
||||||
|
|
||||||
interface LoadSenderOptions {
|
interface LoadSenderOptions {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Message } from "../types";
|
|||||||
const WEBSOCKET_DAEMON_URL = "ws://localhost:9556";
|
const WEBSOCKET_DAEMON_URL = "ws://localhost:9556";
|
||||||
|
|
||||||
|
|
||||||
type DisconnectListener = (port: browser.runtime.Port) => void;;
|
type DisconnectListener = (port: browser.runtime.Port) => void;
|
||||||
type MessageListener = (message: any) => void;
|
type MessageListener = (message: any) => void;
|
||||||
|
|
||||||
function connectNative (application: string) {
|
function connectNative (application: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import defaultOptions from "../defaultOptions";
|
import defaultOptions from "../defaultOptions";
|
||||||
|
|
||||||
import { ReceiverSelectorType } from "../receiver_selectors/";
|
import { ReceiverSelectorType } from "../background/receiverSelector";
|
||||||
import { Message } from "../types";
|
import { Message } from "../types";
|
||||||
import { TypedEventTarget } from "./typedEvents";
|
import { TypedEventTarget } from "./typedEvents";
|
||||||
|
|
||||||
@@ -16,13 +16,13 @@ export interface Options {
|
|||||||
localMediaServerPort: number;
|
localMediaServerPort: number;
|
||||||
mirroringEnabled: boolean;
|
mirroringEnabled: boolean;
|
||||||
mirroringAppId: string;
|
mirroringAppId: string;
|
||||||
receiverSelectorType: ReceiverSelectorType.Popup;
|
receiverSelectorType: ReceiverSelectorType;
|
||||||
receiverSelectorCloseIfFocusLost: boolean;
|
receiverSelectorCloseIfFocusLost: boolean;
|
||||||
receiverSelectorWaitForConnection: boolean;
|
receiverSelectorWaitForConnection: boolean;
|
||||||
userAgentWhitelistEnabled: boolean;
|
userAgentWhitelistEnabled: boolean;
|
||||||
userAgentWhitelist: string[];
|
userAgentWhitelist: string[];
|
||||||
|
|
||||||
[key: string]: Options[keyof Options]
|
[key: string]: Options[keyof Options];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -49,28 +49,30 @@ export default new class extends TypedEventTarget<EventMap> {
|
|||||||
const { oldValue, newValue } = _changes.options;
|
const { oldValue, newValue } = _changes.options;
|
||||||
const changedKeys = [];
|
const changedKeys = [];
|
||||||
|
|
||||||
for (const key in newValue) {
|
for (const key of Object.keys(newValue)) {
|
||||||
// Don't track added keys
|
if (oldValue) {
|
||||||
if (!(key in oldValue)) {
|
// Don't track added keys
|
||||||
continue;
|
if (!(key in oldValue)) {
|
||||||
}
|
|
||||||
|
|
||||||
const oldKeyValue = oldValue[key];
|
|
||||||
const newKeyValue = newValue[key];
|
|
||||||
|
|
||||||
// Equality comparison
|
|
||||||
if (oldKeyValue === newKeyValue) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array comparison
|
|
||||||
if (oldKeyValue instanceof Array
|
|
||||||
&& newKeyValue instanceof Array) {
|
|
||||||
if (oldKeyValue.length === newKeyValue.length
|
|
||||||
&& oldKeyValue.every((value, index) =>
|
|
||||||
value === newKeyValue[index])) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldKeyValue = oldValue[key];
|
||||||
|
const newKeyValue = newValue[key];
|
||||||
|
|
||||||
|
// Equality comparison
|
||||||
|
if (oldKeyValue === newKeyValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array comparison
|
||||||
|
if (oldKeyValue instanceof Array
|
||||||
|
&& newKeyValue instanceof Array) {
|
||||||
|
if (oldKeyValue.length === newKeyValue.length
|
||||||
|
&& oldKeyValue.every((value, index) =>
|
||||||
|
value === newKeyValue[index])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changedKeys.push(key);
|
changedKeys.push(key);
|
||||||
|
|||||||
@@ -8,20 +8,19 @@ import options, { Options } from "./lib/options";
|
|||||||
import { getChromeUserAgent } from "./lib/userAgents";
|
import { getChromeUserAgent } from "./lib/userAgents";
|
||||||
import { stringify } from "./lib/utils";
|
import { stringify } from "./lib/utils";
|
||||||
|
|
||||||
import { ReceiverSelection
|
|
||||||
, ReceiverSelectorMediaType } from "./receiver_selectors";
|
|
||||||
|
|
||||||
import { Message } from "./types";
|
import { Message } from "./types";
|
||||||
|
|
||||||
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
import { CAST_FRAMEWORK_LOADER_SCRIPT_URL
|
||||||
, CAST_LOADER_SCRIPT_URL } from "./lib/endpoints";
|
, CAST_LOADER_SCRIPT_URL } from "./lib/endpoints";
|
||||||
|
|
||||||
|
import { ReceiverSelection
|
||||||
|
, ReceiverSelectorMediaType } from "./background/receiverSelector";
|
||||||
|
|
||||||
import SelectorManager from "./SelectorManager";
|
import ReceiverSelectorManager
|
||||||
import StatusManager from "./StatusManager";
|
from "./background/receiverSelector/ReceiverSelectorManager";
|
||||||
|
|
||||||
import createMenus from "./createMenus";
|
import createMenus from "./background/createMenus";
|
||||||
import createShim, { Shim } from "./createShim";
|
import ShimManager from "./background/ShimManager";
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
@@ -48,33 +47,6 @@ browser.runtime.onInstalled.addListener(async details => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a message port connection with the name "shim" is
|
|
||||||
* established, pass it to createShim to handle the setup
|
|
||||||
* and maintenance.
|
|
||||||
*/
|
|
||||||
browser.runtime.onConnect.addListener(async port => {
|
|
||||||
if (port.name === "shim") {
|
|
||||||
createShim(port);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 => {
|
|
||||||
const selection = await SelectorManager.getSelection();
|
|
||||||
|
|
||||||
loadSender({
|
|
||||||
tabId: tab.id
|
|
||||||
, frameId: 0
|
|
||||||
, selection
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
async function initMenus () {
|
async function initMenus () {
|
||||||
console.info("fx_cast (Debug): init (menus)");
|
console.info("fx_cast (Debug): init (menus)");
|
||||||
@@ -93,7 +65,7 @@ async function initMenus () {
|
|||||||
| ReceiverSelectorMediaType.Screen
|
| ReceiverSelectorMediaType.Screen
|
||||||
| ReceiverSelectorMediaType.File;
|
| ReceiverSelectorMediaType.File;
|
||||||
|
|
||||||
const selection = await SelectorManager.getSelection(
|
const selection = await ReceiverSelectorManager.getSelection(
|
||||||
ReceiverSelectorMediaType.App
|
ReceiverSelectorMediaType.App
|
||||||
, allMediaTypes);
|
, allMediaTypes);
|
||||||
|
|
||||||
@@ -134,7 +106,7 @@ async function initMenus () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case menuIdMirroringCast: {
|
case menuIdMirroringCast: {
|
||||||
const selection = await SelectorManager.getSelection();
|
const selection = await ReceiverSelectorManager.getSelection();
|
||||||
|
|
||||||
loadSender({
|
loadSender({
|
||||||
tabId: tab.id
|
tabId: tab.id
|
||||||
@@ -268,8 +240,13 @@ function initWhitelist () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let isInitialized = false;
|
||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
|
if (isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If options haven't been set yet, we can't properly
|
* If options haven't been set yet, we can't properly
|
||||||
* initialize, so wait until init is called again in the
|
* initialize, so wait until init is called again in the
|
||||||
@@ -281,10 +258,42 @@ async function init () {
|
|||||||
|
|
||||||
console.info("fx_cast (Debug): init");
|
console.info("fx_cast (Debug): init");
|
||||||
|
|
||||||
|
isInitialized = true;
|
||||||
|
|
||||||
initMenus();
|
|
||||||
initRequestListener();
|
await initMenus();
|
||||||
initWhitelist();
|
await initRequestListener();
|
||||||
|
await initWhitelist();
|
||||||
|
|
||||||
|
await ShimManager.init();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a message port connection with the name "shim" is
|
||||||
|
* established, pass it to createShim to handle the setup
|
||||||
|
* and maintenance.
|
||||||
|
*/
|
||||||
|
browser.runtime.onConnect.addListener(async port => {
|
||||||
|
if (port.name === "shim") {
|
||||||
|
ShimManager.createShim(port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 => {
|
||||||
|
const selection = await ReceiverSelectorManager.getSelection();
|
||||||
|
|
||||||
|
loadSender({
|
||||||
|
tabId: tab.id
|
||||||
|
, frameId: 0
|
||||||
|
, selection
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import NativeMacReceiverSelector
|
|
||||||
from "./NativeMacReceiverSelector";
|
|
||||||
import PopupReceiverSelector
|
|
||||||
from "./PopupReceiverSelector";
|
|
||||||
|
|
||||||
|
|
||||||
import { ReceiverSelection
|
|
||||||
, ReceiverSelectorMediaType } from "./ReceiverSelector";
|
|
||||||
|
|
||||||
type ReceiverSelector = ReturnType<typeof getReceiverSelector>;
|
|
||||||
|
|
||||||
export {
|
|
||||||
ReceiverSelector
|
|
||||||
, ReceiverSelection
|
|
||||||
, ReceiverSelectorMediaType
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ReceiverSelectorType {
|
|
||||||
Popup
|
|
||||||
, NativeMac
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getReceiverSelector (type: ReceiverSelectorType) {
|
|
||||||
switch (type) {
|
|
||||||
case ReceiverSelectorType.Popup: {
|
|
||||||
return new PopupReceiverSelector();
|
|
||||||
}
|
|
||||||
case ReceiverSelectorType.NativeMac: {
|
|
||||||
return new NativeMacReceiverSelector();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
import options from "../lib/options";
|
import options from "../lib/options";
|
||||||
import cast, { ensureInit } from "../shim/export";
|
import cast, { ensureInit } from "../shim/export";
|
||||||
|
|
||||||
import { ReceiverSelectorMediaType }
|
import { ReceiverSelectorMediaType } from "../background/receiverSelector";
|
||||||
from "../receiver_selectors/ReceiverSelector";
|
|
||||||
|
|
||||||
import { Receiver } from "../types";
|
import { Receiver } from "../types";
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ import { AutoJoinPolicy
|
|||||||
import * as media from "./media";
|
import * as media from "./media";
|
||||||
|
|
||||||
|
|
||||||
import { ReceiverSelectorMediaType }
|
import { ReceiverSelectorMediaType } from "../../background/receiverSelector";
|
||||||
from "../../receiver_selectors/ReceiverSelector";
|
|
||||||
import { Receiver } from "../../types";
|
import { Receiver } from "../../types";
|
||||||
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
|
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import * as cast from "./cast";
|
|||||||
import { BridgeInfo } from "../lib/bridge";
|
import { BridgeInfo } from "../lib/bridge";
|
||||||
import { Message } from "../types";
|
import { Message } from "../types";
|
||||||
|
|
||||||
import { onMessage, onMessageResponse, sendMessage } from "./eventMessageChannel";
|
import { onMessage
|
||||||
|
, onMessageResponse
|
||||||
|
, sendMessage } from "./eventMessageChannel";
|
||||||
|
|
||||||
|
import ShimManager from "../background/ShimManager";
|
||||||
|
|
||||||
|
|
||||||
let initializedBridgeInfo: BridgeInfo;
|
let initializedBridgeInfo: BridgeInfo;
|
||||||
@@ -42,10 +46,9 @@ export function ensureInit (): Promise<MessagePort> {
|
|||||||
* URL.
|
* URL.
|
||||||
*/
|
*/
|
||||||
if (window.location.protocol === "moz-extension:") {
|
if (window.location.protocol === "moz-extension:") {
|
||||||
const { default: createShim } = await import("../createShim");
|
|
||||||
|
|
||||||
// port2 will post bridge messages to port 1
|
// port2 will post bridge messages to port 1
|
||||||
await createShim(channel.port2);
|
await ShimManager.init();
|
||||||
|
await ShimManager.createShim(channel.port2);
|
||||||
|
|
||||||
// bridge -> shim
|
// bridge -> shim
|
||||||
channel.port1.onmessage = ev => {
|
channel.port1.onmessage = ev => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import bridge, { BridgeInfo } from "../../lib/bridge";
|
|||||||
import options, { Options } from "../../lib/options";
|
import options, { Options } from "../../lib/options";
|
||||||
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/utils";
|
import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/utils";
|
||||||
|
|
||||||
import { ReceiverSelectorType } from "../../receiver_selectors";
|
import { ReceiverSelectorType } from "../../background/receiverSelector";
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
@@ -250,7 +250,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
|
|||||||
<option value={ ReceiverSelectorType.Popup }>
|
<option value={ ReceiverSelectorType.Popup }>
|
||||||
{ _("optionsReceiverSelectorTypeBrowser") }
|
{ _("optionsReceiverSelectorTypeBrowser") }
|
||||||
</option>
|
</option>
|
||||||
<option value={ ReceiverSelectorType.NativeMac }>
|
<option value={ ReceiverSelectorType.Native }>
|
||||||
{ _("optionsReceiverSelectorTypeNative") }
|
{ _("optionsReceiverSelectorTypeNative") }
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import ReactDOM from "react-dom";
|
|||||||
import { getNextEllipsis } from "../../lib/utils";
|
import { getNextEllipsis } from "../../lib/utils";
|
||||||
import { Message, Receiver } from "../../types";
|
import { Message, Receiver } from "../../types";
|
||||||
|
|
||||||
import { ReceiverSelectorMediaType }
|
import { ReceiverSelectorMediaType } from "../../background/receiverSelector";
|
||||||
from "../../receiver_selectors/ReceiverSelector";
|
|
||||||
|
|
||||||
|
|
||||||
const _ = browser.i18n.getMessage;
|
const _ = browser.i18n.getMessage;
|
||||||
|
|||||||
Reference in New Issue
Block a user