UNPKG

posthog-js

Version:

Posthog-js allows you to automatically capture usage and send events to PostHog.

206 lines • 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SEVEN_MEGABYTES = exports.CONSOLE_LOG_PLUGIN_NAME = exports.MAX_MESSAGE_SIZE = exports.MUTATION_SOURCE_TYPE = exports.PLUGIN_EVENT_TYPE = exports.INCREMENTAL_SNAPSHOT_EVENT_TYPE = exports.META_EVENT_TYPE = exports.FULL_SNAPSHOT_EVENT_TYPE = exports.replacementImageURI = exports.UNSTRINGIFIABLE_EVENT_SIZE = void 0; exports.circularReferenceReplacer = circularReferenceReplacer; exports.estimateSize = estimateSize; exports.estimateCompressedEventSize = estimateCompressedEventSize; exports.ensureMaxMessageSize = ensureMaxMessageSize; exports.truncateLargeConsoleLogs = truncateLargeConsoleLogs; exports.splitBuffer = splitBuffer; var core_1 = require("@posthog/core"); // taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#circular_references function circularReferenceReplacer() { var ancestors = []; return function (_key, value) { // arrays must be tracked as ancestors too - isObject excludes them, and an // untracked array in the path empties the ancestor stack, so a cycle passing // through an array would be missed and JSON.stringify would throw if ((0, core_1.isObject)(value) || (0, core_1.isArray)(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; } }; } function estimateStringBytes(data) { return new Blob([data]).size; } // an event whose JSON is longer than the engine's maximum string length makes JSON.stringify // throw `RangeError: Invalid string length`. Such an event can never reach the server either - // the request encoder stringifies the whole batch - so estimateSize reports the failure with // this size instead of throwing, and the caller drops that one event. exports.UNSTRINGIFIABLE_EVENT_SIZE = -1; function estimateSize(sizeable) { try { var stringifiedData = JSON.stringify(sizeable, circularReferenceReplacer()); return stringifiedData ? estimateStringBytes(stringifiedData) : 0; } catch (_a) { return exports.UNSTRINGIFIABLE_EVENT_SIZE; } } // Lightweight size estimate for compressed events without allocating a JSON string. // Intentionally loose: does not account for JSON escaping of strings or keys. // This is fine because compressed event strings are base64 gzip output (safe alphabet) // and keys are known-safe identifiers. Used only for buffer threshold checks (~1MB) // and split-buffer decisions (~7MB) where a few bytes of drift don't matter. function estimateCompressedEventSize(value) { if ((0, core_1.isNull)(value)) { return 4; } if ((0, core_1.isUndefined)(value)) { return 0; } switch (typeof value) { case 'string': return value.length + 2; case 'number': return String(value).length; case 'boolean': return value ? 4 : 5; case 'object': { if ((0, core_1.isArray)(value)) { var size_1 = 2; for (var i = 0; i < value.length; i++) { if (i > 0) size_1 += 1; var el = value[i]; size_1 += (0, core_1.isUndefined)(el) || (0, core_1.isNull)(el) ? 4 : estimateCompressedEventSize(el); } return size_1; } var obj = value; var size = 2; var first = true; for (var key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) continue; var val = obj[key]; if ((0, core_1.isUndefined)(val)) continue; if (!first) size += 1; first = false; size += key.length + 3 + estimateCompressedEventSize(val); } return size; } default: return 0; } } exports.replacementImageURI = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNOCAwSDE2TDAgMTZWOEw4IDBaIiBmaWxsPSIjMkQyRDJEIi8+CjxwYXRoIGQ9Ik0xNiA4VjE2SDhMMTYgOFoiIGZpbGw9IiMyRDJEMkQiLz4KPC9zdmc+Cg=='; exports.FULL_SNAPSHOT_EVENT_TYPE = 2; exports.META_EVENT_TYPE = 4; exports.INCREMENTAL_SNAPSHOT_EVENT_TYPE = 3; exports.PLUGIN_EVENT_TYPE = 6; exports.MUTATION_SOURCE_TYPE = 0; exports.MAX_MESSAGE_SIZE = 5000000; // ~5mb /* * 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 */ function ensureMaxMessageSize(data) { var 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 var size = estimateStringBytes(stringifiedData); if (size > exports.MAX_MESSAGE_SIZE) { // Regex that matches the pattern for a dataURI with the shape 'data:{mime type}[;{param}[={value}]]*,{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 (e.g. image/svg+xml) // 3) Allows any number of parameters, e.g. ';base64', ';charset=utf-8;base64' or none // 4) Determines when the data URI ends. It is matched against JSON text, so it must never consume // a " or \ (a quote inside a string is escaped as \", and consuming part of an escape would // produce invalid JSON). It also stops at ), ' and whitespace, which end a URI in css or srcset. var dataURIRegex = /data:([\w/\-.+]+)(?:;[\w\-.+]+(?:=[\w\-.+%]+)?)*,[^)"'\s\\]*/gim; var unfilteredStringifiedData = stringifiedData; stringifiedData = stringifiedData.replace(dataURIRegex, function (_match, mimeType) { return mimeType.toLocaleLowerCase().slice(0, 6) === 'image/' ? exports.replacementImageURI : ''; }); if (stringifiedData !== unfilteredStringifiedData) { size = estimateStringBytes(stringifiedData); } } return { event: JSON.parse(stringifiedData), size: size }; } exports.CONSOLE_LOG_PLUGIN_NAME = 'rrweb/console@1'; // The name of the rr-web plugin that emits console logs // 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. function truncateLargeConsoleLogs(_event) { var event = _event; var MAX_STRING_SIZE = 2000; // Maximum number of characters allowed in a string var MAX_STRINGS_PER_LOG = 10; // A log can consist of multiple strings (e.g. consol.log('string1', 'string2')) if (event && (0, core_1.isObject)(event) && event.type === exports.PLUGIN_EVENT_TYPE && (0, core_1.isObject)(event.data) && event.data.plugin === exports.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]'); } var updatedPayload = []; for (var 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; } exports.SEVEN_MEGABYTES = 1024 * 1024 * 7 * 0.9; // ~7mb (with some wiggle room) // recursively splits large buffers into smaller ones // uses a pretty high size limit to avoid splitting too much function splitBuffer(buffer, sizeLimit) { if (sizeLimit === void 0) { sizeLimit = exports.SEVEN_MEGABYTES; } if (buffer.size >= sizeLimit && buffer.data.length > 1) { var half = Math.floor(buffer.data.length / 2); var firstHalfSizes = buffer.sizes.slice(0, half); var secondHalfSizes = buffer.sizes.slice(half); return [ splitBuffer({ size: firstHalfSizes.reduce(function (a, b) { return a + b; }, 0), data: buffer.data.slice(0, half), sizes: firstHalfSizes, sessionId: buffer.sessionId, windowId: buffer.windowId, }), splitBuffer({ size: secondHalfSizes.reduce(function (a, b) { return a + b; }, 0), data: buffer.data.slice(half), sizes: secondHalfSizes, sessionId: buffer.sessionId, windowId: buffer.windowId, }), ].flatMap(function (x) { return x; }); } else { return [buffer]; } } //# sourceMappingURL=sessionrecording-utils.js.map