diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index efa55ccbc..ef590e96d 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "14.0.11-3", + "version": "14.0.11-6", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index d4d48befa..bf48d3d55 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -168,11 +168,12 @@ type AppOptions = { network?: NetworkOptions /** - * use this flag if you're using Angular + * use this flag to force angular detection to be offline + * * basically goes around window.Zone api changes to mutation observer * and event listeners * */ - angularMode?: boolean + forceNgOff?: boolean } & WebworkerOptions & SessOptions @@ -318,7 +319,7 @@ export default class App { __save_canvas_locally: false, useAnimationFrame: false, }, - angularMode: false, + forceNgOff: false, } this.options = simpleMerge(defaultOptions, options) @@ -338,7 +339,7 @@ export default class App { this.sanitizer = new Sanitizer({ app: this, options }) this.nodes = new Nodes({ node_id: this.options.node_id, - angularMode: Boolean(options.angularMode), + forceNgOff: Boolean(options.forceNgOff), }) this.observer = new Observer({ app: this, options }) this.ticker = new Ticker(this) @@ -935,11 +936,11 @@ export default class App { const createListener = () => target - ? createEventListener(target, type, listener, useCapture, this.options.angularMode) + ? createEventListener(target, type, listener, useCapture, this.options.forceNgOff) : null const deleteListener = () => target - ? deleteEventListener(target, type, listener, useCapture, this.options.angularMode) + ? deleteEventListener(target, type, listener, useCapture, this.options.forceNgOff) : null this.attachStartCallback(createListener, useSafe) diff --git a/tracker/tracker/src/main/app/nodes.ts b/tracker/tracker/src/main/app/nodes.ts index 65f04c206..77f4b5dc3 100644 --- a/tracker/tracker/src/main/app/nodes.ts +++ b/tracker/tracker/src/main/app/nodes.ts @@ -10,11 +10,11 @@ export default class Nodes { private readonly elementListeners: Map> = new Map() private nextNodeId = 0 private readonly node_id: string - private readonly angularMode: boolean + private readonly forceNgOff: boolean - constructor(params: { node_id: string; angularMode: boolean }) { + constructor(params: { node_id: string; forceNgOff: boolean }) { this.node_id = params.node_id - this.angularMode = params.angularMode + this.forceNgOff = params.forceNgOff } syntheticMode(frameOrder: number) { @@ -48,7 +48,7 @@ export default class Nodes { if (id === undefined) { return } - createEventListener(node, type, listener, useCapture, this.angularMode) + createEventListener(node, type, listener, useCapture, this.forceNgOff) let listeners = this.elementListeners.get(id) if (listeners === undefined) { listeners = [] @@ -80,7 +80,7 @@ export default class Nodes { if (listeners !== undefined) { this.elementListeners.delete(id) listeners.forEach((listener) => - deleteEventListener(node, listener[0], listener[1], listener[2], this.angularMode), + deleteEventListener(node, listener[0], listener[1], listener[2], this.forceNgOff), ) } this.totalNodeAmount-- diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index a7b5a01c5..e177ef740 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -119,7 +119,7 @@ export default abstract class Observer { } this.commitNodes() }) as MutationCallback, - this.app.options.angularMode, + this.app.options.forceNgOff, ) } diff --git a/tracker/tracker/src/main/modules/img.ts b/tracker/tracker/src/main/modules/img.ts index 28c672ea2..c3cec1b97 100644 --- a/tracker/tracker/src/main/modules/img.ts +++ b/tracker/tracker/src/main/modules/img.ts @@ -99,7 +99,7 @@ export default function (app: App): void { } } }) as MutationCallback, - app.options.angularMode, + app.options.forceNgOff, ) app.attachStopCallback(() => { diff --git a/tracker/tracker/src/main/utils.ts b/tracker/tracker/src/main/utils.ts index 653d7b9d6..521ab7334 100644 --- a/tracker/tracker/src/main/utils.ts +++ b/tracker/tracker/src/main/utils.ts @@ -95,15 +95,27 @@ export function canAccessIframe(iframe: HTMLIFrameElement) { } } -export function canAccessTarget(target: any) { +export function canAccessTarget(target: EventTarget): boolean { try { - if (target.contentWindow) { - // If this property is inaccessible, it will throw due to cross-origin restrictions - return Boolean(target.contentWindow.location) + if (target instanceof HTMLIFrameElement) { + void target.contentDocument + } else if (target instanceof Window) { + void target.document + } else if (target instanceof Document) { + void target.defaultView + } else if ('nodeType' in target) { + void (target as Node).nodeType + } else if ('addEventListener' in target) { + void (target as EventTarget).addEventListener } + return true } catch (e) { - return false + if (e instanceof DOMException && e.name === 'SecurityError') { + return false + } } + + return true } function dec2hex(dec: number) { @@ -143,8 +155,8 @@ export function ngSafeBrowserMethod(method: string): string { : method } -export function createMutationObserver(cb: MutationCallback, angularMode?: boolean) { - if (angularMode) { +export function createMutationObserver(cb: MutationCallback, forceNgOff?: boolean) { + if (!forceNgOff) { const mObserver = ngSafeBrowserMethod('MutationObserver') as 'MutationObserver' return new window[mObserver](cb) } else { @@ -157,21 +169,24 @@ export function createEventListener( event: string, cb: EventListenerOrEventListenerObject, capture?: boolean, - angularMode?: boolean, + forceNgOff?: boolean, ) { // we need to check if target is crossorigin frame or no and if we can access it - if (target instanceof HTMLIFrameElement && !canAccessIframe(target)) { + if (!canAccessTarget(target)) { return } - let safeAddEventListener: 'addEventListener' - if (angularMode) { + let safeAddEventListener = 'addEventListener' as unknown as 'addEventListener' + if (!forceNgOff) { safeAddEventListener = ngSafeBrowserMethod('addEventListener') as 'addEventListener' - } else { - safeAddEventListener = 'addEventListener' } try { - target[safeAddEventListener](event, cb, capture) - target.addEventListener(event, cb, capture) + // parent has angular, but child frame don't + if (target[safeAddEventListener]) { + target[safeAddEventListener](event, cb, capture) + } else { + // @ts-ignore + target.addEventListener(event, cb, capture) + } } catch (e) { const msg = e.message console.error( @@ -188,20 +203,22 @@ export function deleteEventListener( event: string, cb: EventListenerOrEventListenerObject, capture?: boolean, - angularMode?: boolean, + forceNgOff?: boolean, ) { - if (target instanceof HTMLIFrameElement && !canAccessIframe(target)) { + if (!canAccessTarget(target)) { return } - let safeRemoveEventListener: 'removeEventListener' - if (angularMode) { + let safeRemoveEventListener = 'removeEventListener' as unknown as 'removeEventListener' + if (!forceNgOff) { safeRemoveEventListener = ngSafeBrowserMethod('removeEventListener') as 'removeEventListener' - } else { - safeRemoveEventListener = 'removeEventListener' } try { - target[safeRemoveEventListener](event, cb, capture) - target.removeEventListener(event, cb, capture) + if (target[safeRemoveEventListener]) { + target[safeRemoveEventListener](event, cb, capture) + } else { + // @ts-ignore + target.removeEventListener(event, cb, capture) + } } catch (e) { const msg = e.message console.error(