UNPKG

@perceptr/web-sdk

Version:

Perceptr Web SDK for recording and monitoring user sessions

86 lines (85 loc) 4.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MutationRateLimiter = void 0; const number_utils_1 = require("../../utils/number-utils"); const defaults_1 = require("../defaults"); class MutationRateLimiter { constructor(rrweb, options = {}) { var _a, _b; this.rrweb = rrweb; this.options = options; this.bucketSize = 100; this.refillRate = 10; this.mutationBuckets = {}; this.loggedTracker = {}; this.refillBuckets = () => { Object.keys(this.mutationBuckets).forEach((key) => { this.mutationBuckets[key] = this.mutationBuckets[key] + this.refillRate; if (this.mutationBuckets[key] >= this.bucketSize) { delete this.mutationBuckets[key]; } }); }; this.getNodeOrRelevantParent = (id) => { // For some nodes we know they are part of a larger tree such as an SVG. // For those we want to block the entire node, not just the specific attribute const node = this.rrweb.mirror.getNode(id); // Check if the node is an Element and then find the closest parent that is an SVG if ((node === null || node === void 0 ? void 0 : node.nodeName) !== "svg" && node instanceof Element) { const closestSVG = node.closest("svg"); if (closestSVG) { return [this.rrweb.mirror.getId(closestSVG), closestSVG]; } } return [id, node]; }; this.numberOfChanges = (data) => { var _a, _b, _c, _d, _e, _f, _g, _h; return (((_b = (_a = data.removes) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) + ((_d = (_c = data.attributes) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) + ((_f = (_e = data.texts) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0) + ((_h = (_g = data.adds) === null || _g === void 0 ? void 0 : _g.length) !== null && _h !== void 0 ? _h : 0)); }; this.throttleMutations = (event) => { if (event.type !== defaults_1.INCREMENTAL_SNAPSHOT_EVENT_TYPE || event.data.source !== defaults_1.MUTATION_SOURCE_TYPE) { return false; } const data = event.data; const initialMutationCount = this.numberOfChanges(data); if (data.attributes) { // Most problematic mutations come from attrs where the style or minor properties are changed rapidly data.attributes = data.attributes.filter((attr) => { var _a, _b, _c; const [nodeId, node] = this.getNodeOrRelevantParent(attr.id); if (this.mutationBuckets[nodeId] === 0) { return false; } this.mutationBuckets[nodeId] = (_a = this.mutationBuckets[nodeId]) !== null && _a !== void 0 ? _a : this.bucketSize; this.mutationBuckets[nodeId] = Math.max(this.mutationBuckets[nodeId] - 1, 0); if (this.mutationBuckets[nodeId] === 0) { if (!this.loggedTracker[nodeId]) { this.loggedTracker[nodeId] = true; (_c = (_b = this.options).onBlockedNode) === null || _c === void 0 ? void 0 : _c.call(_b, nodeId, node); } } return attr; }); } // Check if every part of the mutation is empty in which case there is nothing to do const mutationCount = this.numberOfChanges(data); if (mutationCount === 0 && initialMutationCount !== mutationCount) { // If we have modified the mutation count and the remaining count is 0, then we don't need the event. return true; } return false; }; this.refillRate = (0, number_utils_1.clampToRange)((_a = this.options.refillRate) !== null && _a !== void 0 ? _a : this.refillRate, 0, 100, "mutation throttling refill rate"); this.bucketSize = (0, number_utils_1.clampToRange)((_b = this.options.bucketSize) !== null && _b !== void 0 ? _b : this.bucketSize, 0, 100, "mutation throttling bucket size"); setInterval(() => { this.refillBuckets(); }, 1000); } } exports.MutationRateLimiter = MutationRateLimiter;