UNPKG

rrweb

Version:
776 lines (773 loc) 32.1 kB
import { __assign, __values, __read } from '../../node_modules/tslib/tslib.es6.js'; import { rebuild, NodeType, buildNodeWithSN } from '../../node_modules/rrweb-snapshot/es/rrweb-snapshot.js'; import { ReplayerEvents, EventType, IncrementalSource, MediaInteractions, MouseInteractions } from '../types.js'; import { mirror, polyfill, TreeIndex } from '../utils.js'; import * as mittProxy 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, createSpeedService } from './machine.js'; import getInjectStyleRules from './styles/inject-style.js'; var SKIP_TIME_THRESHOLD = 10 * 1000; var SKIP_TIME_INTERVAL = 5 * 1000; var mitt = mitt$1 || mittProxy; var REPLAY_CONSOLE_PREFIX = '[replayer]'; var defaultConfig = { speed: 1, root: document.body, loadTimeout: 0, skipInactive: false, showWarning: true, showDebug: false, blockClass: 'rr-block', liveMode: false, insertStyleRules: [], triggerFocus: true, }; var Replayer = (function () { function Replayer(events, config) { var _this = this; this.emitter = mitt(); this.legacy_missingNodeRetryMap = {}; 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(); }); var timer = new Timer([], (config === null || config === void 0 ? void 0 : config.speed) || defaultConfig.speed); this.service = createPlayerService({ events: events.map(function (e) { if (config && config.unpackFn) { return config.unpackFn(e); } return e; }), timer: timer, timeOffset: 0, baselineTime: 0, lastPlayedEvent: null, }, { getCastFn: this.getCastFn, emitter: this.emitter, }); this.service.start(); this.service.subscribe(function (state) { _this.emitter.emit(ReplayerEvents.StateChange, { player: state, }); }); this.speedService = createSpeedService({ normalSpeed: -1, timer: timer, }); this.speedService.start(); this.speedService.subscribe(function (state) { _this.emitter.emit(ReplayerEvents.StateChange, { speed: state, }); }); var firstMeta = this.service.state.context.events.find(function (e) { return e.type === EventType.Meta; }); var firstFullsnapshot = this.service.state.context.events.find(function (e) { return e.type === EventType.FullSnapshot; }); if (firstMeta) { var _a = firstMeta.data, width_1 = _a.width, height_1 = _a.height; setTimeout(function () { _this.emitter.emit(ReplayerEvents.Resize, { width: width_1, height: height_1, }); }, 0); } 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.backToNormal(); } }; Replayer.prototype.getMetaData = function () { var firstEvent = this.service.state.context.events[0]; var lastEvent = this.service.state.context.events[this.service.state.context.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; } if (this.service.state.matches('paused')) { this.service.send({ type: 'PLAY', payload: { timeOffset: timeOffset } }); } else { this.service.send({ type: 'PAUSE' }); this.service.send({ type: 'PLAY', payload: { timeOffset: timeOffset } }); } this.emitter.emit(ReplayerEvents.Start); }; Replayer.prototype.pause = function (timeOffset) { if (timeOffset === undefined && this.service.state.matches('playing')) { this.service.send({ type: 'PAUSE' }); } if (typeof timeOffset === 'number') { this.play(timeOffset); this.service.send({ type: 'PAUSE' }); } this.emitter.emit(ReplayerEvents.Pause); }; Replayer.prototype.resume = function (timeOffset) { if (timeOffset === void 0) { timeOffset = 0; } console.warn("The 'resume' will be departed in 1.0. Please use 'play' method which has the same interface."); this.play(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.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.mouse = document.createElement('div'); this.mouse.classList.add('replayer-mouse'); this.wrapper.appendChild(this.mouse); this.iframe = document.createElement('iframe'); this.iframe.setAttribute('sandbox', 'allow-same-origin'); this.disableInteract(); this.wrapper.appendChild(this.iframe); }; Replayer.prototype.handleResize = function (dimension) { this.iframe.setAttribute('width', String(dimension.width)); this.iframe.setAttribute('height', String(dimension.height)); }; Replayer.prototype.getCastFn = function (event, isSync) { var _this = this; if (isSync === void 0) { isSync = false; } var castFn; switch (event.type) { case EventType.DomContentLoaded: case EventType.Load: 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); }; break; case EventType.IncrementalSnapshot: castFn = function () { var e_4, _a; _this.applyIncremental(event, isSync); if (isSync) { return; } if (event === _this.nextUserInteractionEvent) { _this.nextUserInteractionEvent = null; _this.backToNormal(); } if (_this.config.skipInactive && !_this.nextUserInteractionEvent) { try { for (var _b = __values(_this.service.state.context.events), _c = _b.next(); !_c.done; _c = _b.next()) { var _event = _c.value; if (_event.timestamp <= event.timestamp) { continue; } if (_this.isUserInteraction(_event)) { if (_event.delay - event.delay > SKIP_TIME_THRESHOLD * _this.speedService.state.context.timer.speed) { _this.nextUserInteractionEvent = _event; } break; } } } 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; } } if (_this.nextUserInteractionEvent) { var skipTime = _this.nextUserInteractionEvent.delay - event.delay; var payload = { speed: Math.min(Math.round(skipTime / SKIP_TIME_INTERVAL), 360), }; _this.speedService.send({ type: 'FAST_FORWARD', payload: payload }); _this.emitter.emit(ReplayerEvents.SkipStart, payload); } } }; break; } var wrappedCastFn = function () { if (castFn) { castFn(); } _this.service.send({ type: 'CAST_EVENT', payload: { event: event } }); if (event === _this.service.state.context.events[_this.service.state.context.events.length - 1]) { _this.backToNormal(); _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)[1]; var styleEl = document.createElement('style'); var _a = this.iframe.contentDocument, documentElement = _a.documentElement, head = _a.head; documentElement.insertBefore(styleEl, head); var injectStylesRules = getInjectStyleRules(this.config.blockClass).concat(this.config.insertStyleRules); for (var idx = 0; idx < injectStylesRules.length; idx++) { styleEl.sheet.insertRule(injectStylesRules[idx], idx); } this.emitter.emit(ReplayerEvents.FullsnapshotRebuilded, event); this.waitForStylesheetLoad(); }; 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 unsubscribe_1 = this.service.subscribe(function (state) { beforeLoadState_1 = state; }).unsubscribe; head .querySelectorAll('link[rel="stylesheet"]') .forEach(function (css) { if (!css.sheet) { unloadSheets_1.add(css); css.addEventListener('load', function () { unloadSheets_1.delete(css); if (unloadSheets_1.size === 0 && timer_1 !== -1) { if (beforeLoadState_1.matches('playing')) { _this.play(_this.getCurrentTime()); } _this.emitter.emit(ReplayerEvents.LoadStylesheetEnd); if (timer_1) { window.clearTimeout(timer_1); } unsubscribe_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.play(_this.getCurrentTime()); } timer_1 = -1; unsubscribe_1(); }, this.config.loadTimeout); } } }; Replayer.prototype.applyIncremental = function (e, isSync) { var _this = this; 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.MouseMove: if (isSync) { var lastPosition = d.positions[d.positions.length - 1]; this.moveAndHover(d, lastPosition.x, lastPosition.y, lastPosition.id); } else { d.positions.forEach(function (p) { var action = { doAction: function () { _this.moveAndHover(d, p.x, p.y, p.id); }, delay: p.timeOffset + e.timestamp - _this.service.state.context.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); 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: 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.MediaInteraction: { var target = mirror.getNode(d.id); if (!target) { return this.debugNodeNotFound(d, d.id); } var mediaEl_1 = target; if (d.type === MediaInteractions.Pause) { mediaEl_1.pause(); } if (d.type === MediaInteractions.Play) { if (mediaEl_1.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) { mediaEl_1.play(); } else { mediaEl_1.addEventListener('canplay', function () { mediaEl_1.play(); }); } } 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; } } }; 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); } var target = buildNodeWithSN(mutation.node, _this.iframe.contentDocument, mirror.map, true); 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') { var value = mutation.attributes[attributeName]; if (value !== null) { 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) { this.mouse.style.left = x + "px"; this.mouse.style.top = y + "px"; var target = mirror.getNode(id); if (!target) { return this.debugNodeNotFound(d, id); } this.hoverElements(target); }; 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.backToNormal = function () { this.nextUserInteractionEvent = null; if (this.speedService.state.matches('normal')) { return; } this.speedService.send({ type: 'BACK_TO_NORMAL' }); this.emitter.emit(ReplayerEvents.SkipEnd, { speed: this.speedService.state.context.normalSpeed, }); }; 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.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 };