Restructure background script (#70)

Splits some background script functionality into separate modules:
 - Receiver selector handling is moved to ./SelectorManager.
 - Status bridge handling is moved to ./StatusManager.
 - Menu creation and updates are handled in ./createMenus.
 - Shim creation is handled in ./createShim.

TypedEventTarget allows EventTarget-derived classes to export typed events.

Options type definition is moved to ./lib/options, module assumes more responsibility for update handling and provides a "changed" event.

Private cast._requestSession method allows bypassing receiver selector.
This commit is contained in:
Matt Hensman
2019-07-26 00:09:51 +01:00
committed by GitHub
parent 2fe72ed24c
commit ba8c28bf39
40 changed files with 1751 additions and 1241 deletions

View File

@@ -6,9 +6,6 @@ import SessionRequest from "./SessionRequest";
import { AutoJoinPolicy
, DefaultActionPolicy } from "../enums";
import { ReceiverSelectorMediaType }
from "../../../receiver_selectors/ReceiverSelector";
export default class ApiConfig {
public additionalSessionRequests: any[] = [];
@@ -23,15 +20,5 @@ export default class ApiConfig {
, public autoJoinPolicy: string
= AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED
, public defaultActionPolicy: string
= DefaultActionPolicy.CREATE_SESSION
// TODO: Remove awful hack for mirror casting
, public _defaultMediaType: ReceiverSelectorMediaType
= ReceiverSelectorMediaType.App
, public _availableMediaTypes: ReceiverSelectorMediaType
= ReceiverSelectorMediaType.App
| ReceiverSelectorMediaType.Tab
| ReceiverSelectorMediaType.Screen
| ReceiverSelectorMediaType.File) {
}
= DefaultActionPolicy.CREATE_SESSION) {}
}

View File

