tracker: better crossdomain check; angularMode -> forceNgOff toggle
This commit is contained in:
parent
d39e9b8816
commit
5009491f63
6 changed files with 55 additions and 37 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ export default class Nodes {
|
|||
private readonly elementListeners: Map<number, Array<ElementListener>> = 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--
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ export default abstract class Observer {
|
|||
}
|
||||
this.commitNodes()
|
||||
}) as MutationCallback,
|
||||
this.app.options.angularMode,
|
||||
this.app.options.forceNgOff,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export default function (app: App): void {
|
|||
}
|
||||
}
|
||||
}) as MutationCallback,
|
||||
app.options.angularMode,
|
||||
app.options.forceNgOff,
|
||||
)
|
||||
|
||||
app.attachStopCallback(() => {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue