diff --git a/app/tsconfig.json b/app/tsconfig.json index 72c0df6..f377a96 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -4,7 +4,4 @@ "./src/**/*" , "./@types/**/*" ] - , "compilerOptions": { - "strict": true - } } diff --git a/ext/src/background/ShimManager.ts b/ext/src/background/ShimManager.ts index f4e4334..fc93a91 100644 --- a/ext/src/background/ShimManager.ts +++ b/ext/src/background/ShimManager.ts @@ -2,6 +2,7 @@ import bridge from "../lib/bridge"; import loadSender from "../lib/loadSender"; +import logger from "../lib/logger"; import options from "../lib/options"; import { Message } from "../types"; @@ -87,6 +88,11 @@ export default new class ShimManager { private async createShimFromContent ( contentPort: browser.runtime.Port): Promise { + if (contentPort.sender?.tab?.id === undefined + || contentPort.sender?.frameId === undefined) { + throw logger.error("Content shim created with an invalid port context."); + } + /** * If there's already an active shim for the sender * tab/frame ID, disconnect it. @@ -135,6 +141,11 @@ export default new class ShimManager { } private async handleContentMessage (shim: Shim, message: Message) { + if (shim.contentTabId === undefined + || shim.contentFrameId === undefined) { + throw logger.error("Shim associated with content sender missing tab/frame ID"); + } + const [ destination ] = message.subject.split(":/"); if (destination === "bridge") { shim.bridgePort.postMessage(message); diff --git a/ext/src/background/StatusManager.ts b/ext/src/background/StatusManager.ts index 87d95f1..4f0a0de 100644 --- a/ext/src/background/StatusManager.ts +++ b/ext/src/background/StatusManager.ts @@ -1,6 +1,7 @@ "use strict"; import bridge from "../lib/bridge"; +import logger from "../lib/logger"; import { TypedEventTarget } from "../lib/typedEvents"; import { Message, Receiver, ReceiverStatus } from "../types"; @@ -38,7 +39,7 @@ interface EventMap { export default new class StatusManager extends TypedEventTarget { - private bridgePort: browser.runtime.Port; + private bridgePort: (browser.runtime.Port | null) = null; private receivers = new Map(); constructor () { @@ -131,6 +132,10 @@ export default new class StatusManager const receiver = this.receivers.get(id); + if (!receiver) { + throw logger.error(`Could not find receiver (${id}) specified in status message.`); + } + // Merge with existing this.receivers.set(id, { ...receiver diff --git a/ext/src/background/background.ts b/ext/src/background/background.ts index 11f7962..b84eebf 100755 --- a/ext/src/background/background.ts +++ b/ext/src/background/background.ts @@ -2,6 +2,7 @@ import defaultOptions from "../defaultOptions"; import loadSender from "../lib/loadSender"; +import logger from "../lib/logger"; import options from "../lib/options"; import { getChromeUserAgent } from "../lib/userAgents"; @@ -67,6 +68,10 @@ function initBrowserAction () { * top-level frame. */ browser.browserAction.onClicked.addListener(async tab => { + if (tab.id === undefined) { + throw logger.error("Tab ID not found in browser action handler."); + } + const selection = await ReceiverSelectorManager.getSelection(tab.id); if (selection) { @@ -140,6 +145,10 @@ async function initMenus () { 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}.`); + } + const whitelist = await options.get("userAgentWhitelist"); // Add to whitelist and update options @@ -150,6 +159,17 @@ async function initMenus () { } + if (tab?.id === undefined) { + throw logger.error("Menu handler tab ID not found."); + } + if (info.frameId === undefined) { + throw logger.error("Menu handler frame ID not found."); + } + if (!info.pageUrl) { + throw logger.error("Menu handler page URL not found."); + } + + const availableMediaTypes = getMediaTypesForPageUrl(info.pageUrl); switch (info.menuItemId) { @@ -157,6 +177,11 @@ async function initMenus () { const selection = await ReceiverSelectorManager.getSelection( tab.id, info.frameId); + // Selection cancelled + if (!selection) { + break; + } + loadSender({ tabId: tab.id , frameId: info.frameId @@ -447,6 +472,10 @@ function initWhitelist () { const { os } = await browser.runtime.getPlatformInfo(); const chromeUserAgent = getChromeUserAgent(os); + if (!details.requestHeaders) { + throw logger.error("OnBeforeSendHeaders handler details missing requestHeaders."); + } + const host = details.requestHeaders.find( header => header.name === "Host"); @@ -457,7 +486,7 @@ function initWhitelist () { * so pretend to be an old version of Chrome to get the old * site. */ - if (host.value === "www.youtube.com") { + if (host?.value === "www.youtube.com") { header.value = getChromeUserAgent(os, true); break; } diff --git a/ext/src/background/receiverSelector/NativeReceiverSelector.ts b/ext/src/background/receiverSelector/NativeReceiverSelector.ts index a3061c2..821632c 100644 --- a/ext/src/background/receiverSelector/NativeReceiverSelector.ts +++ b/ext/src/background/receiverSelector/NativeReceiverSelector.ts @@ -33,7 +33,7 @@ export default class NativeReceiverSelector extends TypedEventTarget implements ReceiverSelector { - private bridgePort: browser.runtime.Port; + private bridgePort: (browser.runtime.Port | null) = null; private wasReceiverSelected: boolean = false; private _isOpen: boolean = false; diff --git a/ext/src/background/receiverSelector/PopupReceiverSelector.ts b/ext/src/background/receiverSelector/PopupReceiverSelector.ts index 0c90a8c..e14cf39 100644 --- a/ext/src/background/receiverSelector/PopupReceiverSelector.ts +++ b/ext/src/background/receiverSelector/PopupReceiverSelector.ts @@ -4,10 +4,11 @@ import ReceiverSelector, { ReceiverSelectorEvents , ReceiverSelectorMediaType } from "./ReceiverSelector"; +import logger from "../../lib/logger"; import options from "../../lib/options"; import { TypedEventTarget } from "../../lib/typedEvents"; -import { getWindowCenteredProps } from "../../lib/utils"; +import { getWindowCenteredProps, WindowCenteredProps } from "../../lib/utils"; import { Message, Receiver } from "../../types"; @@ -15,19 +16,19 @@ export default class PopupReceiverSelector extends TypedEventTarget implements ReceiverSelector { - private windowId: number; + private windowId?: number; - private messagePort: browser.runtime.Port; - private messagePortDisconnected: boolean; + private messagePort?: browser.runtime.Port; + private messagePortDisconnected?: boolean; - private receivers: Receiver[]; - private defaultMediaType: ReceiverSelectorMediaType; - private availableMediaTypes: ReceiverSelectorMediaType; + private receivers?: Receiver[]; + private defaultMediaType?: ReceiverSelectorMediaType; + private availableMediaTypes?: ReceiverSelectorMediaType; private wasReceiverSelected: boolean = false; private _isOpen: boolean = false; - private requestedAppId: string; + private requestedAppId?: string; constructor () { @@ -97,9 +98,22 @@ export default class PopupReceiverSelector this.defaultMediaType = defaultMediaType; this.availableMediaTypes = availableMediaTypes; - // Calculate centered size/position based on current window - const centeredProps = getWindowCenteredProps( - await browser.windows.getCurrent(), 350, 200); + + let centeredProps: WindowCenteredProps = { + 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); + } catch { + // Shouldn't ever hit this, but defaults are provided in case + } const popup = await browser.windows.create({ url: "ui/popup/index.html" @@ -107,6 +121,10 @@ export default class PopupReceiverSelector , ...centeredProps }); + if (popup?.id === undefined) { + throw logger.error("Failed to create receiver selector popup."); + } + this._isOpen = true; this.windowId = popup.id; @@ -132,7 +150,7 @@ export default class PopupReceiverSelector } this._isOpen = false; - this.requestedAppId = null; + this.requestedAppId = undefined; if (this.messagePort && !this.messagePortDisconnected) { this.messagePort.disconnect(); @@ -183,10 +201,11 @@ export default class PopupReceiverSelector } // Cleanup - this.windowId = null; - this.messagePort = null; - this.receivers = null; - this.defaultMediaType = null; + this.windowId = undefined; + this.messagePort = undefined; + this.receivers = undefined; + this.defaultMediaType = undefined; + this.availableMediaTypes = undefined; this.wasReceiverSelected = false; } @@ -203,7 +222,9 @@ export default class PopupReceiverSelector browser.windows.onFocusChanged.removeListener( this.onWindowsFocusChanged); - browser.windows.remove(this.windowId); + if (this.windowId) { + browser.windows.remove(this.windowId); + } } } } diff --git a/ext/src/background/receiverSelector/ReceiverSelectorManager.ts b/ext/src/background/receiverSelector/ReceiverSelectorManager.ts index 738262b..2896557 100644 --- a/ext/src/background/receiverSelector/ReceiverSelectorManager.ts +++ b/ext/src/background/receiverSelector/ReceiverSelectorManager.ts @@ -1,6 +1,7 @@ "use strict"; import options from "../../lib/options"; +import logger from "../../lib/logger"; import ShimManager from "../ShimManager"; import StatusManager from "../StatusManager"; @@ -38,7 +39,11 @@ let sharedSelector: ReceiverSelector; async function getSelector () { if (!sharedSelector) { - sharedSelector = await createSelector(); + try { + sharedSelector = await createSelector(); + } catch (err) { + throw logger.error("Failed to create receiver selector."); + } } return sharedSelector; @@ -59,7 +64,7 @@ async function getSelection ( contextTabId: number , contextFrameId = 0 , withMediaSender = false) - : Promise { + : Promise { return new Promise(async (resolve, reject) => { let currentShim = ShimManager.getShim( @@ -71,7 +76,7 @@ async function getSelection ( */ if (currentShim?.requestedAppId === await options.get("mirroringAppId")) { - currentShim = null; + currentShim = undefined; } let defaultMediaType = ReceiverSelectorMediaType.Tab; @@ -164,8 +169,7 @@ async function getSelection ( Array.from(StatusManager.getReceivers()) , defaultMediaType , availableMediaTypes - , currentShim?.requestedAppId - ?? (withMediaSender && DEFAULT_MEDIA_RECEIVER_APP_ID)); + , currentShim?.requestedAppId ?? DEFAULT_MEDIA_RECEIVER_APP_ID); }); } diff --git a/ext/src/lib/bridge.ts b/ext/src/lib/bridge.ts index 8890b9c..c33294d 100644 --- a/ext/src/lib/bridge.ts +++ b/ext/src/lib/bridge.ts @@ -2,6 +2,7 @@ import semver from "semver"; +import logger from "./logger"; import nativeMessaging from "./nativeMessaging"; import options from "./options"; @@ -13,7 +14,7 @@ async function connect (): Promise { bridgePort.onDisconnect.addListener(() => { if (bridgePort.error) { console.error(`${applicationName} disconnected:` - , this.bridgePort.error.message); + , bridgePort.error.message); } else { console.info(`${applicationName} disconnected`); } @@ -35,6 +36,10 @@ export interface BridgeInfo { async function getInfo (): Promise { const applicationName = await options.get("bridgeApplicationName"); + if (!applicationName) { + throw logger.error("Bridge application name not found."); + } + let applicationVersion: string; try { @@ -45,7 +50,7 @@ async function getInfo (): Promise { , { subject: "bridge:/getInfo" , data: version }); } catch (err) { - return null; + throw logger.error("Failed to connect to bridge application"); } /** diff --git a/ext/src/lib/loadSender.ts b/ext/src/lib/loadSender.ts index 9f5f586..fa15d9c 100644 --- a/ext/src/lib/loadSender.ts +++ b/ext/src/lib/loadSender.ts @@ -1,5 +1,6 @@ "use strict"; +import logger from "./logger"; import { stringify } from "./utils"; import { ReceiverSelection @@ -27,6 +28,11 @@ export default async function loadSender (opts: LoadSenderOptions) { switch (opts.selection.mediaType) { case ReceiverSelectorMediaType.App: { const shim = ShimManager.getShim(opts.tabId, opts.frameId); + if (!shim) { + throw logger.error(`Shim not found at tabId ${ + opts.tabId} / frameId ${opts.frameId}`) + } + shim.contentPort.postMessage({ subject: "shim:/launchApp" , data: { receiver: opts.selection.receiver } diff --git a/ext/src/lib/logger.ts b/ext/src/lib/logger.ts new file mode 100644 index 0000000..bf2cdcd --- /dev/null +++ b/ext/src/lib/logger.ts @@ -0,0 +1,19 @@ +"use strict"; + +export class Logger { + constructor (private prefix: string) {} + + log (message: string) { + console.log(`${this.prefix} (Log): ${message}`); + } + debug (message: string) { + console.debug(`${this.prefix} (Debug): ${message}`); + } + error (message: string) { + const formattedMessage = `${this.prefix} (Error): ${message}`; + console.error(formattedMessage); + return new Error(formattedMessage); + } +} + +export default new Logger("fx_cast"); diff --git a/ext/src/lib/nativeMessaging.ts b/ext/src/lib/nativeMessaging.ts index 9a6a482..b8cc38b 100644 --- a/ext/src/lib/nativeMessaging.ts +++ b/ext/src/lib/nativeMessaging.ts @@ -116,7 +116,7 @@ function connectNative (application: string) { socket.addEventListener("close", ev => { if (ev.code !== 1000) { - this.error = { + portObject.error = { // TODO: Set a proper error message message: "" }; diff --git a/ext/src/lib/options.ts b/ext/src/lib/options.ts index 01534c8..899c5cf 100644 --- a/ext/src/lib/options.ts +++ b/ext/src/lib/options.ts @@ -2,6 +2,8 @@ import defaultOptions from "../defaultOptions"; +import logger from "./logger"; + import { ReceiverSelectorType } from "../background/receiverSelector"; import { TypedEventTarget } from "./typedEvents"; import { TypedStorageArea } from "./typedStorage"; @@ -116,6 +118,8 @@ export default new class extends TypedEventTarget { if (options.hasOwnProperty(name)) { return options[name]; + } else { + throw logger.error(`Failed to find option ${name} in storage.`); } } @@ -139,20 +143,16 @@ export default new class extends TypedEventTarget { * storage are set. Does not override any existing options. */ public async update (defaults = defaultOptions): Promise { - const oldOpts = await this.getAll(); - const newOpts: Partial = {}; + const newOpts = await this.getAll(); // Find options not already in storage for (const [ optName, optVal ] of Object.entries(defaults)) { - if (!oldOpts.hasOwnProperty(optName)) { + if (!newOpts.hasOwnProperty(optName)) { newOpts[optName] = optVal; } } // Update storage with default values of new options - return this.setAll({ - ...oldOpts - , ...newOpts - }); + return this.setAll(newOpts); } }; diff --git a/ext/src/lib/typedEvents.ts b/ext/src/lib/typedEvents.ts index dea1770..ef2f9b4 100644 --- a/ext/src/lib/typedEvents.ts +++ b/ext/src/lib/typedEvents.ts @@ -5,13 +5,17 @@ export interface TypedEvents { } export class TypedEventTarget extends EventTarget { + // @ts-ignore public addEventListener ( type: K, listener: (ev: CustomEvent) => void): void { + // @ts-ignore super.addEventListener(type as string, listener); } + // @ts-ignore public removeEventListener ( type: K, listener: (ev: CustomEvent) => void): void { + // @ts-ignore super.removeEventListener(type as string, listener); } diff --git a/ext/src/lib/userAgents.ts b/ext/src/lib/userAgents.ts index 47acf2c..5eeb132 100644 --- a/ext/src/lib/userAgents.ts +++ b/ext/src/lib/userAgents.ts @@ -11,7 +11,7 @@ const UA_CHROME_LEGACY = "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.288 const UA_SAFARI = "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Safari/605.1.15"; -function getPlatformComponent (platform: string): string { +function getPlatformComponent (platform: string): string | undefined { switch (platform) { case "mac": return PLATFORM_MAC; break; case "win": return PLATFORM_WIN; break; diff --git a/ext/src/lib/utils.ts b/ext/src/lib/utils.ts index f2ecccd..01bdf7f 100644 --- a/ext/src/lib/utils.ts +++ b/ext/src/lib/utils.ts @@ -1,5 +1,7 @@ "use strict"; +import logger from "./logger"; + import { ReceiverSelectorMediaType } from "../background/receiverSelector"; @@ -10,6 +12,8 @@ export function getNextEllipsis (ellipsis: string): string { if (ellipsis === "..") return "..."; if (ellipsis === "...") return ""; /* tslint:enable:curly */ + + return ""; } /** @@ -79,7 +83,7 @@ export function getMediaTypesForPageUrl ( } -interface WindowCenteredProps { +export interface WindowCenteredProps { width: number; height: number; left: number; @@ -91,6 +95,11 @@ export function getWindowCenteredProps ( , width: number , height: number): WindowCenteredProps { + if (refWin.left === undefined || refWin.width === undefined + || refWin.top === undefined || refWin.height === undefined) { + throw logger.error("refWin missing positional attributes."); + } + const centerX = refWin.left + (refWin.width / 2); const centerY = refWin.top + (refWin.height / 3); diff --git a/ext/src/senders/media/index.ts b/ext/src/senders/media/index.ts index fb129df..ed4c640 100644 --- a/ext/src/senders/media/index.ts +++ b/ext/src/senders/media/index.ts @@ -1,5 +1,6 @@ "use strict"; +import logger from "../../lib/logger"; import options from "../../lib/options"; import cast, { ensureInit } from "../../shim/export"; @@ -8,7 +9,7 @@ import { Message, Receiver } from "../../types"; function getLocalAddress () { const pc = new RTCPeerConnection(); - pc.createDataChannel(null); + pc.createDataChannel(""); pc.createOffer().then(pc.setLocalDescription.bind(pc)); return new Promise((resolve, reject) => { @@ -90,6 +91,9 @@ function getSession (opts: InitOptions): Promise { } } + // TODO: Handle this + function sessionListener () {} + function onRequestSessionSuccess (session: cast.Session) { resolve(session); } @@ -103,7 +107,7 @@ function getSession (opts: InitOptions): Promise { const apiConfig = new cast.ApiConfig( sessionRequest - , null // sessionListener + , sessionListener // sessionListener , receiverListener); // receiverListener @@ -112,7 +116,7 @@ function getSession (opts: InitOptions): Promise { } function getMedia (opts: InitOptions): Promise { - return new Promise(async resolve => { + return new Promise(async (resolve, reject) => { let mediaUrl = new URL(opts.mediaUrl); let subtitleUrls: URL[] = []; @@ -137,14 +141,13 @@ function getMedia (opts: InitOptions): Promise { path => new URL(path, baseUrl)); } catch (err) { - console.error("Failed to start media server"); - return; + throw logger.error("Failed to start media server"); } } const activeTrackIds: number[] = []; - const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, null); + const mediaInfo = new cast.media.MediaInfo(mediaUrl.href, ""); mediaInfo.metadata = new cast.media.GenericMediaMetadata(); mediaInfo.metadata.metadataType = cast.media.MetadataType.GENERIC; @@ -222,7 +225,7 @@ function getMedia (opts: InitOptions): Promise { } // Add track to mediaInfo - mediaInfo.tracks.push(castTrack); + mediaInfo.tracks?.push(castTrack); // If enabled, mark as active track for load request if (track.mode === "showing" || trackElement.default) { @@ -238,9 +241,7 @@ function getMedia (opts: InitOptions): Promise { loadRequest.autoplay = false; loadRequest.activeTrackIds = activeTrackIds; - currentSession.loadMedia(loadRequest - , (media) => resolve(media) - , null); + currentSession.loadMedia(loadRequest, resolve, reject); }); } @@ -266,11 +267,11 @@ async function registerMediaElementListeners () { mediaElement.addEventListener("play", () => { - currentMedia.play(null, null, null); + currentMedia.play(); }); mediaElement.addEventListener("pause", () => { - currentMedia.pause(null, null, null); + currentMedia.pause(); }); mediaElement.addEventListener("suspend", () => { @@ -281,7 +282,7 @@ async function registerMediaElementListeners () { const seekRequest = new cast.media.SeekRequest(); seekRequest.currentTime = mediaElement.currentTime; - currentMedia.seek(seekRequest, null, null); + currentMedia.seek(seekRequest); }); mediaElement.addEventListener("ratechange", () => { @@ -394,7 +395,7 @@ export async function init (opts: InitOptions) { }); if (await options.get("mediaStopOnUnload")) { - currentSession.stop(null, null); + currentSession.stop(() => {}, () => {}); } }); } diff --git a/ext/src/senders/media/overlay/overlayContent.ts b/ext/src/senders/media/overlay/overlayContent.ts index 2db5415..096256e 100644 --- a/ext/src/senders/media/overlay/overlayContent.ts +++ b/ext/src/senders/media/overlay/overlayContent.ts @@ -202,11 +202,11 @@ class AudioPlayerElement extends PlayerElement {} class VideoPlayerElement extends PlayerElement { set overlayHidden (val: boolean) { const shadowRoot = internalShadowRoots.get(this); - (shadowRoot.querySelector(".overlay") as HTMLDivElement).hidden = val; + (shadowRoot?.querySelector(".overlay") as HTMLDivElement).hidden = val; } get overlayHidden () { const shadowRoot = internalShadowRoots.get(this); - return (shadowRoot.querySelector(".overlay") as HTMLDivElement).hidden; + return (shadowRoot?.querySelector(".overlay") as HTMLDivElement).hidden; } } diff --git a/ext/src/senders/mirroring.ts b/ext/src/senders/mirroring.ts index 0c87620..56c3cdf 100644 --- a/ext/src/senders/mirroring.ts +++ b/ext/src/senders/mirroring.ts @@ -35,7 +35,7 @@ function sendAppMessage (subject: string, data: any) { session.sendMessage(FX_CAST_RECEIVER_APP_NAMESPACE, { subject , data - }, null, null); + }, () => {}, () => {}); } @@ -76,6 +76,11 @@ async function onRequestSessionSuccess (newSession: cast.Session) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); + // Shouldn't be possible + if (!ctx) { + break; + } + // Set initial size canvas.width = window.innerWidth; canvas.height = window.innerHeight; diff --git a/ext/src/shim/cast/classes/DialRequest.ts b/ext/src/shim/cast/classes/DialRequest.ts index 3caa404..4dbed77 100755 --- a/ext/src/shim/cast/classes/DialRequest.ts +++ b/ext/src/shim/cast/classes/DialRequest.ts @@ -4,6 +4,6 @@ export default class DialRequest { constructor ( public appName: string - , public launchParameter: string = null) { + , public launchParameter: (string | null) = null) { } } diff --git a/ext/src/shim/cast/classes/Error.ts b/ext/src/shim/cast/classes/Error.ts index 47912b4..09e1115 100755 --- a/ext/src/shim/cast/classes/Error.ts +++ b/ext/src/shim/cast/classes/Error.ts @@ -4,7 +4,7 @@ export default class Error { constructor ( public code: string - , public description: string = null + , public description: (string | null) = null , public details: any = null) { } } diff --git a/ext/src/shim/cast/classes/Image.ts b/ext/src/shim/cast/classes/Image.ts index 03cdf88..cd44c10 100755 --- a/ext/src/shim/cast/classes/Image.ts +++ b/ext/src/shim/cast/classes/Image.ts @@ -2,8 +2,8 @@ // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Image export default class Image { - public width: number = null; - public height: number = null; + public width: (number | null) = null; + public height: (number | null) = null; constructor (public url: string) {} } diff --git a/ext/src/shim/cast/classes/Receiver.ts b/ext/src/shim/cast/classes/Receiver.ts index 29db92c..13245f5 100755 --- a/ext/src/shim/cast/classes/Receiver.ts +++ b/ext/src/shim/cast/classes/Receiver.ts @@ -8,14 +8,14 @@ import { ReceiverType } from "../enums"; // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Receiver export default class Receiver { - public displayStatus: ReceiverDisplayStatus = null; - public isActiveInput: boolean = null; + public displayStatus: (ReceiverDisplayStatus | null) = null; + public isActiveInput: (boolean | null) = null; public receiverType: string = ReceiverType.CAST; constructor ( public label: string , public friendlyName: string , public capabilities: string[] = [] - , public volume: Volume = null) { + , public volume: (Volume | null) = null) { } } diff --git a/ext/src/shim/cast/classes/ReceiverDisplayStatus.ts b/ext/src/shim/cast/classes/ReceiverDisplayStatus.ts index d6883fd..910ed59 100755 --- a/ext/src/shim/cast/classes/ReceiverDisplayStatus.ts +++ b/ext/src/shim/cast/classes/ReceiverDisplayStatus.ts @@ -5,7 +5,7 @@ import Image from "./Image"; // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.ReceiverDisplayStatus export default class ReceiverDisplayStatus { - public showStop: boolean = null; + public showStop: (boolean | null) = null; constructor ( public statusText: string diff --git a/ext/src/shim/cast/classes/SenderApplication.ts b/ext/src/shim/cast/classes/SenderApplication.ts index 3e8f1af..ea6bd60 100755 --- a/ext/src/shim/cast/classes/SenderApplication.ts +++ b/ext/src/shim/cast/classes/SenderApplication.ts @@ -2,8 +2,8 @@ // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.SenderApplication export default class SenderApplication { - public packageId: string = null; - public url: string = null; + public packageId: (string | null) = null; + public url: (string | null) = null; constructor (public platform: string) {} } diff --git a/ext/src/shim/cast/classes/Session.ts b/ext/src/shim/cast/classes/Session.ts index b1aa7c5..c1a9e44 100755 --- a/ext/src/shim/cast/classes/Session.ts +++ b/ext/src/shim/cast/classes/Session.ts @@ -50,7 +50,7 @@ export default class Session { public namespaces: Array<{ name: "string" }>; public senderApps: SenderApplication[]; public status: SessionStatus; - public statusText: string; + public statusText: string | null; public transportId: string; constructor ( @@ -102,12 +102,15 @@ export default class Session { switch (message.subject) { case "shim:/session/stopped": { // Disconnect from extension messages - _listener.get(this).disconnect(); + _listener.get(this)?.disconnect(); this.status = SessionStatus.STOPPED; - for (const listener of _updateListeners.get(this)) { - listener(false); + const updateListeners = _updateListeners.get(this); + if (updateListeners) { + for (const listener of updateListeners) { + listener(false); + } } break; @@ -147,8 +150,11 @@ export default class Session { } } - for (const listener of _updateListeners.get(this)) { - listener(true); + const updateListeners = _updateListeners.get(this); + if (updateListeners) { + for (const listener of updateListeners) { + listener(true); + } } break; @@ -156,19 +162,28 @@ export default class Session { case "shim:/session/impl_addMessageListener": { - const { namespace, data } = message.data; - for (const listener of - _messageListeners.get(this).get(namespace)) { - listener(namespace, data); + const { namespace, data } + : { namespace: string, data: string } = message.data; + + const messageListeners = _messageListeners + .get(this)?.get(namespace); + + if (messageListeners) { + for (const listener of messageListeners) { + listener(namespace, data); + } } break; } case "shim:/session/impl_sendMessage": { - const { messageId, error } = message.data; - const [ successCallback, errorCallback ] - = _sendMessageCallbacks.get(this).get(messageId); + const { messageId, error } + : { messageId: string, error: boolean } = message.data; + + const [ successCallback, errorCallback ] = + _sendMessageCallbacks + .get(this)?.get(messageId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); @@ -176,17 +191,16 @@ export default class Session { successCallback(); } - _sendMessageCallbacks.get(this).delete(messageId); + _sendMessageCallbacks.get(this)?.delete(messageId); break; } case "shim:/session/impl_setReceiverMuted": { const { volumeId, error } = message.data; - const [ successCallback, errorCallback ] - = _setReceiverMutedCallbacks - .get(this) - .get(volumeId); + const [ successCallback, errorCallback ] = + _setReceiverMutedCallbacks + .get(this)?.get(volumeId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); @@ -194,16 +208,16 @@ export default class Session { successCallback(); } - _setReceiverMutedCallbacks.get(this).delete(volumeId); + _setReceiverMutedCallbacks.get(this)?.delete(volumeId); break; } case "shim:/session/impl_setReceiverVolumeLevel": { const { volumeId, error } = message.data; - const [ successCallback, errorCallback ] - = _setReceiverVolumeLevelCallbacks.get(this) - .get(volumeId); + const [ successCallback, errorCallback ] = + _setReceiverVolumeLevelCallbacks + .get(this)?.get(volumeId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); @@ -211,7 +225,8 @@ export default class Session { successCallback(); } - _setReceiverVolumeLevelCallbacks.get(this).delete(volumeId); + _setReceiverVolumeLevelCallbacks + .get(this)?.delete(volumeId); break; } @@ -219,18 +234,21 @@ export default class Session { case "shim:/session/impl_stop": { const { stopId, error } = message.data; const [ successCallback, errorCallback ] - = _stopCallbacks.get(this).get(stopId); + = _stopCallbacks.get(this)?.get(stopId) ?? []; // Disconnect from extension messages - _listener.get(this).disconnect(); + _listener.get(this)?.disconnect(); if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); } else { this.status = SessionStatus.STOPPED; - for (const listener of _updateListeners.get(this)) { - listener(false); + const updateListeners = _updateListeners.get(this); + if (updateListeners) { + for (const listener of updateListeners) { + listener(false); + } } if (successCallback) { @@ -238,7 +256,7 @@ export default class Session { } } - _stopCallbacks.get(this).delete(stopId); + _stopCallbacks.get(this)?.delete(stopId); break; } @@ -257,11 +275,11 @@ export default class Session { namespace: string , listener: MessageListener) { - if (!_messageListeners.get(this).has(namespace)) { - _messageListeners.get(this).set(namespace, new Set()); + if (!_messageListeners.get(this)?.has(namespace)) { + _messageListeners.get(this)?.set(namespace, new Set()); } - _messageListeners.get(this).get(namespace).add(listener); + _messageListeners.get(this)?.get(namespace)?.add(listener); sendMessageResponse({ subject: "bridge:/session/impl_addMessageListener" @@ -271,7 +289,7 @@ export default class Session { } public addUpdateListener (listener: UpdateListener) { - _updateListeners.get(this).add(listener); + _updateListeners.get(this)?.add(listener); } public leave ( @@ -286,7 +304,7 @@ export default class Session { , _id: _id.get(this) }); - _leaveCallbacks.get(this).set(id, [ + _leaveCallbacks.get(this)?.set(id, [ successCallback , errorCallback ]); @@ -322,12 +340,17 @@ export default class Session { const message = JSON.parse(data); if (message.status && message.status.length > 0) { + const sessionId = _id.get(this); + if (!sessionId) { + return; + } + hasResponded = true; const media = new Media( this.sessionId , message.status[0].mediaSessionId - , _id.get(this)); + , sessionId); media.media = loadRequest.media; this.media = [ media ]; @@ -361,14 +384,14 @@ export default class Session { namespace: string , listener: MessageListener): void { - _messageListeners.get(this).get(namespace).delete(listener); + _messageListeners.get(this)?.get(namespace)?.delete(listener); } public removeUpdateListener ( _namespace: string , listener: UpdateListener): void { - _updateListeners.get(this).delete(listener); + _updateListeners.get(this)?.delete(listener); } public sendMessage ( @@ -389,7 +412,7 @@ export default class Session { , _id: _id.get(this) }); - _sendMessageCallbacks.get(this).set(messageId, [ + _sendMessageCallbacks.get(this)?.set(messageId, [ successCallback , errorCallback ]); @@ -408,7 +431,7 @@ export default class Session { , _id: _id.get(this) }); - _setReceiverMutedCallbacks.get(this).set(volumeId, [ + _setReceiverMutedCallbacks.get(this)?.set(volumeId, [ successCallback , errorCallback ]); @@ -427,7 +450,7 @@ export default class Session { , _id: _id.get(this) }); - _setReceiverVolumeLevelCallbacks.get(this).set(volumeId, [ + _setReceiverVolumeLevelCallbacks.get(this)?.set(volumeId, [ successCallback , errorCallback ]); @@ -445,7 +468,7 @@ export default class Session { , _id: _id.get(this) }); - _stopCallbacks.get(this).set(stopId, [ + _stopCallbacks.get(this)?.set(stopId, [ successCallback , errorCallback ]); @@ -456,6 +479,6 @@ export default class Session { this.sendMessage( "urn:x-cast:com.google.cast.media" , message - , null, null); + , () => {}, () => {}); } } diff --git a/ext/src/shim/cast/classes/SessionRequest.ts b/ext/src/shim/cast/classes/SessionRequest.ts index 2851b67..7308ed4 100755 --- a/ext/src/shim/cast/classes/SessionRequest.ts +++ b/ext/src/shim/cast/classes/SessionRequest.ts @@ -6,7 +6,7 @@ import Timeout from "./Timeout"; // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.SessionRequest export default class SessionRequest { - public language: string = null; + public language: (string | null) = null; public dialRequest: any = null; constructor ( diff --git a/ext/src/shim/cast/classes/Volume.ts b/ext/src/shim/cast/classes/Volume.ts index 00e67cb..51a3b7a 100755 --- a/ext/src/shim/cast/classes/Volume.ts +++ b/ext/src/shim/cast/classes/Volume.ts @@ -5,11 +5,11 @@ import { VolumeControlType } from "../enums"; // https://developers.google.com/cast/docs/reference/chrome/chrome.cast.Volume export default class Volume { - public controlType: VolumeControlType; - public stepInterval: number; + public controlType?: VolumeControlType; + public stepInterval?: number; constructor ( - public level: number = null - , public muted: boolean = null) { + public level: (number | null) = null + , public muted: (boolean | null) = null) { } } diff --git a/ext/src/shim/cast/index.ts b/ext/src/shim/cast/index.ts index 39775f8..82862d5 100755 --- a/ext/src/shim/cast/index.ts +++ b/ext/src/shim/cast/index.ts @@ -129,8 +129,8 @@ export function removeReceiverActionListener ( } export function requestSession ( - successCallback?: RequestSessionSuccessCallback - , errorCallback?: ErrorCallback + successCallback: RequestSessionSuccessCallback + , errorCallback: ErrorCallback , _sessionRequest: SessionRequest = apiConfig.sessionRequest): void { console.info("fx_cast (Debug): cast.requestSession"); @@ -176,8 +176,8 @@ export function requestSession ( export function _requestSession ( _receiver: Receiver - , successCallback?: RequestSessionSuccessCallback - , errorCallback?: ErrorCallback): void { + , successCallback: RequestSessionSuccessCallback + , errorCallback: ErrorCallback): void { console.info("fx_cast (Debug): cast._requestSession"); @@ -241,7 +241,7 @@ export function _requestSession ( const lastSession = sessionList[sessionList.length - 1]; if (lastSession.status !== SessionStatus.STOPPED) { - lastSession.stop(createSession, null); + lastSession.stop(createSession, () => {}); } } else { createSession(); @@ -354,7 +354,7 @@ onMessage(async message => { const lastSession = sessionList[sessionList.length - 1]; if (lastSession.status !== SessionStatus.STOPPED) { - lastSession.stop(createSession, null); + lastSession.stop(createSession, () => {}); } } else { createSession(); @@ -380,9 +380,11 @@ onMessage(async message => { case "shim:/launchApp": { const receiver: Receiver = message.data.receiver; - _requestSession(receiver, session => { - apiConfig.sessionListener(session); - }); + _requestSession(receiver + , session => { + apiConfig.sessionListener(session); + } + , () => {}); break; } diff --git a/ext/src/shim/cast/media/classes/EditTracksInfoRequest.ts b/ext/src/shim/cast/media/classes/EditTracksInfoRequest.ts index 46b6578..1325b75 100644 --- a/ext/src/shim/cast/media/classes/EditTracksInfoRequest.ts +++ b/ext/src/shim/cast/media/classes/EditTracksInfoRequest.ts @@ -4,7 +4,7 @@ export default class EditTracksInfoRequest { public requestId = 0; constructor ( - public activeTrackIds: number[] = null - , public textTrackStyle: string = null) { + public activeTrackIds: (number[] | null) = null + , public textTrackStyle: (string | null) = null) { } } diff --git a/ext/src/shim/cast/media/classes/GenericMediaMetadata.ts b/ext/src/shim/cast/media/classes/GenericMediaMetadata.ts index ba4d35c..f353853 100644 --- a/ext/src/shim/cast/media/classes/GenericMediaMetadata.ts +++ b/ext/src/shim/cast/media/classes/GenericMediaMetadata.ts @@ -6,11 +6,11 @@ import { MetadataType } from "../enums"; export default class GenericMediaMetadata { - public images: Image[] = undefined; + public images: (Image[] | undefined) = undefined; public metadataType: number = MetadataType.GENERIC; - public releaseDate: string = undefined; - public releaseYear: number = undefined; - public subtitle: string = undefined; - public title: string = undefined; + public releaseDate: (string | undefined) = undefined; + public releaseYear: (number | undefined) = undefined; + public subtitle: (string | undefined) = undefined; + public title: (string | undefined) = undefined; public type: number = MetadataType.GENERIC; } diff --git a/ext/src/shim/cast/media/classes/LoadRequest.ts b/ext/src/shim/cast/media/classes/LoadRequest.ts index b729eac..f636978 100644 --- a/ext/src/shim/cast/media/classes/LoadRequest.ts +++ b/ext/src/shim/cast/media/classes/LoadRequest.ts @@ -4,13 +4,13 @@ import MediaInfo from "./MediaInfo"; export default class LoadRequest { - public activeTrackIds: number[] = null; - public autoplay: boolean = true; - public currentTime: number = null; + public activeTrackIds: (number[] | null) = null; + public autoplay: (boolean | null) = true; + public currentTime: (number | null) = null; public customData: any = null; public media: MediaInfo; public requestId: number = 0; - public sessionId: string = null; + public sessionId: (string | null) = null; public type: string = "LOAD"; constructor (mediaInfo: MediaInfo) { diff --git a/ext/src/shim/cast/media/classes/Media.ts b/ext/src/shim/cast/media/classes/Media.ts index 5b0fb5b..a4a5684 100644 --- a/ext/src/shim/cast/media/classes/Media.ts +++ b/ext/src/shim/cast/media/classes/Media.ts @@ -40,17 +40,17 @@ const _lastCurrentTime = new WeakMap(); export default class Media { - public activeTrackIds: number[] = null; - public currentItemId: number = null; + public activeTrackIds: (number[] | null) = null; + public currentItemId: (number | null) = null; public customData: any = null; public currentTime: number = 0; - public idleReason: string = null; - public items: QueueItem[] = null; - public loadingItemId: number = null; - public media: MediaInfo = null; + public idleReason: (string | null) = null; + public items: (QueueItem[] | null) = null; + public loadingItemId: (number | null) = null; + public media: (MediaInfo | null) = null; public playbackRate: number = 1; public playerState: string = PlayerState.IDLE; - public preloadedItemId: number = null; + public preloadedItemId: (number | null) = null; public repeatMode: string = RepeatMode.OFF; public supportedMediaCommands: string[] = []; public volume: Volume = new Volume(); @@ -66,8 +66,6 @@ export default class Media { _updateListeners.set(this, new Set()); _sendMediaMessageCallbacks.set(this, new Map()); - _lastCurrentTime.set(this, undefined); - sendMessageResponse({ subject: "bridge:/media/initialize" @@ -109,19 +107,23 @@ export default class Media { } // Call update listeners - for (const listener of _updateListeners.get(this)) { - listener(true); + const updateListeners = _updateListeners.get(this); + if (updateListeners) { + for (const listener of updateListeners) { + listener(true); + } } break; } case "shim:/media/sendMediaMessageResponse": { - const { messageId, error } = message.data; + const { messageId, error } + : { messageId: string, error: any } = message.data; + const [ successCallback, errorCallback ] = _sendMediaMessageCallbacks - .get(this) - .get(messageId); + .get(this)?.get(messageId) ?? []; if (error && errorCallback) { errorCallback(new _Error(ErrorCode.SESSION_ERROR)); @@ -137,7 +139,7 @@ export default class Media { } public addUpdateListener (listener: UpdateListener): void { - _updateListeners.get(this).add(listener); + _updateListeners.get(this)?.add(listener); } public editTracksInfo ( @@ -149,12 +151,13 @@ export default class Media { } public getEstimatedTime (): number { - if (!this.currentTime) { + const lastTime = _lastCurrentTime.get(this); + + if (this.currentTime === undefined || lastTime === undefined) { return 0; } - return this.currentTime - + ((Date.now() / 1000) - _lastCurrentTime.get(this)); + return this.currentTime + ((Date.now() / 1000) - lastTime); } public getStatus ( @@ -167,7 +170,7 @@ export default class Media { } public pause ( - _pauseRequest: PauseRequest + _pauseRequest?: PauseRequest , successCallback?: SuccessCallback , errorCallback?: ErrorCallback): void { @@ -254,7 +257,7 @@ export default class Media { } public removeUpdateListener (listener: UpdateListener) { - _updateListeners.get(this).delete(listener); + _updateListeners.get(this)?.delete(listener); } public seek ( @@ -307,7 +310,7 @@ export default class Media { const messageId = uuid(); - _sendMediaMessageCallbacks.get(this).set(messageId, [ + _sendMediaMessageCallbacks.get(this)?.set(messageId, [ successCallback , errorCallback ]); diff --git a/ext/src/shim/cast/media/classes/MediaInfo.ts b/ext/src/shim/cast/media/classes/MediaInfo.ts index 38437be..700b612 100644 --- a/ext/src/shim/cast/media/classes/MediaInfo.ts +++ b/ext/src/shim/cast/media/classes/MediaInfo.ts @@ -20,12 +20,12 @@ type Metadata = | TvShowMediaMetadata; export default class MediaInfo { - public customData: string = null; - public duration: number = null; - public metadata: Metadata = null; + public customData: any = null; + public duration: (number | null) = null; + public metadata: (Metadata | null) = null; public streamType: string = StreamType.BUFFERED; - public textTrackStyle: TextTrackStyle = null; - public tracks: Track[] = null; + public textTrackStyle: (TextTrackStyle | null) = null; + public tracks: (Track[] | null) = null; constructor ( public contentId: string diff --git a/ext/src/shim/cast/media/classes/MovieMediaMetadata.ts b/ext/src/shim/cast/media/classes/MovieMediaMetadata.ts index 7741a98..ff478ea 100644 --- a/ext/src/shim/cast/media/classes/MovieMediaMetadata.ts +++ b/ext/src/shim/cast/media/classes/MovieMediaMetadata.ts @@ -6,12 +6,12 @@ import { MetadataType } from "../enums"; export default class MovieMediaMetadata { - public images: Image[] = undefined; + public images: (Image[] | undefined) = undefined; public metadataType: number = MetadataType.MOVIE; - public releaseDate: string = undefined; - public releaseYear: number = undefined; - public studio: string = undefined; - public subtitle: string = undefined; - public title: string = undefined; + public releaseDate: (string | undefined) = undefined; + public releaseYear: (number | undefined) = undefined; + public studio: (string | undefined) = undefined; + public subtitle: (string | undefined) = undefined; + public title: (string | undefined) = undefined; public type: number = MetadataType.MOVIE; } diff --git a/ext/src/shim/cast/media/classes/MusicTrackMediaMetadata.ts b/ext/src/shim/cast/media/classes/MusicTrackMediaMetadata.ts index 8144b4f..5859489 100644 --- a/ext/src/shim/cast/media/classes/MusicTrackMediaMetadata.ts +++ b/ext/src/shim/cast/media/classes/MusicTrackMediaMetadata.ts @@ -6,18 +6,18 @@ import { MetadataType } from "../enums"; export default class MusicTrackMediaMetadata { - public albumArtist: string = undefined; - public albumName: string = undefined; - public artist: string = undefined; - public artistName: string = undefined; - public composer: string = undefined; - public discNumber: number = undefined; - public images: Image[] = undefined; + public albumArtist: (string | undefined) = undefined; + public albumName: (string | undefined) = undefined; + public artist: (string | undefined) = undefined; + public artistName: (string | undefined) = undefined; + public composer: (string | undefined) = undefined; + public discNumber: (number | undefined) = undefined; + public images: (Image[] | undefined) = undefined; public metadataType: number = MetadataType.MUSIC_TRACK; - public releaseDate: string = undefined; - public releaseYear: number = undefined; - public songName: string = undefined; - public title: string = undefined; - public trackNumber: number = undefined; + public releaseDate: (string | undefined) = undefined; + public releaseYear: (number | undefined) = undefined; + public songName: (string | undefined) = undefined; + public title: (string | undefined) = undefined; + public trackNumber: (number | undefined) = undefined; public type: number = MetadataType.MUSIC_TRACK; } diff --git a/ext/src/shim/cast/media/classes/PhotoMediaMetadata.ts b/ext/src/shim/cast/media/classes/PhotoMediaMetadata.ts index 54c54d2..b29c7b6 100644 --- a/ext/src/shim/cast/media/classes/PhotoMediaMetadata.ts +++ b/ext/src/shim/cast/media/classes/PhotoMediaMetadata.ts @@ -6,15 +6,15 @@ import { MetadataType } from "../enums"; export default class PhotoMediaMetadata { - public artist: string = undefined; - public creationDateTime: string = undefined; - public height: number = undefined; - public images: Image[] = undefined; - public latitude: number = undefined; - public location: string = undefined; - public longitude: number = undefined; + public artist: (string | undefined) = undefined; + public creationDateTime: (string | undefined) = undefined; + public height: (number | undefined) = undefined; + public images: (Image[] | undefined) = undefined; + public latitude: (number | undefined) = undefined; + public location: (string | undefined) = undefined; + public longitude: (number | undefined) = undefined; public metadataType: number = MetadataType.PHOTO; - public title: string = undefined; + public title: (string | undefined) = undefined; public type: number = MetadataType.PHOTO; - public width: number = undefined; + public width: (number | undefined) = undefined; } diff --git a/ext/src/shim/cast/media/classes/QueueInsertItemsRequest.ts b/ext/src/shim/cast/media/classes/QueueInsertItemsRequest.ts index f2bb8ba..bfc31cb 100644 --- a/ext/src/shim/cast/media/classes/QueueInsertItemsRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueInsertItemsRequest.ts @@ -5,9 +5,9 @@ import QueueItem from "./QueueItem"; export default class QueueInsertItemsRequest { public customData: any = null; - public insertBefore: number = null; - public requestId: number = null; - public sessionId: string = null; + public insertBefore: (number | null) = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public type: string = "QUEUE_INSERT"; constructor ( diff --git a/ext/src/shim/cast/media/classes/QueueItem.ts b/ext/src/shim/cast/media/classes/QueueItem.ts index 64d12e1..418d447 100644 --- a/ext/src/shim/cast/media/classes/QueueItem.ts +++ b/ext/src/shim/cast/media/classes/QueueItem.ts @@ -4,12 +4,12 @@ import MediaInfo from "./MediaInfo"; export default class QueueItem { - public activeTrackIds: number[] = null; + public activeTrackIds: (number[] | null) = null; public autoplay: boolean = true; public customData: any = null; - public itemId: number = null; + public itemId: (number | null) = null; public media: MediaInfo; - public playbackDuration: number = null; + public playbackDuration: (number | null) = null; public preloadTime: number = 0; public startTime: number = 0; diff --git a/ext/src/shim/cast/media/classes/QueueLoadRequest.ts b/ext/src/shim/cast/media/classes/QueueLoadRequest.ts index 80fc4a8..3e4c03c 100644 --- a/ext/src/shim/cast/media/classes/QueueLoadRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueLoadRequest.ts @@ -8,8 +8,8 @@ import { RepeatMode } from "../enums"; export default class QueueLoadRequest { public customData: any = null; public repeatMode: string = RepeatMode.OFF; - public requestId: number = null; - public sessionId: string = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public startIndex: number = 0; public type: string = "QUEUE_LOAD"; diff --git a/ext/src/shim/cast/media/classes/QueueRemoveItemsRequest.ts b/ext/src/shim/cast/media/classes/QueueRemoveItemsRequest.ts index 9e95136..78e9587 100644 --- a/ext/src/shim/cast/media/classes/QueueRemoveItemsRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueRemoveItemsRequest.ts @@ -2,8 +2,8 @@ export default class QueueRemoveItemsRequest { public customData: any = null; - public requestId: number = null; - public sessionId: string = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public type: string = "QUEUE_REMOVE"; constructor ( diff --git a/ext/src/shim/cast/media/classes/QueueReorderItemsRequest.ts b/ext/src/shim/cast/media/classes/QueueReorderItemsRequest.ts index 26ffb4e..9a8c83e 100644 --- a/ext/src/shim/cast/media/classes/QueueReorderItemsRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueReorderItemsRequest.ts @@ -2,9 +2,9 @@ export default class QueueReorderItemsRequest { public customData: any = null; - public insertBefore: number = null; - public requestId: number = null; - public sessionId: string = null; + public insertBefore: (number | null) = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public type: string = "QUEUE_REORDER"; constructor ( diff --git a/ext/src/shim/cast/media/classes/QueueSetPropertiesRequest.ts b/ext/src/shim/cast/media/classes/QueueSetPropertiesRequest.ts index 799f6c4..54222a2 100644 --- a/ext/src/shim/cast/media/classes/QueueSetPropertiesRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueSetPropertiesRequest.ts @@ -2,8 +2,8 @@ export default class QueueSetPropertiesRequest { public customData: any = null; - public repeatMode: string = null; - public requestId: number = null; - public sessionId: string = null; + public repeatMode: (string | null) = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public type: string = "QUEUE_UPDATE"; } diff --git a/ext/src/shim/cast/media/classes/QueueUpdateItemsRequest.ts b/ext/src/shim/cast/media/classes/QueueUpdateItemsRequest.ts index 31c71c2..ffcc767 100644 --- a/ext/src/shim/cast/media/classes/QueueUpdateItemsRequest.ts +++ b/ext/src/shim/cast/media/classes/QueueUpdateItemsRequest.ts @@ -5,8 +5,8 @@ import QueueItem from "./QueueItem"; export default class QueueUpdateItemsRequest { public customData: any = null; - public requestId: number = null; - public sessionId: string = null; + public requestId: (number | null) = null; + public sessionId: (string | null) = null; public type: string = "QUEUE_UPDATE"; constructor ( diff --git a/ext/src/shim/cast/media/classes/SeekRequest.ts b/ext/src/shim/cast/media/classes/SeekRequest.ts index e87f43e..9266e8c 100644 --- a/ext/src/shim/cast/media/classes/SeekRequest.ts +++ b/ext/src/shim/cast/media/classes/SeekRequest.ts @@ -1,7 +1,7 @@ "use strict"; export default class SeekRequest { - public currentTime: number = null; + public currentTime: (number | null) = null; public customData: any = null; - public resumeState: string = null; + public resumeState: (string | null) = null; } diff --git a/ext/src/shim/cast/media/classes/TextTrackStyle.ts b/ext/src/shim/cast/media/classes/TextTrackStyle.ts index 11b132a..50b836c 100644 --- a/ext/src/shim/cast/media/classes/TextTrackStyle.ts +++ b/ext/src/shim/cast/media/classes/TextTrackStyle.ts @@ -1,16 +1,16 @@ "use strict"; export default class TextTrackStyle { - public backgroundColor: string = null; + public backgroundColor: (string | null) = null; public customData: any = null; - public edgeColor: string = null; - public edgeType: string = null; - public fontFamily: string = null; - public fontGenericFamily: string = null; - public fontScale: number = null; - public fontStyle: string = null; - public foregroundColor: string = null; - public windowColor: string = null; - public windowRoundedCornerRadius: number = null; - public windowType: string = null; + public edgeColor: (string | null) = null; + public edgeType: (string | null) = null; + public fontFamily: (string | null) = null; + public fontGenericFamily: (string | null) = null; + public fontScale: (number | null) = null; + public fontStyle: (string | null) = null; + public foregroundColor: (string | null) = null; + public windowColor: (string | null) = null; + public windowRoundedCornerRadius: (number | null) = null; + public windowType: (string | null) = null; } diff --git a/ext/src/shim/cast/media/classes/Track.ts b/ext/src/shim/cast/media/classes/Track.ts index f436aea..1909a46 100644 --- a/ext/src/shim/cast/media/classes/Track.ts +++ b/ext/src/shim/cast/media/classes/Track.ts @@ -2,11 +2,11 @@ export default class Track { public customData: any = null; - public language: string = null; - public name: string = null; - public subtype: string = null; - public trackContentId: string = null; - public trackContentType: string = null; + public language: (string | null) = null; + public name: (string | null) = null; + public subtype: (string | null) = null; + public trackContentId: (string | null) = null; + public trackContentType: (string | null) = null; constructor ( public trackId: number diff --git a/ext/src/shim/cast/media/classes/TvShowMediaMetadata.ts b/ext/src/shim/cast/media/classes/TvShowMediaMetadata.ts index 878a017..63df9ce 100644 --- a/ext/src/shim/cast/media/classes/TvShowMediaMetadata.ts +++ b/ext/src/shim/cast/media/classes/TvShowMediaMetadata.ts @@ -6,16 +6,16 @@ import { MetadataType } from "../enums"; export default class TvShowMediaMetadata { - public episode: number = undefined; - public episodeNumber: number = undefined; - public episodeTitle: string = undefined; - public images: Image[] = undefined; + public episode: (number | undefined) = undefined; + public episodeNumber: (number | undefined) = undefined; + public episodeTitle: (string | undefined) = undefined; + public images: (Image[] | undefined) = undefined; public metadataType: number = MetadataType.TV_SHOW; - public originalAirdate: string = undefined; - public releaseYear: number = undefined; - public season: number = undefined; - public seasonNumber: number = undefined; - public seriesTitle: string = undefined; - public title: string = undefined; + public originalAirdate: (string | undefined) = undefined; + public releaseYear: (number | undefined) = undefined; + public season: (number | undefined) = undefined; + public seasonNumber: (number | undefined) = undefined; + public seriesTitle: (string | undefined) = undefined; + public title: (string | undefined) = undefined; public type: number = MetadataType.TV_SHOW; } diff --git a/ext/src/shim/content.ts b/ext/src/shim/content.ts index 34d52b6..b57ba02 100644 --- a/ext/src/shim/content.ts +++ b/ext/src/shim/content.ts @@ -15,7 +15,7 @@ import { CAST_LOADER_SCRIPT_URL * URLs, replace it with the standard API URL, the request for * which is handled in the main script. */ -const { get, set } = Reflect.getOwnPropertyDescriptor( +const desc = Reflect.getOwnPropertyDescriptor( HTMLScriptElement.prototype.wrappedJSObject, "src"); Reflect.defineProperty( @@ -23,13 +23,13 @@ Reflect.defineProperty( configurable: true , enumerable: true - , get + , get: desc?.get - , set: exportFunction(function (value) { + , set: exportFunction(function setFunc (this: HTMLScriptElement, value) { if (CAST_SCRIPT_URLS.includes(value)) { - return set.call(this, CAST_LOADER_SCRIPT_URL); + return desc?.set?.call(this, CAST_LOADER_SCRIPT_URL); } - return set.call(this, value); + return desc?.set?.call(this, value); }, window) }); diff --git a/ext/src/shim/eventMessageChannel.ts b/ext/src/shim/eventMessageChannel.ts index 7d32913..8603bbd 100644 --- a/ext/src/shim/eventMessageChannel.ts +++ b/ext/src/shim/eventMessageChannel.ts @@ -25,12 +25,14 @@ export function onMessage (listener: ListenerFunc): ListenerObject { ev.stopPropagation(); } + // @ts-ignore document.addEventListener( "__castMessage" , on__castMessage, true); return { disconnect () { + // @ts-ignore document.removeEventListener( "__castMessage" , on__castMessage, true); @@ -52,12 +54,14 @@ export function onMessageResponse (listener: ListenerFunc): ListenerObject { listener(JSON.parse(ev.detail)); } + // @ts-ignore document.addEventListener( "__castMessageResponse" , on__castMessageResponse, true); return { disconnect () { + // @ts-ignore document.removeEventListener( "__castMessageResponse" , on__castMessageResponse, true); diff --git a/ext/src/shim/framework/classes/CastOptions.ts b/ext/src/shim/framework/classes/CastOptions.ts index 36ebd97..0f33985 100644 --- a/ext/src/shim/framework/classes/CastOptions.ts +++ b/ext/src/shim/framework/classes/CastOptions.ts @@ -5,8 +5,8 @@ import * as cast from "../../cast"; export default class CastOptions { public autoJoinPolicy: string = cast.AutoJoinPolicy.TAB_AND_ORIGIN_SCOPED; - public language: string = null; - public receiverApplicationId: string = null; + public language: (string | null) = null; + public receiverApplicationId: (string | null) = null; public resumeSavedSession: boolean = true; constructor (options: CastOptions = ({} as CastOptions)) { diff --git a/ext/src/shim/framework/classes/RemotePlayer.ts b/ext/src/shim/framework/classes/RemotePlayer.ts index 476a628..6780af8 100644 --- a/ext/src/shim/framework/classes/RemotePlayer.ts +++ b/ext/src/shim/framework/classes/RemotePlayer.ts @@ -15,19 +15,19 @@ export default class RemotePlayer { public canControlVolume = false; public canPause = false; public canSeek = false; - public controller: RemotePlayerController = null; + public controller: (RemotePlayerController | null) = null; public currentTime = 0; public displayName = ""; public displayStatus = ""; public duration = 0; - public imageUrl: string = null; + public imageUrl: (string | null) = null; public isConnected = false; public isMediaLoaded = false; public isMuted = false; public isPaused = false; - public mediaInfo: cast.media.MediaInfo = null; - public playerState: string = null; - public savedPlayerState: SavedPlayerState = null; + public mediaInfo: (cast.media.MediaInfo | null) = null; + public playerState: (string | null) = null; + public savedPlayerState: (SavedPlayerState | null) = null; public statusText = ""; public title = ""; public volumeLevel = 1; diff --git a/ext/src/shim/framework/classes/SessionStateEventData.ts b/ext/src/shim/framework/classes/SessionStateEventData.ts index 800350c..b244f16 100644 --- a/ext/src/shim/framework/classes/SessionStateEventData.ts +++ b/ext/src/shim/framework/classes/SessionStateEventData.ts @@ -10,7 +10,7 @@ export default class SessionStateEventData extends EventData { constructor ( public session: CastSession , public sessionState: string - , public errorCode: string = null) { + , public errorCode: (string | null) = null) { super(SessionEventType.APPLICATION_STATUS_CHANGED); } diff --git a/ext/src/shim/types.ts b/ext/src/shim/types.ts index ffe6f70..7d5da03 100644 --- a/ext/src/shim/types.ts +++ b/ext/src/shim/types.ts @@ -11,5 +11,5 @@ export type MessageListener = (namespace: string, message: string) => void; export type UpdateListener = (isAlive: boolean) => void; export type LoadSuccessCallback = (media: Media) => void; -export type Callbacks = [ SuccessCallback, ErrorCallback ]; +export type Callbacks = [ SuccessCallback?, ErrorCallback? ]; export type CallbacksMap = Map; diff --git a/ext/src/ui/options/Bridge.tsx b/ext/src/ui/options/Bridge.tsx index 245476d..4dbebe1 100644 --- a/ext/src/ui/options/Bridge.tsx +++ b/ext/src/ui/options/Bridge.tsx @@ -68,12 +68,12 @@ interface BridgeState { isUpdateAvailable: boolean; wasErrorCheckingUpdates: boolean; checkUpdatesEllipsis: string; - updateStatus: string; + updateStatus?: string; } export default class Bridge extends Component { private updateData: any; - private updateStatusTimeout: number; + private updateStatusTimeout?: number; constructor (props: BridgeProps) { super(props); @@ -83,7 +83,6 @@ export default class Bridge extends Component { , isUpdateAvailable: false , wasErrorCheckingUpdates: false , checkUpdatesEllipsis: "..." - , updateStatus: null }; this.onCheckUpdates = this.onCheckUpdates.bind(this); @@ -142,7 +141,7 @@ export default class Bridge extends Component { let statusIcon: string; let statusTitle: string; - let statusText: string; + let statusText: (string | null); if (!this.props.info) { statusIcon = "assets/icons8-cancel-120.png"; @@ -156,6 +155,8 @@ export default class Bridge extends Component { statusIcon = "assets/icons8-warn-120.png"; statusTitle = _("optionsBridgeIssueStatusTitle"); } + + statusText = null; } return ( @@ -213,11 +214,14 @@ export default class Bridge extends Component { this.setState({ isCheckingUpdates: false , isUpdateAvailable - , updateStatus: !isUpdateAvailable - ? _("optionsBridgeUpdateStatusNoUpdates") - : null }); + if (!isUpdateAvailable) { + this.setState({ + updateStatus: _("optionsBridgeUpdateStatusNoUpdates") + }); + } + this.showUpdateStatus(); } @@ -237,7 +241,7 @@ export default class Bridge extends Component { } this.updateStatusTimeout = window.setTimeout(() => { this.setState({ - updateStatus: null + updateStatus: undefined }); }, 1500); } diff --git a/ext/src/ui/options/EditableList.tsx b/ext/src/ui/options/EditableList.tsx index 96edec9..23cf117 100644 --- a/ext/src/ui/options/EditableList.tsx +++ b/ext/src/ui/options/EditableList.tsx @@ -23,7 +23,7 @@ interface EditableListState { export default class EditableList extends Component< EditableListProps, EditableListState> { - private rawViewTextArea: HTMLTextAreaElement; + private rawViewTextArea: (HTMLTextAreaElement | null) = null; constructor (props: EditableListProps) { super(props); @@ -140,13 +140,13 @@ export default class EditableList extends Component< if ("itemPattern" in this.props) { for (const item of newItems) { if (!this.props.itemPattern.test(item)) { - this.rawViewTextArea.setCustomValidity( + this.rawViewTextArea?.setCustomValidity( this.props.itemPatternError(item)); return; } } - this.rawViewTextArea.setCustomValidity(""); + this.rawViewTextArea?.setCustomValidity(""); } this.props.onChange(newItems); @@ -154,6 +154,10 @@ export default class EditableList extends Component< } private handleRawViewTextAreaChange (ev: React.ChangeEvent) { + if (!this.rawViewTextArea) { + return; + } + if (this.rawViewTextArea.scrollHeight > this.rawViewTextArea.clientHeight) { this.rawViewTextArea.style.height = `${this.rawViewTextArea.scrollHeight}px`; } diff --git a/ext/src/ui/options/EditableListItem.tsx b/ext/src/ui/options/EditableListItem.tsx index a6e3197..cd69ae8 100644 --- a/ext/src/ui/options/EditableListItem.tsx +++ b/ext/src/ui/options/EditableListItem.tsx @@ -23,7 +23,7 @@ interface EditableListItemState { export default class EditableListItem extends Component< EditableListItemProps, EditableListItemState> { - private input: HTMLInputElement; + private input: (HTMLInputElement | null) = null; constructor (props: EditableListItemProps) { super(props); @@ -96,7 +96,7 @@ export default class EditableListItem extends Component< editing: true , editValue: this.props.text }, () => { - this.input.focus(); + this.input?.focus(); }); } diff --git a/ext/src/ui/options/index.tsx b/ext/src/ui/options/index.tsx index 6c7383b..17019a6 100644 --- a/ext/src/ui/options/index.tsx +++ b/ext/src/ui/options/index.tsx @@ -10,6 +10,7 @@ import Bridge from "./Bridge"; import EditableList from "./EditableList"; import bridge, { BridgeInfo } from "../../lib/bridge"; +import logger from "../../lib/logger"; import options, { Options } from "../../lib/options"; import { REMOTE_MATCH_PATTERN_REGEX } from "../../lib/utils"; @@ -83,25 +84,23 @@ function getInputValue (input: HTMLInputElement) { interface OptionsAppState { hasLoaded: boolean; - options: Options; - bridgeInfo: BridgeInfo; - platform: string; bridgeLoading: boolean; isFormValid: boolean; hasSaved: boolean; + + options?: Options; + bridgeInfo?: BridgeInfo; + platform?: string; } class OptionsApp extends Component<{}, OptionsAppState> { - private form: HTMLFormElement; + private form: (HTMLFormElement | null) = null; constructor (props: {}) { super(props); this.state = { hasLoaded: false - , options: null - , bridgeInfo: null - , platform: null , bridgeLoading: true , isFormValid: true , hasSaved: false @@ -113,11 +112,11 @@ class OptionsApp extends Component<{}, OptionsAppState> { this.handleInputChange = this.handleInputChange.bind(this); this.handleWhitelistChange = this.handleWhitelistChange.bind(this); - this.handleReceiverSelectorTypeChange - = this.handleReceiverSelectorTypeChange.bind(this); + this.handleReceiverSelectorTypeChange = + this.handleReceiverSelectorTypeChange.bind(this); - this.getWhitelistItemPatternError - = this.getWhitelistItemPatternError.bind(this); + this.getWhitelistItemPatternError = + this.getWhitelistItemPatternError.bind(this); } public async componentDidMount () { @@ -126,14 +125,18 @@ class OptionsApp extends Component<{}, OptionsAppState> { , options: await options.getAll() }); - const bridgeInfo = await bridge.getInfo(); - const { os } = await browser.runtime.getPlatformInfo(); + try { + const bridgeInfo = await bridge.getInfo(); + const { os } = await browser.runtime.getPlatformInfo(); - this.setState({ - bridgeInfo - , platform: os - , bridgeLoading: false - }); + this.setState({ + bridgeInfo + , platform: os + , bridgeLoading: false + }); + } catch { + logger.error("Failed to fetch bridge/platform info."); + } } public render () { @@ -143,9 +146,10 @@ class OptionsApp extends Component<{}, OptionsAppState> { return (
- + { this.state.bridgeInfo && this.state.platform && + }
{ this.form = form; }} onSubmit={ this.handleFormSubmit } @@ -163,7 +167,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -175,7 +179,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -190,7 +194,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -202,7 +206,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -219,7 +223,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -240,7 +244,7 @@ class OptionsApp extends Component<{}, OptionsAppState> { required min="1025" max="65535" - value={ this.state.options.localMediaServerPort } + value={ this.state.options?.localMediaServerPort } onChange={ this.handleInputChange } />
@@ -258,7 +262,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -274,7 +278,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
{ _("optionsMirroringAppIdDescription") } @@ -298,7 +302,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -329,7 +333,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -350,7 +354,7 @@ class OptionsApp extends Component<{}, OptionsAppState> {
@@ -363,10 +367,11 @@ class OptionsApp extends Component<{}, OptionsAppState> { { _("optionsUserAgentWhitelistContent") }
- + { this.state.options?.userAgentWhitelist && + }
@@ -400,20 +405,22 @@ class OptionsApp extends Component<{}, OptionsAppState> { private async handleFormSubmit (ev: React.FormEvent) { ev.preventDefault(); - this.form.reportValidity(); + this.form?.reportValidity(); try { - await options.setAll(this.state.options); + if (this.state.options) { + await options.setAll(this.state.options); - this.setState({ - hasSaved: true - }, () => { - window.setTimeout(() => { - this.setState({ - hasSaved: false - }); - }, 1000); - }); + this.setState({ + hasSaved: true + }, () => { + window.setTimeout(() => { + this.setState({ + hasSaved: false + }); + }, 1000); + }); + } } catch (err) { console.error("Failed to save options"); } @@ -422,14 +429,20 @@ class OptionsApp extends Component<{}, OptionsAppState> { private handleFormChange (ev: React.FormEvent) { ev.preventDefault(); - this.setState({ - isFormValid: this.form.checkValidity() - }); + const isFormValid = this.form?.checkValidity(); + if (isFormValid !== undefined) { + this.setState({ + isFormValid + }); + } } private handleInputChange (ev: React.ChangeEvent) { this.setState(currentState => { - currentState.options[ev.target.name] = getInputValue(ev.target); + if (currentState.options) { + currentState.options[ev.target.name] = getInputValue(ev.target); + } + return currentState; }); } @@ -438,14 +451,20 @@ class OptionsApp extends Component<{}, OptionsAppState> { ev: React.ChangeEvent) { this.setState(currentState => { - currentState.options[ev.target.name] = parseInt(ev.target.value); + if (currentState.options) { + currentState.options[ev.target.name] = parseInt(ev.target.value); + } + return currentState; }); } private handleWhitelistChange (whitelist: string[]) { this.setState(currentState => { - currentState.options.userAgentWhitelist = whitelist; + if (currentState.options) { + currentState.options.userAgentWhitelist = whitelist; + } + return currentState; }); } diff --git a/ext/src/ui/popup/index.tsx b/ext/src/ui/popup/index.tsx index b4f5aa9..e2aa853 100755 --- a/ext/src/ui/popup/index.tsx +++ b/ext/src/ui/popup/index.tsx @@ -31,14 +31,15 @@ interface PopupAppState { mediaType: ReceiverSelectorMediaType; availableMediaTypes: ReceiverSelectorMediaType; isLoading: boolean; - filePath: string; - requestedAppId: string; + + filePath?: string; + requestedAppId?: string; } class PopupApp extends Component<{}, PopupAppState> { - private port: browser.runtime.Port; - private win: browser.windows.Window; - private defaultMediaType: ReceiverSelectorMediaType; + private port?: browser.runtime.Port; + private win?: browser.windows.Window; + private defaultMediaType?: ReceiverSelectorMediaType; constructor (props: {}) { super(props); @@ -48,8 +49,6 @@ class PopupApp extends Component<{}, PopupAppState> { , mediaType: ReceiverSelectorMediaType.App , availableMediaTypes: ReceiverSelectorMediaType.App , isLoading: false - , filePath: null - , requestedAppId: null }; // Store window ref @@ -78,7 +77,12 @@ class PopupApp extends Component<{}, PopupAppState> { } case "popup:/populateReceiverList": { - this.defaultMediaType = message.data.defaultMediaType; + const { receivers, availableMediaTypes, defaultMediaType } + : { receivers: Receiver[] + , availableMediaTypes: ReceiverSelectorMediaType + , defaultMediaType: ReceiverSelectorMediaType } = message.data; + + this.defaultMediaType = defaultMediaType; this.setState({ receivers: message.data.receivers @@ -99,6 +103,10 @@ class PopupApp extends Component<{}, PopupAppState> { public componentDidUpdate () { setTimeout(() => { + if (this.win?.id === undefined) { + return; + } + // Fit window to content height const frameHeight = window.outerHeight - window.innerHeight; const windowHeight = document.body.clientHeight + frameHeight; @@ -137,8 +145,9 @@ class PopupApp extends Component<{}, PopupAppState> { @@ -188,7 +197,7 @@ class PopupApp extends Component<{}, PopupAppState> { isLoading: true }); - this.port.postMessage({ + this.port?.postMessage({ subject: "receiverSelector:/selected" , data: { receiver @@ -199,7 +208,7 @@ class PopupApp extends Component<{}, PopupAppState> { } private onStop (receiver: Receiver) { - this.port.postMessage({ + this.port?.postMessage({ subject: "receiverSelector:/stop" , data: { receiver } }); @@ -220,9 +229,11 @@ class PopupApp extends Component<{}, PopupAppState> { } // Set media type to default if failed to set filePath - this.setState({ - mediaType: this.defaultMediaType - }); + if (this.defaultMediaType) { + this.setState({ + mediaType: this.defaultMediaType + }); + } } else { this.setState({ mediaType @@ -230,7 +241,7 @@ class PopupApp extends Component<{}, PopupAppState> { } this.setState({ - filePath: null + filePath: undefined }); } } @@ -279,6 +290,10 @@ class ReceiverEntry extends Component { } public render () { + if (!this.props.receiver.status) { + return; + } + const { application } = this.props.receiver.status; return ( @@ -287,7 +302,7 @@ class ReceiverEntry extends Component { { this.props.receiver.friendlyName }
+ title={ !application.isIdleScreen ? application.statusText : "" }> { application.isIdleScreen ? `${this.props.receiver.host}:${this.props.receiver.port}` : application.statusText } @@ -311,6 +326,10 @@ class ReceiverEntry extends Component { } private handleCast () { + if (!this.props.receiver.status) { + return; + } + const { application } = this.props.receiver.status; if (!application.isIdleScreen && this.state.showAlternateAction) { diff --git a/tsconfig.json b/tsconfig.json index 1d9ab69..71c7d1a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,5 +7,6 @@ , "removeComments": true , "resolveJsonModule": true , "target": "es6" + . "strict": true } }