UNPKG

@xysfe/memento-core

Version:

record and replay the web

689 lines (686 loc) 26.8 kB
import { __spread, __assign, __values } from '../../node_modules/tslib/tslib.es6.js'; import { throttle, isTouchEvent, mirror, on, isBlocked, getWindowHeight, getWindowWidth, hookSetter, isArray, isElement, isFunction, isWindow, isObject, isString, isPlainObject } from '../utils.js'; import { IncrementalSource, MouseInteractions, UserDefinedEvent, MediaInteractions } from '../types.js'; import MutationBuffer from './mutation.js'; var mql = window.matchMedia('(orientation: portrait)'); var isPortrait = mql.matches; function initMutationObserver(cb, blockClass, inlineStylesheet, maskInputOptions, blockElements, asyncClass) { if (blockElements === void 0) { blockElements = []; } var mutationBuffer = new MutationBuffer(cb, blockClass, inlineStylesheet, maskInputOptions, blockElements, asyncClass); var observer = new MutationObserver(mutationBuffer.processMutations); observer.observe(document, { attributes: true, attributeOldValue: true, characterData: true, characterDataOldValue: true, childList: true, subtree: true, }); return observer; } function initMoveObserver(cb, sampling) { if (sampling.mousemove === false) { return function () { }; } var threshold = typeof sampling.mousemove === 'number' ? sampling.mousemove : 50; var positions = []; var timeBaseline; var wrappedCb = throttle(function (isTouch) { var totalOffset = Date.now() - timeBaseline; cb(positions.map(function (p) { p.timeOffset -= totalOffset; return p; }), isTouch ? IncrementalSource.TouchMove : IncrementalSource.MouseMove); positions = []; timeBaseline = null; }, 500); var updatePosition = throttle(function (evt) { var target = evt.target; var _a = isTouchEvent(evt) ? evt.changedTouches[0] : evt, clientX = _a.clientX, clientY = _a.clientY; if (!timeBaseline) { timeBaseline = Date.now(); } positions.push({ x: clientX, y: clientY, id: mirror.getId(target), timeOffset: Date.now() - timeBaseline, }); wrappedCb(isTouchEvent(evt)); }, threshold, { trailing: false, }); var handlers = [ on('mousemove', updatePosition), on('touchmove', updatePosition), ]; return function () { handlers.forEach(function (h) { return h(); }); }; } function initMouseInteractionObserver(cb, blockClass, sampling, blockElements) { if (blockElements === void 0) { blockElements = []; } if (sampling.mouseInteraction === false) { return function () { }; } var disableMap = sampling.mouseInteraction === true || sampling.mouseInteraction === undefined ? {} : sampling.mouseInteraction; var handlers = []; var getHandler = function (eventKey) { return function (event) { if (isBlocked(event.target, blockClass, blockElements)) { return; } var id = mirror.getId(event.target); var _a = isTouchEvent(event) ? event.changedTouches[0] : event, clientX = _a.clientX, clientY = _a.clientY; cb({ type: MouseInteractions[eventKey], id: id, x: clientX, y: clientY, }); }; }; Object.keys(MouseInteractions) .filter(function (key) { return Number.isNaN(Number(key)) && !key.endsWith('_Departed') && disableMap[key] !== false; }) .forEach(function (eventKey) { var eventName = eventKey.toLowerCase(); var handler = getHandler(eventKey); handlers.push(on(eventName, handler)); }); return function () { handlers.forEach(function (h) { return h(); }); }; } function initScrollObserver(cb, blockClass, sampling, blockElements) { if (blockElements === void 0) { blockElements = []; } var updatePosition = throttle(function (evt) { if (!evt.target || isBlocked(evt.target, blockClass, blockElements)) { return; } var id = mirror.getId(evt.target); if (evt.target === document) { var scrollEl = (document.scrollingElement || document.documentElement); cb({ id: id, x: scrollEl.scrollLeft, y: scrollEl.scrollTop, }); } else { cb({ id: id, x: evt.target.scrollLeft, y: evt.target.scrollTop, }); } }, sampling.scroll || 100); return on('scroll', updatePosition); } function initViewportResizeObserver(cb) { var updateDimension = throttle(function () { var height = getWindowHeight(); var width = getWindowWidth(); cb({ width: Number(width), height: Number(height), rotate: isPortrait !== mql.matches ? 1 : 0, }); isPortrait = mql.matches; }, 200); return on('resize', updateDimension, window); } var INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT']; var lastInputValueMap = new WeakMap(); function initInputObserver(cb, blockClass, ignoreClass, maskInputOptions, sampling, blockElements) { if (blockElements === void 0) { blockElements = []; } function eventHandler(event) { var target = event.target; if (!target || !target.tagName || INPUT_TAGS.indexOf(target.tagName) < 0 || isBlocked(target, blockClass, blockElements)) { return; } var type = target.type; if (type === 'password' || target.classList.contains(ignoreClass)) { return; } var text = target.value; var isChecked = false; if (type === 'radio' || type === 'checkbox') { isChecked = target.checked; } else if (maskInputOptions[target.tagName.toLowerCase()] || maskInputOptions[type]) { text = '*'.repeat(text.length); } cbWithDedup(target, { text: text, isChecked: isChecked }); var name = target.name; if (type === 'radio' && name && isChecked) { document .querySelectorAll("input[type=\"radio\"][name=\"" + name + "\"]") .forEach(function (el) { if (el !== target) { cbWithDedup(el, { text: el.value, isChecked: !isChecked, }); } }); } } function cbWithDedup(target, v) { var lastInputValue = lastInputValueMap.get(target); if (!lastInputValue || lastInputValue.text !== v.text || lastInputValue.isChecked !== v.isChecked) { lastInputValueMap.set(target, v); var id = mirror.getId(target); cb(__assign(__assign({}, v), { id: id })); } } var events = sampling.input === 'last' ? ['change'] : ['input', 'change']; var handlers = events.map(function (eventName) { return on(eventName, eventHandler); }); var propertyDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); var hookProperties = [ [HTMLInputElement.prototype, 'value'], [HTMLInputElement.prototype, 'checked'], [HTMLSelectElement.prototype, 'value'], [HTMLTextAreaElement.prototype, 'value'], ]; if (propertyDescriptor && propertyDescriptor.set) { handlers.push.apply(handlers, __spread(hookProperties.map(function (p) { return hookSetter(p[0], p[1], { set: function () { eventHandler({ target: this }); }, }); }))); } return function () { handlers.forEach(function (h) { return h(); }); }; } function initStyleSheetObserver(cb) { var insertRule = CSSStyleSheet.prototype.insertRule; CSSStyleSheet.prototype.insertRule = function (rule, index) { var id = mirror.getId(this.ownerNode); if (id !== -1) { cb({ id: id, adds: [{ rule: rule, index: index }], }); } return insertRule.apply(this, arguments); }; var deleteRule = CSSStyleSheet.prototype.deleteRule; CSSStyleSheet.prototype.deleteRule = function (index) { var id = mirror.getId(this.ownerNode); if (id !== -1) { cb({ id: id, removes: [{ index: index }], }); } return deleteRule.apply(this, arguments); }; return function () { CSSStyleSheet.prototype.insertRule = insertRule; CSSStyleSheet.prototype.deleteRule = deleteRule; }; } function initMediaInteractionObserver(mediaInteractionCb, blockClass, blockElements) { if (blockElements === void 0) { blockElements = []; } var handler = function (type) { return function (event) { var target = event.target; if (!target || isBlocked(target, blockClass, blockElements)) { return; } mediaInteractionCb({ type: type === 'play' ? MediaInteractions.Play : MediaInteractions.Pause, id: mirror.getId(target), }); }; }; var handlers = [on('play', handler('play')), on('pause', handler('pause'))]; return function () { handlers.forEach(function (h) { return h(); }); }; } function initUserDefinedEventObserver(userDefinedEventCb) { var handler = function (evt) { return function (event) { if ('detail' in event && event.detail) { var detail = event.detail; userDefinedEventCb(__assign({ evt: evt }, detail)); } }; }; var handlers = [ on('memento-live-play', handler(UserDefinedEvent.LivePlay)) ]; return function () { handlers.forEach(function (h) { return h(); }); }; } function initConsoleObserver(cb) { var _console = {}; var resource = ['script', 'img', 'link', 'video', 'audio']; var methodList = ['log', 'info', 'warn', 'error']; var resourceIgnoreList = []; if (!window.console) { window.console = {}; } else { methodList.map(function (method) { _console[method] = window.console[method]; }); } methodList.map(function (method) { window.console[method] = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } _console[method].apply(window.console, args); var logs = args; for (var i = 0; i < logs.length; i++) { if (isArray(logs[i])) { logs[i] = '[Array]'; } else if (isElement(logs[i])) { logs[i] = '[HTMLElement]'; } else if (isFunction(logs[i])) { logs[i] = '[Function]'; } else if (isWindow(logs[i])) { logs[i] = '[Window]'; } else if (isObject(logs[i])) { logs[i] = '[Object]'; } } args[0] !== '__memento__' && cb({ logType: method, logs: logs }); }; }); window.addEventListener('error', function (e) { var target = e.target || e.srcElement; var src = target.attributes && target.attributes.src && target.attributes.src.value || ''; if (((target.nodeName && resource.indexOf(target.nodeName.toLowerCase()) >= 0) || (target.tagName && resource.indexOf(target.tagName.toLowerCase()) >= 0)) && src && resourceIgnoreList.indexOf(src.toLowerCase()) < 0) { resourceIgnoreList.push(src.toLowerCase()); var msg = (target.nodeName || target.tagName || '') + ' ' + (src || 'unkonw') + ' loaded fail'; cb({ logType: 'resourceError', logs: [msg] }); } }); var _onerror = window.onerror; window.onerror = function (msg, url, lineNo, columnNo, error) { if (msg && !(msg.indexOf('WeixinJSBridge') >= 0)) { cb({ logType: 'jsError', logs: [ "pageUrl => " + window.location.href, "errorMsg => " + (msg || ''), "scriptUrl => " + (url || ''), "lineNo => " + lineNo, "columnNo => " + columnNo, "errorType => " + (error ? error.name : ''), "errorStack => " + (error ? error.stack : '') ] }); } return _onerror.apply(this, arguments); }; return function () { window.console.log = _console.log; window.console.info = _console.info; window.console.warn = _console.warn; window.console.error = _console.error; window.onerror = _onerror; }; } function initNetworkObserver(cb) { var _XMLHttpRequest = window.XMLHttpRequest; if (!_XMLHttpRequest) { return function () { }; } var _mockopen = window.XMLHttpRequest.prototype.open; var _mocksend = window.XMLHttpRequest.prototype.send; var reqList = {}; window.XMLHttpRequest.prototype.open = function () { var XMLReq = this; var args = [].slice.call(arguments), method = args[0], url = args[1], id = getUniqueID(); var timer = null; XMLReq._mockRequestID = id; XMLReq._mockMethod = method; XMLReq._mockUrl = url; var _mocknoMementoConsole = XMLReq.onreadystatechange || function () { }; var onreadystatechange = function () { var item = reqList[id] || {}; item.readyState = XMLReq.readyState; item.status = 0; if (XMLReq.readyState > 1) { item.status = XMLReq.status; } item.responseType = XMLReq.responseType; if (XMLReq.readyState == 0) { if (!item.startTime) { item.startTime = (+new Date()); } } else if (XMLReq.readyState == 1) { if (!item.startTime) { item.startTime = (+new Date()); } } else if (XMLReq.readyState == 2) ; else if (XMLReq.readyState == 3) ; else if (XMLReq.readyState == 4) { clearInterval(timer); var query = XMLReq._mockUrl.split('?'); item.url = query.shift() || XMLReq._mockUrl; item.endTime = +new Date(); item.costTime = item.endTime - (item.startTime || item.endTime); item.ua = (window.navigator && window.navigator.userAgent) || ''; item.response = XMLReq.response; item.responseSize = XMLReq.response.length; } else { clearInterval(timer); } if (!XMLReq._noMementoConsole) { updateRequest(id, item); } return _mocknoMementoConsole.apply(XMLReq, arguments); }; XMLReq.onreadystatechange = onreadystatechange; var preState = -1; timer = setInterval(function () { if (preState != XMLReq.readyState) { preState = XMLReq.readyState; onreadystatechange.call(XMLReq); } }, 10); return _mockopen.apply(XMLReq, args); }; window.XMLHttpRequest.prototype.send = function () { var e_1, _a, e_2, _b; var XMLReq = this; var args = [].slice.call(arguments), data = args[0]; var item = reqList[XMLReq._mockRequestID] || {}; item.method = XMLReq._mockMethod.toUpperCase(); item.ck = document.cookie; var query = XMLReq._mockUrl.split('?'); if (query.shift() && query.length > 0) { item.getData = {}; query = query.join('?'); query = query.split('&'); try { for (var query_1 = __values(query), query_1_1 = query_1.next(); !query_1_1.done; query_1_1 = query_1.next()) { var q = query_1_1.value; q = q.split('='); item.getData[q[0]] = decodeURIComponent(q[1]); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (query_1_1 && !query_1_1.done && (_a = query_1.return)) _a.call(query_1); } finally { if (e_1) throw e_1.error; } } item.getData && (item.getSize = JSON.stringify(item.getData).length); } if (item.method == 'POST') { if (isString(data) && data !== '') { var arr = data.split('&'); item.postData = {}; try { for (var arr_1 = __values(arr), arr_1_1 = arr_1.next(); !arr_1_1.done; arr_1_1 = arr_1.next()) { var q = arr_1_1.value; q = q.split('='); item.postData[decodeURIComponent(q[0])] = decodeURIComponent(q[1]); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (arr_1_1 && !arr_1_1.done && (_b = arr_1.return)) _b.call(arr_1); } finally { if (e_2) throw e_2.error; } } } else if (isPlainObject(data)) { item.postData = data; } item.postData && (item.requestSize = JSON.stringify(item.postData).length); } var _mockontimeout = XMLReq.ontimeout || function () { }; var ontimeout = function () { var target = arguments[0].target || arguments[0].srcElement; item.timeOut = target.timeout; item.errorType = 'timeout_error'; updateRequest(XMLReq._mockRequestID, item); return _mockontimeout.apply(XMLReq, arguments); }; XMLReq.ontimeout = ontimeout; if (!XMLReq._noMementoConsole) { updateRequest(XMLReq._mockRequestID, item); } return _mocksend.apply(XMLReq, args); }; function updateRequest(id, data) { var item = reqList[id] || {}; for (var key in data) { item[key] = data[key]; } reqList[id] = item; if (!item.hasOwnProperty('getData') && !item.hasOwnProperty('postData')) { delete reqList[id]; } if (item && item.requestSize && item.requestSize > 10000) { item.postData = 'Post Request => Too long has been blocked'; } if (item && item.getSize && item.getSize > 10000) { item.getData = 'Get Request => Too long has been blocked'; } if (item && (item.responseType === '' || item.responseType === 'text')) { if (isString(item.response) && item.response !== '') { try { item.response = JSON.parse(item.response); } catch (e) { item.response = item.response; } } else if (typeof item.response != 'undefined') { if (item.response === '') { item.response = ''; } else { item.response = Object.prototype.toString.call(item.response); } } } if (item.readyState && item.readyState === 4) { if (item && +item.status === 0) { if (item.errorType === 'timeout_error') { item.errorMsg = 'request error => ' + 'timeout ( responseTime > ' + (item.timeOut || item.costTime) + 'ms )'; delete item.timeOut; } else { item.errorMsg = 'request error => cross_domain_error'; item.errorType = 'cross_domain_error'; } cb(item); } else { cb(item || {}); } } } function getUniqueID() { var id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); return id; } return function () { window.XMLHttpRequest.prototype.open = _mockopen; window.XMLHttpRequest.prototype.send = _mocksend; }; } function mergeHooks(o, hooks) { var mutationCb = o.mutationCb, mousemoveCb = o.mousemoveCb, mouseInteractionCb = o.mouseInteractionCb, scrollCb = o.scrollCb, viewportResizeCb = o.viewportResizeCb, inputCb = o.inputCb, mediaInteractionCb = o.mediaInteractionCb, styleSheetRuleCb = o.styleSheetRuleCb; o.mutationCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.mutation) { hooks.mutation.apply(hooks, __spread(p)); } mutationCb.apply(void 0, __spread(p)); }; o.mousemoveCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.mousemove) { hooks.mousemove.apply(hooks, __spread(p)); } mousemoveCb.apply(void 0, __spread(p)); }; o.mouseInteractionCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.mouseInteraction) { hooks.mouseInteraction.apply(hooks, __spread(p)); } mouseInteractionCb.apply(void 0, __spread(p)); }; o.scrollCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.scroll) { hooks.scroll.apply(hooks, __spread(p)); } scrollCb.apply(void 0, __spread(p)); }; o.viewportResizeCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.viewportResize) { hooks.viewportResize.apply(hooks, __spread(p)); } viewportResizeCb.apply(void 0, __spread(p)); }; o.inputCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.input) { hooks.input.apply(hooks, __spread(p)); } inputCb.apply(void 0, __spread(p)); }; o.mediaInteractionCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.mediaInteaction) { hooks.mediaInteaction.apply(hooks, __spread(p)); } mediaInteractionCb.apply(void 0, __spread(p)); }; o.styleSheetRuleCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.styleSheetRule) { hooks.styleSheetRule.apply(hooks, __spread(p)); } styleSheetRuleCb.apply(void 0, __spread(p)); }; } function initObservers(o, hooks) { if (hooks === void 0) { hooks = {}; } mergeHooks(o, hooks); var mutationObserver = initMutationObserver(o.mutationCb, o.blockClass, o.inlineStylesheet, o.maskInputOptions, o.blockElements, o.asyncClass); var mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling); var mouseInteractionHandler = initMouseInteractionObserver(o.mouseInteractionCb, o.blockClass, o.sampling, o.blockElements); var scrollHandler = initScrollObserver(o.scrollCb, o.blockClass, o.sampling, o.blockElements); var viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb); var inputHandler = initInputObserver(o.inputCb, o.blockClass, o.ignoreClass, o.maskInputOptions, o.sampling); initMediaInteractionObserver(o.mediaInteractionCb, o.blockClass, o.blockElements); var styleSheetObserver = initStyleSheetObserver(o.styleSheetRuleCb); var userDefinedEventHandler = initUserDefinedEventObserver(o.userDefinedEventCb); return function () { mutationObserver.disconnect(); mousemoveHandler(); mouseInteractionHandler(); scrollHandler(); viewportResizeHandler(); inputHandler(); styleSheetObserver(); userDefinedEventHandler(); }; } function mergeOtherHooks(o, hooks) { if (hooks === void 0) { hooks = {}; } var consoleCb = o.consoleCb, networkCb = o.networkCb; o.consoleCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.console) { hooks.console.apply(hooks, __spread(p)); } consoleCb.apply(void 0, __spread(p)); }; o.networkCb = function () { var p = []; for (var _i = 0; _i < arguments.length; _i++) { p[_i] = arguments[_i]; } if (hooks.network) { hooks.network.apply(hooks, __spread(p)); } networkCb.apply(void 0, __spread(p)); }; } function initOtherObserver(o, hooks) { mergeOtherHooks(o, hooks); var consoleObserver = initConsoleObserver(o.consoleCb); var networkObserver = initNetworkObserver(o.networkCb); return function () { consoleObserver(); networkObserver(); }; } export { INPUT_TAGS, initObservers as default, initOtherObserver };