@xysfe/memento-core
Version:
record and replay the web
718 lines (715 loc) • 28.2 kB
JavaScript
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 _mockSetRequestHeader = window.XMLHttpRequest.prototype.setRequestHeader;
var reqList = {};
window.XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
var XMLReq = this;
var headerLower = header.toLowerCase();
if (headerLower === 'memento-ignore') {
XMLReq._noMementoConsole = ~~value === 1;
}
return _mockSetRequestHeader.apply(XMLReq, arguments);
};
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;
if (typeof XMLReq.response === 'string') {
item.responseSize = XMLReq.response.length;
}
else if (XMLReq.response && typeof XMLReq.response.size === 'number') {
item.responseSize = XMLReq.response.size;
}
else if (XMLReq.response && typeof XMLReq.response.byteLength === 'number') {
item.responseSize = XMLReq.response.byteLength;
}
else {
try {
item.responseSize = JSON.stringify(XMLReq.response).length;
}
catch (e) {
item.responseSize = 0;
}
}
}
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.responseSize && item.responseSize > 30 * 1024 * 1024) {
item.response = 'Post Request => Too long has been blocked';
}
else 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.setRequestHeader = _mockSetRequestHeader;
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 };