@@ -4,7 +4,7 @@ import ApiConfig from "./classes/ApiConfig";
import DialRequest from "./classes/DialRequest";
import Error_ from "./classes/Error";
import Image_ from "./classes/Image";
import Receiver from "./classes/Receiver";
import Receiver_ from "./classes/Receiver";
import ReceiverDisplayStatus from "./classes/ReceiverDisplayStatus";
import SenderApplication from "./classes/SenderApplication";
import Session from "./classes/Session";
@@ -26,18 +26,18 @@ import { AutoJoinPolicy
import * as media from "./media";
import { ReceiverSelectorMediaType }
from "../../receiver_selectors/ReceiverSelector";
from "../../receiver_selectors/ReceiverSelector";
import { Receiver } from "../../types";
import { onMessage, sendMessageResponse } from "../eventMessageChannel";
type ReceiverActionListener = (
receiver: Receiver
receiver: Receiver_
, receiverAction: string) => void;
type RequestSessionSuccessCallback = (
session: Session
, selectedMedia: ReceiverSelectorMediaType) => void;
type RequestSessionSuccessCallback = (session: Session) => void;
type SuccessCallback = () => void;
type ErrorCallback = (err: Error_) => void;
@@ -61,12 +61,13 @@ export {
, SenderPlatform, SessionStatus, VolumeControlType
// Classes
, ApiConfig, DialRequest, Receiver, ReceiverDisplayStatus
, ApiConfig, DialRequest, ReceiverDisplayStatus
, SenderApplication, Session, SessionRequest, Timeout
, Volume
, Error_ as Error
, Image_ as Image
, Receiver_ as Receiver
, media
};
@@ -84,14 +85,17 @@ export function addReceiverActionListener (
export function initialize (
newApiConfig: ApiConfig
, successCallback: SuccessCallback
, errorCallback: ErrorCallback): void {
, successCallback?: SuccessCallback
, errorCallback?: ErrorCallback): void {
console.info("fx_cast (Debug): cast.initialize");
// Already initialized
if (apiConfig) {
errorCallback(new Error_(ErrorCode.INVALID_PARAMETER));
if (errorCallback) {
errorCallback(new Error_(ErrorCode.INVALID_PARAMETER));
}
return;
}
@@ -106,7 +110,9 @@ export function initialize (
? ReceiverAvailability.AVAILABLE
: ReceiverAvailability.UNAVAILABLE);
successCallback();
if (successCallback) {
successCallback();
}
}
export function logMessage (message: string): void {
@@ -125,29 +131,37 @@ export function removeReceiverActionListener (
}
export function requestSession (
successCallback: RequestSessionSuccessCallback
, errorCallback: ErrorCallback
, sessionRequest: SessionRequest
= apiConfig.sessionRequest): void {
successCallback?: RequestSessionSuccessCallback
, errorCallback?: ErrorCallback
, sessionRequest: SessionRequest = apiConfig.sessionRequest): void {
console.info("fx_cast (Debug): cast.requestSession");
// Called before initialization
if (!apiConfig) {
errorCallback(new Error_(ErrorCode.API_NOT_INITIALIZED));
if (errorCallback) {
errorCallback(new Error_(ErrorCode.API_NOT_INITIALIZED));
}
return;
}
// Already requesting session
if (sessionRequestInProgress) {
errorCallback(new Error_(ErrorCode.INVALID_PARAMETER
, "Session request already in progress."));
if (errorCallback) {
errorCallback(new Error_(ErrorCode.INVALID_PARAMETER
, "Session request already in progress."));
}
return;
}
// No available receivers
if (!receiverList.length) {
errorCallback(new Error_(ErrorCode.RECEIVER_UNAVAILABLE));
if (errorCallback) {
errorCallback(new Error_(ErrorCode.RECEIVER_UNAVAILABLE));
}
return;
}
@@ -159,21 +173,94 @@ export function requestSession (
// Open destination chooser
sendMessageResponse({
subject: "main:/selectReceiverBegin"
, data: {
defaultMediaType: apiConfig._defaultMediaType
, availableMediaTypes: apiConfig._availableMediaTypes
}
});
}
export function _requestSession (
_receiver: Receiver
, successCallback?: RequestSessionSuccessCallback
, errorCallback?: ErrorCallback): void {
console.info("fx_cast (Debug): cast._requestSession");
if (!apiConfig) {
if (errorCallback) {
errorCallback(new Error_(ErrorCode.API_NOT_INITIALIZED));
}
return;
}
if (sessionRequestInProgress) {
if (errorCallback) {
errorCallback(new Error_(ErrorCode.INVALID_PARAMETER
, "Session request already in progress."));
}
return;
}
if (!receiverList.length) {
if (errorCallback) {
errorCallback(new Error_(ErrorCode.RECEIVER_UNAVAILABLE));
}
return;
}
sessionRequestInProgress = true;
sessionSuccessCallback = successCallback;
sessionErrorCallback = errorCallback;
const selectedReceiver = new Receiver_(
_receiver.id
, _receiver.friendlyName);
(selectedReceiver as any)._address = _receiver.host;
(selectedReceiver as any)._port = _receiver.port;
function createSession () {
sessionList.push(new Session(
sessionList.length.toString() // sessionId
, apiConfig.sessionRequest.appId // appId
, _receiver.friendlyName // displayName
, [] // appImages
, selectedReceiver // receiver
, (session: Session) => {
sendMessageResponse({
subject: "main:/sessionCreated"
});
sessionRequestInProgress = false;
if (sessionSuccessCallback) {
sessionSuccessCallback(session);
}
}));
}
// If an existing session is active, stop it and start new one
if (sessionList.length) {
const lastSession = sessionList[sessionList.length - 1];
if (lastSession.status !== SessionStatus.STOPPED) {
lastSession.stop(createSession, null);
}
} else {
createSession();
}
}
export function requestSessionById (sessionId: string): void {
console.info("STUB :: cast.requestSessionById");
}
export function setCustomReceivers (
receivers: Receiver[]
, successCallback: SuccessCallback
, errorCallback: ErrorCallback): void {
receivers: Receiver_[]
, successCallback?: SuccessCallback
, errorCallback?: ErrorCallback): void {
console.info("STUB :: cast.setCustomReceivers");
}
@@ -240,7 +327,7 @@ onMessage(async message => {
case "shim:/selectReceiverEnd": {
console.info("fx_cast (Debug): Selected receiver");
const selectedReceiver = new Receiver(
const selectedReceiver = new Receiver_(
message.data.receiver.id
, message.data.receiver.friendlyName);
@@ -261,9 +348,9 @@ onMessage(async message => {
sessionRequestInProgress = false;
sessionSuccessCallback(
session
, message.data.mediaType);
if (sessionSuccessCallback) {
sessionSuccessCallback(session);
}
}));
}
@@ -287,7 +374,10 @@ onMessage(async message => {
case "shim:/selectReceiverCancelled": {
if (sessionRequestInProgress) {
sessionRequestInProgress = false;
sessionErrorCallback(new Error_(ErrorCode.CANCEL));
if (sessionErrorCallback) {
sessionErrorCallback(new Error_(ErrorCode.CANCEL));
}
}
break;

View File

@@ -1,7 +1,7 @@
"use strict";
import { CAST_LOADER_SCRIPT_URL
, CAST_SCRIPT_URLS } from "../endpoints";
, CAST_SCRIPT_URLS } from "../lib/endpoints";
(window.wrappedJSObject as any).chrome = cloneInto({}, window);

View File

@@ -1,6 +1,7 @@
"use strict";
import { loadScript } from "../lib/utils";
import { Message } from "../types";
import { onMessageResponse, sendMessage } from "./eventMessageChannel";
@@ -17,12 +18,17 @@ if (isFramework) {
// Message port to background script
const backgroundPort = browser.runtime.connect({ name: "shim" });
export const backgroundPort = browser.runtime.connect({ name: "shim" });
// Forward background messages to shim
backgroundPort.onMessage.addListener(sendMessage);
const forwardToShim = (message: Message) => sendMessage(message);
const forwardToMain = (message: Message) => backgroundPort.postMessage(message);
// Forward shim messages to background
onMessageResponse(message => {
backgroundPort.postMessage(message);
// Add message listeners
backgroundPort.onMessage.addListener(forwardToShim);
const listener = onMessageResponse(forwardToMain);
// Remove listeners
backgroundPort.onDisconnect.addListener(() => {
backgroundPort.onMessage.removeListener(forwardToShim);
listener.disconnect();
});

View File

@@ -2,11 +2,14 @@
import * as cast from "./cast";
import { BridgeInfo } from "../lib/getBridgeInfo";
import { BridgeInfo } from "../lib/bridge";
import { Message } from "../types";
import { onMessage } from "./eventMessageChannel";
let initializedBridgeInfo: BridgeInfo;
let initializedBackgroundPort: browser.runtime.Port;
/**
* To support exporting an API from a module, we need to
* retain the event-based message passing despite not
@@ -14,17 +17,44 @@ import { onMessage } from "./eventMessageChannel";
* for and emits these messages, and changing that behavior
* is too messy.
*/
export function init (): Promise<BridgeInfo> {
export function ensureInit (): Promise<browser.runtime.Port> {
return new Promise(async (resolve, reject) => {
// Trigger message port setup side-effects
import("./contentBridge");
// If already initialized, just return existing bridge info
if (initializedBridgeInfo) {
if (initializedBridgeInfo.isVersionCompatible) {
resolve(initializedBackgroundPort);
} else {
reject();
}
return;
}
/**
* If the module is imported into a background script
* context, the location will be the internal extension URL,
* whereas in a content script, it will be the content page
* URL.
*/
if (window.location.protocol === "moz-extension:") {
//
} else {
// Trigger message port setup side-effects
const { backgroundPort } = await import("./contentBridge");
initializedBackgroundPort = backgroundPort;
}
onMessage(message => {
switch (message.subject) {
case "shim:/initialized": {
const bridgeInfo: BridgeInfo = message.data;
resolve(bridgeInfo);
initializedBridgeInfo = message.data;
if (initializedBridgeInfo.isVersionCompatible) {
resolve(initializedBackgroundPort);
} else {
reject();
}
}
}
});

View File

@@ -2,7 +2,7 @@
import * as cast from "./cast";
import { CAST_FRAMEWORK_SCRIPT_URL } from "../endpoints";
import { CAST_FRAMEWORK_SCRIPT_URL } from "../lib/endpoints";
import { loadScript } from "../lib/utils";
import { onMessage } from "./eventMessageChannel";
@@ -13,6 +13,10 @@ if (!_window.chrome) {
_window.chrome = {};
}
// Remove private APIs
delete cast._requestSession;
// Create page-accessible API object
_window.chrome.cast = cast;
@@ -61,7 +65,6 @@ if (document.currentScript) {
}
}
onMessage(message => {
switch (message.subject) {
case "shim:/initialized": {