@perceptr/web-sdk
Version:
Perceptr Web SDK for recording and monitoring user sessions
111 lines (110 loc) • 4.96 kB
JavaScript
import { isObject } from "./type-utils";
import { MAX_MESSAGE_SIZE, replacementImageURI, PLUGIN_EVENT_TYPE, CONSOLE_LOG_PLUGIN_NAME, } from "../common/defaults";
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#circular_references
export function circularReferenceReplacer() {
const ancestors = [];
return function (_key, value) {
if (isObject(value)) {
// `this` is the object that value is contained in,
// i.e., its direct parent.
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {
ancestors.pop();
}
if (ancestors.includes(value)) {
return "[Circular]";
}
ancestors.push(value);
return value;
}
else {
return value;
}
};
}
export function estimateSize(sizeable) {
var _a;
return ((_a = JSON.stringify(sizeable, circularReferenceReplacer())) === null || _a === void 0 ? void 0 : _a.length) || 0;
}
/*
* Check whether a data payload is nearing 5mb. If it is, it checks the data for
* data URIs (the likely culprit for large payloads). If it finds data URIs, it either replaces
* it with a generic image (if it's an image) or removes it.
* @data {object} the rr-web data object
* @returns {object} the rr-web data object with data uris filtered out
*/
export function ensureMaxMessageSize(data) {
let stringifiedData = JSON.stringify(data);
// Note: with compression, this limit may be able to be increased
// but we're assuming most of the size is from a data uri which
// is unlikely to be compressed further
if (stringifiedData.length > MAX_MESSAGE_SIZE) {
// Regex that matches the pattern for a dataURI with the shape 'data:{mime type};{encoding},{data}'. It:
// 1) Checks if the pattern starts with 'data:' (potentially, not at the start of the string)
// 2) Extracts the mime type of the data uri in the first group
// 3) Determines when the data URI ends.Depending on if it's used in the src tag or css, it can end with a ) or "
const dataURIRegex = /data:([\w/\-.]+);(\w+),([^)"]*)/gim;
const matches = stringifiedData.matchAll(dataURIRegex);
for (const match of matches) {
if (match[1].toLocaleLowerCase().slice(0, 6) === "image/") {
stringifiedData = stringifiedData.replace(match[0], replacementImageURI);
}
else {
stringifiedData = stringifiedData.replace(match[0], "");
}
}
}
return { event: JSON.parse(stringifiedData), size: stringifiedData.length };
}
// Console logs can be really large. This function truncates large logs
// It's a simple function that just truncates long strings.
// TODO: Ideally this function would have better handling of objects + lists,
// so they could still be rendered in a pretty way after truncation.
export function truncateLargeConsoleLogs(_event) {
const event = _event;
const MAX_STRING_SIZE = 2000; // Maximum number of characters allowed in a string
const MAX_STRINGS_PER_LOG = 10; // A log can consist of multiple strings (e.g. consol.log('string1', 'string2'))
if (event &&
isObject(event) &&
event.type === PLUGIN_EVENT_TYPE &&
isObject(event.data) &&
event.data.plugin === CONSOLE_LOG_PLUGIN_NAME) {
// Note: event.data.payload.payload comes from rr-web, and is an array of strings
if (event.data.payload.payload.length > MAX_STRINGS_PER_LOG) {
event.data.payload.payload = event.data.payload.payload.slice(0, MAX_STRINGS_PER_LOG);
event.data.payload.payload.push("...[truncated]");
}
const updatedPayload = [];
for (let i = 0; i < event.data.payload.payload.length; i++) {
if (event.data.payload.payload[i] && // Value can be null
event.data.payload.payload[i].length > MAX_STRING_SIZE) {
updatedPayload.push(event.data.payload.payload[i].slice(0, MAX_STRING_SIZE) +
"...[truncated]");
}
else {
updatedPayload.push(event.data.payload.payload[i]);
}
}
event.data.payload.payload = updatedPayload;
// Return original type
return _event;
}
return _event;
}
export function sessionRecordingUrlTriggerMatches(url, triggers) {
return triggers.some((trigger) => {
switch (trigger.matching) {
case "regex":
return new RegExp(trigger.url).test(url);
default:
return false;
}
});
}
export function scheduleIdleTask(task, timeout = 1000) {
if (typeof window.requestIdleCallback !== "undefined") {
window.requestIdleCallback(task, { timeout });
}
else {
setTimeout(task, timeout);
}
}