From 14cf32e625f64872a51bf6a96440ca142d8f81de Mon Sep 17 00:00:00 2001 From: Brandon Horton Date: Tue, 14 Jul 2020 13:20:13 -0700 Subject: [PATCH] Gracefully fail when globals (e.g. window, document, ipcMain) don't exist (#7) * return ErrorObservable when no document is available * added similar checks to renderer observable tools * added more error checks to main * added error catch to common * upped version * added more error catching on nonexistant DOM globals Co-authored-by: Brandon Horton --- package.json | 2 +- src/common/create-monitor.ts | 6 ++++ src/main/index.ts | 1 + src/main/on-all-webcontents.ts | 5 ++++ src/renderer/dom-observables.ts | 42 +++++++++++++++++++--------- src/renderer/monitor-ipc-renderer.ts | 4 +++ src/renderer/monitor-webviews.ts | 21 ++++++++++++-- 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 11c76ea..f098d05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipc-monitor", - "version": "1.0.2", + "version": "1.0.3", "description": "Observable-based IPC Monitoring Tool", "main": "index.js", "scripts": { diff --git a/src/common/create-monitor.ts b/src/common/create-monitor.ts index 164c11d..b2e1f2b 100644 --- a/src/common/create-monitor.ts +++ b/src/common/create-monitor.ts @@ -1,4 +1,5 @@ import { Observable } from "rxjs/Observable"; +import { _throw as throwError } from "rxjs/observable/throw"; import { Observer } from "rxjs/Observer"; import { Subscription } from "rxjs/Subscription"; import { IpcMark } from "./types"; @@ -13,5 +14,10 @@ type MonitorOptions = { export default function createMonitor({ wrap, }: MonitorOptions): Observable { + if (!wrap) { + return throwError( + new Error("No IPC wrapper provided to Observable constructor") + ); + } return Observable.create(wrap).share(); } diff --git a/src/main/index.ts b/src/main/index.ts index d6bc1c4..bbaf9e8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,5 +1,6 @@ import { merge } from "rxjs/observable/merge"; import "rxjs/add/operator/share"; +import "rxjs/add/operator/mergeMap"; import { IpcMonitor } from "../common/types"; import createIpcMainMonitor from "./monitor-ipc-main"; diff --git a/src/main/on-all-webcontents.ts b/src/main/on-all-webcontents.ts index 2d13f94..68472e6 100644 --- a/src/main/on-all-webcontents.ts +++ b/src/main/on-all-webcontents.ts @@ -4,8 +4,13 @@ import { concat } from "rxjs/observable/concat"; import { defer } from "rxjs/observable/defer"; import { from } from "rxjs/observable/from"; import { fromEvent } from "rxjs/observable/fromEvent"; +import { _throw as throwError } from "rxjs/observable/throw"; function onAllWebContents(): Observable { + if (!(webContents || app)) { + return throwError("Unable to detect webContents in this process"); + } + const newWebContents = fromEvent( app, "web-contents-created", diff --git a/src/renderer/dom-observables.ts b/src/renderer/dom-observables.ts index 6b05cf4..d5c106d 100644 --- a/src/renderer/dom-observables.ts +++ b/src/renderer/dom-observables.ts @@ -1,15 +1,24 @@ import { WebviewTag } from "electron"; import { Observable } from "rxjs/Observable"; import { from } from "rxjs/observable/from"; +import { _throw as throwError } from "rxjs/observable/throw"; import { Observer } from "rxjs/Observer"; +import "rxjs/add/operator/filter"; import "rxjs/add/operator/startWith"; import "rxjs/add/operator/mergeMap"; +import "rxjs/add/operator/switchMap"; export function onDomMutations( - target: Node = document.documentElement, + target: Node = document?.documentElement, config: MutationObserverInit = { subtree: true, childList: true } ): Observable { + if (!target) { + return throwError( + new Error("DOM Mutations Observable: Node DOM Target Provided") + ); + } + return Observable.create((observer: Observer) => { const mutation = new MutationObserver((mutationRecords) => mutationRecords.map((m) => observer.next(m)) @@ -21,17 +30,23 @@ export function onDomMutations( }); } -const windowReady = new Promise((resolve) => { - const loadedHandler = () => { - resolve(); - window.removeEventListener("load", loadedHandler); - }; - if (document.readyState === "complete") { - resolve(); - } else { - window.addEventListener("load", loadedHandler); - } -}); +const whenDomReady: Observable = from( + new Promise((resolve, reject) => { + if (!(window || document)) { + reject(new Error("Global Window/DOM not present")); + } + + if (document?.readyState === "complete") { + resolve(); + } else { + const loadedHandler = () => { + resolve(); + window.removeEventListener("load", loadedHandler); + }; + window.addEventListener("load", loadedHandler); + } + }) +); function extractWebviewElements(nodes: NodeList): WebviewTag[] { return [...nodes.values()] @@ -40,11 +55,12 @@ function extractWebviewElements(nodes: NodeList): WebviewTag[] { } export default function onWebviews(): Observable { - return from(windowReady).switchMap(() => { + return whenDomReady.switchMap(() => { const preExistingWebviews = extractWebviewElements( document.querySelectorAll("webview") ); return onDomMutations() + .filter((mutation) => mutation?.addedNodes !== undefined) .mergeMap((mutation) => extractWebviewElements(mutation.addedNodes)) .startWith(...preExistingWebviews); }); diff --git a/src/renderer/monitor-ipc-renderer.ts b/src/renderer/monitor-ipc-renderer.ts index be2570f..726d87e 100644 --- a/src/renderer/monitor-ipc-renderer.ts +++ b/src/renderer/monitor-ipc-renderer.ts @@ -11,6 +11,10 @@ import { IpcMark, ObservableConstructor } from "../common/types"; function createIpcWrapper(ipc: IpcRenderer): ObservableConstructor { return (observer: Observer) => { + if (!observer) { + throw new Error("No Observer provided to Observable constructor Fn"); + } + /** Helper Functions */ const mark = createMarker({ sink: observer, module: "ipcRenderer" }); const [wrapEventSender, wrapEventReceiver] = createFunctionWrappers({ diff --git a/src/renderer/monitor-webviews.ts b/src/renderer/monitor-webviews.ts index 3656ef6..0da8c54 100644 --- a/src/renderer/monitor-webviews.ts +++ b/src/renderer/monitor-webviews.ts @@ -16,6 +16,10 @@ function createWebviewWrapper( webview: WebviewTag ): ObservableConstructor { return (observer: Observer) => { + if (!observer) { + throw new Error("No Observer provided to Observable constructor Fn"); + } + /** Helper Functions */ const mark = createMarker({ sink: observer, module: "webviewTag" }); const [wrapEventSender] = createFunctionWrappers({ @@ -43,10 +47,23 @@ function createWebviewWrapper( }; } +function isWebviewTag(webview: WebviewTag): webview is WebviewTag { + return ( + webview && + webview instanceof HTMLElement && + typeof webview.send === "function" + ); +} + export default function createWebviewMonitor(webview: WebviewTag): IpcMonitor { - const isRendererProcess = process?.type === "renderer"; + const isRendererProcess = + process?.type === "renderer" && isWebviewTag(webview); if (!isRendererProcess) { - return throwError(new Error("Cannot access webContents from this process")); + return throwError( + new Error( + "Provided argument is not compatible with this Renderer process" + ) + ); } // monitor the WebContents object (for outgoing messages)