port tracker-14 fixes to latest
This commit is contained in:
parent
d26e6ec098
commit
87d45714b1
10 changed files with 189 additions and 68 deletions
|
|
@ -6,9 +6,10 @@ import {
|
|||
} from '@ant-design/icons';
|
||||
import { Button, InputNumber, Popover } from 'antd';
|
||||
import { Slider } from 'antd';
|
||||
import cn from 'classnames';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
||||
function DropdownAudioPlayer({
|
||||
|
|
@ -27,15 +28,24 @@ function DropdownAudioPlayer({
|
|||
const fileLengths = useRef<Record<string, number>>({});
|
||||
const { time = 0, speed = 1, playing, sessionStart } = store?.get() ?? {};
|
||||
|
||||
const files = React.useMemo(() => audioEvents.map((pa) => {
|
||||
const data = pa.payload;
|
||||
const nativeTs = data.timestamp
|
||||
return {
|
||||
url: data.url,
|
||||
timestamp: data.timestamp,
|
||||
start: nativeTs ? nativeTs : pa.timestamp - sessionStart,
|
||||
};
|
||||
}), [audioEvents.length, sessionStart])
|
||||
const files = React.useMemo(
|
||||
() =>
|
||||
audioEvents.map((pa) => {
|
||||
const data = pa.payload;
|
||||
const nativeTs = data.timestamp;
|
||||
const startTs = nativeTs
|
||||
? nativeTs > sessionStart
|
||||
? nativeTs - sessionStart
|
||||
: nativeTs
|
||||
: pa.timestamp - sessionStart;
|
||||
return {
|
||||
url: data.url,
|
||||
timestamp: data.timestamp,
|
||||
start: startTs,
|
||||
};
|
||||
}),
|
||||
[audioEvents.length, sessionStart]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
Object.entries(audioRefs.current).forEach(([url, audio]) => {
|
||||
|
|
@ -43,10 +53,10 @@ function DropdownAudioPlayer({
|
|||
audio.loop = false;
|
||||
audio.addEventListener('loadedmetadata', () => {
|
||||
fileLengths.current[url] = audio.duration;
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
}, [audioRefs.current])
|
||||
});
|
||||
}, [audioRefs.current]);
|
||||
|
||||
const toggleMute = () => {
|
||||
Object.values(audioRefs.current).forEach((audio) => {
|
||||
|
|
@ -125,7 +135,7 @@ function DropdownAudioPlayer({
|
|||
|
||||
useEffect(() => {
|
||||
const deltaMs = delta * 1000;
|
||||
const deltaTime = Math.abs(lastPlayerTime.current - time - deltaMs)
|
||||
const deltaTime = Math.abs(lastPlayerTime.current - time - deltaMs);
|
||||
if (deltaTime >= 250) {
|
||||
handleSeek(time);
|
||||
}
|
||||
|
|
@ -134,7 +144,7 @@ function DropdownAudioPlayer({
|
|||
const file = files.find((f) => f.url === url);
|
||||
const fileLength = fileLengths.current[url];
|
||||
if (file) {
|
||||
if (fileLength && (fileLength*1000)+file.start < time) {
|
||||
if (fileLength && fileLength * 1000 + file.start < time) {
|
||||
return;
|
||||
}
|
||||
if (time >= file.start) {
|
||||
|
|
@ -155,8 +165,8 @@ function DropdownAudioPlayer({
|
|||
if (audio) {
|
||||
audio.muted = isMuted;
|
||||
}
|
||||
})
|
||||
}, [isMuted])
|
||||
});
|
||||
}, [isMuted]);
|
||||
|
||||
useEffect(() => {
|
||||
changePlaybackSpeed(speed);
|
||||
|
|
@ -168,7 +178,7 @@ function DropdownAudioPlayer({
|
|||
const file = files.find((f) => f.url === url);
|
||||
const fileLength = fileLengths.current[url];
|
||||
if (file) {
|
||||
if (fileLength && (fileLength*1000)+file.start < time) {
|
||||
if (fileLength && fileLength * 1000 + file.start < time) {
|
||||
audio.pause();
|
||||
return;
|
||||
}
|
||||
|
|
@ -183,7 +193,8 @@ function DropdownAudioPlayer({
|
|||
setVolume(isMuted ? 0 : volume);
|
||||
}, [playing]);
|
||||
|
||||
const buttonIcon = 'px-2 cursor-pointer border border-gray-light hover:border-main hover:text-main hover:z-10 h-fit'
|
||||
const buttonIcon =
|
||||
'px-2 cursor-pointer border border-gray-light hover:border-main hover:text-main hover:z-10 h-fit';
|
||||
return (
|
||||
<div className={'relative'}>
|
||||
<div className={'flex items-center'} style={{ height: 24 }}>
|
||||
|
|
@ -205,20 +216,14 @@ function DropdownAudioPlayer({
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={
|
||||
cn(buttonIcon, 'rounded-l')
|
||||
}
|
||||
>
|
||||
<div className={cn(buttonIcon, 'rounded-l')}>
|
||||
{isMuted ? <MutedOutlined /> : <SoundOutlined />}
|
||||
</div>
|
||||
</Popover>
|
||||
<div
|
||||
onClick={toggleVisible}
|
||||
style={{ marginLeft: -1 }}
|
||||
className={
|
||||
cn(buttonIcon, 'rounded-r')
|
||||
}
|
||||
className={cn(buttonIcon, 'rounded-r')}
|
||||
>
|
||||
<CaretDownOutlined />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import logger from 'App/logger';
|
||||
import { resolveURL } from "../../messages/rewriter/urlResolve";
|
||||
|
||||
import type Screen from '../../Screen/Screen';
|
||||
import type { Message, SetNodeScroll } from '../../messages';
|
||||
|
|
@ -32,6 +31,8 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
private readonly vTexts: Map<number, VText> = new Map() // map vs object here?
|
||||
private readonly vElements: Map<number, VElement> = new Map()
|
||||
private readonly olVRoots: Map<number, OnloadVRoot> = new Map()
|
||||
/** required to keep track of iframes, frameId : vnodeId */
|
||||
private readonly iframeRoots: Record<number, number> = {}
|
||||
/** Constructed StyleSheets https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
|
||||
* as well as <style> tag owned StyleSheets
|
||||
*/
|
||||
|
|
@ -219,6 +220,10 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
if (['STYLE', 'style', 'LINK'].includes(msg.tag)) {
|
||||
vElem.prioritized = true
|
||||
}
|
||||
if (this.vElements.has(msg.id)) {
|
||||
logger.error("CreateElementNode: Node already exists", msg)
|
||||
return
|
||||
}
|
||||
this.vElements.set(msg.id, vElem)
|
||||
this.insertNode(msg)
|
||||
this.removeBodyScroll(msg.id, vElem)
|
||||
|
|
@ -316,6 +321,10 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
case MType.CreateIFrameDocument: {
|
||||
const vElem = this.vElements.get(msg.frameID)
|
||||
if (!vElem) { logger.error("CreateIFrameDocument: Node not found", msg); return }
|
||||
if (this.iframeRoots[msg.frameID] && !this.olVRoots.has(msg.id)) {
|
||||
this.olVRoots.delete(this.iframeRoots[msg.frameID])
|
||||
}
|
||||
this.iframeRoots[msg.frameID] = msg.id
|
||||
const vRoot = OnloadVRoot.fromVElement(vElem)
|
||||
vRoot.catch(e => logger.warn(e, msg))
|
||||
this.olVRoots.set(msg.id, vRoot)
|
||||
|
|
|
|||
|
|
@ -167,6 +167,12 @@ type AppOptions = {
|
|||
}
|
||||
|
||||
network?: NetworkOptions
|
||||
/**
|
||||
* use this flag if you're using Angular
|
||||
* basically goes around window.Zone api changes to mutation observer
|
||||
* and event listeners
|
||||
* */
|
||||
angularMode?: boolean
|
||||
} & WebworkerOptions &
|
||||
SessOptions
|
||||
|
||||
|
|
@ -312,6 +318,7 @@ export default class App {
|
|||
__save_canvas_locally: false,
|
||||
useAnimationFrame: false,
|
||||
},
|
||||
angularMode: false,
|
||||
}
|
||||
this.options = simpleMerge(defaultOptions, options)
|
||||
|
||||
|
|
@ -329,7 +336,7 @@ export default class App {
|
|||
this.localStorage = this.options.localStorage ?? window.localStorage
|
||||
this.sessionStorage = this.options.sessionStorage ?? window.sessionStorage
|
||||
this.sanitizer = new Sanitizer(this, options)
|
||||
this.nodes = new Nodes(this.options.node_id)
|
||||
this.nodes = new Nodes(this.options.node_id, Boolean(options.angularMode))
|
||||
this.observer = new Observer(this, options)
|
||||
this.ticker = new Ticker(this)
|
||||
this.ticker.attach(() => this.commit())
|
||||
|
|
@ -366,6 +373,7 @@ export default class App {
|
|||
window.parent.postMessage(
|
||||
{
|
||||
line: proto.polling,
|
||||
context: this.contextId,
|
||||
},
|
||||
'*',
|
||||
)
|
||||
|
|
@ -420,6 +428,7 @@ export default class App {
|
|||
}
|
||||
}
|
||||
|
||||
/** used by child iframes for crossdomain only */
|
||||
/** used by child iframes for crossdomain only */
|
||||
parentActive = false
|
||||
checkStatus = () => {
|
||||
|
|
@ -447,7 +456,6 @@ export default class App {
|
|||
this.frameOderNumber = data.frameOrderNumber
|
||||
this.debug.log('starting iframe tracking', data)
|
||||
this.allowAppStart()
|
||||
this.delay = data.frameTimeOffset
|
||||
}
|
||||
if (data.line === proto.killIframe) {
|
||||
if (this.active()) {
|
||||
|
|
@ -456,7 +464,11 @@ export default class App {
|
|||
}
|
||||
}
|
||||
|
||||
trackedFrames: number[] = []
|
||||
/**
|
||||
* context ids for iframes,
|
||||
* order is not so important as long as its consistent
|
||||
* */
|
||||
trackedFrames: string[] = []
|
||||
crossDomainIframeListener = (event: MessageEvent) => {
|
||||
if (!this.active() || event.source === window) return
|
||||
const { data } = event
|
||||
|
|
@ -471,24 +483,34 @@ export default class App {
|
|||
return console.error('Couldnt connect to event.source for child iframe tracking')
|
||||
}
|
||||
const id = await this.checkNodeId(pageIframes, event.source)
|
||||
if (id && !this.trackedFrames.includes(id)) {
|
||||
if (id && !this.trackedFrames.includes(data.context)) {
|
||||
try {
|
||||
this.trackedFrames.push(id)
|
||||
this.trackedFrames.push(data.context)
|
||||
await this.waitStarted()
|
||||
const token = this.session.getSessionToken()
|
||||
const order = this.trackedFrames.findIndex((f) => f === data.context) + 1
|
||||
if (order === 0) {
|
||||
this.debug.error(
|
||||
'Couldnt get order number for iframe',
|
||||
data.context,
|
||||
this.trackedFrames,
|
||||
)
|
||||
}
|
||||
const iframeData = {
|
||||
line: proto.iframeId,
|
||||
context: this.contextId,
|
||||
id,
|
||||
token,
|
||||
frameOrderNumber: this.trackedFrames.length,
|
||||
frameTimeOffset: this.timestamp(),
|
||||
// since indexes go from 0 we +1
|
||||
frameOrderNumber: order,
|
||||
}
|
||||
this.debug.log('Got child frame signal; nodeId', id, event.source, iframeData)
|
||||
// @ts-ignore
|
||||
event.source?.postMessage(iframeData, '*')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
} else {
|
||||
this.debug.log('Couldnt get node id for iframe', event.source, pageIframes)
|
||||
}
|
||||
}
|
||||
void signalId()
|
||||
|
|
@ -544,25 +566,42 @@ export default class App {
|
|||
this.messages.push(...mappedMessages)
|
||||
}
|
||||
if (data.line === proto.polling) {
|
||||
if (!this.pollingQueue.length) {
|
||||
if (!this.pollingQueue.order.length) {
|
||||
return
|
||||
}
|
||||
while (this.pollingQueue.length) {
|
||||
const msg = this.pollingQueue.shift()
|
||||
const nextCommand = this.pollingQueue.order[0]
|
||||
if (this.pollingQueue[nextCommand].includes(data.context)) {
|
||||
this.pollingQueue[nextCommand] = this.pollingQueue[nextCommand].filter(
|
||||
(c: string) => c !== data.context,
|
||||
)
|
||||
// @ts-ignore
|
||||
event.source?.postMessage({ line: msg }, '*')
|
||||
event.source?.postMessage({ line: nextCommand }, '*')
|
||||
if (this.pollingQueue[nextCommand].length === 0) {
|
||||
this.pollingQueue.order.shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pollingQueue: string[] = []
|
||||
/**
|
||||
* { command : [remaining iframes] }
|
||||
* + order of commands
|
||||
**/
|
||||
pollingQueue: Record<string, any> = {
|
||||
order: [],
|
||||
}
|
||||
private readonly addCommand = (cmd: string) => {
|
||||
this.pollingQueue.order.push(cmd)
|
||||
this.pollingQueue[cmd] = [...this.trackedFrames]
|
||||
}
|
||||
|
||||
public bootChildrenFrames = async () => {
|
||||
await this.waitStarted()
|
||||
this.pollingQueue.push(proto.startIframe)
|
||||
this.addCommand(proto.startIframe)
|
||||
}
|
||||
|
||||
public killChildrenFrames = () => {
|
||||
this.pollingQueue.push(proto.killIframe)
|
||||
this.addCommand(proto.killIframe)
|
||||
}
|
||||
|
||||
signalIframeTracker = () => {
|
||||
|
|
@ -753,6 +792,7 @@ export default class App {
|
|||
if (this.worker === undefined || !this.messages.length) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
requestIdleCb(() => {
|
||||
this.messages.unshift(TabData(this.session.getTabId()))
|
||||
|
|
@ -854,9 +894,13 @@ export default class App {
|
|||
}
|
||||
|
||||
const createListener = () =>
|
||||
target ? createEventListener(target, type, listener, useCapture) : null
|
||||
target
|
||||
? createEventListener(target, type, listener, useCapture, this.options.angularMode)
|
||||
: null
|
||||
const deleteListener = () =>
|
||||
target ? deleteEventListener(target, type, listener, useCapture) : null
|
||||
target
|
||||
? deleteEventListener(target, type, listener, useCapture, this.options.angularMode)
|
||||
: null
|
||||
|
||||
this.attachStartCallback(createListener, useSafe)
|
||||
this.attachStopCallback(deleteListener, useSafe)
|
||||
|
|
@ -1640,7 +1684,6 @@ export default class App {
|
|||
|
||||
stop(stopWorker = true): void {
|
||||
if (this.activityState !== ActivityState.NotActive) {
|
||||
console.trace('stopped')
|
||||
try {
|
||||
if (!this.insideIframe && this.options.crossdomain?.enabled) {
|
||||
this.killChildrenFrames()
|
||||
|
|
@ -1660,6 +1703,7 @@ export default class App {
|
|||
this.trackedFrames = []
|
||||
this.parentActive = false
|
||||
this.canStart = false
|
||||
this.pollingQueue = { order: [] }
|
||||
} finally {
|
||||
this.activityState = ActivityState.NotActive
|
||||
this.debug.log('OpenReplay tracking stopped.')
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ export default class Nodes {
|
|||
private readonly elementListeners: Map<number, Array<ElementListener>> = new Map()
|
||||
private nextNodeId = 0
|
||||
|
||||
constructor(private readonly node_id: string) {}
|
||||
constructor(
|
||||
private readonly node_id: string,
|
||||
private readonly angularMode: boolean,
|
||||
) {}
|
||||
|
||||
syntheticMode(frameOrder: number) {
|
||||
const maxSafeNumber = 9007199254740900
|
||||
const maxSafeNumber = Number.MAX_SAFE_INTEGER
|
||||
const placeholderSize = 99999999
|
||||
const nextFrameId = placeholderSize * frameOrder
|
||||
// I highly doubt that this will ever happen,
|
||||
|
|
@ -25,7 +28,7 @@ export default class Nodes {
|
|||
}
|
||||
|
||||
// Attached once per Tracker instance
|
||||
attachNodeCallback(nodeCallback: NodeCallback): void {
|
||||
attachNodeCallback = (nodeCallback: NodeCallback): void => {
|
||||
this.nodeCallbacks.push(nodeCallback)
|
||||
}
|
||||
|
||||
|
|
@ -33,12 +36,12 @@ export default class Nodes {
|
|||
this.nodes.forEach((node) => cb(node))
|
||||
}
|
||||
|
||||
attachNodeListener(node: Node, type: string, listener: EventListener, useCapture = true): void {
|
||||
attachNodeListener = (node: Node, type: string, listener: EventListener, useCapture = true): void => {
|
||||
const id = this.getID(node)
|
||||
if (id === undefined) {
|
||||
return
|
||||
}
|
||||
createEventListener(node, type, listener, useCapture)
|
||||
createEventListener(node, type, listener, useCapture, this.angularMode)
|
||||
let listeners = this.elementListeners.get(id)
|
||||
if (listeners === undefined) {
|
||||
listeners = []
|
||||
|
|
@ -70,7 +73,7 @@ export default class Nodes {
|
|||
if (listeners !== undefined) {
|
||||
this.elementListeners.delete(id)
|
||||
listeners.forEach((listener) =>
|
||||
deleteEventListener(node, listener[0], listener[1], listener[2]),
|
||||
deleteEventListener(node, listener[0], listener[1], listener[2], this.angularMode),
|
||||
)
|
||||
}
|
||||
this.totalNodeAmount--
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ export default class IFrameObserver extends Observer {
|
|||
})
|
||||
}
|
||||
|
||||
syntheticObserve(selfId: number, doc: Document) {
|
||||
syntheticObserve(rootNodeId: number, doc: Document) {
|
||||
this.observeRoot(doc, (docID) => {
|
||||
if (docID === undefined) {
|
||||
this.app.debug.log('OpenReplay: Iframe document not bound')
|
||||
return
|
||||
}
|
||||
this.app.send(CreateIFrameDocument(selfId, docID))
|
||||
this.app.send(CreateIFrameDocument(rootNodeId, docID))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { createMutationObserver, ngSafeBrowserMethod } from '../../utils.js'
|
||||
import { createMutationObserver } from '../../utils.js'
|
||||
import {
|
||||
RemoveNodeAttribute,
|
||||
SetNodeAttributeURLBased,
|
||||
|
|
@ -105,6 +105,9 @@ export default abstract class Observer {
|
|||
if (name === null) {
|
||||
continue
|
||||
}
|
||||
if (target instanceof HTMLIFrameElement && name === 'src') {
|
||||
this.handleIframeSrcChange(target)
|
||||
}
|
||||
let attr = this.attributesMap.get(id)
|
||||
if (attr === undefined) {
|
||||
this.attributesMap.set(id, (attr = new Set()))
|
||||
|
|
@ -119,6 +122,7 @@ export default abstract class Observer {
|
|||
}
|
||||
this.commitNodes()
|
||||
}) as MutationCallback,
|
||||
this.app.options.angularMode,
|
||||
)
|
||||
}
|
||||
private clear(): void {
|
||||
|
|
@ -129,10 +133,49 @@ export default abstract class Observer {
|
|||
this.textSet.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds the removed nodes in case of iframe src change.
|
||||
*/
|
||||
private handleIframeSrcChange(iframe: HTMLIFrameElement): void {
|
||||
const oldContentDocument = iframe.contentDocument
|
||||
if (oldContentDocument) {
|
||||
const id = this.app.nodes.getID(oldContentDocument)
|
||||
if (id !== undefined) {
|
||||
const walker = document.createTreeWalker(
|
||||
oldContentDocument,
|
||||
NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT,
|
||||
{
|
||||
acceptNode: (node) =>
|
||||
isIgnored(node) || this.app.nodes.getID(node) === undefined
|
||||
? NodeFilter.FILTER_REJECT
|
||||
: NodeFilter.FILTER_ACCEPT,
|
||||
},
|
||||
// @ts-ignore
|
||||
false,
|
||||
)
|
||||
|
||||
let removed = 0
|
||||
const totalBeforeRemove = this.app.nodes.getNodeCount()
|
||||
|
||||
while (walker.nextNode()) {
|
||||
if (!iframe.contentDocument.contains(walker.currentNode)) {
|
||||
removed += 1
|
||||
this.app.nodes.unregisterNode(walker.currentNode)
|
||||
}
|
||||
}
|
||||
|
||||
const removedPercent = Math.floor((removed / totalBeforeRemove) * 100)
|
||||
if (removedPercent > 30) {
|
||||
this.app.send(UnbindNodes(removedPercent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sendNodeAttribute(id: number, node: Element, name: string, value: string | null): void {
|
||||
if (isSVGElement(node)) {
|
||||
if (name.substr(0, 6) === 'xlink:') {
|
||||
name = name.substr(6)
|
||||
if (name.substring(0, 6) === 'xlink:') {
|
||||
name = name.substring(6)
|
||||
}
|
||||
if (value === null) {
|
||||
this.app.send(RemoveNodeAttribute(id, name))
|
||||
|
|
@ -152,7 +195,7 @@ export default abstract class Observer {
|
|||
name === 'integrity' ||
|
||||
name === 'crossorigin' ||
|
||||
name === 'autocomplete' ||
|
||||
name.substr(0, 2) === 'on'
|
||||
name.substring(0, 2) === 'on'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ export default class TopObserver extends Observer {
|
|||
)
|
||||
}
|
||||
|
||||
crossdomainObserve(selfId: number, frameOder: number) {
|
||||
crossdomainObserve(rootNodeId: number, frameOder: number) {
|
||||
const observer = this
|
||||
Element.prototype.attachShadow = function () {
|
||||
// eslint-disable-next-line
|
||||
|
|
@ -152,7 +152,7 @@ export default class TopObserver extends Observer {
|
|||
this.app.nodes.syntheticMode(frameOder)
|
||||
const iframeObserver = new IFrameObserver(this.app)
|
||||
this.iframeObservers.push(iframeObserver)
|
||||
iframeObserver.syntheticObserve(selfId, window.document)
|
||||
iframeObserver.syntheticObserve(rootNodeId, window.document)
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ export default function (app: App): void {
|
|||
}
|
||||
}
|
||||
}) as MutationCallback,
|
||||
app.options.angularMode,
|
||||
)
|
||||
|
||||
app.attachStopCallback(() => {
|
||||
|
|
|
|||
|
|
@ -132,9 +132,13 @@ export function ngSafeBrowserMethod(method: string): string {
|
|||
: method
|
||||
}
|
||||
|
||||
export function createMutationObserver(cb: MutationCallback) {
|
||||
const mObserver = ngSafeBrowserMethod('MutationObserver') as 'MutationObserver'
|
||||
return new window[mObserver](cb)
|
||||
export function createMutationObserver(cb: MutationCallback, angularMode?: boolean) {
|
||||
if (angularMode) {
|
||||
const mObserver = ngSafeBrowserMethod('MutationObserver') as 'MutationObserver'
|
||||
return new window[mObserver](cb)
|
||||
} else {
|
||||
return new MutationObserver(cb)
|
||||
}
|
||||
}
|
||||
|
||||
export function createEventListener(
|
||||
|
|
@ -142,8 +146,14 @@ export function createEventListener(
|
|||
event: string,
|
||||
cb: EventListenerOrEventListenerObject,
|
||||
capture?: boolean,
|
||||
angularMode?: boolean,
|
||||
) {
|
||||
const safeAddEventListener = ngSafeBrowserMethod('addEventListener') as 'addEventListener'
|
||||
let safeAddEventListener: 'addEventListener'
|
||||
if (angularMode) {
|
||||
safeAddEventListener = ngSafeBrowserMethod('addEventListener') as 'addEventListener'
|
||||
} else {
|
||||
safeAddEventListener = 'addEventListener'
|
||||
}
|
||||
try {
|
||||
target[safeAddEventListener](event, cb, capture)
|
||||
} catch (e) {
|
||||
|
|
@ -152,6 +162,7 @@ export function createEventListener(
|
|||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`,
|
||||
event,
|
||||
target,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -161,10 +172,14 @@ export function deleteEventListener(
|
|||
event: string,
|
||||
cb: EventListenerOrEventListenerObject,
|
||||
capture?: boolean,
|
||||
angularMode?: boolean,
|
||||
) {
|
||||
const safeRemoveEventListener = ngSafeBrowserMethod(
|
||||
'removeEventListener',
|
||||
) as 'removeEventListener'
|
||||
let safeRemoveEventListener: 'removeEventListener'
|
||||
if (angularMode) {
|
||||
safeRemoveEventListener = ngSafeBrowserMethod('removeEventListener') as 'removeEventListener'
|
||||
} else {
|
||||
safeRemoveEventListener = 'removeEventListener'
|
||||
}
|
||||
try {
|
||||
target[safeRemoveEventListener](event, cb, capture)
|
||||
} catch (e) {
|
||||
|
|
@ -173,6 +188,7 @@ export function deleteEventListener(
|
|||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`Openreplay: ${msg}; if this error is caused by an IframeObserver, ignore it`,
|
||||
event,
|
||||
target,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe('Nodes', () => {
|
|||
const mockCallback = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
nodes = new Nodes(nodeId)
|
||||
nodes = new Nodes(nodeId, false)
|
||||
mockCallback.mockClear()
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue