UNPKG

@xysfe/memento-core

Version:

record and replay the web

1,032 lines (1,029 loc) 44.3 kB
import { __values, __awaiter, __assign, __read, __generator } from '../../node_modules/tslib/tslib.es6.js'; import { rebuild, NodeType, buildNodeWithSN } from '../../node_modules/@xysfe/memento-snapshot/es/memento-snapshot.js'; import * as mitt_es from '../../node_modules/mitt/dist/mitt.es.js'; import mitt$1 from '../../node_modules/mitt/dist/mitt.es.js'; import { polyfill as smoothscroll_1 } from '../../node_modules/smoothscroll-polyfill/dist/smoothscroll.js'; import { Timer } from './timer.js'; import { createPlayerService } from './machine.js'; import { ReplayerEvents, EventType, IncrementalSource, UserDefinedEvent, MouseInteractions } from '../types.js'; import { mirror, getBaseDimension, polyfill, TreeIndex, dateFormat } from '../utils.js'; import rules from './styles/inject-style.js'; import canvasMutation from './canvas/index.js'; import { deserializeArg } from './canvas/deserialize-args.js'; var SKIP_TIME_THRESHOLD = 10 * 1000; var SKIP_TIME_INTERVAL = 5 * 1000; var FORCE_SKIP_TIME_INTERVAL = 5 * 60 * 1000; var mitt = mitt$1 || mitt_es; var REPLAY_CONSOLE_PREFIX = '[replayer]'; var defaultMouseTailConfig = { duration: 500, lineCap: 'round', lineWidth: 3, strokeStyle: 'red', }; var defaultConfig = { normalSpeed: 1, speed: 1, root: document.body, loadTimeout: 0, skipInactive: false, showWarning: true, showDebug: false, blockClass: 'mem-block', liveMode: false, insertStyleRules: [], triggerFocus: true, UNSAFE_replayCanvas: false, mouseTail: defaultMouseTailConfig, forceSkipInactive: false, mode: 'player' }; var Replayer = (function () { function Replayer(events, config) { var _this = this; this.mouseTail = null; this.tailPositions = []; this.emitter = mitt(); this.normalSpeed = -1; this.legacy_missingNodeRetryMap = {}; this.unloadStylesheetMap = {}; this.imageMap = new Map(); this.canvasEventMap = new Map(); if (!(config === null || config === void 0 ? void 0 : config.liveMode) && events.length < 2) { throw new Error('Replayer need at least 2 events.'); } this.config = Object.assign({}, defaultConfig, config); this.handleResize = this.handleResize.bind(this); this.getCastFn = this.getCastFn.bind(this); this.emitter.on(ReplayerEvents.Resize, this.handleResize); smoothscroll_1(); polyfill(); this.setupDom(); this.treeIndex = new TreeIndex(); this.fragmentParentMap = new Map(); this.emitter.on(ReplayerEvents.Flush, function () { var e_1, _a, e_2, _b, e_3, _c; var _d = _this.treeIndex.flush(), scrollMap = _d.scrollMap, inputMap = _d.inputMap; try { for (var _e = __values(scrollMap.values()), _f = _e.next(); !_f.done; _f = _e.next()) { var d = _f.value; _this.applyScroll(d); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_f && !_f.done && (_a = _e.return)) _a.call(_e); } finally { if (e_1) throw e_1.error; } } try { for (var _g = __values(inputMap.values()), _h = _g.next(); !_h.done; _h = _g.next()) { var d = _h.value; _this.applyInput(d); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_h && !_h.done && (_b = _g.return)) _b.call(_g); } finally { if (e_2) throw e_2.error; } } try { for (var _j = __values(_this.fragmentParentMap.entries()), _k = _j.next(); !_k.done; _k = _j.next()) { var _l = __read(_k.value, 2), frag = _l[0], parent = _l[1]; mirror.map[parent.__sn.id] = parent; if (parent.__sn.type === NodeType.Element && parent.__sn.tagName === 'textarea' && frag.textContent) { parent.value = frag.textContent; } parent.appendChild(frag); } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_k && !_k.done && (_c = _j.return)) _c.call(_j); } finally { if (e_3) throw e_3.error; } } _this.fragmentParentMap.clear(); }); this.emitter.on(ReplayerEvents.EventCast, function (event) { if (_this.timeDisplayer) { _this.timeDisplayer.innerText = dateFormat(new Date(event.timestamp), 'yyyy/M/d hh:mm:ss'); } }); this.service = createPlayerService({ events: events.map(function (e) { if (config && config.unpackFn) { return config.unpackFn(e); } return e; }), timer: new Timer(this.config), speed: (config === null || config === void 0 ? void 0 : config.speed) || defaultConfig.speed, timeOffset: 0, baselineTime: 0, lastPlayedEvent: null, }, { getCastFn: this.getCastFn, emitter: this.emitter, }); this.service.start(); this.service.subscribe(function (state) { if (!state.changed) { return; } }); var contextEvents = this.service.state.context.events; var firstMeta = contextEvents.find(function (e) { return e.type === EventType.Meta; }); var firstFullsnapshot = contextEvents.find(function (e) { return e.type === EventType.FullSnapshot; }); if (firstMeta) { var _a = firstMeta.data, width = _a.width, height = _a.height; this.emitter.emit(ReplayerEvents.Resize, { width: width, height: height, }); } if (firstFullsnapshot) { this.rebuildFullSnapshot(firstFullsnapshot); } } Object.defineProperty(Replayer.prototype, "timer", { get: function () { return this.service.state.context.timer; }, enumerable: false, configurable: true }); Replayer.prototype.on = function (event, handler) { this.emitter.on(event, handler); }; Replayer.prototype.setConfig = function (config) { var _this = this; Object.keys(config).forEach(function (key) { _this.config[key] = config[key]; }); if (!this.config.skipInactive) { this.normalSpeed = -1; } if (!('normalSpeed' in config) && 'speed' in config) { this.config.normalSpeed = config.speed; } if (typeof config.mouseTail !== 'undefined') { if (config.mouseTail === false) { if (this.mouseTail) { this.mouseTail.style.display = 'none'; } } else { if (!this.mouseTail) { this.mouseTail = document.createElement('canvas'); this.mouseTail.width = Number.parseFloat(this.iframe.width); this.mouseTail.height = Number.parseFloat(this.iframe.height); this.mouseTail.classList.add('replayer-mouse-tail'); this.wrapper.insertBefore(this.mouseTail, this.iframe); } this.mouseTail.style.display = 'inherit'; } } }; Replayer.prototype.getMetaData = function () { var events = this.service.state.context.events; var firstEvent = events[0]; var lastEvent = events[events.length - 1]; return { startTime: firstEvent.timestamp, endTime: lastEvent.timestamp, totalTime: lastEvent.timestamp - firstEvent.timestamp, }; }; Replayer.prototype.getCurrentTime = function () { return this.timer.timeOffset + this.getTimeOffset(); }; Replayer.prototype.getTimeOffset = function () { var _a = this.service.state.context, baselineTime = _a.baselineTime, events = _a.events; return baselineTime - events[0].timestamp; }; Replayer.prototype.play = function (timeOffset) { if (timeOffset === void 0) { timeOffset = 0; } this.nextUserInteractionEvent = null; this.restoreSpeed(); if (this.service.state.value === 'ended') { this.service.state.context.lastPlayedEvent = null; this.service.send({ type: 'REPLAY', payload: { timeOffset: timeOffset } }); } if (this.service.state.value === 'paused') { this.service.send({ type: 'RESUME', payload: { timeOffset: timeOffset } }); } else { this.service.send({ type: 'PLAY', payload: { timeOffset: timeOffset } }); } this.emitter.emit(ReplayerEvents.Start); }; Replayer.prototype.pause = function () { this.service.send({ type: 'PAUSE' }); this.emitter.emit(ReplayerEvents.Pause); }; Replayer.prototype.resume = function (timeOffset) { if (timeOffset === void 0) { timeOffset = 0; } this.service.send({ type: 'RESUME', payload: { timeOffset: timeOffset } }); this.emitter.emit(ReplayerEvents.Resume); }; Replayer.prototype.startLive = function (baselineTime) { this.service.send({ type: 'TO_LIVE', payload: { baselineTime: baselineTime } }); }; Replayer.prototype.addEvent = function (rawEvent) { var _this = this; var event = this.config.unpackFn ? this.config.unpackFn(rawEvent) : rawEvent; Promise.resolve().then(function () { return _this.service.send({ type: 'ADD_EVENT', payload: { event: event } }); }); }; Replayer.prototype.addEvents = function (rawEvents) { var _this = this; var events = rawEvents.map(function (e) { if (_this.config.unpackFn) { return _this.config.unpackFn(e); } return e; }); Promise.resolve().then(function () { return _this.service.send({ type: 'ADD_EVENTS', payload: { events: events } }); }); }; Replayer.prototype.enableInteract = function () { this.iframe.setAttribute('scrolling', 'auto'); this.iframe.style.pointerEvents = 'auto'; }; Replayer.prototype.disableInteract = function () { this.iframe.setAttribute('scrolling', 'no'); this.iframe.style.pointerEvents = 'none'; }; Replayer.prototype.setupDom = function () { this.wrapper = document.createElement('div'); this.wrapper.classList.add('replayer-wrapper'); this.config.root.appendChild(this.wrapper); this.timeDisplayer = document.createElement('span'); var timeContainer = document.createElement('div'); timeContainer.classList.add('replayer-time'); timeContainer.appendChild(this.timeDisplayer); this.wrapper.appendChild(timeContainer); this.mouse = document.createElement('div'); this.mouse.classList.add('replayer-mouse'); this.wrapper.appendChild(this.mouse); if (this.config.mouseTail !== false) { this.mouseTail = document.createElement('canvas'); this.mouseTail.classList.add('replayer-mouse-tail'); this.mouseTail.style.display = 'inherit'; this.wrapper.appendChild(this.mouseTail); } this.iframe = document.createElement('iframe'); var attributes = ['allow-same-origin']; if (this.config.UNSAFE_replayCanvas) { attributes.push('allow-scripts'); } this.iframe.style.display = 'none'; this.iframe.setAttribute('sandbox', attributes.join(' ')); this.disableInteract(); this.wrapper.appendChild(this.iframe); }; Replayer.prototype.handleResize = function (dimension) { var e_4, _a; this.iframe.style.display = 'inherit'; try { for (var _b = __values([this.mouseTail, this.iframe]), _c = _b.next(); !_c.done; _c = _b.next()) { var el = _c.value; if (!el) { continue; } el.setAttribute('width', String(dimension.width)); el.setAttribute('height', String(dimension.height)); } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_4) throw e_4.error; } } }; Replayer.prototype.getCastFn = function (event, isSync) { var _this = this; if (isSync === void 0) { isSync = false; } var castFn; var handleNextActiveSnapshot = function () { if (isSync) { return; } var events = _this.service.state.context.events; var eventIndex = 0; for (var i = 0, n = events.length; i < n; i++) { if (events[i] === event) { eventIndex = i; break; } } if (_this.config.forceSkipInactive) { var needSkip = events[eventIndex + 1] && (events[eventIndex + 1].timestamp - event.timestamp > FORCE_SKIP_TIME_INTERVAL); if (needSkip) { _this.play(events[eventIndex + 1].timestamp - events[0].timestamp); return; } } if (event === _this.nextUserInteractionEvent) { _this.nextUserInteractionEvent = null; _this.restoreSpeed(); } if (_this.config.skipInactive && !_this.nextUserInteractionEvent) { var events_1 = _this.service.state.context.events; for (var i = eventIndex + 1, n = events_1.length; i < n; i++) { var _event = events_1[i]; if (_this.isUserInteraction(_event) || _event.type === EventType.FullSnapshot) { if (_event.delay - event.delay > SKIP_TIME_THRESHOLD * _this.config.speed) { _this.nextUserInteractionEvent = _event; } break; } } if (_this.nextUserInteractionEvent) { _this.normalSpeed = _this.config.normalSpeed; var skipTime = _this.nextUserInteractionEvent.delay - event.delay; var payload = { speed: Math.min(Math.round(skipTime / SKIP_TIME_INTERVAL), 3600), normalSpeed: _this.normalSpeed, }; _this.setConfig(payload); _this.emitter.emit(ReplayerEvents.SkipStart, payload); } } }; switch (event.type) { case EventType.DomContentLoaded: case EventType.Load: case EventType.PageChange: break; case EventType.Custom: castFn = function () { _this.emitter.emit(ReplayerEvents.CustomEvent, event); }; break; case EventType.Meta: castFn = function () { return _this.emitter.emit(ReplayerEvents.Resize, { width: event.data.width, height: event.data.height, }); }; break; case EventType.FullSnapshot: castFn = function () { _this.rebuildFullSnapshot(event); _this.iframe.contentWindow.scrollTo(event.data.initialOffset); handleNextActiveSnapshot(); }; break; case EventType.IncrementalSnapshot: castFn = function () { try { _this.applyIncremental(event, isSync); } catch (error) { console.error(error); } handleNextActiveSnapshot(); }; break; } var wrappedCastFn = function () { var events = _this.service.state.context.events; if (castFn) { castFn(); } _this.service.send({ type: 'CAST_EVENT', payload: { event: event } }); if (event === events[events.length - 1]) { _this.restoreSpeed(); _this.service.send('END'); _this.emitter.emit(ReplayerEvents.Finish); } }; return wrappedCastFn; }; Replayer.prototype.rebuildFullSnapshot = function (event) { if (!this.iframe.contentDocument) { return console.warn('Looks like your replayer has been destroyed.'); } if (Object.keys(this.legacy_missingNodeRetryMap).length) { console.warn('Found unresolved missing node map', this.legacy_missingNodeRetryMap); } this.legacy_missingNodeRetryMap = {}; mirror.map = rebuild(event.data.node, this.iframe.contentDocument, true, this.config.cdnHttps)[1]; var styleEl = document.createElement('style'); var _a = this.iframe.contentDocument, documentElement = _a.documentElement, head = _a.head; documentElement.insertBefore(styleEl, head); var injectStylesRules = rules({ blockClass: this.config.blockClass, mode: this.config.mode }).concat(this.config.insertStyleRules); for (var idx = 0; idx < injectStylesRules.length; idx++) { styleEl.sheet.insertRule(injectStylesRules[idx], idx); } this.emitter.emit(ReplayerEvents.FullsnapshotRebuilded, event); if (this.config.mode !== 'page') { this.waitForStylesheetLoad(); } if (this.config.UNSAFE_replayCanvas) { this.preloadAllImages(); } }; Replayer.prototype.waitForStylesheetLoad = function () { var _this = this; var _a; var head = (_a = this.iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.head; if (head) { var unloadSheets_1 = new Set(); var timer_1; var beforeLoadState_1 = this.service.state; var stylesheets = head.querySelectorAll('link[rel="stylesheet"]'); stylesheets = Array.prototype.filter.call(stylesheets, function (css) { return !!_this.unloadStylesheetMap[css.href]; }); stylesheets.forEach(function (css) { if (!css.sheet) { unloadSheets_1.add(css); _this.unloadStylesheetMap[css.href] = 1; css.addEventListener('load', function () { unloadSheets_1.delete(css); if (unloadSheets_1.size === 0 && timer_1 !== -1) { if (beforeLoadState_1.matches('playing')) { _this.resume(_this.getCurrentTime()); } _this.emitter.emit(ReplayerEvents.LoadStylesheetEnd); if (timer_1) { window.clearTimeout(timer_1); } } }); } }); if (unloadSheets_1.size > 0) { this.service.send({ type: 'PAUSE' }); this.emitter.emit(ReplayerEvents.LoadStylesheetStart); timer_1 = window.setTimeout(function () { if (beforeLoadState_1.matches('playing')) { _this.resume(_this.getCurrentTime()); } timer_1 = -1; }, this.config.loadTimeout); } } }; Replayer.prototype.preloadAllImages = function () { return __awaiter(this, void 0, Promise, function () { var stateHandler, promises, _loop_1, this_1, _a, _b, event; var e_5, _c; var _this = this; return __generator(this, function (_d) { this.service.state; stateHandler = function () { _this.service.state; }; this.emitter.on(ReplayerEvents.Start, stateHandler); this.emitter.on(ReplayerEvents.Pause, stateHandler); promises = []; _loop_1 = function (event) { if (event.type === EventType.IncrementalSnapshot && event.data.source === IncrementalSource.CanvasMutation) { promises.push(this_1.deserializeAndPreloadCanvasEvents(event.data, event)); var commands = 'commands' in event.data ? event.data.commands : [event.data]; commands.forEach(function (c) { _this.preloadImages(c, event); }); } }; this_1 = this; try { for (_a = __values(this.service.state.context.events), _b = _a.next(); !_b.done; _b = _a.next()) { event = _b.value; _loop_1(event); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (_b && !_b.done && (_c = _a.return)) _c.call(_a); } finally { if (e_5) throw e_5.error; } } return [2, Promise.all(promises)]; }); }); }; Replayer.prototype.preloadImages = function (data, event) { if (data.property === 'drawImage' && typeof data.args[0] === 'string' && !this.imageMap.has(event)) { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); var imgd = ctx === null || ctx === void 0 ? void 0 : ctx.createImageData(canvas.width, canvas.height); imgd === null || imgd === void 0 ? void 0 : imgd.data; JSON.parse(data.args[0]); ctx === null || ctx === void 0 ? void 0 : ctx.putImageData(imgd, 0, 0); } }; Replayer.prototype.deserializeAndPreloadCanvasEvents = function (data, event) { return __awaiter(this, void 0, void 0, function () { var status_1, commands, args; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!!this.canvasEventMap.has(event)) return [3, 4]; status_1 = { isUnchanged: true, }; if (!('commands' in data)) return [3, 2]; return [4, Promise.all(data.commands.map(function (c) { return __awaiter(_this, void 0, void 0, function () { var args; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4, Promise.all(c.args.map(deserializeArg(this.imageMap, null, status_1)))]; case 1: args = _a.sent(); return [2, __assign(__assign({}, c), { args: args })]; } }); }); }))]; case 1: commands = _a.sent(); if (status_1.isUnchanged === false) this.canvasEventMap.set(event, __assign(__assign({}, data), { commands: commands })); return [3, 4]; case 2: return [4, Promise.all(data.args.map(deserializeArg(this.imageMap, null, status_1)))]; case 3: args = _a.sent(); if (status_1.isUnchanged === false) this.canvasEventMap.set(event, __assign(__assign({}, data), { args: args })); _a.label = 4; case 4: return [2]; } }); }); }; Replayer.prototype.applyIncremental = function (e, isSync) { var _this = this; var baselineTime = this.service.state.context.baselineTime; var d = e.data; switch (d.source) { case IncrementalSource.Mutation: { if (isSync) { d.adds.forEach(function (m) { return _this.treeIndex.add(m); }); d.texts.forEach(function (m) { return _this.treeIndex.text(m); }); d.attributes.forEach(function (m) { return _this.treeIndex.attribute(m); }); d.removes.forEach(function (m) { return _this.treeIndex.remove(m); }); } this.applyMutation(d, isSync); break; } case IncrementalSource.Drag: case IncrementalSource.TouchMove: case IncrementalSource.MouseMove: if (isSync) { var lastPosition = d.positions[d.positions.length - 1]; this.moveAndHover(d, lastPosition.x, lastPosition.y, lastPosition.id, isSync); } else { d.positions.forEach(function (p) { var action = { doAction: function () { _this.moveAndHover(d, p.x, p.y, p.id, isSync); }, delay: p.timeOffset + e.timestamp - baselineTime, }; _this.timer.addAction(action); }); } break; case IncrementalSource.MouseInteraction: { if (d.id === -1) { break; } var event = new Event(MouseInteractions[d.type].toLowerCase()); var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } this.emitter.emit(ReplayerEvents.MouseInteraction, { type: d.type, target: target, }); var triggerFocus = this.config.triggerFocus; switch (d.type) { case MouseInteractions.Blur: if ('blur' in target) { target.blur(); } break; case MouseInteractions.Focus: if (triggerFocus && target.focus) { target.focus({ preventScroll: true, }); } break; case MouseInteractions.Click: case MouseInteractions.TouchStart: case MouseInteractions.TouchEnd: if (!isSync) { this.moveAndHover(d, d.x, d.y, d.id, isSync); this.mouse.classList.remove('active'); void this.mouse.offsetWidth; this.mouse.classList.add('active'); } break; default: target.dispatchEvent(event); } break; } case IncrementalSource.Scroll: { if (d.id === -1) { break; } if (isSync) { this.treeIndex.scroll(d); break; } this.applyScroll(d); break; } case IncrementalSource.ViewportResize: if (d.rotate) { this.emitter.emit(ReplayerEvents.Resize, { width: d.width, height: d.height, }); } break; case IncrementalSource.Input: { if (d.id === -1) { break; } if (isSync) { this.treeIndex.input(d); break; } this.applyInput(d); break; } case IncrementalSource.StyleSheetRule: { var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } var styleEl = target; var styleSheet_1 = styleEl.sheet; if (d.adds) { d.adds.forEach(function (_a) { var rule = _a.rule, index = _a.index; var _index = index === undefined ? undefined : Math.min(index, styleSheet_1.rules.length); try { styleSheet_1.insertRule(rule, _index); } catch (e) { } }); } if (d.removes) { d.removes.forEach(function (_a) { var index = _a.index; styleSheet_1.deleteRule(index); }); } break; } case IncrementalSource.CanvasMutation: { if (!this.config.UNSAFE_replayCanvas) { return; } var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } canvasMutation({ event: e, mutation: d, target: target, imageMap: this.imageMap, canvasEventMap: this.canvasEventMap, errorHandler: this.warnCanvasMutationFailed.bind(this), }); break; } case IncrementalSource.UserDefinedEvent: { if (d.evt === UserDefinedEvent.LivePlay) { var document = this.iframe.contentDocument; if (!document || !d.selector || !d.url) { return; } var element = document.querySelector(d.selector); if (element && element.tagName && element.tagName.toLowerCase() === 'video') { element.setAttribute('src', d.url); element.play(); } } break; } } }; Replayer.prototype.applyMutation = function (d, useVirtualParent) { var _this = this; d.removes.forEach(function (mutation) { var target = mirror.getNode(mutation.id); if (!target) { return _this.warnNodeNotFound(d, mutation.id); } var parent = mirror.getNode(mutation.parentId); if (!parent) { return _this.warnNodeNotFound(d, mutation.parentId); } mirror.removeNodeFromMap(target); if (parent) { var realParent = _this.fragmentParentMap.get(parent); if (realParent && realParent.contains(target)) { realParent.removeChild(target); } else { parent.removeChild(target); } } }); var legacy_missingNodeMap = __assign({}, this.legacy_missingNodeRetryMap); var queue = []; var appendNode = function (mutation) { if (!_this.iframe.contentDocument) { return console.warn('Looks like your replayer has been destroyed.'); } var parent = mirror.getNode(mutation.parentId); if (!parent) { return queue.push(mutation); } var parentInDocument = _this.iframe.contentDocument.contains(parent); if (useVirtualParent && parentInDocument) { var virtualParent = document.createDocumentFragment(); mirror.map[mutation.parentId] = virtualParent; _this.fragmentParentMap.set(virtualParent, parent); while (parent.firstChild) { virtualParent.appendChild(parent.firstChild); } parent = virtualParent; } var previous = null; var next = null; if (mutation.previousId) { previous = mirror.getNode(mutation.previousId); } if (mutation.nextId) { next = mirror.getNode(mutation.nextId); } if (mutation.nextId !== null && mutation.nextId !== -1 && !next) { return queue.push(mutation); } if (mirror.has(mutation.node.id)) { return; } var target = buildNodeWithSN(mutation.node, _this.iframe.contentDocument, mirror.map, true, true, _this.config.cdnHttps); if (mutation.previousId === -1 || mutation.nextId === -1) { legacy_missingNodeMap[mutation.node.id] = { node: target, mutation: mutation, }; return; } if (previous && previous.nextSibling && previous.nextSibling.parentNode) { parent.insertBefore(target, previous.nextSibling); } else if (next && next.parentNode) { parent.contains(next) ? parent.insertBefore(target, next) : parent.insertBefore(target, null); } else { parent.appendChild(target); } if (mutation.previousId || mutation.nextId) { _this.legacy_resolveMissingNode(legacy_missingNodeMap, parent, target, mutation); } }; d.adds.forEach(function (mutation) { appendNode(mutation); }); while (queue.length) { if (queue.every(function (m) { return !Boolean(mirror.getNode(m.parentId)); })) { return queue.forEach(function (m) { return _this.warnNodeNotFound(d, m.node.id); }); } var mutation = queue.shift(); appendNode(mutation); } if (Object.keys(legacy_missingNodeMap).length) { Object.assign(this.legacy_missingNodeRetryMap, legacy_missingNodeMap); } d.texts.forEach(function (mutation) { var target = mirror.getNode(mutation.id); if (!target) { return _this.warnNodeNotFound(d, mutation.id); } if (_this.fragmentParentMap.has(target)) { target = _this.fragmentParentMap.get(target); } target.textContent = mutation.value; }); d.attributes.forEach(function (mutation) { var target = mirror.getNode(mutation.id); if (!target) { return _this.warnNodeNotFound(d, mutation.id); } if (_this.fragmentParentMap.has(target)) { target = _this.fragmentParentMap.get(target); } for (var attributeName in mutation.attributes) { if (typeof attributeName === 'string') { if (mutation.attributes.hasOwnProperty("_memimage_" + attributeName)) { continue; } var value = mutation.attributes[attributeName]; attributeName = attributeName.replace('_memimage_', ''); if (value !== null) { if (target.tagName === 'video' && attributeName === 'mem_placeholder') { target.style.background = "center / cover no-repeat url(" + value + ")"; } else { target.setAttribute(attributeName, value); } } else { target.removeAttribute(attributeName); } } } }); }; Replayer.prototype.applyScroll = function (d) { var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } if (target === this.iframe.contentDocument) { this.iframe.contentWindow.scrollTo({ top: d.y, left: d.x, behavior: 'smooth', }); } else { try { target.scrollTop = d.y; target.scrollLeft = d.x; } catch (error) { } } }; Replayer.prototype.applyInput = function (d) { var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } try { target.checked = d.isChecked; target.value = d.text; } catch (error) { } }; Replayer.prototype.legacy_resolveMissingNode = function (map, parent, target, targetMutation) { var previousId = targetMutation.previousId, nextId = targetMutation.nextId; var previousInMap = previousId && map[previousId]; var nextInMap = nextId && map[nextId]; if (previousInMap) { var _a = previousInMap, node = _a.node, mutation = _a.mutation; parent.insertBefore(node, target); delete map[mutation.node.id]; delete this.legacy_missingNodeRetryMap[mutation.node.id]; if (mutation.previousId || mutation.nextId) { this.legacy_resolveMissingNode(map, parent, node, mutation); } } if (nextInMap) { var _b = nextInMap, node = _b.node, mutation = _b.mutation; parent.insertBefore(node, target.nextSibling); delete map[mutation.node.id]; delete this.legacy_missingNodeRetryMap[mutation.node.id]; if (mutation.previousId || mutation.nextId) { this.legacy_resolveMissingNode(map, parent, node, mutation); } } }; Replayer.prototype.moveAndHover = function (d, x, y, id, isSync) { this.mouse.style.left = x + "px"; this.mouse.style.top = y + "px"; var target = mirror.getNode(id); if (!target) { return this.debugNodeNotFound(d, id); } var base = getBaseDimension(target, this.iframe); var _x = x * base.absoluteScale + base.x; var _y = y * base.absoluteScale + base.y; this.mouse.style.left = _x + "px"; this.mouse.style.top = _y + "px"; if (!isSync) { this.drawMouseTail({ x: _x, y: _y }); } this.hoverElements(target); }; Replayer.prototype.drawMouseTail = function (position) { var _this = this; if (!this.mouseTail) { return; } var _a = this.config.mouseTail === true ? defaultMouseTailConfig : Object.assign({}, defaultMouseTailConfig, this.config.mouseTail), lineCap = _a.lineCap, lineWidth = _a.lineWidth, strokeStyle = _a.strokeStyle, duration = _a.duration; var draw = function () { if (!_this.mouseTail) { return; } var ctx = _this.mouseTail.getContext('2d'); if (!ctx || !_this.tailPositions.length) { return; } ctx.clearRect(0, 0, _this.mouseTail.width, _this.mouseTail.height); ctx.beginPath(); ctx.lineWidth = lineWidth; ctx.lineCap = lineCap; ctx.strokeStyle = strokeStyle; ctx.moveTo(_this.tailPositions[0].x, _this.tailPositions[0].y); _this.tailPositions.forEach(function (p) { return ctx.lineTo(p.x, p.y); }); ctx.stroke(); }; this.tailPositions.push(position); draw(); setTimeout(function () { _this.tailPositions = _this.tailPositions.filter(function (p) { return p !== position; }); draw(); }, duration / this.config.speed); }; Replayer.prototype.hoverElements = function (el) { var _a; (_a = this.iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.\\:hover').forEach(function (hoveredEl) { hoveredEl.classList.remove(':hover'); }); var currentEl = el; while (currentEl) { if (currentEl.classList) { currentEl.classList.add(':hover'); } currentEl = currentEl.parentElement; } }; Replayer.prototype.isUserInteraction = function (event) { if (event.type !== EventType.IncrementalSnapshot) { return false; } return (event.data.source > IncrementalSource.Mutation && event.data.source <= IncrementalSource.Input); }; Replayer.prototype.restoreSpeed = function () { if (this.normalSpeed === -1) { return; } var payload = { speed: this.normalSpeed }; this.setConfig(payload); this.emitter.emit(ReplayerEvents.SkipEnd, payload); this.normalSpeed = -1; }; Replayer.prototype.warnNodeNotFound = function (d, id) { if (!this.config.showWarning) { return; } console.warn(REPLAY_CONSOLE_PREFIX, "Node with id '" + id + "' not found in", d); }; Replayer.prototype.warnCanvasMutationFailed = function (d, error) { console.warn("Has error on canvas update", error, 'canvas mutation:', d); }; Replayer.prototype.debugNodeNotFound = function (d, id) { if (!this.config.showDebug) { return; } console.log(REPLAY_CONSOLE_PREFIX, "Node with id '" + id + "' not found in", d); }; return Replayer; }()); export { Replayer };