summaryrefslogtreecommitdiff
path: root/shared/logger/node_modules/@sentry/replay/esm/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'shared/logger/node_modules/@sentry/replay/esm/index.js')
-rw-r--r--shared/logger/node_modules/@sentry/replay/esm/index.js8542
1 files changed, 8542 insertions, 0 deletions
diff --git a/shared/logger/node_modules/@sentry/replay/esm/index.js b/shared/logger/node_modules/@sentry/replay/esm/index.js
new file mode 100644
index 0000000..84a600d
--- /dev/null
+++ b/shared/logger/node_modules/@sentry/replay/esm/index.js
@@ -0,0 +1,8542 @@
+import { getCurrentHub, addGlobalEventProcessor, prepareEvent, setContext, captureException } from '@sentry/core';
+import { GLOBAL_OBJ, normalize, fill, htmlTreeAsString, logger, uuid4, SENTRY_XHR_DATA_KEY, dropUndefinedKeys, stringMatchesSomePattern, addInstrumentationHandler, browserPerformanceTimeOrigin, createEnvelope, createEventEnvelopeHeaders, getSdkMetadataForEnvelopeHeader, isNodeEnv } from '@sentry/utils';
+
+// exporting a separate copy of `WINDOW` rather than exporting the one from `@sentry/browser`
+// prevents the browser package from being bundled in the CDN bundle, and avoids a
+// circular dependency between the browser and replay packages should `@sentry/browser` import
+// from `@sentry/replay` in the future
+const WINDOW = GLOBAL_OBJ ;
+
+const REPLAY_SESSION_KEY = 'sentryReplaySession';
+const REPLAY_EVENT_NAME = 'replay_event';
+const UNABLE_TO_SEND_REPLAY = 'Unable to send Replay';
+
+// The idle limit for a session after which recording is paused.
+const SESSION_IDLE_PAUSE_DURATION = 300000; // 5 minutes in ms
+
+// The idle limit for a session after which the session expires.
+const SESSION_IDLE_EXPIRE_DURATION = 900000; // 15 minutes in ms
+
+// The maximum length of a session
+const MAX_SESSION_LIFE = 3600000; // 60 minutes in ms
+
+/** Default flush delays */
+const DEFAULT_FLUSH_MIN_DELAY = 5000;
+// XXX: Temp fix for our debounce logic where `maxWait` would never occur if it
+// was the same as `wait`
+const DEFAULT_FLUSH_MAX_DELAY = 5500;
+
+/* How long to wait for error checkouts */
+const BUFFER_CHECKOUT_TIME = 60000;
+
+const RETRY_BASE_INTERVAL = 5000;
+const RETRY_MAX_COUNT = 3;
+
+/* The max (uncompressed) size in bytes of a network body. Any body larger than this will be truncated. */
+const NETWORK_BODY_MAX_SIZE = 150000;
+
+/* The max size of a single console arg that is captured. Any arg larger than this will be truncated. */
+const CONSOLE_ARG_MAX_SIZE = 5000;
+
+/* Min. time to wait before we consider something a slow click. */
+const SLOW_CLICK_THRESHOLD = 3000;
+/* For scroll actions after a click, we only look for a very short time period to detect programmatic scrolling. */
+const SLOW_CLICK_SCROLL_TIMEOUT = 300;
+/* Clicks in this time period are considered e.g. double/triple clicks. */
+const MULTI_CLICK_TIMEOUT = 1000;
+
+/** When encountering a total segment size exceeding this size, stop the replay (as we cannot properly ingest it). */
+const REPLAY_MAX_EVENT_BUFFER_SIZE = 20000000; // ~20MB
+
+var NodeType$1;
+(function (NodeType) {
+ NodeType[NodeType["Document"] = 0] = "Document";
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
+ NodeType[NodeType["Element"] = 2] = "Element";
+ NodeType[NodeType["Text"] = 3] = "Text";
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
+ NodeType[NodeType["Comment"] = 5] = "Comment";
+})(NodeType$1 || (NodeType$1 = {}));
+
+function isElement(n) {
+ return n.nodeType === n.ELEMENT_NODE;
+}
+function isShadowRoot(n) {
+ const host = n === null || n === void 0 ? void 0 : n.host;
+ return Boolean(host && host.shadowRoot && host.shadowRoot === n);
+}
+function isInputTypeMasked({ maskInputOptions, tagName, type, }) {
+ if (tagName.toLowerCase() === 'option') {
+ tagName = 'select';
+ }
+ const actualType = typeof type === 'string' ? type.toLowerCase() : undefined;
+ return (maskInputOptions[tagName.toLowerCase()] ||
+ (actualType && maskInputOptions[actualType]) ||
+ actualType === 'password' ||
+ (tagName === 'input' && !type && maskInputOptions['text']));
+}
+function hasInputMaskOptions({ tagName, type, maskInputOptions, maskInputSelector, }) {
+ return (maskInputSelector || isInputTypeMasked({ maskInputOptions, tagName, type }));
+}
+function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInputOptions, tagName, type, value, maskInputFn, }) {
+ let text = value || '';
+ if (unmaskInputSelector && input.matches(unmaskInputSelector)) {
+ return text;
+ }
+ if (input.hasAttribute('data-rr-is-password')) {
+ type = 'password';
+ }
+ if (isInputTypeMasked({ maskInputOptions, tagName, type }) ||
+ (maskInputSelector && input.matches(maskInputSelector))) {
+ if (maskInputFn) {
+ text = maskInputFn(text);
+ }
+ else {
+ text = '*'.repeat(text.length);
+ }
+ }
+ return text;
+}
+const ORIGINAL_ATTRIBUTE_NAME = '__rrweb_original__';
+function is2DCanvasBlank(canvas) {
+ const ctx = canvas.getContext('2d');
+ if (!ctx)
+ return true;
+ const chunkSize = 50;
+ for (let x = 0; x < canvas.width; x += chunkSize) {
+ for (let y = 0; y < canvas.height; y += chunkSize) {
+ const getImageData = ctx.getImageData;
+ const originalGetImageData = ORIGINAL_ATTRIBUTE_NAME in getImageData
+ ? getImageData[ORIGINAL_ATTRIBUTE_NAME]
+ : getImageData;
+ const pixelBuffer = new Uint32Array(originalGetImageData.call(ctx, x, y, Math.min(chunkSize, canvas.width - x), Math.min(chunkSize, canvas.height - y)).data.buffer);
+ if (pixelBuffer.some((pixel) => pixel !== 0))
+ return false;
+ }
+ }
+ return true;
+}
+function getInputType(element) {
+ const type = element.type;
+ return element.hasAttribute('data-rr-is-password')
+ ? 'password'
+ : type
+ ? type.toLowerCase()
+ : null;
+}
+function getInputValue(el, tagName, type) {
+ typeof type === 'string' ? type.toLowerCase() : '';
+ if (tagName === 'INPUT' && (type === 'radio' || type === 'checkbox')) {
+ return el.getAttribute('value') || '';
+ }
+ return el.value;
+}
+
+let _id = 1;
+const tagNameRegex = new RegExp('[^a-z0-9-_:]');
+const IGNORED_NODE = -2;
+function defaultMaskFn(str) {
+ return str ? str.replace(/[\S]/g, '*') : '';
+}
+function genId() {
+ return _id++;
+}
+function getValidTagName(element) {
+ if (element instanceof HTMLFormElement) {
+ return 'form';
+ }
+ const processedTagName = element.tagName.toLowerCase().trim();
+ if (tagNameRegex.test(processedTagName)) {
+ return 'div';
+ }
+ return processedTagName;
+}
+function getCssRulesString(s) {
+ try {
+ const rules = s.rules || s.cssRules;
+ return rules ? Array.from(rules).map(getCssRuleString).join('') : null;
+ }
+ catch (error) {
+ return null;
+ }
+}
+function getCssRuleString(rule) {
+ let cssStringified = rule.cssText;
+ if (isCSSImportRule(rule)) {
+ try {
+ cssStringified = getCssRulesString(rule.styleSheet) || cssStringified;
+ }
+ catch (_a) {
+ }
+ }
+ return validateStringifiedCssRule(cssStringified);
+}
+function validateStringifiedCssRule(cssStringified) {
+ if (cssStringified.indexOf(':') > -1) {
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
+ return cssStringified.replace(regex, '$1\\$2');
+ }
+ return cssStringified;
+}
+function isCSSImportRule(rule) {
+ return 'styleSheet' in rule;
+}
+function stringifyStyleSheet(sheet) {
+ return sheet.cssRules
+ ? Array.from(sheet.cssRules)
+ .map((rule) => rule.cssText ? validateStringifiedCssRule(rule.cssText) : '')
+ .join('')
+ : '';
+}
+function extractOrigin(url) {
+ let origin = '';
+ if (url.indexOf('//') > -1) {
+ origin = url.split('/').slice(0, 3).join('/');
+ }
+ else {
+ origin = url.split('/')[0];
+ }
+ origin = origin.split('?')[0];
+ return origin;
+}
+let canvasService;
+let canvasCtx;
+const URL_IN_CSS_REF = /url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm;
+const RELATIVE_PATH = /^(?!www\.|(?:http|ftp)s?:\/\/|[A-Za-z]:\\|\/\/|#).*/;
+const DATA_URI = /^(data:)([^,]*),(.*)/i;
+function absoluteToStylesheet(cssText, href) {
+ return (cssText || '').replace(URL_IN_CSS_REF, (origin, quote1, path1, quote2, path2, path3) => {
+ const filePath = path1 || path2 || path3;
+ const maybeQuote = quote1 || quote2 || '';
+ if (!filePath) {
+ return origin;
+ }
+ if (!RELATIVE_PATH.test(filePath)) {
+ return `url(${maybeQuote}${filePath}${maybeQuote})`;
+ }
+ if (DATA_URI.test(filePath)) {
+ return `url(${maybeQuote}${filePath}${maybeQuote})`;
+ }
+ if (filePath[0] === '/') {
+ return `url(${maybeQuote}${extractOrigin(href) + filePath}${maybeQuote})`;
+ }
+ const stack = href.split('/');
+ const parts = filePath.split('/');
+ stack.pop();
+ for (const part of parts) {
+ if (part === '.') {
+ continue;
+ }
+ else if (part === '..') {
+ stack.pop();
+ }
+ else {
+ stack.push(part);
+ }
+ }
+ return `url(${maybeQuote}${stack.join('/')}${maybeQuote})`;
+ });
+}
+const SRCSET_NOT_SPACES = /^[^ \t\n\r\u000c]+/;
+const SRCSET_COMMAS_OR_SPACES = /^[, \t\n\r\u000c]+/;
+function getAbsoluteSrcsetString(doc, attributeValue) {
+ if (attributeValue.trim() === '') {
+ return attributeValue;
+ }
+ let pos = 0;
+ function collectCharacters(regEx) {
+ let chars;
+ let match = regEx.exec(attributeValue.substring(pos));
+ if (match) {
+ chars = match[0];
+ pos += chars.length;
+ return chars;
+ }
+ return '';
+ }
+ let output = [];
+ while (true) {
+ collectCharacters(SRCSET_COMMAS_OR_SPACES);
+ if (pos >= attributeValue.length) {
+ break;
+ }
+ let url = collectCharacters(SRCSET_NOT_SPACES);
+ if (url.slice(-1) === ',') {
+ url = absoluteToDoc(doc, url.substring(0, url.length - 1));
+ output.push(url);
+ }
+ else {
+ let descriptorsStr = '';
+ url = absoluteToDoc(doc, url);
+ let inParens = false;
+ while (true) {
+ let c = attributeValue.charAt(pos);
+ if (c === '') {
+ output.push((url + descriptorsStr).trim());
+ break;
+ }
+ else if (!inParens) {
+ if (c === ',') {
+ pos += 1;
+ output.push((url + descriptorsStr).trim());
+ break;
+ }
+ else if (c === '(') {
+ inParens = true;
+ }
+ }
+ else {
+ if (c === ')') {
+ inParens = false;
+ }
+ }
+ descriptorsStr += c;
+ pos += 1;
+ }
+ }
+ }
+ return output.join(', ');
+}
+function absoluteToDoc(doc, attributeValue) {
+ if (!attributeValue || attributeValue.trim() === '') {
+ return attributeValue;
+ }
+ const a = doc.createElement('a');
+ a.href = attributeValue;
+ return a.href;
+}
+function isSVGElement(el) {
+ return Boolean(el.tagName === 'svg' || el.ownerSVGElement);
+}
+function getHref() {
+ const a = document.createElement('a');
+ a.href = '';
+ return a.href;
+}
+function transformAttribute(doc, element, _tagName, _name, value, maskAllText, unmaskTextSelector, maskTextFn) {
+ if (!value) {
+ return value;
+ }
+ const name = _name.toLowerCase();
+ const tagName = _tagName.toLowerCase();
+ if (name === 'src' || name === 'href') {
+ return absoluteToDoc(doc, value);
+ }
+ else if (name === 'xlink:href' && value[0] !== '#') {
+ return absoluteToDoc(doc, value);
+ }
+ else if (name === 'background' &&
+ (tagName === 'table' || tagName === 'td' || tagName === 'th')) {
+ return absoluteToDoc(doc, value);
+ }
+ else if (name === 'srcset') {
+ return getAbsoluteSrcsetString(doc, value);
+ }
+ else if (name === 'style') {
+ return absoluteToStylesheet(value, getHref());
+ }
+ else if (tagName === 'object' && name === 'data') {
+ return absoluteToDoc(doc, value);
+ }
+ else if (maskAllText &&
+ _shouldMaskAttribute(element, name, tagName, unmaskTextSelector)) {
+ return maskTextFn ? maskTextFn(value) : defaultMaskFn(value);
+ }
+ return value;
+}
+function _shouldMaskAttribute(element, attribute, tagName, unmaskTextSelector) {
+ if (unmaskTextSelector && element.matches(unmaskTextSelector)) {
+ return false;
+ }
+ return (['placeholder', 'title', 'aria-label'].indexOf(attribute) > -1 ||
+ (tagName === 'input' &&
+ attribute === 'value' &&
+ element.hasAttribute('type') &&
+ ['submit', 'button'].indexOf(element.getAttribute('type').toLowerCase()) > -1));
+}
+function _isBlockedElement(element, blockClass, blockSelector, unblockSelector) {
+ if (unblockSelector && element.matches(unblockSelector)) {
+ return false;
+ }
+ if (typeof blockClass === 'string') {
+ if (element.classList.contains(blockClass)) {
+ return true;
+ }
+ }
+ else {
+ for (let eIndex = 0; eIndex < element.classList.length; eIndex++) {
+ const className = element.classList[eIndex];
+ if (blockClass.test(className)) {
+ return true;
+ }
+ }
+ }
+ if (blockSelector) {
+ return element.matches(blockSelector);
+ }
+ return false;
+}
+function needMaskingText(node, maskTextClass, maskTextSelector, unmaskTextSelector, maskAllText) {
+ if (!node) {
+ return false;
+ }
+ if (node.nodeType !== node.ELEMENT_NODE) {
+ return needMaskingText(node.parentNode, maskTextClass, maskTextSelector, unmaskTextSelector, maskAllText);
+ }
+ if (unmaskTextSelector) {
+ if (node.matches(unmaskTextSelector) ||
+ node.closest(unmaskTextSelector)) {
+ return false;
+ }
+ }
+ if (maskAllText) {
+ return true;
+ }
+ if (typeof maskTextClass === 'string') {
+ if (node.classList.contains(maskTextClass)) {
+ return true;
+ }
+ }
+ else {
+ for (let eIndex = 0; eIndex < node.classList.length; eIndex++) {
+ const className = node.classList[eIndex];
+ if (maskTextClass.test(className)) {
+ return true;
+ }
+ }
+ }
+ if (maskTextSelector) {
+ if (node.matches(maskTextSelector)) {
+ return true;
+ }
+ }
+ return needMaskingText(node.parentNode, maskTextClass, maskTextSelector, unmaskTextSelector, maskAllText);
+}
+function onceIframeLoaded(iframeEl, listener, iframeLoadTimeout) {
+ const win = iframeEl.contentWindow;
+ if (!win) {
+ return;
+ }
+ let fired = false;
+ let readyState;
+ try {
+ readyState = win.document.readyState;
+ }
+ catch (error) {
+ return;
+ }
+ if (readyState !== 'complete') {
+ const timer = setTimeout(() => {
+ if (!fired) {
+ listener();
+ fired = true;
+ }
+ }, iframeLoadTimeout);
+ iframeEl.addEventListener('load', () => {
+ clearTimeout(timer);
+ fired = true;
+ listener();
+ });
+ return;
+ }
+ const blankUrl = 'about:blank';
+ if (win.location.href !== blankUrl ||
+ iframeEl.src === blankUrl ||
+ iframeEl.src === '') {
+ setTimeout(listener, 0);
+ return;
+ }
+ iframeEl.addEventListener('load', listener);
+}
+function serializeNode(n, options) {
+ var _a;
+ const { doc, blockClass, blockSelector, unblockSelector, maskTextClass, maskTextSelector, unmaskTextSelector, inlineStylesheet, maskInputSelector, unmaskInputSelector, maskAllText, maskInputOptions = {}, maskTextFn, maskInputFn, dataURLOptions = {}, inlineImages, recordCanvas, keepIframeSrcFn, } = options;
+ let rootId;
+ if (doc.__sn) {
+ const docId = doc.__sn.id;
+ rootId = docId === 1 ? undefined : docId;
+ }
+ switch (n.nodeType) {
+ case n.DOCUMENT_NODE:
+ if (n.compatMode !== 'CSS1Compat') {
+ return {
+ type: NodeType$1.Document,
+ childNodes: [],
+ compatMode: n.compatMode,
+ rootId,
+ };
+ }
+ else {
+ return {
+ type: NodeType$1.Document,
+ childNodes: [],
+ rootId,
+ };
+ }
+ case n.DOCUMENT_TYPE_NODE:
+ return {
+ type: NodeType$1.DocumentType,
+ name: n.name,
+ publicId: n.publicId,
+ systemId: n.systemId,
+ rootId,
+ };
+ case n.ELEMENT_NODE:
+ const needBlock = _isBlockedElement(n, blockClass, blockSelector, unblockSelector);
+ const tagName = getValidTagName(n);
+ let attributes = {};
+ for (const { name, value } of Array.from(n.attributes)) {
+ if (!skipAttribute(tagName, name)) {
+ attributes[name] = transformAttribute(doc, n, tagName, name, value, maskAllText, unmaskTextSelector, maskTextFn);
+ }
+ }
+ if (tagName === 'link' && inlineStylesheet) {
+ const stylesheet = Array.from(doc.styleSheets).find((s) => {
+ return s.href === n.href;
+ });
+ let cssText = null;
+ if (stylesheet) {
+ cssText = getCssRulesString(stylesheet);
+ }
+ if (cssText) {
+ delete attributes.rel;
+ delete attributes.href;
+ attributes._cssText = absoluteToStylesheet(cssText, stylesheet.href);
+ }
+ }
+ if (tagName === 'style' &&
+ n.sheet &&
+ !(n.innerText ||
+ n.textContent ||
+ '').trim().length) {
+ const cssText = getCssRulesString(n.sheet);
+ if (cssText) {
+ attributes._cssText = absoluteToStylesheet(cssText, getHref());
+ }
+ }
+ if (tagName === 'input' ||
+ tagName === 'textarea' ||
+ tagName === 'select' ||
+ tagName === 'option') {
+ const el = n;
+ const type = getInputType(el);
+ const value = getInputValue(el, tagName.toUpperCase(), type);
+ const checked = n.checked;
+ if (type !== 'submit' &&
+ type !== 'button' &&
+ value) {
+ attributes.value = maskInputValue({
+ input: el,
+ type,
+ tagName,
+ value,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskInputOptions,
+ maskInputFn,
+ });
+ }
+ if (checked) {
+ attributes.checked = checked;
+ }
+ }
+ if (tagName === 'option') {
+ if (n.selected && !maskInputOptions['select']) {
+ attributes.selected = true;
+ }
+ else {
+ delete attributes.selected;
+ }
+ }
+ if (tagName === 'canvas' && recordCanvas) {
+ if (n.__context === '2d') {
+ if (!is2DCanvasBlank(n)) {
+ attributes.rr_dataURL = n.toDataURL(dataURLOptions.type, dataURLOptions.quality);
+ }
+ }
+ else if (!('__context' in n)) {
+ const canvasDataURL = n.toDataURL(dataURLOptions.type, dataURLOptions.quality);
+ const blankCanvas = document.createElement('canvas');
+ blankCanvas.width = n.width;
+ blankCanvas.height = n.height;
+ const blankCanvasDataURL = blankCanvas.toDataURL(dataURLOptions.type, dataURLOptions.quality);
+ if (canvasDataURL !== blankCanvasDataURL) {
+ attributes.rr_dataURL = canvasDataURL;
+ }
+ }
+ }
+ if (tagName === 'img' && inlineImages) {
+ if (!canvasService) {
+ canvasService = doc.createElement('canvas');
+ canvasCtx = canvasService.getContext('2d');
+ }
+ const image = n;
+ const oldValue = image.crossOrigin;
+ image.crossOrigin = 'anonymous';
+ const recordInlineImage = () => {
+ try {
+ canvasService.width = image.naturalWidth;
+ canvasService.height = image.naturalHeight;
+ canvasCtx.drawImage(image, 0, 0);
+ attributes.rr_dataURL = canvasService.toDataURL(dataURLOptions.type, dataURLOptions.quality);
+ }
+ catch (err) {
+ console.warn(`Cannot inline img src=${image.currentSrc}! Error: ${err}`);
+ }
+ oldValue
+ ? (attributes.crossOrigin = oldValue)
+ : delete attributes.crossOrigin;
+ };
+ if (image.complete && image.naturalWidth !== 0)
+ recordInlineImage();
+ else
+ image.onload = recordInlineImage;
+ }
+ if (tagName === 'audio' || tagName === 'video') {
+ attributes.rr_mediaState = n.paused
+ ? 'paused'
+ : 'played';
+ attributes.rr_mediaCurrentTime = n.currentTime;
+ }
+ if (n.scrollLeft) {
+ attributes.rr_scrollLeft = n.scrollLeft;
+ }
+ if (n.scrollTop) {
+ attributes.rr_scrollTop = n.scrollTop;
+ }
+ if (needBlock) {
+ const { width, height } = n.getBoundingClientRect();
+ attributes = {
+ class: attributes.class,
+ rr_width: `${width}px`,
+ rr_height: `${height}px`,
+ };
+ }
+ if (tagName === 'iframe' && !keepIframeSrcFn(attributes.src)) {
+ if (!n.contentDocument) {
+ attributes.rr_src = attributes.src;
+ }
+ delete attributes.src;
+ }
+ return {
+ type: NodeType$1.Element,
+ tagName,
+ attributes,
+ childNodes: [],
+ isSVG: isSVGElement(n) || undefined,
+ needBlock,
+ rootId,
+ };
+ case n.TEXT_NODE:
+ const parentTagName = n.parentNode && n.parentNode.tagName;
+ let textContent = n.textContent;
+ const isStyle = parentTagName === 'STYLE' ? true : undefined;
+ const isScript = parentTagName === 'SCRIPT' ? true : undefined;
+ if (isStyle && textContent) {
+ try {
+ if (n.nextSibling || n.previousSibling) {
+ }
+ else if ((_a = n.parentNode.sheet) === null || _a === void 0 ? void 0 : _a.cssRules) {
+ textContent = stringifyStyleSheet(n.parentNode.sheet);
+ }
+ }
+ catch (err) {
+ console.warn(`Cannot get CSS styles from text's parentNode. Error: ${err}`, n);
+ }
+ textContent = absoluteToStylesheet(textContent, getHref());
+ }
+ if (isScript) {
+ textContent = 'SCRIPT_PLACEHOLDER';
+ }
+ if (parentTagName === 'TEXTAREA' && textContent) {
+ textContent = '';
+ }
+ else if (parentTagName === 'OPTION' && textContent) {
+ const option = n.parentNode;
+ textContent = maskInputValue({
+ input: option,
+ type: null,
+ tagName: parentTagName,
+ value: textContent,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskInputOptions,
+ maskInputFn,
+ });
+ }
+ else if (!isStyle &&
+ !isScript &&
+ needMaskingText(n, maskTextClass, maskTextSelector, unmaskTextSelector, maskAllText) &&
+ textContent) {
+ textContent = maskTextFn
+ ? maskTextFn(textContent)
+ : defaultMaskFn(textContent);
+ }
+ return {
+ type: NodeType$1.Text,
+ textContent: textContent || '',
+ isStyle,
+ rootId,
+ };
+ case n.CDATA_SECTION_NODE:
+ return {
+ type: NodeType$1.CDATA,
+ textContent: '',
+ rootId,
+ };
+ case n.COMMENT_NODE:
+ return {
+ type: NodeType$1.Comment,
+ textContent: n.textContent || '',
+ rootId,
+ };
+ default:
+ return false;
+ }
+}
+function lowerIfExists(maybeAttr) {
+ if (maybeAttr === undefined || maybeAttr === null) {
+ return '';
+ }
+ else {
+ return maybeAttr.toLowerCase();
+ }
+}
+function slimDOMExcluded(sn, slimDOMOptions) {
+ if (slimDOMOptions.comment && sn.type === NodeType$1.Comment) {
+ return true;
+ }
+ else if (sn.type === NodeType$1.Element) {
+ if (slimDOMOptions.script &&
+ (sn.tagName === 'script' ||
+ (sn.tagName === 'link' &&
+ (sn.attributes.rel === 'preload' ||
+ sn.attributes.rel === 'modulepreload') &&
+ sn.attributes.as === 'script') ||
+ (sn.tagName === 'link' &&
+ sn.attributes.rel === 'prefetch' &&
+ typeof sn.attributes.href === 'string' &&
+ sn.attributes.href.endsWith('.js')))) {
+ return true;
+ }
+ else if (slimDOMOptions.headFavicon &&
+ ((sn.tagName === 'link' && sn.attributes.rel === 'shortcut icon') ||
+ (sn.tagName === 'meta' &&
+ (lowerIfExists(sn.attributes.name).match(/^msapplication-tile(image|color)$/) ||
+ lowerIfExists(sn.attributes.name) === 'application-name' ||
+ lowerIfExists(sn.attributes.rel) === 'icon' ||
+ lowerIfExists(sn.attributes.rel) === 'apple-touch-icon' ||
+ lowerIfExists(sn.attributes.rel) === 'shortcut icon')))) {
+ return true;
+ }
+ else if (sn.tagName === 'meta') {
+ if (slimDOMOptions.headMetaDescKeywords &&
+ lowerIfExists(sn.attributes.name).match(/^description|keywords$/)) {
+ return true;
+ }
+ else if (slimDOMOptions.headMetaSocial &&
+ (lowerIfExists(sn.attributes.property).match(/^(og|twitter|fb):/) ||
+ lowerIfExists(sn.attributes.name).match(/^(og|twitter):/) ||
+ lowerIfExists(sn.attributes.name) === 'pinterest')) {
+ return true;
+ }
+ else if (slimDOMOptions.headMetaRobots &&
+ (lowerIfExists(sn.attributes.name) === 'robots' ||
+ lowerIfExists(sn.attributes.name) === 'googlebot' ||
+ lowerIfExists(sn.attributes.name) === 'bingbot')) {
+ return true;
+ }
+ else if (slimDOMOptions.headMetaHttpEquiv &&
+ sn.attributes['http-equiv'] !== undefined) {
+ return true;
+ }
+ else if (slimDOMOptions.headMetaAuthorship &&
+ (lowerIfExists(sn.attributes.name) === 'author' ||
+ lowerIfExists(sn.attributes.name) === 'generator' ||
+ lowerIfExists(sn.attributes.name) === 'framework' ||
+ lowerIfExists(sn.attributes.name) === 'publisher' ||
+ lowerIfExists(sn.attributes.name) === 'progid' ||
+ lowerIfExists(sn.attributes.property).match(/^article:/) ||
+ lowerIfExists(sn.attributes.property).match(/^product:/))) {
+ return true;
+ }
+ else if (slimDOMOptions.headMetaVerification &&
+ (lowerIfExists(sn.attributes.name) === 'google-site-verification' ||
+ lowerIfExists(sn.attributes.name) === 'yandex-verification' ||
+ lowerIfExists(sn.attributes.name) === 'csrf-token' ||
+ lowerIfExists(sn.attributes.name) === 'p:domain_verify' ||
+ lowerIfExists(sn.attributes.name) === 'verify-v1' ||
+ lowerIfExists(sn.attributes.name) === 'verification' ||
+ lowerIfExists(sn.attributes.name) === 'shopify-checkout-api-token')) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+function serializeNodeWithId(n, options) {
+ const { doc, map, blockClass, blockSelector, unblockSelector, maskTextClass, maskTextSelector, unmaskTextSelector, skipChild = false, inlineStylesheet = true, maskInputSelector, unmaskInputSelector, maskAllText, maskInputOptions = {}, maskTextFn, maskInputFn, slimDOMOptions, dataURLOptions = {}, inlineImages = false, recordCanvas = false, onSerialize, onIframeLoad, iframeLoadTimeout = 5000, keepIframeSrcFn = () => false, } = options;
+ let { preserveWhiteSpace = true } = options;
+ const _serializedNode = serializeNode(n, {
+ doc,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ inlineStylesheet,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskAllText,
+ maskInputOptions,
+ maskTextFn,
+ maskInputFn,
+ dataURLOptions,
+ inlineImages,
+ recordCanvas,
+ keepIframeSrcFn,
+ });
+ if (!_serializedNode) {
+ console.warn(n, 'not serialized');
+ return null;
+ }
+ let id;
+ if ('__sn' in n) {
+ id = n.__sn.id;
+ }
+ else if (slimDOMExcluded(_serializedNode, slimDOMOptions) ||
+ (!preserveWhiteSpace &&
+ _serializedNode.type === NodeType$1.Text &&
+ !_serializedNode.isStyle &&
+ !_serializedNode.textContent.replace(/^\s+|\s+$/gm, '').length)) {
+ id = IGNORED_NODE;
+ }
+ else {
+ id = genId();
+ }
+ const serializedNode = Object.assign(_serializedNode, { id });
+ n.__sn = serializedNode;
+ if (id === IGNORED_NODE) {
+ return null;
+ }
+ map[id] = n;
+ if (onSerialize) {
+ onSerialize(n);
+ }
+ let recordChild = !skipChild;
+ if (serializedNode.type === NodeType$1.Element) {
+ recordChild = recordChild && !serializedNode.needBlock;
+ delete serializedNode.needBlock;
+ if (n.shadowRoot)
+ serializedNode.isShadowHost = true;
+ }
+ if ((serializedNode.type === NodeType$1.Document ||
+ serializedNode.type === NodeType$1.Element) &&
+ recordChild) {
+ if (slimDOMOptions.headWhitespace &&
+ _serializedNode.type === NodeType$1.Element &&
+ _serializedNode.tagName === 'head') {
+ preserveWhiteSpace = false;
+ }
+ const bypassOptions = {
+ doc,
+ map,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ skipChild,
+ inlineStylesheet,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskAllText,
+ maskInputOptions,
+ maskTextFn,
+ maskInputFn,
+ slimDOMOptions,
+ dataURLOptions,
+ inlineImages,
+ recordCanvas,
+ preserveWhiteSpace,
+ onSerialize,
+ onIframeLoad,
+ iframeLoadTimeout,
+ keepIframeSrcFn,
+ };
+ for (const childN of Array.from(n.childNodes)) {
+ const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
+ if (serializedChildNode) {
+ serializedNode.childNodes.push(serializedChildNode);
+ }
+ }
+ if (isElement(n) && n.shadowRoot) {
+ for (const childN of Array.from(n.shadowRoot.childNodes)) {
+ const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
+ if (serializedChildNode) {
+ serializedChildNode.isShadow = true;
+ serializedNode.childNodes.push(serializedChildNode);
+ }
+ }
+ }
+ }
+ if (n.parentNode && isShadowRoot(n.parentNode)) {
+ serializedNode.isShadow = true;
+ }
+ if (serializedNode.type === NodeType$1.Element &&
+ serializedNode.tagName === 'iframe') {
+ onceIframeLoaded(n, () => {
+ const iframeDoc = n.contentDocument;
+ if (iframeDoc && onIframeLoad) {
+ const serializedIframeNode = serializeNodeWithId(iframeDoc, {
+ doc: iframeDoc,
+ map,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ skipChild: false,
+ inlineStylesheet,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskAllText,
+ maskInputOptions,
+ maskTextFn,
+ maskInputFn,
+ slimDOMOptions,
+ dataURLOptions,
+ inlineImages,
+ recordCanvas,
+ preserveWhiteSpace,
+ onSerialize,
+ onIframeLoad,
+ iframeLoadTimeout,
+ keepIframeSrcFn,
+ });
+ if (serializedIframeNode) {
+ onIframeLoad(n, serializedIframeNode);
+ }
+ }
+ }, iframeLoadTimeout);
+ }
+ return serializedNode;
+}
+function snapshot(n, options) {
+ const { blockClass = 'rr-block', blockSelector = null, unblockSelector = null, maskTextClass = 'rr-mask', maskTextSelector = null, unmaskTextSelector = null, inlineStylesheet = true, inlineImages = false, recordCanvas = false, maskInputSelector = null, unmaskInputSelector = null, maskAllText = false, maskAllInputs = false, maskTextFn, maskInputFn, slimDOM = false, dataURLOptions, preserveWhiteSpace, onSerialize, onIframeLoad, iframeLoadTimeout, keepIframeSrcFn = () => false, } = options || {};
+ const idNodeMap = {};
+ const maskInputOptions = maskAllInputs === true
+ ? {
+ color: true,
+ date: true,
+ 'datetime-local': true,
+ email: true,
+ month: true,
+ number: true,
+ range: true,
+ search: true,
+ tel: true,
+ text: true,
+ time: true,
+ url: true,
+ week: true,
+ textarea: true,
+ select: true,
+ }
+ : maskAllInputs === false
+ ? {}
+ : maskAllInputs;
+ const slimDOMOptions = slimDOM === true || slimDOM === 'all'
+ ?
+ {
+ script: true,
+ comment: true,
+ headFavicon: true,
+ headWhitespace: true,
+ headMetaDescKeywords: slimDOM === 'all',
+ headMetaSocial: true,
+ headMetaRobots: true,
+ headMetaHttpEquiv: true,
+ headMetaAuthorship: true,
+ headMetaVerification: true,
+ }
+ : slimDOM === false
+ ? {}
+ : slimDOM;
+ return [
+ serializeNodeWithId(n, {
+ doc: n,
+ map: idNodeMap,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ skipChild: false,
+ inlineStylesheet,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskAllText,
+ maskInputOptions,
+ maskTextFn,
+ maskInputFn,
+ slimDOMOptions,
+ dataURLOptions,
+ inlineImages,
+ recordCanvas,
+ preserveWhiteSpace,
+ onSerialize,
+ onIframeLoad,
+ iframeLoadTimeout,
+ keepIframeSrcFn,
+ }),
+ idNodeMap,
+ ];
+}
+function skipAttribute(tagName, attributeName, value) {
+ return ((tagName === 'video' || tagName === 'audio') && attributeName === 'autoplay');
+}
+
+var EventType;
+(function (EventType) {
+ EventType[EventType["DomContentLoaded"] = 0] = "DomContentLoaded";
+ EventType[EventType["Load"] = 1] = "Load";
+ EventType[EventType["FullSnapshot"] = 2] = "FullSnapshot";
+ EventType[EventType["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
+ EventType[EventType["Meta"] = 4] = "Meta";
+ EventType[EventType["Custom"] = 5] = "Custom";
+ EventType[EventType["Plugin"] = 6] = "Plugin";
+})(EventType || (EventType = {}));
+var IncrementalSource;
+(function (IncrementalSource) {
+ IncrementalSource[IncrementalSource["Mutation"] = 0] = "Mutation";
+ IncrementalSource[IncrementalSource["MouseMove"] = 1] = "MouseMove";
+ IncrementalSource[IncrementalSource["MouseInteraction"] = 2] = "MouseInteraction";
+ IncrementalSource[IncrementalSource["Scroll"] = 3] = "Scroll";
+ IncrementalSource[IncrementalSource["ViewportResize"] = 4] = "ViewportResize";
+ IncrementalSource[IncrementalSource["Input"] = 5] = "Input";
+ IncrementalSource[IncrementalSource["TouchMove"] = 6] = "TouchMove";
+ IncrementalSource[IncrementalSource["MediaInteraction"] = 7] = "MediaInteraction";
+ IncrementalSource[IncrementalSource["StyleSheetRule"] = 8] = "StyleSheetRule";
+ IncrementalSource[IncrementalSource["CanvasMutation"] = 9] = "CanvasMutation";
+ IncrementalSource[IncrementalSource["Font"] = 10] = "Font";
+ IncrementalSource[IncrementalSource["Log"] = 11] = "Log";
+ IncrementalSource[IncrementalSource["Drag"] = 12] = "Drag";
+ IncrementalSource[IncrementalSource["StyleDeclaration"] = 13] = "StyleDeclaration";
+})(IncrementalSource || (IncrementalSource = {}));
+var MouseInteractions;
+(function (MouseInteractions) {
+ MouseInteractions[MouseInteractions["MouseUp"] = 0] = "MouseUp";
+ MouseInteractions[MouseInteractions["MouseDown"] = 1] = "MouseDown";
+ MouseInteractions[MouseInteractions["Click"] = 2] = "Click";
+ MouseInteractions[MouseInteractions["ContextMenu"] = 3] = "ContextMenu";
+ MouseInteractions[MouseInteractions["DblClick"] = 4] = "DblClick";
+ MouseInteractions[MouseInteractions["Focus"] = 5] = "Focus";
+ MouseInteractions[MouseInteractions["Blur"] = 6] = "Blur";
+ MouseInteractions[MouseInteractions["TouchStart"] = 7] = "TouchStart";
+ MouseInteractions[MouseInteractions["TouchMove_Departed"] = 8] = "TouchMove_Departed";
+ MouseInteractions[MouseInteractions["TouchEnd"] = 9] = "TouchEnd";
+ MouseInteractions[MouseInteractions["TouchCancel"] = 10] = "TouchCancel";
+})(MouseInteractions || (MouseInteractions = {}));
+var CanvasContext;
+(function (CanvasContext) {
+ CanvasContext[CanvasContext["2D"] = 0] = "2D";
+ CanvasContext[CanvasContext["WebGL"] = 1] = "WebGL";
+ CanvasContext[CanvasContext["WebGL2"] = 2] = "WebGL2";
+})(CanvasContext || (CanvasContext = {}));
+var MediaInteractions;
+(function (MediaInteractions) {
+ MediaInteractions[MediaInteractions["Play"] = 0] = "Play";
+ MediaInteractions[MediaInteractions["Pause"] = 1] = "Pause";
+ MediaInteractions[MediaInteractions["Seeked"] = 2] = "Seeked";
+ MediaInteractions[MediaInteractions["VolumeChange"] = 3] = "VolumeChange";
+})(MediaInteractions || (MediaInteractions = {}));
+var ReplayerEvents;
+(function (ReplayerEvents) {
+ ReplayerEvents["Start"] = "start";
+ ReplayerEvents["Pause"] = "pause";
+ ReplayerEvents["Resume"] = "resume";
+ ReplayerEvents["Resize"] = "resize";
+ ReplayerEvents["Finish"] = "finish";
+ ReplayerEvents["FullsnapshotRebuilded"] = "fullsnapshot-rebuilded";
+ ReplayerEvents["LoadStylesheetStart"] = "load-stylesheet-start";
+ ReplayerEvents["LoadStylesheetEnd"] = "load-stylesheet-end";
+ ReplayerEvents["SkipStart"] = "skip-start";
+ ReplayerEvents["SkipEnd"] = "skip-end";
+ ReplayerEvents["MouseInteraction"] = "mouse-interaction";
+ ReplayerEvents["EventCast"] = "event-cast";
+ ReplayerEvents["CustomEvent"] = "custom-event";
+ ReplayerEvents["Flush"] = "flush";
+ ReplayerEvents["StateChange"] = "state-change";
+ ReplayerEvents["PlayBack"] = "play-back";
+})(ReplayerEvents || (ReplayerEvents = {}));
+
+function on(type, fn, target = document) {
+ const options = { capture: true, passive: true };
+ target.addEventListener(type, fn, options);
+ return () => target.removeEventListener(type, fn, options);
+}
+function createMirror() {
+ return {
+ map: {},
+ getId(n) {
+ if (!n || !n.__sn) {
+ return -1;
+ }
+ return n.__sn.id;
+ },
+ getNode(id) {
+ return this.map[id] || null;
+ },
+ removeNodeFromMap(n) {
+ const id = n.__sn && n.__sn.id;
+ delete this.map[id];
+ if (n.childNodes) {
+ n.childNodes.forEach((child) => this.removeNodeFromMap(child));
+ }
+ },
+ has(id) {
+ return this.map.hasOwnProperty(id);
+ },
+ reset() {
+ this.map = {};
+ },
+ };
+}
+const DEPARTED_MIRROR_ACCESS_WARNING = 'Please stop import mirror directly. Instead of that,' +
+ '\r\n' +
+ 'now you can use replayer.getMirror() to access the mirror instance of a replayer,' +
+ '\r\n' +
+ 'or you can use record.mirror to access the mirror instance during recording.';
+let _mirror = {
+ map: {},
+ getId() {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ return -1;
+ },
+ getNode() {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ return null;
+ },
+ removeNodeFromMap() {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ },
+ has() {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ return false;
+ },
+ reset() {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ },
+};
+if (typeof window !== 'undefined' && window.Proxy && window.Reflect) {
+ _mirror = new Proxy(_mirror, {
+ get(target, prop, receiver) {
+ if (prop === 'map') {
+ console.error(DEPARTED_MIRROR_ACCESS_WARNING);
+ }
+ return Reflect.get(target, prop, receiver);
+ },
+ });
+}
+function throttle$1(func, wait, options = {}) {
+ let timeout = null;
+ let previous = 0;
+ return function (arg) {
+ let now = Date.now();
+ if (!previous && options.leading === false) {
+ previous = now;
+ }
+ let remaining = wait - (now - previous);
+ let context = this;
+ let args = arguments;
+ if (remaining <= 0 || remaining > wait) {
+ if (timeout) {
+ clearTimeout(timeout);
+ timeout = null;
+ }
+ previous = now;
+ func.apply(context, args);
+ }
+ else if (!timeout && options.trailing !== false) {
+ timeout = setTimeout(() => {
+ previous = options.leading === false ? 0 : Date.now();
+ timeout = null;
+ func.apply(context, args);
+ }, remaining);
+ }
+ };
+}
+function hookSetter(target, key, d, isRevoked, win = window) {
+ const original = win.Object.getOwnPropertyDescriptor(target, key);
+ win.Object.defineProperty(target, key, isRevoked
+ ? d
+ : {
+ set(value) {
+ setTimeout(() => {
+ d.set.call(this, value);
+ }, 0);
+ if (original && original.set) {
+ original.set.call(this, value);
+ }
+ },
+ });
+ return () => hookSetter(target, key, original || {}, true);
+}
+function patch(source, name, replacement) {
+ try {
+ if (!(name in source)) {
+ return () => { };
+ }
+ const original = source[name];
+ const wrapped = replacement(original);
+ if (typeof wrapped === 'function') {
+ wrapped.prototype = wrapped.prototype || {};
+ Object.defineProperties(wrapped, {
+ __rrweb_original__: {
+ enumerable: false,
+ value: original,
+ },
+ });
+ }
+ source[name] = wrapped;
+ return () => {
+ source[name] = original;
+ };
+ }
+ catch (_a) {
+ return () => { };
+ }
+}
+function getWindowHeight() {
+ return (window.innerHeight ||
+ (document.documentElement && document.documentElement.clientHeight) ||
+ (document.body && document.body.clientHeight));
+}
+function getWindowWidth() {
+ return (window.innerWidth ||
+ (document.documentElement && document.documentElement.clientWidth) ||
+ (document.body && document.body.clientWidth));
+}
+function isBlocked(node, blockClass, blockSelector, unblockSelector) {
+ if (!node) {
+ return false;
+ }
+ if (node.nodeType === node.ELEMENT_NODE) {
+ let needBlock = false;
+ const needUnblock = unblockSelector && node.matches(unblockSelector);
+ if (typeof blockClass === 'string') {
+ if (node.closest !== undefined) {
+ needBlock =
+ !needUnblock &&
+ node.closest('.' + blockClass) !== null;
+ }
+ else {
+ needBlock =
+ !needUnblock && node.classList.contains(blockClass);
+ }
+ }
+ else {
+ !needUnblock &&
+ node.classList.forEach((className) => {
+ if (blockClass.test(className)) {
+ needBlock = true;
+ }
+ });
+ }
+ if (!needBlock && blockSelector) {
+ needBlock = node.matches(blockSelector);
+ }
+ return ((!needUnblock && needBlock) ||
+ isBlocked(node.parentNode, blockClass, blockSelector, unblockSelector));
+ }
+ if (node.nodeType === node.TEXT_NODE) {
+ return isBlocked(node.parentNode, blockClass, blockSelector, unblockSelector);
+ }
+ return isBlocked(node.parentNode, blockClass, blockSelector, unblockSelector);
+}
+function isIgnored(n) {
+ if ('__sn' in n) {
+ return n.__sn.id === IGNORED_NODE;
+ }
+ return false;
+}
+function isAncestorRemoved(target, mirror) {
+ if (isShadowRoot(target)) {
+ return false;
+ }
+ const id = mirror.getId(target);
+ if (!mirror.has(id)) {
+ return true;
+ }
+ if (target.parentNode &&
+ target.parentNode.nodeType === target.DOCUMENT_NODE) {
+ return false;
+ }
+ if (!target.parentNode) {
+ return true;
+ }
+ return isAncestorRemoved(target.parentNode, mirror);
+}
+function isTouchEvent(event) {
+ return Boolean(event.changedTouches);
+}
+function polyfill(win = window) {
+ if ('NodeList' in win && !win.NodeList.prototype.forEach) {
+ win.NodeList.prototype.forEach = Array.prototype
+ .forEach;
+ }
+ if ('DOMTokenList' in win && !win.DOMTokenList.prototype.forEach) {
+ win.DOMTokenList.prototype.forEach = Array.prototype
+ .forEach;
+ }
+ if (!Node.prototype.contains) {
+ Node.prototype.contains = function contains(node) {
+ if (!(0 in arguments)) {
+ throw new TypeError('1 argument is required');
+ }
+ do {
+ if (this === node) {
+ return true;
+ }
+ } while ((node = node && node.parentNode));
+ return false;
+ };
+ }
+}
+function isIframeINode(node) {
+ if ('__sn' in node) {
+ return (node.__sn.type === NodeType$1.Element && node.__sn.tagName === 'iframe');
+ }
+ return false;
+}
+function hasShadowRoot(n) {
+ return Boolean(n === null || n === void 0 ? void 0 : n.shadowRoot);
+}
+
+function isNodeInLinkedList(n) {
+ return '__ln' in n;
+}
+class DoubleLinkedList {
+ constructor() {
+ this.length = 0;
+ this.head = null;
+ }
+ get(position) {
+ if (position >= this.length) {
+ throw new Error('Position outside of list range');
+ }
+ let current = this.head;
+ for (let index = 0; index < position; index++) {
+ current = (current === null || current === void 0 ? void 0 : current.next) || null;
+ }
+ return current;
+ }
+ addNode(n) {
+ const node = {
+ value: n,
+ previous: null,
+ next: null,
+ };
+ n.__ln = node;
+ if (n.previousSibling && isNodeInLinkedList(n.previousSibling)) {
+ const current = n.previousSibling.__ln.next;
+ node.next = current;
+ node.previous = n.previousSibling.__ln;
+ n.previousSibling.__ln.next = node;
+ if (current) {
+ current.previous = node;
+ }
+ }
+ else if (n.nextSibling &&
+ isNodeInLinkedList(n.nextSibling) &&
+ n.nextSibling.__ln.previous) {
+ const current = n.nextSibling.__ln.previous;
+ node.previous = current;
+ node.next = n.nextSibling.__ln;
+ n.nextSibling.__ln.previous = node;
+ if (current) {
+ current.next = node;
+ }
+ }
+ else {
+ if (this.head) {
+ this.head.previous = node;
+ }
+ node.next = this.head;
+ this.head = node;
+ }
+ this.length++;
+ }
+ removeNode(n) {
+ const current = n.__ln;
+ if (!this.head) {
+ return;
+ }
+ if (!current.previous) {
+ this.head = current.next;
+ if (this.head) {
+ this.head.previous = null;
+ }
+ }
+ else {
+ current.previous.next = current.next;
+ if (current.next) {
+ current.next.previous = current.previous;
+ }
+ }
+ if (n.__ln) {
+ delete n.__ln;
+ }
+ this.length--;
+ }
+}
+const moveKey = (id, parentId) => `${id}@${parentId}`;
+function isINode(n) {
+ return '__sn' in n;
+}
+class MutationBuffer {
+ constructor() {
+ this.frozen = false;
+ this.locked = false;
+ this.texts = [];
+ this.attributes = [];
+ this.removes = [];
+ this.mapRemoves = [];
+ this.movedMap = {};
+ this.addedSet = new Set();
+ this.movedSet = new Set();
+ this.droppedSet = new Set();
+ this.processMutations = (mutations) => {
+ mutations.forEach(this.processMutation);
+ this.emit();
+ };
+ this.emit = () => {
+ if (this.frozen || this.locked) {
+ return;
+ }
+ const adds = [];
+ const addList = new DoubleLinkedList();
+ const getNextId = (n) => {
+ let ns = n;
+ let nextId = IGNORED_NODE;
+ while (nextId === IGNORED_NODE) {
+ ns = ns && ns.nextSibling;
+ nextId = ns && this.mirror.getId(ns);
+ }
+ return nextId;
+ };
+ const pushAdd = (n) => {
+ var _a, _b, _c, _d, _e;
+ const shadowHost = n.getRootNode
+ ? (_a = n.getRootNode()) === null || _a === void 0 ? void 0 : _a.host
+ : null;
+ let rootShadowHost = shadowHost;
+ while ((_c = (_b = rootShadowHost === null || rootShadowHost === void 0 ? void 0 : rootShadowHost.getRootNode) === null || _b === void 0 ? void 0 : _b.call(rootShadowHost)) === null || _c === void 0 ? void 0 : _c.host)
+ rootShadowHost =
+ ((_e = (_d = rootShadowHost === null || rootShadowHost === void 0 ? void 0 : rootShadowHost.getRootNode) === null || _d === void 0 ? void 0 : _d.call(rootShadowHost)) === null || _e === void 0 ? void 0 : _e.host) ||
+ null;
+ const notInDoc = !this.doc.contains(n) &&
+ (!rootShadowHost || !this.doc.contains(rootShadowHost));
+ if (!n.parentNode || notInDoc) {
+ return;
+ }
+ const parentId = isShadowRoot(n.parentNode)
+ ? this.mirror.getId(shadowHost)
+ : this.mirror.getId(n.parentNode);
+ const nextId = getNextId(n);
+ if (parentId === -1 || nextId === -1) {
+ return addList.addNode(n);
+ }
+ let sn = serializeNodeWithId(n, {
+ doc: this.doc,
+ map: this.mirror.map,
+ blockClass: this.blockClass,
+ blockSelector: this.blockSelector,
+ unblockSelector: this.unblockSelector,
+ maskTextClass: this.maskTextClass,
+ maskTextSelector: this.maskTextSelector,
+ unmaskTextSelector: this.unmaskTextSelector,
+ maskInputSelector: this.maskInputSelector,
+ unmaskInputSelector: this.unmaskInputSelector,
+ skipChild: true,
+ inlineStylesheet: this.inlineStylesheet,
+ maskAllText: this.maskAllText,
+ maskInputOptions: this.maskInputOptions,
+ maskTextFn: this.maskTextFn,
+ maskInputFn: this.maskInputFn,
+ slimDOMOptions: this.slimDOMOptions,
+ recordCanvas: this.recordCanvas,
+ inlineImages: this.inlineImages,
+ onSerialize: (currentN) => {
+ if (isIframeINode(currentN)) {
+ this.iframeManager.addIframe(currentN);
+ }
+ if (hasShadowRoot(n)) {
+ this.shadowDomManager.addShadowRoot(n.shadowRoot, document);
+ }
+ },
+ onIframeLoad: (iframe, childSn) => {
+ this.iframeManager.attachIframe(iframe, childSn);
+ this.shadowDomManager.observeAttachShadow(iframe);
+ },
+ });
+ if (sn) {
+ adds.push({
+ parentId,
+ nextId,
+ node: sn,
+ });
+ }
+ };
+ while (this.mapRemoves.length) {
+ this.mirror.removeNodeFromMap(this.mapRemoves.shift());
+ }
+ for (const n of this.movedSet) {
+ if (isParentRemoved(this.removes, n, this.mirror) &&
+ !this.movedSet.has(n.parentNode)) {
+ continue;
+ }
+ pushAdd(n);
+ }
+ for (const n of this.addedSet) {
+ if (!isAncestorInSet(this.droppedSet, n) &&
+ !isParentRemoved(this.removes, n, this.mirror)) {
+ pushAdd(n);
+ }
+ else if (isAncestorInSet(this.movedSet, n)) {
+ pushAdd(n);
+ }
+ else {
+ this.droppedSet.add(n);
+ }
+ }
+ let candidate = null;
+ while (addList.length) {
+ let node = null;
+ if (candidate) {
+ const parentId = this.mirror.getId(candidate.value.parentNode);
+ const nextId = getNextId(candidate.value);
+ if (parentId !== -1 && nextId !== -1) {
+ node = candidate;
+ }
+ }
+ if (!node) {
+ for (let index = addList.length - 1; index >= 0; index--) {
+ const _node = addList.get(index);
+ if (_node) {
+ const parentId = this.mirror.getId(_node.value.parentNode);
+ const nextId = getNextId(_node.value);
+ if (parentId !== -1 && nextId !== -1) {
+ node = _node;
+ break;
+ }
+ }
+ }
+ }
+ if (!node) {
+ while (addList.head) {
+ addList.removeNode(addList.head.value);
+ }
+ break;
+ }
+ candidate = node.previous;
+ addList.removeNode(node.value);
+ pushAdd(node.value);
+ }
+ const payload = {
+ texts: this.texts
+ .map((text) => ({
+ id: this.mirror.getId(text.node),
+ value: text.value,
+ }))
+ .filter((text) => this.mirror.has(text.id)),
+ attributes: this.attributes
+ .map((attribute) => ({
+ id: this.mirror.getId(attribute.node),
+ attributes: attribute.attributes,
+ }))
+ .filter((attribute) => this.mirror.has(attribute.id)),
+ removes: this.removes,
+ adds,
+ };
+ if (!payload.texts.length &&
+ !payload.attributes.length &&
+ !payload.removes.length &&
+ !payload.adds.length) {
+ return;
+ }
+ this.texts = [];
+ this.attributes = [];
+ this.removes = [];
+ this.addedSet = new Set();
+ this.movedSet = new Set();
+ this.droppedSet = new Set();
+ this.movedMap = {};
+ this.mutationCb(payload);
+ };
+ this.processMutation = (m) => {
+ if (isIgnored(m.target)) {
+ return;
+ }
+ switch (m.type) {
+ case 'characterData': {
+ const value = m.target.textContent;
+ if (!isBlocked(m.target, this.blockClass, this.blockSelector, this.unblockSelector) && value !== m.oldValue) {
+ this.texts.push({
+ value: needMaskingText(m.target, this.maskTextClass, this.maskTextSelector, this.unmaskTextSelector, this.maskAllText) && value
+ ? this.maskTextFn
+ ? this.maskTextFn(value)
+ : value.replace(/[\S]/g, '*')
+ : value,
+ node: m.target,
+ });
+ }
+ break;
+ }
+ case 'attributes': {
+ const target = m.target;
+ let value = target.getAttribute(m.attributeName);
+ if (m.attributeName === 'value') {
+ value = maskInputValue({
+ input: target,
+ maskInputSelector: this.maskInputSelector,
+ unmaskInputSelector: this.unmaskInputSelector,
+ maskInputOptions: this.maskInputOptions,
+ tagName: target.tagName,
+ type: target.getAttribute('type'),
+ value,
+ maskInputFn: this.maskInputFn,
+ });
+ }
+ if (isBlocked(m.target, this.blockClass, this.blockSelector, this.unblockSelector) || value === m.oldValue) {
+ return;
+ }
+ let item = this.attributes.find((a) => a.node === m.target);
+ if (!item) {
+ item = {
+ node: m.target,
+ attributes: {},
+ };
+ this.attributes.push(item);
+ }
+ if (m.attributeName === 'type' &&
+ target.tagName === 'INPUT' &&
+ (m.oldValue || '').toLowerCase() === 'password') {
+ target.setAttribute('data-rr-is-password', 'true');
+ }
+ if (m.attributeName === 'style') {
+ const old = this.doc.createElement('span');
+ if (m.oldValue) {
+ old.setAttribute('style', m.oldValue);
+ }
+ if (item.attributes.style === undefined ||
+ item.attributes.style === null) {
+ item.attributes.style = {};
+ }
+ try {
+ const styleObj = item.attributes.style;
+ for (const pname of Array.from(target.style)) {
+ const newValue = target.style.getPropertyValue(pname);
+ const newPriority = target.style.getPropertyPriority(pname);
+ if (newValue !== old.style.getPropertyValue(pname) ||
+ newPriority !== old.style.getPropertyPriority(pname)) {
+ if (newPriority === '') {
+ styleObj[pname] = newValue;
+ }
+ else {
+ styleObj[pname] = [newValue, newPriority];
+ }
+ }
+ }
+ for (const pname of Array.from(old.style)) {
+ if (target.style.getPropertyValue(pname) === '') {
+ styleObj[pname] = false;
+ }
+ }
+ }
+ catch (error) {
+ console.warn('[rrweb] Error when parsing update to style attribute:', error);
+ }
+ }
+ else {
+ const element = m.target;
+ item.attributes[m.attributeName] = transformAttribute(this.doc, element, element.tagName, m.attributeName, value, this.maskAllText, this.unmaskTextSelector, this.maskTextFn);
+ }
+ break;
+ }
+ case 'childList': {
+ m.addedNodes.forEach((n) => this.genAdds(n, m.target));
+ m.removedNodes.forEach((n) => {
+ const nodeId = this.mirror.getId(n);
+ const parentId = isShadowRoot(m.target)
+ ? this.mirror.getId(m.target.host)
+ : this.mirror.getId(m.target);
+ if (isBlocked(m.target, this.blockClass, this.blockSelector, this.unblockSelector) || isIgnored(n)) {
+ return;
+ }
+ if (this.addedSet.has(n)) {
+ deepDelete(this.addedSet, n);
+ this.droppedSet.add(n);
+ }
+ else if (this.addedSet.has(m.target) && nodeId === -1) ;
+ else if (isAncestorRemoved(m.target, this.mirror)) ;
+ else if (this.movedSet.has(n) &&
+ this.movedMap[moveKey(nodeId, parentId)]) {
+ deepDelete(this.movedSet, n);
+ }
+ else {
+ this.removes.push({
+ parentId,
+ id: nodeId,
+ isShadow: isShadowRoot(m.target) ? true : undefined,
+ });
+ }
+ this.mapRemoves.push(n);
+ });
+ break;
+ }
+ }
+ };
+ this.genAdds = (n, target) => {
+ if (target && isBlocked(target, this.blockClass, this.blockSelector, this.unblockSelector)) {
+ return;
+ }
+ if (isINode(n)) {
+ if (isIgnored(n)) {
+ return;
+ }
+ this.movedSet.add(n);
+ let targetId = null;
+ if (target && isINode(target)) {
+ targetId = target.__sn.id;
+ }
+ if (targetId) {
+ this.movedMap[moveKey(n.__sn.id, targetId)] = true;
+ }
+ }
+ else {
+ this.addedSet.add(n);
+ this.droppedSet.delete(n);
+ }
+ if (!isBlocked(n, this.blockClass, this.blockSelector, this.unblockSelector))
+ n.childNodes.forEach((childN) => this.genAdds(childN));
+ };
+ }
+ init(options) {
+ [
+ 'mutationCb',
+ 'blockClass',
+ 'blockSelector',
+ 'unblockSelector',
+ 'maskTextClass',
+ 'maskTextSelector',
+ 'unmaskTextSelector',
+ 'maskInputSelector',
+ 'unmaskInputSelector',
+ 'inlineStylesheet',
+ 'maskAllText',
+ 'maskInputOptions',
+ 'maskTextFn',
+ 'maskInputFn',
+ 'recordCanvas',
+ 'inlineImages',
+ 'slimDOMOptions',
+ 'doc',
+ 'mirror',
+ 'iframeManager',
+ 'shadowDomManager',
+ 'canvasManager',
+ ].forEach((key) => {
+ this[key] = options[key];
+ });
+ }
+ freeze() {
+ this.frozen = true;
+ this.canvasManager.freeze();
+ }
+ unfreeze() {
+ this.frozen = false;
+ this.canvasManager.unfreeze();
+ this.emit();
+ }
+ isFrozen() {
+ return this.frozen;
+ }
+ lock() {
+ this.locked = true;
+ this.canvasManager.lock();
+ }
+ unlock() {
+ this.locked = false;
+ this.canvasManager.unlock();
+ this.emit();
+ }
+ reset() {
+ this.shadowDomManager.reset();
+ this.canvasManager.reset();
+ }
+}
+function deepDelete(addsSet, n) {
+ addsSet.delete(n);
+ n.childNodes.forEach((childN) => deepDelete(addsSet, childN));
+}
+function isParentRemoved(removes, n, mirror) {
+ const { parentNode } = n;
+ if (!parentNode) {
+ return false;
+ }
+ const parentId = mirror.getId(parentNode);
+ if (removes.some((r) => r.id === parentId)) {
+ return true;
+ }
+ return isParentRemoved(removes, parentNode, mirror);
+}
+function isAncestorInSet(set, n) {
+ const { parentNode } = n;
+ if (!parentNode) {
+ return false;
+ }
+ if (set.has(parentNode)) {
+ return true;
+ }
+ return isAncestorInSet(set, parentNode);
+}
+
+const callbackWrapper = (cb) => {
+ const rrwebWrapped = (...rest) => {
+ try {
+ return cb(...rest);
+ }
+ catch (error) {
+ try {
+ error.__rrweb__ = true;
+ }
+ catch (_a) {
+ }
+ throw error;
+ }
+ };
+ return rrwebWrapped;
+};
+
+const mutationBuffers = [];
+function getEventTarget(event) {
+ try {
+ if ('composedPath' in event) {
+ const path = event.composedPath();
+ if (path.length) {
+ return path[0];
+ }
+ }
+ else if ('path' in event && event.path.length) {
+ return event.path[0];
+ }
+ }
+ catch (_a) { }
+ return event && event.target;
+}
+function initMutationObserver(options, rootEl) {
+ var _a, _b;
+ const mutationBuffer = new MutationBuffer();
+ mutationBuffers.push(mutationBuffer);
+ mutationBuffer.init(options);
+ let mutationObserverCtor = window.MutationObserver ||
+ window.__rrMutationObserver;
+ const angularZoneSymbol = (_b = (_a = window === null || window === void 0 ? void 0 : window.Zone) === null || _a === void 0 ? void 0 : _a.__symbol__) === null || _b === void 0 ? void 0 : _b.call(_a, 'MutationObserver');
+ if (angularZoneSymbol &&
+ window[angularZoneSymbol]) {
+ mutationObserverCtor = window[angularZoneSymbol];
+ }
+ const observer = new mutationObserverCtor(callbackWrapper((mutations) => {
+ if (options.onMutation && options.onMutation(mutations) === false) {
+ return;
+ }
+ mutationBuffer.processMutations(mutations);
+ }));
+ observer.observe(rootEl, {
+ attributes: true,
+ attributeOldValue: true,
+ characterData: true,
+ characterDataOldValue: true,
+ childList: true,
+ subtree: true,
+ });
+ return observer;
+}
+function initMoveObserver({ mousemoveCb, sampling, doc, mirror, }) {
+ if (sampling.mousemove === false) {
+ return () => { };
+ }
+ const threshold = typeof sampling.mousemove === 'number' ? sampling.mousemove : 50;
+ const callbackThreshold = typeof sampling.mousemoveCallback === 'number'
+ ? sampling.mousemoveCallback
+ : 500;
+ let positions = [];
+ let timeBaseline;
+ const wrappedCb = throttle$1((source) => {
+ const totalOffset = Date.now() - timeBaseline;
+ callbackWrapper(mousemoveCb)(positions.map((p) => {
+ p.timeOffset -= totalOffset;
+ return p;
+ }), source);
+ positions = [];
+ timeBaseline = null;
+ }, callbackThreshold);
+ const updatePosition = throttle$1((evt) => {
+ const target = getEventTarget(evt);
+ const { clientX, clientY } = isTouchEvent(evt)
+ ? evt.changedTouches[0]
+ : evt;
+ if (!timeBaseline) {
+ timeBaseline = Date.now();
+ }
+ positions.push({
+ x: clientX,
+ y: clientY,
+ id: mirror.getId(target),
+ timeOffset: Date.now() - timeBaseline,
+ });
+ wrappedCb(typeof DragEvent !== 'undefined' && evt instanceof DragEvent
+ ? IncrementalSource.Drag
+ : evt instanceof MouseEvent
+ ? IncrementalSource.MouseMove
+ : IncrementalSource.TouchMove);
+ }, threshold, {
+ trailing: false,
+ });
+ const handlers = [
+ on('mousemove', callbackWrapper(updatePosition), doc),
+ on('touchmove', callbackWrapper(updatePosition), doc),
+ on('drag', callbackWrapper(updatePosition), doc),
+ ];
+ return callbackWrapper(() => {
+ handlers.forEach((h) => h());
+ });
+}
+function initMouseInteractionObserver({ mouseInteractionCb, doc, mirror, blockClass, blockSelector, unblockSelector, sampling, }) {
+ if (sampling.mouseInteraction === false) {
+ return () => { };
+ }
+ const disableMap = sampling.mouseInteraction === true ||
+ sampling.mouseInteraction === undefined
+ ? {}
+ : sampling.mouseInteraction;
+ const handlers = [];
+ const getHandler = (eventKey) => {
+ return (event) => {
+ const target = getEventTarget(event);
+ if (isBlocked(target, blockClass, blockSelector, unblockSelector)) {
+ return;
+ }
+ const e = isTouchEvent(event) ? event.changedTouches[0] : event;
+ if (!e) {
+ return;
+ }
+ const id = mirror.getId(target);
+ const { clientX, clientY } = e;
+ callbackWrapper(mouseInteractionCb)({
+ type: MouseInteractions[eventKey],
+ id,
+ x: clientX,
+ y: clientY,
+ });
+ };
+ };
+ Object.keys(MouseInteractions)
+ .filter((key) => Number.isNaN(Number(key)) &&
+ !key.endsWith('_Departed') &&
+ disableMap[key] !== false)
+ .forEach((eventKey) => {
+ const eventName = eventKey.toLowerCase();
+ const handler = callbackWrapper(getHandler(eventKey));
+ handlers.push(on(eventName, handler, doc));
+ });
+ return callbackWrapper(() => {
+ handlers.forEach((h) => h());
+ });
+}
+function initScrollObserver({ scrollCb, doc, mirror, blockClass, blockSelector, unblockSelector, sampling, }) {
+ const updatePosition = throttle$1((evt) => {
+ const target = getEventTarget(evt);
+ if (!target ||
+ isBlocked(target, blockClass, blockSelector, unblockSelector)) {
+ return;
+ }
+ const id = mirror.getId(target);
+ if (target === doc) {
+ const scrollEl = (doc.scrollingElement || doc.documentElement);
+ callbackWrapper(scrollCb)({
+ id,
+ x: scrollEl.scrollLeft,
+ y: scrollEl.scrollTop,
+ });
+ }
+ else {
+ callbackWrapper(scrollCb)({
+ id,
+ x: target.scrollLeft,
+ y: target.scrollTop,
+ });
+ }
+ }, sampling.scroll || 100);
+ return on('scroll', callbackWrapper(updatePosition), doc);
+}
+function initViewportResizeObserver({ viewportResizeCb, }) {
+ let lastH = -1;
+ let lastW = -1;
+ const updateDimension = throttle$1(() => {
+ const height = getWindowHeight();
+ const width = getWindowWidth();
+ if (lastH !== height || lastW !== width) {
+ callbackWrapper(viewportResizeCb)({
+ width: Number(width),
+ height: Number(height),
+ });
+ lastH = height;
+ lastW = width;
+ }
+ }, 200);
+ return on('resize', callbackWrapper(updateDimension), window);
+}
+function wrapEventWithUserTriggeredFlag(v, enable) {
+ const value = Object.assign({}, v);
+ if (!enable)
+ delete value.userTriggered;
+ return value;
+}
+const INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
+const lastInputValueMap = new WeakMap();
+function initInputObserver({ inputCb, doc, mirror, blockClass, blockSelector, unblockSelector, ignoreClass, ignoreSelector, maskInputSelector, unmaskInputSelector, maskInputOptions, maskInputFn, sampling, userTriggeredOnInput, }) {
+ function eventHandler(event) {
+ let target = getEventTarget(event);
+ const tagName = target && target.tagName;
+ const userTriggered = event.isTrusted;
+ if (tagName === 'OPTION')
+ target = target.parentElement;
+ if (!target ||
+ !tagName ||
+ INPUT_TAGS.indexOf(tagName) < 0 ||
+ isBlocked(target, blockClass, blockSelector, unblockSelector)) {
+ return;
+ }
+ const el = target;
+ const type = getInputType(el);
+ if (el.classList.contains(ignoreClass) ||
+ (ignoreSelector && el.matches(ignoreSelector))) {
+ return;
+ }
+ let text = getInputValue(el, tagName, type);
+ let isChecked = false;
+ if (type === 'radio' || type === 'checkbox') {
+ isChecked = target.checked;
+ }
+ if (hasInputMaskOptions({
+ maskInputOptions,
+ maskInputSelector,
+ tagName,
+ type,
+ })) {
+ text = maskInputValue({
+ input: el,
+ maskInputOptions,
+ maskInputSelector,
+ unmaskInputSelector,
+ tagName,
+ type,
+ value: text,
+ maskInputFn,
+ });
+ }
+ cbWithDedup(target, callbackWrapper(wrapEventWithUserTriggeredFlag)({ text, isChecked, userTriggered }, userTriggeredOnInput));
+ const name = target.name;
+ if (type === 'radio' && name && isChecked) {
+ doc
+ .querySelectorAll(`input[type="radio"][name="${name}"]`)
+ .forEach((el) => {
+ if (el !== target) {
+ const text = maskInputValue({
+ input: el,
+ maskInputOptions,
+ maskInputSelector,
+ unmaskInputSelector,
+ tagName,
+ type,
+ value: getInputValue(el, tagName, type),
+ maskInputFn,
+ });
+ cbWithDedup(el, callbackWrapper(wrapEventWithUserTriggeredFlag)({
+ text,
+ isChecked: !isChecked,
+ userTriggered: false,
+ }, userTriggeredOnInput));
+ }
+ });
+ }
+ }
+ function cbWithDedup(target, v) {
+ const lastInputValue = lastInputValueMap.get(target);
+ if (!lastInputValue ||
+ lastInputValue.text !== v.text ||
+ lastInputValue.isChecked !== v.isChecked) {
+ lastInputValueMap.set(target, v);
+ const id = mirror.getId(target);
+ inputCb(Object.assign(Object.assign({}, v), { id }));
+ }
+ }
+ const events = sampling.input === 'last' ? ['change'] : ['input', 'change'];
+ const handlers = events.map((eventName) => on(eventName, callbackWrapper(eventHandler), doc));
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
+ const hookProperties = [
+ [HTMLInputElement.prototype, 'value'],
+ [HTMLInputElement.prototype, 'checked'],
+ [HTMLSelectElement.prototype, 'value'],
+ [HTMLTextAreaElement.prototype, 'value'],
+ [HTMLSelectElement.prototype, 'selectedIndex'],
+ [HTMLOptionElement.prototype, 'selected'],
+ ];
+ if (propertyDescriptor && propertyDescriptor.set) {
+ handlers.push(...hookProperties.map((p) => hookSetter(p[0], p[1], {
+ set() {
+ callbackWrapper(eventHandler)({ target: this });
+ },
+ })));
+ }
+ return callbackWrapper(() => {
+ handlers.forEach((h) => h());
+ });
+}
+function getNestedCSSRulePositions(rule) {
+ const positions = [];
+ function recurse(childRule, pos) {
+ if ((hasNestedCSSRule('CSSGroupingRule') &&
+ childRule.parentRule instanceof CSSGroupingRule) ||
+ (hasNestedCSSRule('CSSMediaRule') &&
+ childRule.parentRule instanceof CSSMediaRule) ||
+ (hasNestedCSSRule('CSSSupportsRule') &&
+ childRule.parentRule instanceof CSSSupportsRule) ||
+ (hasNestedCSSRule('CSSConditionRule') &&
+ childRule.parentRule instanceof CSSConditionRule)) {
+ const rules = Array.from(childRule.parentRule.cssRules);
+ const index = rules.indexOf(childRule);
+ pos.unshift(index);
+ }
+ else {
+ const rules = Array.from(childRule.parentStyleSheet.cssRules);
+ const index = rules.indexOf(childRule);
+ pos.unshift(index);
+ }
+ return pos;
+ }
+ return recurse(rule, positions);
+}
+function initStyleSheetObserver({ styleSheetRuleCb, mirror }, { win }) {
+ if (!win.CSSStyleSheet || !win.CSSStyleSheet.prototype) {
+ return () => { };
+ }
+ const insertRule = win.CSSStyleSheet.prototype.insertRule;
+ win.CSSStyleSheet.prototype.insertRule = new Proxy(insertRule, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ const [rule, index] = argumentsList;
+ const id = mirror.getId(thisArg.ownerNode);
+ if (id !== -1) {
+ styleSheetRuleCb({
+ id,
+ adds: [{ rule, index }],
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ const deleteRule = win.CSSStyleSheet.prototype.deleteRule;
+ win.CSSStyleSheet.prototype.deleteRule = new Proxy(deleteRule, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ const [index] = argumentsList;
+ const id = mirror.getId(thisArg.ownerNode);
+ if (id !== -1) {
+ styleSheetRuleCb({
+ id,
+ removes: [{ index }],
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ const supportedNestedCSSRuleTypes = {};
+ if (canMonkeyPatchNestedCSSRule('CSSGroupingRule')) {
+ supportedNestedCSSRuleTypes.CSSGroupingRule = win.CSSGroupingRule;
+ }
+ else {
+ if (canMonkeyPatchNestedCSSRule('CSSMediaRule')) {
+ supportedNestedCSSRuleTypes.CSSMediaRule = win.CSSMediaRule;
+ }
+ if (canMonkeyPatchNestedCSSRule('CSSConditionRule')) {
+ supportedNestedCSSRuleTypes.CSSConditionRule = win.CSSConditionRule;
+ }
+ if (canMonkeyPatchNestedCSSRule('CSSSupportsRule')) {
+ supportedNestedCSSRuleTypes.CSSSupportsRule = win.CSSSupportsRule;
+ }
+ }
+ const unmodifiedFunctions = {};
+ Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
+ unmodifiedFunctions[typeKey] = {
+ insertRule: type.prototype.insertRule,
+ deleteRule: type.prototype.deleteRule,
+ };
+ type.prototype.insertRule = new Proxy(unmodifiedFunctions[typeKey].insertRule, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ const [rule, index] = argumentsList;
+ const id = mirror.getId(thisArg.parentStyleSheet.ownerNode);
+ if (id !== -1) {
+ styleSheetRuleCb({
+ id,
+ adds: [
+ {
+ rule,
+ index: [
+ ...getNestedCSSRulePositions(thisArg),
+ index || 0,
+ ],
+ },
+ ],
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ type.prototype.deleteRule = new Proxy(unmodifiedFunctions[typeKey].deleteRule, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ const [index] = argumentsList;
+ const id = mirror.getId(thisArg.parentStyleSheet.ownerNode);
+ if (id !== -1) {
+ styleSheetRuleCb({
+ id,
+ removes: [
+ { index: [...getNestedCSSRulePositions(thisArg), index] },
+ ],
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ });
+ return callbackWrapper(() => {
+ win.CSSStyleSheet.prototype.insertRule = insertRule;
+ win.CSSStyleSheet.prototype.deleteRule = deleteRule;
+ Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
+ type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule;
+ type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule;
+ });
+ });
+}
+function initStyleDeclarationObserver({ styleDeclarationCb, mirror }, { win }) {
+ const setProperty = win.CSSStyleDeclaration.prototype.setProperty;
+ win.CSSStyleDeclaration.prototype.setProperty = new Proxy(setProperty, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ var _a, _b;
+ const [property, value, priority] = argumentsList;
+ const id = mirror.getId((_b = (_a = thisArg.parentRule) === null || _a === void 0 ? void 0 : _a.parentStyleSheet) === null || _b === void 0 ? void 0 : _b.ownerNode);
+ if (id !== -1) {
+ styleDeclarationCb({
+ id,
+ set: {
+ property,
+ value,
+ priority,
+ },
+ index: getNestedCSSRulePositions(thisArg.parentRule),
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ const removeProperty = win.CSSStyleDeclaration.prototype.removeProperty;
+ win.CSSStyleDeclaration.prototype.removeProperty = new Proxy(removeProperty, {
+ apply: callbackWrapper((target, thisArg, argumentsList) => {
+ var _a, _b;
+ const [property] = argumentsList;
+ const id = mirror.getId((_b = (_a = thisArg.parentRule) === null || _a === void 0 ? void 0 : _a.parentStyleSheet) === null || _b === void 0 ? void 0 : _b.ownerNode);
+ if (id !== -1) {
+ styleDeclarationCb({
+ id,
+ remove: {
+ property,
+ },
+ index: getNestedCSSRulePositions(thisArg.parentRule),
+ });
+ }
+ return target.apply(thisArg, argumentsList);
+ }),
+ });
+ return callbackWrapper(() => {
+ win.CSSStyleDeclaration.prototype.setProperty = setProperty;
+ win.CSSStyleDeclaration.prototype.removeProperty = removeProperty;
+ });
+}
+function initMediaInteractionObserver({ mediaInteractionCb, blockClass, blockSelector, unblockSelector, mirror, sampling, }) {
+ const handler = (type) => throttle$1(callbackWrapper((event) => {
+ const target = getEventTarget(event);
+ if (!target ||
+ isBlocked(target, blockClass, blockSelector, unblockSelector)) {
+ return;
+ }
+ const { currentTime, volume, muted } = target;
+ mediaInteractionCb({
+ type,
+ id: mirror.getId(target),
+ currentTime,
+ volume,
+ muted,
+ });
+ }), sampling.media || 500);
+ const handlers = [
+ on('play', handler(0)),
+ on('pause', handler(1)),
+ on('seeked', handler(2)),
+ on('volumechange', handler(3)),
+ ];
+ return callbackWrapper(() => {
+ handlers.forEach((h) => h());
+ });
+}
+function initFontObserver({ fontCb, doc }) {
+ const win = doc.defaultView;
+ if (!win) {
+ return () => { };
+ }
+ const handlers = [];
+ const fontMap = new WeakMap();
+ const originalFontFace = win.FontFace;
+ win.FontFace = function FontFace(family, source, descriptors) {
+ const fontFace = new originalFontFace(family, source, descriptors);
+ fontMap.set(fontFace, {
+ family,
+ buffer: typeof source !== 'string',
+ descriptors,
+ fontSource: typeof source === 'string'
+ ? source
+ :
+ JSON.stringify(Array.from(new Uint8Array(source))),
+ });
+ return fontFace;
+ };
+ const restoreHandler = patch(doc.fonts, 'add', function (original) {
+ return function (fontFace) {
+ setTimeout(() => {
+ const p = fontMap.get(fontFace);
+ if (p) {
+ fontCb(p);
+ fontMap.delete(fontFace);
+ }
+ }, 0);
+ return original.apply(this, [fontFace]);
+ };
+ });
+ handlers.push(() => {
+ win.FontFace = originalFontFace;
+ });
+ handlers.push(restoreHandler);
+ return callbackWrapper(() => {
+ handlers.forEach((h) => h());
+ });
+}
+function mergeHooks(o, hooks) {
+ const { mutationCb, mousemoveCb, mouseInteractionCb, scrollCb, viewportResizeCb, inputCb, mediaInteractionCb, styleSheetRuleCb, styleDeclarationCb, canvasMutationCb, fontCb, } = o;
+ o.mutationCb = (...p) => {
+ if (hooks.mutation) {
+ hooks.mutation(...p);
+ }
+ mutationCb(...p);
+ };
+ o.mousemoveCb = (...p) => {
+ if (hooks.mousemove) {
+ hooks.mousemove(...p);
+ }
+ mousemoveCb(...p);
+ };
+ o.mouseInteractionCb = (...p) => {
+ if (hooks.mouseInteraction) {
+ hooks.mouseInteraction(...p);
+ }
+ mouseInteractionCb(...p);
+ };
+ o.scrollCb = (...p) => {
+ if (hooks.scroll) {
+ hooks.scroll(...p);
+ }
+ scrollCb(...p);
+ };
+ o.viewportResizeCb = (...p) => {
+ if (hooks.viewportResize) {
+ hooks.viewportResize(...p);
+ }
+ viewportResizeCb(...p);
+ };
+ o.inputCb = (...p) => {
+ if (hooks.input) {
+ hooks.input(...p);
+ }
+ inputCb(...p);
+ };
+ o.mediaInteractionCb = (...p) => {
+ if (hooks.mediaInteaction) {
+ hooks.mediaInteaction(...p);
+ }
+ mediaInteractionCb(...p);
+ };
+ o.styleSheetRuleCb = (...p) => {
+ if (hooks.styleSheetRule) {
+ hooks.styleSheetRule(...p);
+ }
+ styleSheetRuleCb(...p);
+ };
+ o.styleDeclarationCb = (...p) => {
+ if (hooks.styleDeclaration) {
+ hooks.styleDeclaration(...p);
+ }
+ styleDeclarationCb(...p);
+ };
+ o.canvasMutationCb = (...p) => {
+ if (hooks.canvasMutation) {
+ hooks.canvasMutation(...p);
+ }
+ canvasMutationCb(...p);
+ };
+ o.fontCb = (...p) => {
+ if (hooks.font) {
+ hooks.font(...p);
+ }
+ fontCb(...p);
+ };
+}
+function initObservers(o, hooks = {}) {
+ const currentWindow = o.doc.defaultView;
+ if (!currentWindow) {
+ return () => { };
+ }
+ mergeHooks(o, hooks);
+ const mutationObserver = initMutationObserver(o, o.doc);
+ const mousemoveHandler = initMoveObserver(o);
+ const mouseInteractionHandler = initMouseInteractionObserver(o);
+ const scrollHandler = initScrollObserver(o);
+ const viewportResizeHandler = initViewportResizeObserver(o);
+ const inputHandler = initInputObserver(o);
+ const mediaInteractionHandler = initMediaInteractionObserver(o);
+ const styleSheetObserver = initStyleSheetObserver(o, { win: currentWindow });
+ const styleDeclarationObserver = initStyleDeclarationObserver(o, {
+ win: currentWindow,
+ });
+ const fontObserver = o.collectFonts ? initFontObserver(o) : () => { };
+ const pluginHandlers = [];
+ for (const plugin of o.plugins) {
+ pluginHandlers.push(plugin.observer(plugin.callback, currentWindow, plugin.options));
+ }
+ return callbackWrapper(() => {
+ mutationBuffers.forEach((b) => b.reset());
+ mutationObserver.disconnect();
+ mousemoveHandler();
+ mouseInteractionHandler();
+ scrollHandler();
+ viewportResizeHandler();
+ inputHandler();
+ mediaInteractionHandler();
+ try {
+ styleSheetObserver();
+ styleDeclarationObserver();
+ }
+ catch (e) {
+ }
+ fontObserver();
+ pluginHandlers.forEach((h) => h());
+ });
+}
+function hasNestedCSSRule(prop) {
+ return typeof window[prop] !== 'undefined';
+}
+function canMonkeyPatchNestedCSSRule(prop) {
+ return Boolean(typeof window[prop] !== 'undefined' &&
+ window[prop].prototype &&
+ 'insertRule' in window[prop].prototype &&
+ 'deleteRule' in window[prop].prototype);
+}
+
+class IframeManager {
+ constructor(options) {
+ this.iframes = new WeakMap();
+ this.mutationCb = options.mutationCb;
+ }
+ addIframe(iframeEl) {
+ this.iframes.set(iframeEl, true);
+ }
+ addLoadListener(cb) {
+ this.loadListener = cb;
+ }
+ attachIframe(iframeEl, childSn) {
+ var _a;
+ this.mutationCb({
+ adds: [
+ {
+ parentId: iframeEl.__sn.id,
+ nextId: null,
+ node: childSn,
+ },
+ ],
+ removes: [],
+ texts: [],
+ attributes: [],
+ isAttachIframe: true,
+ });
+ (_a = this.loadListener) === null || _a === void 0 ? void 0 : _a.call(this, iframeEl);
+ }
+}
+
+class ShadowDomManager {
+ constructor(options) {
+ this.restorePatches = [];
+ this.mutationCb = options.mutationCb;
+ this.scrollCb = options.scrollCb;
+ this.bypassOptions = options.bypassOptions;
+ this.mirror = options.mirror;
+ const manager = this;
+ this.restorePatches.push(patch(HTMLElement.prototype, 'attachShadow', function (original) {
+ return function () {
+ const shadowRoot = original.apply(this, arguments);
+ if (this.shadowRoot)
+ manager.addShadowRoot(this.shadowRoot, this.ownerDocument);
+ return shadowRoot;
+ };
+ }));
+ }
+ addShadowRoot(shadowRoot, doc) {
+ initMutationObserver(Object.assign(Object.assign({}, this.bypassOptions), { doc, mutationCb: this.mutationCb, mirror: this.mirror, shadowDomManager: this }), shadowRoot);
+ initScrollObserver(Object.assign(Object.assign({}, this.bypassOptions), { scrollCb: this.scrollCb, doc: shadowRoot, mirror: this.mirror }));
+ }
+ observeAttachShadow(iframeElement) {
+ if (iframeElement.contentWindow) {
+ const manager = this;
+ this.restorePatches.push(patch(iframeElement.contentWindow.HTMLElement.prototype, 'attachShadow', function (original) {
+ return function () {
+ const shadowRoot = original.apply(this, arguments);
+ if (this.shadowRoot)
+ manager.addShadowRoot(this.shadowRoot, iframeElement.contentDocument);
+ return shadowRoot;
+ };
+ }));
+ }
+ }
+ reset() {
+ this.restorePatches.forEach((restorePatch) => restorePatch());
+ }
+}
+
+/******************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+
+function __rest(s, e) {
+ var t = {};
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
+ t[p] = s[p];
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
+ t[p[i]] = s[p[i]];
+ }
+ return t;
+}
+
+function initCanvas2DMutationObserver(cb, win, blockClass, unblockSelector, blockSelector, mirror) {
+ const handlers = [];
+ const props2D = Object.getOwnPropertyNames(win.CanvasRenderingContext2D.prototype);
+ for (const prop of props2D) {
+ try {
+ if (typeof win.CanvasRenderingContext2D.prototype[prop] !== 'function') {
+ continue;
+ }
+ const restoreHandler = patch(win.CanvasRenderingContext2D.prototype, prop, function (original) {
+ return function (...args) {
+ if (!isBlocked(this.canvas, blockClass, blockSelector, unblockSelector)) {
+ setTimeout(() => {
+ const recordArgs = [...args];
+ if (prop === 'drawImage') {
+ if (recordArgs[0] &&
+ recordArgs[0] instanceof HTMLCanvasElement) {
+ const canvas = recordArgs[0];
+ const ctx = canvas.getContext('2d');
+ let imgd = ctx === null || ctx === void 0 ? void 0 : ctx.getImageData(0, 0, canvas.width, canvas.height);
+ let pix = imgd === null || imgd === void 0 ? void 0 : imgd.data;
+ recordArgs[0] = JSON.stringify(pix);
+ }
+ }
+ cb(this.canvas, {
+ type: CanvasContext['2D'],
+ property: prop,
+ args: recordArgs,
+ });
+ }, 0);
+ }
+ return original.apply(this, args);
+ };
+ });
+ handlers.push(restoreHandler);
+ }
+ catch (_a) {
+ const hookHandler = hookSetter(win.CanvasRenderingContext2D.prototype, prop, {
+ set(v) {
+ cb(this.canvas, {
+ type: CanvasContext['2D'],
+ property: prop,
+ args: [v],
+ setter: true,
+ });
+ },
+ });
+ handlers.push(hookHandler);
+ }
+ }
+ return () => {
+ handlers.forEach((h) => h());
+ };
+}
+
+function initCanvasContextObserver(win, blockClass, blockSelector, unblockSelector) {
+ const handlers = [];
+ try {
+ const restoreHandler = patch(win.HTMLCanvasElement.prototype, 'getContext', function (original) {
+ return function (contextType, ...args) {
+ if (!isBlocked(this, blockClass, blockSelector, unblockSelector)) {
+ if (!('__context' in this))
+ this.__context = contextType;
+ }
+ return original.apply(this, [contextType, ...args]);
+ };
+ });
+ handlers.push(restoreHandler);
+ }
+ catch (_a) {
+ console.error('failed to patch HTMLCanvasElement.prototype.getContext');
+ }
+ return () => {
+ handlers.forEach((h) => h());
+ };
+}
+
+/*
+ * base64-arraybuffer 1.0.2 <https://github.com/niklasvh/base64-arraybuffer>
+ * Copyright (c) 2022 Niklas von Hertzen <https://hertzen.com>
+ * Released under MIT License
+ */
+var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+// Use a lookup table to find the index.
+var lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256);
+for (var i = 0; i < chars.length; i++) {
+ lookup[chars.charCodeAt(i)] = i;
+}
+var encode = function (arraybuffer) {
+ var bytes = new Uint8Array(arraybuffer), i, len = bytes.length, base64 = '';
+ for (i = 0; i < len; i += 3) {
+ base64 += chars[bytes[i] >> 2];
+ base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
+ base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
+ base64 += chars[bytes[i + 2] & 63];
+ }
+ if (len % 3 === 2) {
+ base64 = base64.substring(0, base64.length - 1) + '=';
+ }
+ else if (len % 3 === 1) {
+ base64 = base64.substring(0, base64.length - 2) + '==';
+ }
+ return base64;
+};
+
+const webGLVarMap = new Map();
+function variableListFor(ctx, ctor) {
+ let contextMap = webGLVarMap.get(ctx);
+ if (!contextMap) {
+ contextMap = new Map();
+ webGLVarMap.set(ctx, contextMap);
+ }
+ if (!contextMap.has(ctor)) {
+ contextMap.set(ctor, []);
+ }
+ return contextMap.get(ctor);
+}
+const saveWebGLVar = (value, win, ctx) => {
+ if (!value ||
+ !(isInstanceOfWebGLObject(value, win) || typeof value === 'object'))
+ return;
+ const name = value.constructor.name;
+ const list = variableListFor(ctx, name);
+ let index = list.indexOf(value);
+ if (index === -1) {
+ index = list.length;
+ list.push(value);
+ }
+ return index;
+};
+function serializeArg(value, win, ctx) {
+ if (value instanceof Array) {
+ return value.map((arg) => serializeArg(arg, win, ctx));
+ }
+ else if (value === null) {
+ return value;
+ }
+ else if (value instanceof Float32Array ||
+ value instanceof Float64Array ||
+ value instanceof Int32Array ||
+ value instanceof Uint32Array ||
+ value instanceof Uint8Array ||
+ value instanceof Uint16Array ||
+ value instanceof Int16Array ||
+ value instanceof Int8Array ||
+ value instanceof Uint8ClampedArray) {
+ const name = value.constructor.name;
+ return {
+ rr_type: name,
+ args: [Object.values(value)],
+ };
+ }
+ else if (value instanceof ArrayBuffer) {
+ const name = value.constructor.name;
+ const base64 = encode(value);
+ return {
+ rr_type: name,
+ base64,
+ };
+ }
+ else if (value instanceof DataView) {
+ const name = value.constructor.name;
+ return {
+ rr_type: name,
+ args: [
+ serializeArg(value.buffer, win, ctx),
+ value.byteOffset,
+ value.byteLength,
+ ],
+ };
+ }
+ else if (value instanceof HTMLImageElement) {
+ const name = value.constructor.name;
+ const { src } = value;
+ return {
+ rr_type: name,
+ src,
+ };
+ }
+ else if (value instanceof ImageData) {
+ const name = value.constructor.name;
+ return {
+ rr_type: name,
+ args: [serializeArg(value.data, win, ctx), value.width, value.height],
+ };
+ }
+ else if (isInstanceOfWebGLObject(value, win) || typeof value === 'object') {
+ const name = value.constructor.name;
+ const index = saveWebGLVar(value, win, ctx);
+ return {
+ rr_type: name,
+ index: index,
+ };
+ }
+ return value;
+}
+const serializeArgs = (args, win, ctx) => {
+ return [...args].map((arg) => serializeArg(arg, win, ctx));
+};
+const isInstanceOfWebGLObject = (value, win) => {
+ const webGLConstructorNames = [
+ 'WebGLActiveInfo',
+ 'WebGLBuffer',
+ 'WebGLFramebuffer',
+ 'WebGLProgram',
+ 'WebGLRenderbuffer',
+ 'WebGLShader',
+ 'WebGLShaderPrecisionFormat',
+ 'WebGLTexture',
+ 'WebGLUniformLocation',
+ 'WebGLVertexArrayObject',
+ 'WebGLVertexArrayObjectOES',
+ ];
+ const supportedWebGLConstructorNames = webGLConstructorNames.filter((name) => typeof win[name] === 'function');
+ return Boolean(supportedWebGLConstructorNames.find((name) => value instanceof win[name]));
+};
+
+function patchGLPrototype(prototype, type, cb, blockClass, unblockSelector, blockSelector, mirror, win) {
+ const handlers = [];
+ const props = Object.getOwnPropertyNames(prototype);
+ for (const prop of props) {
+ try {
+ if (typeof prototype[prop] !== 'function') {
+ continue;
+ }
+ const restoreHandler = patch(prototype, prop, function (original) {
+ return function (...args) {
+ const result = original.apply(this, args);
+ saveWebGLVar(result, win, prototype);
+ if (!isBlocked(this.canvas, blockClass, blockSelector, unblockSelector)) {
+ const id = mirror.getId(this.canvas);
+ const recordArgs = serializeArgs([...args], win, prototype);
+ const mutation = {
+ type,
+ property: prop,
+ args: recordArgs,
+ };
+ cb(this.canvas, mutation);
+ }
+ return result;
+ };
+ });
+ handlers.push(restoreHandler);
+ }
+ catch (_a) {
+ const hookHandler = hookSetter(prototype, prop, {
+ set(v) {
+ cb(this.canvas, {
+ type,
+ property: prop,
+ args: [v],
+ setter: true,
+ });
+ },
+ });
+ handlers.push(hookHandler);
+ }
+ }
+ return handlers;
+}
+function initCanvasWebGLMutationObserver(cb, win, blockClass, blockSelector, unblockSelector, mirror) {
+ const handlers = [];
+ handlers.push(...patchGLPrototype(win.WebGLRenderingContext.prototype, CanvasContext.WebGL, cb, blockClass, blockSelector, unblockSelector, mirror, win));
+ if (typeof win.WebGL2RenderingContext !== 'undefined') {
+ handlers.push(...patchGLPrototype(win.WebGL2RenderingContext.prototype, CanvasContext.WebGL2, cb, blockClass, blockSelector, unblockSelector, mirror, win));
+ }
+ return () => {
+ handlers.forEach((h) => h());
+ };
+}
+
+class CanvasManager {
+ reset() {
+ this.pendingCanvasMutations.clear();
+ this.resetObservers && this.resetObservers();
+ }
+ freeze() {
+ this.frozen = true;
+ }
+ unfreeze() {
+ this.frozen = false;
+ }
+ lock() {
+ this.locked = true;
+ }
+ unlock() {
+ this.locked = false;
+ }
+ constructor(options) {
+ this.pendingCanvasMutations = new Map();
+ this.rafStamps = { latestId: 0, invokeId: null };
+ this.frozen = false;
+ this.locked = false;
+ this.processMutation = function (target, mutation) {
+ const newFrame = this.rafStamps.invokeId &&
+ this.rafStamps.latestId !== this.rafStamps.invokeId;
+ if (newFrame || !this.rafStamps.invokeId)
+ this.rafStamps.invokeId = this.rafStamps.latestId;
+ if (!this.pendingCanvasMutations.has(target)) {
+ this.pendingCanvasMutations.set(target, []);
+ }
+ this.pendingCanvasMutations.get(target).push(mutation);
+ };
+ this.mutationCb = options.mutationCb;
+ this.mirror = options.mirror;
+ if (options.recordCanvas === true)
+ this.initCanvasMutationObserver(options.win, options.blockClass, options.blockSelector, options.unblockSelector);
+ }
+ initCanvasMutationObserver(win, blockClass, unblockSelector, blockSelector) {
+ this.startRAFTimestamping();
+ this.startPendingCanvasMutationFlusher();
+ const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector, unblockSelector);
+ const canvas2DReset = initCanvas2DMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector, unblockSelector, this.mirror);
+ const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector, unblockSelector, this.mirror);
+ this.resetObservers = () => {
+ canvasContextReset();
+ canvas2DReset();
+ canvasWebGL1and2Reset();
+ };
+ }
+ startPendingCanvasMutationFlusher() {
+ requestAnimationFrame(() => this.flushPendingCanvasMutations());
+ }
+ startRAFTimestamping() {
+ const setLatestRAFTimestamp = (timestamp) => {
+ this.rafStamps.latestId = timestamp;
+ requestAnimationFrame(setLatestRAFTimestamp);
+ };
+ requestAnimationFrame(setLatestRAFTimestamp);
+ }
+ flushPendingCanvasMutations() {
+ this.pendingCanvasMutations.forEach((values, canvas) => {
+ const id = this.mirror.getId(canvas);
+ this.flushPendingCanvasMutationFor(canvas, id);
+ });
+ requestAnimationFrame(() => this.flushPendingCanvasMutations());
+ }
+ flushPendingCanvasMutationFor(canvas, id) {
+ if (this.frozen || this.locked) {
+ return;
+ }
+ const valuesWithType = this.pendingCanvasMutations.get(canvas);
+ if (!valuesWithType || id === -1)
+ return;
+ const values = valuesWithType.map((value) => {
+ const rest = __rest(value, ["type"]);
+ return rest;
+ });
+ const { type } = valuesWithType[0];
+ this.mutationCb({ id, type, commands: values });
+ this.pendingCanvasMutations.delete(canvas);
+ }
+}
+
+function wrapEvent(e) {
+ return Object.assign(Object.assign({}, e), { timestamp: Date.now() });
+}
+let wrappedEmit;
+let takeFullSnapshot;
+const mirror = createMirror();
+function record(options = {}) {
+ const { emit, checkoutEveryNms, checkoutEveryNth, blockClass = 'rr-block', blockSelector = null, unblockSelector = null, ignoreClass = 'rr-ignore', ignoreSelector = null, maskTextClass = 'rr-mask', maskTextSelector = null, maskInputSelector = null, unmaskTextSelector = null, unmaskInputSelector = null, inlineStylesheet = true, maskAllText = false, maskAllInputs, maskInputOptions: _maskInputOptions, slimDOMOptions: _slimDOMOptions, maskInputFn, maskTextFn, hooks, packFn, sampling = {}, mousemoveWait, recordCanvas = false, userTriggeredOnInput = false, collectFonts = false, inlineImages = false, plugins, keepIframeSrcFn = () => false, onMutation, } = options;
+ if (!emit) {
+ throw new Error('emit function is required');
+ }
+ if (mousemoveWait !== undefined && sampling.mousemove === undefined) {
+ sampling.mousemove = mousemoveWait;
+ }
+ const maskInputOptions = maskAllInputs === true
+ ? {
+ color: true,
+ date: true,
+ 'datetime-local': true,
+ email: true,
+ month: true,
+ number: true,
+ range: true,
+ search: true,
+ tel: true,
+ text: true,
+ time: true,
+ url: true,
+ week: true,
+ textarea: true,
+ select: true,
+ radio: true,
+ checkbox: true,
+ }
+ : _maskInputOptions !== undefined
+ ? _maskInputOptions
+ : {};
+ const slimDOMOptions = _slimDOMOptions === true || _slimDOMOptions === 'all'
+ ? {
+ script: true,
+ comment: true,
+ headFavicon: true,
+ headWhitespace: true,
+ headMetaSocial: true,
+ headMetaRobots: true,
+ headMetaHttpEquiv: true,
+ headMetaVerification: true,
+ headMetaAuthorship: _slimDOMOptions === 'all',
+ headMetaDescKeywords: _slimDOMOptions === 'all',
+ }
+ : _slimDOMOptions
+ ? _slimDOMOptions
+ : {};
+ polyfill();
+ let lastFullSnapshotEvent;
+ let incrementalSnapshotCount = 0;
+ const eventProcessor = (e) => {
+ for (const plugin of plugins || []) {
+ if (plugin.eventProcessor) {
+ e = plugin.eventProcessor(e);
+ }
+ }
+ if (packFn) {
+ e = packFn(e);
+ }
+ return e;
+ };
+ wrappedEmit = (e, isCheckout) => {
+ var _a;
+ if (((_a = mutationBuffers[0]) === null || _a === void 0 ? void 0 : _a.isFrozen()) &&
+ e.type !== EventType.FullSnapshot &&
+ !(e.type === EventType.IncrementalSnapshot &&
+ e.data.source === IncrementalSource.Mutation)) {
+ mutationBuffers.forEach((buf) => buf.unfreeze());
+ }
+ emit(eventProcessor(e), isCheckout);
+ if (e.type === EventType.FullSnapshot) {
+ lastFullSnapshotEvent = e;
+ incrementalSnapshotCount = 0;
+ }
+ else if (e.type === EventType.IncrementalSnapshot) {
+ if (e.data.source === IncrementalSource.Mutation &&
+ e.data.isAttachIframe) {
+ return;
+ }
+ incrementalSnapshotCount++;
+ const exceedCount = checkoutEveryNth && incrementalSnapshotCount >= checkoutEveryNth;
+ const exceedTime = checkoutEveryNms &&
+ e.timestamp - lastFullSnapshotEvent.timestamp > checkoutEveryNms;
+ if (exceedCount || exceedTime) {
+ takeFullSnapshot(true);
+ }
+ }
+ };
+ const wrappedMutationEmit = (m) => {
+ wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.Mutation }, m),
+ }));
+ };
+ const wrappedScrollEmit = (p) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.Scroll }, p),
+ }));
+ const wrappedCanvasMutationEmit = (p) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.CanvasMutation }, p),
+ }));
+ const iframeManager = new IframeManager({
+ mutationCb: wrappedMutationEmit,
+ });
+ const canvasManager = new CanvasManager({
+ recordCanvas,
+ mutationCb: wrappedCanvasMutationEmit,
+ win: window,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ mirror,
+ });
+ const shadowDomManager = new ShadowDomManager({
+ mutationCb: wrappedMutationEmit,
+ scrollCb: wrappedScrollEmit,
+ bypassOptions: {
+ onMutation,
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ maskInputSelector,
+ unmaskInputSelector,
+ inlineStylesheet,
+ maskAllText,
+ maskInputOptions,
+ maskTextFn,
+ maskInputFn,
+ recordCanvas,
+ inlineImages,
+ sampling,
+ slimDOMOptions,
+ iframeManager,
+ canvasManager,
+ },
+ mirror,
+ });
+ takeFullSnapshot = (isCheckout = false) => {
+ var _a, _b, _c, _d;
+ wrappedEmit(wrapEvent({
+ type: EventType.Meta,
+ data: {
+ href: window.location.href,
+ width: getWindowWidth(),
+ height: getWindowHeight(),
+ },
+ }), isCheckout);
+ mutationBuffers.forEach((buf) => buf.lock());
+ const [node, idNodeMap] = snapshot(document, {
+ blockClass,
+ blockSelector,
+ unblockSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ maskInputSelector,
+ unmaskInputSelector,
+ inlineStylesheet,
+ maskAllText,
+ maskAllInputs: maskInputOptions,
+ maskTextFn,
+ slimDOM: slimDOMOptions,
+ recordCanvas,
+ inlineImages,
+ onSerialize: (n) => {
+ if (isIframeINode(n)) {
+ iframeManager.addIframe(n);
+ }
+ if (hasShadowRoot(n)) {
+ shadowDomManager.addShadowRoot(n.shadowRoot, document);
+ }
+ },
+ onIframeLoad: (iframe, childSn) => {
+ iframeManager.attachIframe(iframe, childSn);
+ shadowDomManager.observeAttachShadow(iframe);
+ },
+ keepIframeSrcFn,
+ });
+ if (!node) {
+ return console.warn('Failed to snapshot the document');
+ }
+ mirror.map = idNodeMap;
+ wrappedEmit(wrapEvent({
+ type: EventType.FullSnapshot,
+ data: {
+ node,
+ initialOffset: {
+ left: window.pageXOffset !== undefined
+ ? window.pageXOffset
+ : (document === null || document === void 0 ? void 0 : document.documentElement.scrollLeft) ||
+ ((_b = (_a = document === null || document === void 0 ? void 0 : document.body) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.scrollLeft) ||
+ (document === null || document === void 0 ? void 0 : document.body.scrollLeft) ||
+ 0,
+ top: window.pageYOffset !== undefined
+ ? window.pageYOffset
+ : (document === null || document === void 0 ? void 0 : document.documentElement.scrollTop) ||
+ ((_d = (_c = document === null || document === void 0 ? void 0 : document.body) === null || _c === void 0 ? void 0 : _c.parentElement) === null || _d === void 0 ? void 0 : _d.scrollTop) ||
+ (document === null || document === void 0 ? void 0 : document.body.scrollTop) ||
+ 0,
+ },
+ },
+ }));
+ mutationBuffers.forEach((buf) => buf.unlock());
+ };
+ try {
+ const handlers = [];
+ handlers.push(on('DOMContentLoaded', () => {
+ wrappedEmit(wrapEvent({
+ type: EventType.DomContentLoaded,
+ data: {},
+ }));
+ }));
+ const observe = (doc) => {
+ var _a;
+ return callbackWrapper(initObservers)({
+ onMutation,
+ mutationCb: wrappedMutationEmit,
+ mousemoveCb: (positions, source) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: {
+ source,
+ positions,
+ },
+ })),
+ mouseInteractionCb: (d) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.MouseInteraction }, d),
+ })),
+ scrollCb: wrappedScrollEmit,
+ viewportResizeCb: (d) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.ViewportResize }, d),
+ })),
+ inputCb: (v) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.Input }, v),
+ })),
+ mediaInteractionCb: (p) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.MediaInteraction }, p),
+ })),
+ styleSheetRuleCb: (r) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.StyleSheetRule }, r),
+ })),
+ styleDeclarationCb: (r) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.StyleDeclaration }, r),
+ })),
+ canvasMutationCb: wrappedCanvasMutationEmit,
+ fontCb: (p) => wrappedEmit(wrapEvent({
+ type: EventType.IncrementalSnapshot,
+ data: Object.assign({ source: IncrementalSource.Font }, p),
+ })),
+ blockClass,
+ ignoreClass,
+ ignoreSelector,
+ maskTextClass,
+ maskTextSelector,
+ unmaskTextSelector,
+ maskInputSelector,
+ unmaskInputSelector,
+ maskInputOptions,
+ inlineStylesheet,
+ sampling,
+ recordCanvas,
+ inlineImages,
+ userTriggeredOnInput,
+ collectFonts,
+ doc,
+ maskAllText,
+ maskInputFn,
+ maskTextFn,
+ blockSelector,
+ unblockSelector,
+ slimDOMOptions,
+ mirror,
+ iframeManager,
+ shadowDomManager,
+ canvasManager,
+ plugins: ((_a = plugins === null || plugins === void 0 ? void 0 : plugins.filter((p) => p.observer)) === null || _a === void 0 ? void 0 : _a.map((p) => ({
+ observer: p.observer,
+ options: p.options,
+ callback: (payload) => wrappedEmit(wrapEvent({
+ type: EventType.Plugin,
+ data: {
+ plugin: p.name,
+ payload,
+ },
+ })),
+ }))) || [],
+ }, hooks);
+ };
+ iframeManager.addLoadListener((iframeEl) => {
+ try {
+ handlers.push(observe(iframeEl.contentDocument));
+ }
+ catch (error) {
+ console.warn(error);
+ }
+ });
+ const init = () => {
+ takeFullSnapshot();
+ handlers.push(observe(document));
+ };
+ if (document.readyState === 'interactive' ||
+ document.readyState === 'complete') {
+ init();
+ }
+ else {
+ handlers.push(on('load', () => {
+ wrappedEmit(wrapEvent({
+ type: EventType.Load,
+ data: {},
+ }));
+ init();
+ }, window));
+ }
+ return () => {
+ handlers.forEach((h) => h());
+ };
+ }
+ catch (error) {
+ console.warn(error);
+ }
+}
+record.addCustomEvent = (tag, payload) => {
+ if (!wrappedEmit) {
+ throw new Error('please add custom event after start recording');
+ }
+ wrappedEmit(wrapEvent({
+ type: EventType.Custom,
+ data: {
+ tag,
+ payload,
+ },
+ }));
+};
+record.freezePage = () => {
+ mutationBuffers.forEach((buf) => buf.freeze());
+};
+record.takeFullSnapshot = (isCheckout) => {
+ if (!takeFullSnapshot) {
+ throw new Error('please take full snapshot after start recording');
+ }
+ takeFullSnapshot(isCheckout);
+};
+record.mirror = mirror;
+
+/**
+ * Add a breadcrumb event to replay.
+ */
+function addBreadcrumbEvent(replay, breadcrumb) {
+ if (breadcrumb.category === 'sentry.transaction') {
+ return;
+ }
+
+ if (['ui.click', 'ui.input'].includes(breadcrumb.category )) {
+ replay.triggerUserActivity();
+ } else {
+ replay.checkAndHandleExpiredSession();
+ }
+
+ replay.addUpdate(() => {
+ void replay.throttledAddEvent({
+ type: EventType.Custom,
+ // TODO: We were converting from ms to seconds for breadcrumbs, spans,
+ // but maybe we should just keep them as milliseconds
+ timestamp: (breadcrumb.timestamp || 0) * 1000,
+ data: {
+ tag: 'breadcrumb',
+ // normalize to max. 10 depth and 1_000 properties per object
+ payload: normalize(breadcrumb, 10, 1000),
+ },
+ });
+
+ // Do not flush after console log messages
+ return breadcrumb.category === 'console';
+ });
+}
+
+const INTERACTIVE_SELECTOR = 'button,a';
+
+/**
+ * For clicks, we check if the target is inside of a button or link
+ * If so, we use this as the target instead
+ * This is useful because if you click on the image in <button><img></button>,
+ * The target will be the image, not the button, which we don't want here
+ */
+function getClickTargetNode(event) {
+ const target = getTargetNode(event);
+
+ if (!target || !(target instanceof Element)) {
+ return target;
+ }
+
+ const closestInteractive = target.closest(INTERACTIVE_SELECTOR);
+ return closestInteractive || target;
+}
+
+/** Get the event target node. */
+function getTargetNode(event) {
+ if (isEventWithTarget(event)) {
+ return event.target ;
+ }
+
+ return event;
+}
+
+function isEventWithTarget(event) {
+ return typeof event === 'object' && !!event && 'target' in event;
+}
+
+let handlers;
+
+/**
+ * Register a handler to be called when `window.open()` is called.
+ * Returns a cleanup function.
+ */
+function onWindowOpen(cb) {
+ // Ensure to only register this once
+ if (!handlers) {
+ handlers = [];
+ monkeyPatchWindowOpen();
+ }
+
+ handlers.push(cb);
+
+ return () => {
+ const pos = handlers ? handlers.indexOf(cb) : -1;
+ if (pos > -1) {
+ (handlers ).splice(pos, 1);
+ }
+ };
+}
+
+function monkeyPatchWindowOpen() {
+ fill(WINDOW, 'open', function (originalWindowOpen) {
+ return function (...args) {
+ if (handlers) {
+ try {
+ handlers.forEach(handler => handler());
+ } catch (e) {
+ // ignore errors in here
+ }
+ }
+
+ return originalWindowOpen.apply(WINDOW, args);
+ };
+ });
+}
+
+/** Handle a click. */
+function handleClick(clickDetector, clickBreadcrumb, node) {
+ clickDetector.handleClick(clickBreadcrumb, node);
+}
+
+/** A click detector class that can be used to detect slow or rage clicks on elements. */
+class ClickDetector {
+ // protected for testing
+ __init() {this._lastMutation = 0;}
+ __init2() {this._lastScroll = 0;}
+
+ __init3() {this._clicks = [];}
+
+ constructor(
+ replay,
+ slowClickConfig,
+ // Just for easier testing
+ _addBreadcrumbEvent = addBreadcrumbEvent,
+ ) {ClickDetector.prototype.__init.call(this);ClickDetector.prototype.__init2.call(this);ClickDetector.prototype.__init3.call(this);
+ // We want everything in s, but options are in ms
+ this._timeout = slowClickConfig.timeout / 1000;
+ this._multiClickTimeout = slowClickConfig.multiClickTimeout / 1000;
+ this._threshold = slowClickConfig.threshold / 1000;
+ this._scollTimeout = slowClickConfig.scrollTimeout / 1000;
+ this._replay = replay;
+ this._ignoreSelector = slowClickConfig.ignoreSelector;
+ this._addBreadcrumbEvent = _addBreadcrumbEvent;
+ }
+
+ /** Register click detection handlers on mutation or scroll. */
+ addListeners() {
+ const mutationHandler = () => {
+ this._lastMutation = nowInSeconds();
+ };
+
+ const scrollHandler = () => {
+ this._lastScroll = nowInSeconds();
+ };
+
+ const cleanupWindowOpen = onWindowOpen(() => {
+ // Treat window.open as mutation
+ this._lastMutation = nowInSeconds();
+ });
+
+ const clickHandler = (event) => {
+ if (!event.target) {
+ return;
+ }
+
+ const node = getClickTargetNode(event);
+ if (node) {
+ this._handleMultiClick(node );
+ }
+ };
+
+ const obs = new MutationObserver(mutationHandler);
+
+ obs.observe(WINDOW.document.documentElement, {
+ attributes: true,
+ characterData: true,
+ childList: true,
+ subtree: true,
+ });
+
+ WINDOW.addEventListener('scroll', scrollHandler, { passive: true });
+ WINDOW.addEventListener('click', clickHandler, { passive: true });
+
+ this._teardown = () => {
+ WINDOW.removeEventListener('scroll', scrollHandler);
+ WINDOW.removeEventListener('click', clickHandler);
+ cleanupWindowOpen();
+
+ obs.disconnect();
+ this._clicks = [];
+ this._lastMutation = 0;
+ this._lastScroll = 0;
+ };
+ }
+
+ /** Clean up listeners. */
+ removeListeners() {
+ if (this._teardown) {
+ this._teardown();
+ }
+
+ if (this._checkClickTimeout) {
+ clearTimeout(this._checkClickTimeout);
+ }
+ }
+
+ /** Handle a click */
+ handleClick(breadcrumb, node) {
+ if (ignoreElement(node, this._ignoreSelector) || !isClickBreadcrumb(breadcrumb)) {
+ return;
+ }
+
+ const click = this._getClick(node);
+
+ if (click) {
+ // this means a click on the same element was captured in the last 1s, so we consider this a multi click
+ return;
+ }
+
+ const newClick = {
+ timestamp: breadcrumb.timestamp,
+ clickBreadcrumb: breadcrumb,
+ // Set this to 0 so we know it originates from the click breadcrumb
+ clickCount: 0,
+ node,
+ };
+ this._clicks.push(newClick);
+
+ // If this is the first new click, set a timeout to check for multi clicks
+ if (this._clicks.length === 1) {
+ this._scheduleCheckClicks();
+ }
+ }
+
+ /** Count multiple clicks on elements. */
+ _handleMultiClick(node) {
+ const click = this._getClick(node);
+
+ if (!click) {
+ return;
+ }
+
+ click.clickCount++;
+ }
+
+ /** Try to get an existing click on the given element. */
+ _getClick(node) {
+ const now = nowInSeconds();
+
+ // Find any click on the same element in the last second
+ // If one exists, we consider this click as a double/triple/etc click
+ return this._clicks.find(click => click.node === node && now - click.timestamp < this._multiClickTimeout);
+ }
+
+ /** Check the clicks that happened. */
+ _checkClicks() {
+ const timedOutClicks = [];
+
+ const now = nowInSeconds();
+
+ this._clicks.forEach(click => {
+ if (!click.mutationAfter && this._lastMutation) {
+ click.mutationAfter = click.timestamp <= this._lastMutation ? this._lastMutation - click.timestamp : undefined;
+ }
+ if (!click.scrollAfter && this._lastScroll) {
+ click.scrollAfter = click.timestamp <= this._lastScroll ? this._lastScroll - click.timestamp : undefined;
+ }
+
+ // If an action happens after the multi click threshold, we can skip waiting and handle the click right away
+ const actionTime = click.scrollAfter || click.mutationAfter || 0;
+ if (actionTime && actionTime >= this._multiClickTimeout) {
+ timedOutClicks.push(click);
+ return;
+ }
+
+ if (click.timestamp + this._timeout <= now) {
+ timedOutClicks.push(click);
+ }
+ });
+
+ // Remove "old" clicks
+ for (const click of timedOutClicks) {
+ this._generateBreadcrumbs(click);
+
+ const pos = this._clicks.indexOf(click);
+ if (pos !== -1) {
+ this._clicks.splice(pos, 1);
+ }
+ }
+
+ // Trigger new check, unless no clicks left
+ if (this._clicks.length) {
+ this._scheduleCheckClicks();
+ }
+ }
+
+ /** Generate matching breadcrumb(s) for the click. */
+ _generateBreadcrumbs(click) {
+ const replay = this._replay;
+ const hadScroll = click.scrollAfter && click.scrollAfter <= this._scollTimeout;
+ const hadMutation = click.mutationAfter && click.mutationAfter <= this._threshold;
+
+ const isSlowClick = !hadScroll && !hadMutation;
+ const { clickCount, clickBreadcrumb } = click;
+
+ // Slow click
+ if (isSlowClick) {
+ // If `mutationAfter` is set, it means a mutation happened after the threshold, but before the timeout
+ // If not, it means we just timed out without scroll & mutation
+ const timeAfterClickMs = Math.min(click.mutationAfter || this._timeout, this._timeout) * 1000;
+ const endReason = timeAfterClickMs < this._timeout * 1000 ? 'mutation' : 'timeout';
+
+ const breadcrumb = {
+ type: 'default',
+ message: clickBreadcrumb.message,
+ timestamp: clickBreadcrumb.timestamp,
+ category: 'ui.slowClickDetected',
+ data: {
+ ...clickBreadcrumb.data,
+ url: WINDOW.location.href,
+ route: replay.getCurrentRoute(),
+ timeAfterClickMs,
+ endReason,
+ // If clickCount === 0, it means multiClick was not correctly captured here
+ // - we still want to send 1 in this case
+ clickCount: clickCount || 1,
+ },
+ };
+
+ this._addBreadcrumbEvent(replay, breadcrumb);
+ return;
+ }
+
+ // Multi click
+ if (clickCount > 1) {
+ const breadcrumb = {
+ type: 'default',
+ message: clickBreadcrumb.message,
+ timestamp: clickBreadcrumb.timestamp,
+ category: 'ui.multiClick',
+ data: {
+ ...clickBreadcrumb.data,
+ url: WINDOW.location.href,
+ route: replay.getCurrentRoute(),
+ clickCount,
+ metric: true,
+ },
+ };
+
+ this._addBreadcrumbEvent(replay, breadcrumb);
+ }
+ }
+
+ /** Schedule to check current clicks. */
+ _scheduleCheckClicks() {
+ this._checkClickTimeout = setTimeout(() => this._checkClicks(), 1000);
+ }
+}
+
+const SLOW_CLICK_TAGS = ['A', 'BUTTON', 'INPUT'];
+
+/** exported for tests only */
+function ignoreElement(node, ignoreSelector) {
+ if (!SLOW_CLICK_TAGS.includes(node.tagName)) {
+ return true;
+ }
+
+ // If <input> tag, we only want to consider input[type='submit'] & input[type='button']
+ if (node.tagName === 'INPUT' && !['submit', 'button'].includes(node.getAttribute('type') || '')) {
+ return true;
+ }
+
+ // If <a> tag, detect special variants that may not lead to an action
+ // If target !== _self, we may open the link somewhere else, which would lead to no action
+ // Also, when downloading a file, we may not leave the page, but still not trigger an action
+ if (
+ node.tagName === 'A' &&
+ (node.hasAttribute('download') || (node.hasAttribute('target') && node.getAttribute('target') !== '_self'))
+ ) {
+ return true;
+ }
+
+ if (ignoreSelector && node.matches(ignoreSelector)) {
+ return true;
+ }
+
+ return false;
+}
+
+function isClickBreadcrumb(breadcrumb) {
+ return !!(breadcrumb.data && typeof breadcrumb.data.nodeId === 'number' && breadcrumb.timestamp);
+}
+
+// This is good enough for us, and is easier to test/mock than `timestampInSeconds`
+function nowInSeconds() {
+ return Date.now() / 1000;
+}
+
+/**
+ * Create a breadcrumb for a replay.
+ */
+function createBreadcrumb(
+ breadcrumb,
+) {
+ return {
+ timestamp: Date.now() / 1000,
+ type: 'default',
+ ...breadcrumb,
+ };
+}
+
+var NodeType;
+(function (NodeType) {
+ NodeType[NodeType["Document"] = 0] = "Document";
+ NodeType[NodeType["DocumentType"] = 1] = "DocumentType";
+ NodeType[NodeType["Element"] = 2] = "Element";
+ NodeType[NodeType["Text"] = 3] = "Text";
+ NodeType[NodeType["CDATA"] = 4] = "CDATA";
+ NodeType[NodeType["Comment"] = 5] = "Comment";
+})(NodeType || (NodeType = {}));
+
+// Note that these are the serialized attributes and not attributes directly on
+// the DOM Node. Attributes we are interested in:
+const ATTRIBUTES_TO_RECORD = new Set([
+ 'id',
+ 'class',
+ 'aria-label',
+ 'role',
+ 'name',
+ 'alt',
+ 'title',
+ 'data-test-id',
+ 'data-testid',
+ 'disabled',
+ 'aria-disabled',
+]);
+
+/**
+ * Inclusion list of attributes that we want to record from the DOM element
+ */
+function getAttributesToRecord(attributes) {
+ const obj = {};
+ for (const key in attributes) {
+ if (ATTRIBUTES_TO_RECORD.has(key)) {
+ let normalizedKey = key;
+
+ if (key === 'data-testid' || key === 'data-test-id') {
+ normalizedKey = 'testId';
+ }
+
+ obj[normalizedKey] = attributes[key];
+ }
+ }
+
+ return obj;
+}
+
+const handleDomListener = (
+ replay,
+) => {
+ return (handlerData) => {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ const result = handleDom(handlerData);
+
+ if (!result) {
+ return;
+ }
+
+ const isClick = handlerData.name === 'click';
+ const event = isClick && (handlerData.event );
+ // Ignore clicks if ctrl/alt/meta keys are held down as they alter behavior of clicks (e.g. open in new tab)
+ if (isClick && replay.clickDetector && event && !event.altKey && !event.metaKey && !event.ctrlKey) {
+ handleClick(
+ replay.clickDetector,
+ result ,
+ getClickTargetNode(handlerData.event) ,
+ );
+ }
+
+ addBreadcrumbEvent(replay, result);
+ };
+};
+
+/** Get the base DOM breadcrumb. */
+function getBaseDomBreadcrumb(target, message) {
+ // `__sn` property is the serialized node created by rrweb
+ const serializedNode = target && isRrwebNode(target) && target.__sn.type === NodeType.Element ? target.__sn : null;
+
+ return {
+ message,
+ data: serializedNode
+ ? {
+ nodeId: serializedNode.id,
+ node: {
+ id: serializedNode.id,
+ tagName: serializedNode.tagName,
+ textContent: target
+ ? Array.from(target.childNodes)
+ .map(
+ (node) => '__sn' in node && node.__sn.type === NodeType.Text && node.__sn.textContent,
+ )
+ .filter(Boolean) // filter out empty values
+ .map(text => (text ).trim())
+ .join('')
+ : '',
+ attributes: getAttributesToRecord(serializedNode.attributes),
+ },
+ }
+ : {},
+ };
+}
+
+/**
+ * An event handler to react to DOM events.
+ * Exported for tests.
+ */
+function handleDom(handlerData) {
+ const { target, message } = getDomTarget(handlerData);
+
+ return createBreadcrumb({
+ category: `ui.${handlerData.name}`,
+ ...getBaseDomBreadcrumb(target, message),
+ });
+}
+
+function getDomTarget(handlerData) {
+ const isClick = handlerData.name === 'click';
+
+ let message;
+ let target = null;
+
+ // Accessing event.target can throw (see getsentry/raven-js#838, #768)
+ try {
+ target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
+ message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
+ } catch (e) {
+ message = '<unknown>';
+ }
+
+ return { target, message };
+}
+
+function isRrwebNode(node) {
+ return '__sn' in node;
+}
+
+/** Handle keyboard events & create breadcrumbs. */
+function handleKeyboardEvent(replay, event) {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ // Update user activity, but do not restart recording as it can create
+ // noisy/low-value replays (e.g. user comes back from idle, hits alt-tab, new
+ // session with a single "keydown" breadcrumb is created)
+ replay.updateUserActivity();
+
+ const breadcrumb = getKeyboardBreadcrumb(event);
+
+ if (!breadcrumb) {
+ return;
+ }
+
+ addBreadcrumbEvent(replay, breadcrumb);
+}
+
+/** exported only for tests */
+function getKeyboardBreadcrumb(event) {
+ const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
+
+ // never capture for input fields
+ if (!target || isInputElement(target ) || !key) {
+ return null;
+ }
+
+ // Note: We do not consider shift here, as that means "uppercase"
+ const hasModifierKey = metaKey || ctrlKey || altKey;
+ const isCharacterKey = key.length === 1; // other keys like Escape, Tab, etc have a longer length
+
+ // Do not capture breadcrumb if only a word key is pressed
+ // This could leak e.g. user input
+ if (!hasModifierKey && isCharacterKey) {
+ return null;
+ }
+
+ const message = htmlTreeAsString(target, { maxStringLength: 200 }) || '<unknown>';
+ const baseBreadcrumb = getBaseDomBreadcrumb(target , message);
+
+ return createBreadcrumb({
+ category: 'ui.keyDown',
+ message,
+ data: {
+ ...baseBreadcrumb.data,
+ metaKey,
+ shiftKey,
+ ctrlKey,
+ altKey,
+ key,
+ },
+ });
+}
+
+function isInputElement(target) {
+ return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable;
+}
+
+const NAVIGATION_ENTRY_KEYS = [
+ 'name',
+ 'type',
+ 'startTime',
+ 'transferSize',
+ 'duration',
+];
+
+function isNavigationEntryEqual(a) {
+ return function (b) {
+ return NAVIGATION_ENTRY_KEYS.every(key => a[key] === b[key]);
+ };
+}
+
+/**
+ * There are some difficulties diagnosing why there are duplicate navigation
+ * entries. We've witnessed several intermittent results:
+ * - duplicate entries have duration = 0
+ * - duplicate entries are the same object reference
+ * - none of the above
+ *
+ * Compare the values of several keys to determine if the entries are duplicates or not.
+ */
+// TODO (high-prio): Figure out wth is returned here
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+function dedupePerformanceEntries(
+ currentList,
+ newList,
+) {
+ // Partition `currentList` into 3 different lists based on entryType
+ const [existingNavigationEntries, existingLcpEntries, existingEntries] = currentList.reduce(
+ (acc, entry) => {
+ if (entry.entryType === 'navigation') {
+ acc[0].push(entry );
+ } else if (entry.entryType === 'largest-contentful-paint') {
+ acc[1].push(entry );
+ } else {
+ acc[2].push(entry);
+ }
+ return acc;
+ },
+ [[], [], []],
+ );
+
+ const newEntries = [];
+ const newNavigationEntries = [];
+ let newLcpEntry = existingLcpEntries.length
+ ? existingLcpEntries[existingLcpEntries.length - 1] // Take the last element as list is sorted
+ : undefined;
+
+ newList.forEach(entry => {
+ if (entry.entryType === 'largest-contentful-paint') {
+ // We want the latest LCP event only
+ if (!newLcpEntry || newLcpEntry.startTime < entry.startTime) {
+ newLcpEntry = entry;
+ }
+ return;
+ }
+
+ if (entry.entryType === 'navigation') {
+ const navigationEntry = entry ;
+
+ // Check if the navigation entry is contained in currentList or newList
+ if (
+ // Ignore any navigation entries with duration 0, as they are likely duplicates
+ entry.duration > 0 &&
+ // Ensure new entry does not already exist in existing entries
+ !existingNavigationEntries.find(isNavigationEntryEqual(navigationEntry)) &&
+ // Ensure new entry does not already exist in new list of navigation entries
+ !newNavigationEntries.find(isNavigationEntryEqual(navigationEntry))
+ ) {
+ newNavigationEntries.push(navigationEntry);
+ }
+
+ // Otherwise this navigation entry is considered a duplicate and is thrown away
+ return;
+ }
+
+ newEntries.push(entry);
+ });
+
+ // Re-combine and sort by startTime
+ return [
+ ...(newLcpEntry ? [newLcpEntry] : []),
+ ...existingNavigationEntries,
+ ...existingEntries,
+ ...newEntries,
+ ...newNavigationEntries,
+ ].sort((a, b) => a.startTime - b.startTime);
+}
+
+/**
+ * Sets up a PerformanceObserver to listen to all performance entry types.
+ */
+function setupPerformanceObserver(replay) {
+ const performanceObserverHandler = (list) => {
+ // For whatever reason the observer was returning duplicate navigation
+ // entries (the other entry types were not duplicated).
+ const newPerformanceEntries = dedupePerformanceEntries(
+ replay.performanceEvents,
+ list.getEntries() ,
+ );
+ replay.performanceEvents = newPerformanceEntries;
+ };
+
+ const performanceObserver = new PerformanceObserver(performanceObserverHandler);
+
+ [
+ 'element',
+ 'event',
+ 'first-input',
+ 'largest-contentful-paint',
+ 'layout-shift',
+ 'longtask',
+ 'navigation',
+ 'paint',
+ 'resource',
+ ].forEach(type => {
+ try {
+ performanceObserver.observe({
+ type,
+ buffered: true,
+ });
+ } catch (e) {
+ // This can throw if an entry type is not supported in the browser.
+ // Ignore these errors.
+ }
+ });
+
+ return performanceObserver;
+}
+
+const r = `/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */
+function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),a=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),i=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),n=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),s=new Array(576);t(s);const r=new Array(60);t(r);const o=new Array(512);t(o);const l=new Array(256);t(l);const h=new Array(29);t(h);const d=new Array(30);function _(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}let f,c,u;function w(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}t(d);const m=t=>t<256?o[t]:o[256+(t>>>7)],b=(t,e)=>{t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},g=(t,e,a)=>{t.bi_valid>16-a?(t.bi_buf|=e<<t.bi_valid&65535,b(t,t.bi_buf),t.bi_buf=e>>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<<t.bi_valid&65535,t.bi_valid+=a)},p=(t,e,a)=>{g(t,a[2*e],a[2*e+1])},k=(t,e)=>{let a=0;do{a|=1&t,t>>>=1,a<<=1}while(--e>0);return a>>>1},v=(t,e,a)=>{const i=new Array(16);let n,s,r=0;for(n=1;n<=15;n++)r=r+a[n-1]<<1,i[n]=r;for(s=0;s<=e;s++){let e=t[2*s+1];0!==e&&(t[2*s]=k(i[e]++,e))}},y=t=>{let e;for(e=0;e<286;e++)t.dyn_ltree[2*e]=0;for(e=0;e<30;e++)t.dyn_dtree[2*e]=0;for(e=0;e<19;e++)t.bl_tree[2*e]=0;t.dyn_ltree[512]=1,t.opt_len=t.static_len=0,t.sym_next=t.matches=0},x=t=>{t.bi_valid>8?b(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},z=(t,e,a,i)=>{const n=2*e,s=2*a;return t[n]<t[s]||t[n]===t[s]&&i[e]<=i[a]},A=(t,e,a)=>{const i=t.heap[a];let n=a<<1;for(;n<=t.heap_len&&(n<t.heap_len&&z(e,t.heap[n+1],t.heap[n],t.depth)&&n++,!z(e,i,t.heap[n],t.depth));)t.heap[a]=t.heap[n],a=n,n<<=1;t.heap[a]=i},E=(t,i,n)=>{let s,r,o,_,f=0;if(0!==t.sym_next)do{s=255&t.pending_buf[t.sym_buf+f++],s+=(255&t.pending_buf[t.sym_buf+f++])<<8,r=t.pending_buf[t.sym_buf+f++],0===s?p(t,r,i):(o=l[r],p(t,o+256+1,i),_=e[o],0!==_&&(r-=h[o],g(t,r,_)),s--,o=m(s),p(t,o,n),_=a[o],0!==_&&(s-=d[o],g(t,s,_)))}while(f<t.sym_next);p(t,256,i)},R=(t,e)=>{const a=e.dyn_tree,i=e.stat_desc.static_tree,n=e.stat_desc.has_stree,s=e.stat_desc.elems;let r,o,l,h=-1;for(t.heap_len=0,t.heap_max=573,r=0;r<s;r++)0!==a[2*r]?(t.heap[++t.heap_len]=h=r,t.depth[r]=0):a[2*r+1]=0;for(;t.heap_len<2;)l=t.heap[++t.heap_len]=h<2?++h:0,a[2*l]=1,t.depth[l]=0,t.opt_len--,n&&(t.static_len-=i[2*l+1]);for(e.max_code=h,r=t.heap_len>>1;r>=1;r--)A(t,a,r);l=s;do{r=t.heap[1],t.heap[1]=t.heap[t.heap_len--],A(t,a,1),o=t.heap[1],t.heap[--t.heap_max]=r,t.heap[--t.heap_max]=o,a[2*l]=a[2*r]+a[2*o],t.depth[l]=(t.depth[r]>=t.depth[o]?t.depth[r]:t.depth[o])+1,a[2*r+1]=a[2*o+1]=l,t.heap[1]=l++,A(t,a,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],((t,e)=>{const a=e.dyn_tree,i=e.max_code,n=e.stat_desc.static_tree,s=e.stat_desc.has_stree,r=e.stat_desc.extra_bits,o=e.stat_desc.extra_base,l=e.stat_desc.max_length;let h,d,_,f,c,u,w=0;for(f=0;f<=15;f++)t.bl_count[f]=0;for(a[2*t.heap[t.heap_max]+1]=0,h=t.heap_max+1;h<573;h++)d=t.heap[h],f=a[2*a[2*d+1]+1]+1,f>l&&(f=l,w++),a[2*d+1]=f,d>i||(t.bl_count[f]++,c=0,d>=o&&(c=r[d-o]),u=a[2*d],t.opt_len+=u*(f+c),s&&(t.static_len+=u*(n[2*d+1]+c)));if(0!==w){do{for(f=l-1;0===t.bl_count[f];)f--;t.bl_count[f]--,t.bl_count[f+1]+=2,t.bl_count[l]--,w-=2}while(w>0);for(f=l;0!==f;f--)for(d=t.bl_count[f];0!==d;)_=t.heap[--h],_>i||(a[2*_+1]!==f&&(t.opt_len+=(f-a[2*_+1])*a[2*_],a[2*_+1]=f),d--)}})(t,e),v(a,h,t.bl_count)},Z=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=r,r=e[2*(i+1)+1],++o<l&&n===r||(o<h?t.bl_tree[2*n]+=o:0!==n?(n!==s&&t.bl_tree[2*n]++,t.bl_tree[32]++):o<=10?t.bl_tree[34]++:t.bl_tree[36]++,o=0,s=n,0===r?(l=138,h=3):n===r?(l=6,h=3):(l=7,h=4))},U=(t,e,a)=>{let i,n,s=-1,r=e[1],o=0,l=7,h=4;for(0===r&&(l=138,h=3),i=0;i<=a;i++)if(n=r,r=e[2*(i+1)+1],!(++o<l&&n===r)){if(o<h)do{p(t,n,t.bl_tree)}while(0!=--o);else 0!==n?(n!==s&&(p(t,n,t.bl_tree),o--),p(t,16,t.bl_tree),g(t,o-3,2)):o<=10?(p(t,17,t.bl_tree),g(t,o-3,3)):(p(t,18,t.bl_tree),g(t,o-11,7));o=0,s=n,0===r?(l=138,h=3):n===r?(l=6,h=3):(l=7,h=4)}};let S=!1;const D=(t,e,a,i)=>{g(t,0+(i?1:0),3),x(t),b(t,a),b(t,~a),a&&t.pending_buf.set(t.window.subarray(e,e+a),t.pending),t.pending+=a};var T=(t,e,a,i)=>{let o,l,h=0;t.level>0?(2===t.strm.data_type&&(t.strm.data_type=(t=>{let e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e<256;e++)if(0!==t.dyn_ltree[2*e])return 1;return 0})(t)),R(t,t.l_desc),R(t,t.d_desc),h=(t=>{let e;for(Z(t,t.dyn_ltree,t.l_desc.max_code),Z(t,t.dyn_dtree,t.d_desc.max_code),R(t,t.bl_desc),e=18;e>=3&&0===t.bl_tree[2*n[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e})(t),o=t.opt_len+3+7>>>3,l=t.static_len+3+7>>>3,l<=o&&(o=l)):o=l=a+5,a+4<=o&&-1!==e?D(t,e,a,i):4===t.strategy||l===o?(g(t,2+(i?1:0),3),E(t,s,r)):(g(t,4+(i?1:0),3),((t,e,a,i)=>{let s;for(g(t,e-257,5),g(t,a-1,5),g(t,i-4,4),s=0;s<i;s++)g(t,t.bl_tree[2*n[s]+1],3);U(t,t.dyn_ltree,e-1),U(t,t.dyn_dtree,a-1)})(t,t.l_desc.max_code+1,t.d_desc.max_code+1,h+1),E(t,t.dyn_ltree,t.dyn_dtree)),y(t),i&&x(t)},O={_tr_init:t=>{S||((()=>{let t,n,w,m,b;const g=new Array(16);for(w=0,m=0;m<28;m++)for(h[m]=w,t=0;t<1<<e[m];t++)l[w++]=m;for(l[w-1]=m,b=0,m=0;m<16;m++)for(d[m]=b,t=0;t<1<<a[m];t++)o[b++]=m;for(b>>=7;m<30;m++)for(d[m]=b<<7,t=0;t<1<<a[m]-7;t++)o[256+b++]=m;for(n=0;n<=15;n++)g[n]=0;for(t=0;t<=143;)s[2*t+1]=8,t++,g[8]++;for(;t<=255;)s[2*t+1]=9,t++,g[9]++;for(;t<=279;)s[2*t+1]=7,t++,g[7]++;for(;t<=287;)s[2*t+1]=8,t++,g[8]++;for(v(s,287,g),t=0;t<30;t++)r[2*t+1]=5,r[2*t]=k(t,5);f=new _(s,e,257,286,15),c=new _(r,a,0,30,15),u=new _(new Array(0),i,0,19,7)})(),S=!0),t.l_desc=new w(t.dyn_ltree,f),t.d_desc=new w(t.dyn_dtree,c),t.bl_desc=new w(t.bl_tree,u),t.bi_buf=0,t.bi_valid=0,y(t)},_tr_stored_block:D,_tr_flush_block:T,_tr_tally:(t,e,a)=>(t.pending_buf[t.sym_buf+t.sym_next++]=e,t.pending_buf[t.sym_buf+t.sym_next++]=e>>8,t.pending_buf[t.sym_buf+t.sym_next++]=a,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(l[a]+256+1)]++,t.dyn_dtree[2*m(e)]++),t.sym_next===t.sym_end),_tr_align:t=>{g(t,2,3),p(t,256,s),(t=>{16===t.bi_valid?(b(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)})(t)}};var F=(t,e,a,i)=>{let n=65535&t|0,s=t>>>16&65535|0,r=0;for(;0!==a;){r=a>2e3?2e3:a,a-=r;do{n=n+e[i++]|0,s=s+n|0}while(--r);n%=65521,s%=65521}return n|s<<16|0};const L=new Uint32Array((()=>{let t,e=[];for(var a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e})());var N=(t,e,a,i)=>{const n=L,s=i+a;t^=-1;for(let a=i;a<s;a++)t=t>>>8^n[255&(t^e[a])];return-1^t},I={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},B={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8};const{_tr_init:C,_tr_stored_block:H,_tr_flush_block:M,_tr_tally:j,_tr_align:K}=O,{Z_NO_FLUSH:P,Z_PARTIAL_FLUSH:Y,Z_FULL_FLUSH:G,Z_FINISH:X,Z_BLOCK:W,Z_OK:q,Z_STREAM_END:J,Z_STREAM_ERROR:Q,Z_DATA_ERROR:V,Z_BUF_ERROR:$,Z_DEFAULT_COMPRESSION:tt,Z_FILTERED:et,Z_HUFFMAN_ONLY:at,Z_RLE:it,Z_FIXED:nt,Z_DEFAULT_STRATEGY:st,Z_UNKNOWN:rt,Z_DEFLATED:ot}=B,lt=(t,e)=>(t.msg=I[e],e),ht=t=>2*t-(t>4?9:0),dt=t=>{let e=t.length;for(;--e>=0;)t[e]=0},_t=t=>{let e,a,i,n=t.w_size;e=t.hash_size,i=e;do{a=t.head[--i],t.head[i]=a>=n?a-n:0}while(--e);e=n,i=e;do{a=t.prev[--i],t.prev[i]=a>=n?a-n:0}while(--e)};let ft=(t,e,a)=>(e<<t.hash_shift^a)&t.hash_mask;const ct=t=>{const e=t.state;let a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},ut=(t,e)=>{M(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,ct(t.strm)},wt=(t,e)=>{t.pending_buf[t.pending++]=e},mt=(t,e)=>{t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},bt=(t,e,a,i)=>{let n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,e.set(t.input.subarray(t.next_in,t.next_in+n),a),1===t.state.wrap?t.adler=F(t.adler,e,n,a):2===t.state.wrap&&(t.adler=N(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)},gt=(t,e)=>{let a,i,n=t.max_chain_length,s=t.strstart,r=t.prev_length,o=t.nice_match;const l=t.strstart>t.w_size-262?t.strstart-(t.w_size-262):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+258;let c=h[s+r-1],u=h[s+r];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(a=e,h[a+r]===u&&h[a+r-1]===c&&h[a]===h[s]&&h[++a]===h[s+1]){s+=2,a++;do{}while(h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&h[++s]===h[++a]&&s<f);if(i=258-(f-s),s=f-258,i>r){if(t.match_start=e,r=i,i>=o)break;c=h[s+r-1],u=h[s+r]}}}while((e=_[e&d])>l&&0!=--n);return r<=t.lookahead?r:t.lookahead},pt=t=>{const e=t.w_size;let a,i,n;do{if(i=t.window_size-t.lookahead-t.strstart,t.strstart>=e+(e-262)&&(t.window.set(t.window.subarray(e,e+e-i),0),t.match_start-=e,t.strstart-=e,t.block_start-=e,t.insert>t.strstart&&(t.insert=t.strstart),_t(t),i+=e),0===t.strm.avail_in)break;if(a=bt(t.strm,t.window,t.strstart+t.lookahead,i),t.lookahead+=a,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=ft(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=ft(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookahead<262&&0!==t.strm.avail_in)},kt=(t,e)=>{let a,i,n,s=t.pending_buf_size-5>t.w_size?t.w_size:t.pending_buf_size-5,r=0,o=t.strm.avail_in;do{if(a=65535,n=t.bi_valid+42>>3,t.strm.avail_out<n)break;if(n=t.strm.avail_out-n,i=t.strstart-t.block_start,a>i+t.strm.avail_in&&(a=i+t.strm.avail_in),a>n&&(a=n),a<s&&(0===a&&e!==X||e===P||a!==i+t.strm.avail_in))break;r=e===X&&a===i+t.strm.avail_in?1:0,H(t,0,0,r),t.pending_buf[t.pending-4]=a,t.pending_buf[t.pending-3]=a>>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,ct(t.strm),i&&(i>a&&(i=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+i),t.strm.next_out),t.strm.next_out+=i,t.strm.avail_out-=i,t.strm.total_out+=i,t.block_start+=i,a-=i),a&&(bt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===r);return o-=t.strm.avail_in,o&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_water<t.strstart&&(t.high_water=t.strstart),r?4:e!==P&&e!==X&&0===t.strm.avail_in&&t.strstart===t.block_start?2:(n=t.window_size-t.strstart,t.strm.avail_in>n&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,n+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),n>t.strm.avail_in&&(n=t.strm.avail_in),n&&(bt(t.strm,t.window,t.strstart,n),t.strstart+=n,t.insert+=n>t.w_size-t.insert?t.w_size-t.insert:n),t.high_water<t.strstart&&(t.high_water=t.strstart),n=t.bi_valid+42>>3,n=t.pending_buf_size-n>65535?65535:t.pending_buf_size-n,s=n>t.w_size?t.w_size:n,i=t.strstart-t.block_start,(i>=s||(i||e===X)&&e!==P&&0===t.strm.avail_in&&i<=n)&&(a=i>n?n:i,r=e===X&&0===t.strm.avail_in&&a===i?1:0,H(t,t.block_start,a,r),t.block_start+=a,ct(t.strm)),r?3:1)},vt=(t,e)=>{let a,i;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a)),t.match_length>=3)if(i=j(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=ft(t,t.ins_h,t.window[t.strstart+1]);else i=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(i&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2},yt=(t,e)=>{let a,i,n;for(;;){if(t.lookahead<262){if(pt(t),t.lookahead<262&&e===P)return 1;if(0===t.lookahead)break}if(a=0,t.lookahead>=3&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length<t.max_lazy_match&&t.strstart-a<=t.w_size-262&&(t.match_length=gt(t,a),t.match_length<=5&&(t.strategy===et||3===t.match_length&&t.strstart-t.match_start>4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-3,i=j(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=ft(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,i&&(ut(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if(i=j(t,0,t.window[t.strstart-1]),i&&ut(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(i=j(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2};function xt(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n}const zt=[new xt(0,0,0,0,kt),new xt(4,4,8,4,vt),new xt(4,5,16,8,vt),new xt(4,6,32,32,vt),new xt(4,4,16,16,yt),new xt(8,16,32,32,yt),new xt(8,16,128,128,yt),new xt(8,32,128,256,yt),new xt(32,128,258,1024,yt),new xt(32,258,258,4096,yt)];function At(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ot,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),dt(this.dyn_ltree),dt(this.dyn_dtree),dt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),dt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),dt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}const Et=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||42!==e.status&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&113!==e.status&&666!==e.status?1:0},Rt=t=>{if(Et(t))return lt(t,Q);t.total_in=t.total_out=0,t.data_type=rt;const e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?42:113,t.adler=2===e.wrap?0:1,e.last_flush=-2,C(e),q},Zt=t=>{const e=Rt(t);var a;return e===q&&((a=t.state).window_size=2*a.w_size,dt(a.head),a.max_lazy_match=zt[a.level].max_lazy,a.good_match=zt[a.level].good_length,a.nice_match=zt[a.level].nice_length,a.max_chain_length=zt[a.level].max_chain,a.strstart=0,a.block_start=0,a.lookahead=0,a.insert=0,a.match_length=a.prev_length=2,a.match_available=0,a.ins_h=0),e},Ut=(t,e,a,i,n,s)=>{if(!t)return Q;let r=1;if(e===tt&&(e=6),i<0?(r=0,i=-i):i>15&&(r=2,i-=16),n<1||n>9||a!==ot||i<8||i>15||e<0||e>9||s<0||s>nt||8===i&&1!==r)return lt(t,Q);8===i&&(i=9);const o=new At;return t.state=o,o.strm=t,o.status=42,o.wrap=r,o.gzhead=null,o.w_bits=i,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=n+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+3-1)/3),o.window=new Uint8Array(2*o.w_size),o.head=new Uint16Array(o.hash_size),o.prev=new Uint16Array(o.w_size),o.lit_bufsize=1<<n+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new Uint8Array(o.pending_buf_size),o.sym_buf=o.lit_bufsize,o.sym_end=3*(o.lit_bufsize-1),o.level=e,o.strategy=s,o.method=a,Zt(t)};var St={deflateInit:(t,e)=>Ut(t,e,ot,15,8,st),deflateInit2:Ut,deflateReset:Zt,deflateResetKeep:Rt,deflateSetHeader:(t,e)=>Et(t)||2!==t.state.wrap?Q:(t.state.gzhead=e,q),deflate:(t,e)=>{if(Et(t)||e>W||e<0)return t?lt(t,Q):Q;const a=t.state;if(!t.output||0!==t.avail_in&&!t.input||666===a.status&&e!==X)return lt(t,0===t.avail_out?$:Q);const i=a.last_flush;if(a.last_flush=e,0!==a.pending){if(ct(t),0===t.avail_out)return a.last_flush=-1,q}else if(0===t.avail_in&&ht(e)<=ht(i)&&e!==X)return lt(t,$);if(666===a.status&&0!==t.avail_in)return lt(t,$);if(42===a.status&&0===a.wrap&&(a.status=113),42===a.status){let e=ot+(a.w_bits-8<<4)<<8,i=-1;if(i=a.strategy>=at||a.level<2?0:a.level<6?1:6===a.level?2:3,e|=i<<6,0!==a.strstart&&(e|=32),e+=31-e%31,mt(a,e),0!==a.strstart&&(mt(a,t.adler>>>16),mt(a,65535&t.adler)),t.adler=1,a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q}if(57===a.status)if(t.adler=0,wt(a,31),wt(a,139),wt(a,8),a.gzhead)wt(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),wt(a,255&a.gzhead.time),wt(a,a.gzhead.time>>8&255),wt(a,a.gzhead.time>>16&255),wt(a,a.gzhead.time>>24&255),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(wt(a,255&a.gzhead.extra.length),wt(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=N(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,0),wt(a,9===a.level?2:a.strategy>=at||a.level<2?4:0),wt(a,3),a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q;if(69===a.status){if(a.gzhead.extra){let e=a.pending,i=(65535&a.gzhead.extra.length)-a.gzindex;for(;a.pending+i>a.pending_buf_size;){let n=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+n),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>e&&(t.adler=N(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex+=n,ct(t),0!==a.pending)return a.last_flush=-1,q;e=0,i-=n}let n=new Uint8Array(a.gzhead.extra);a.pending_buf.set(n.subarray(a.gzindex,a.gzindex+i),a.pending),a.pending+=i,a.gzhead.hcrc&&a.pending>e&&(t.adler=N(t.adler,a.pending_buf,a.pending-e,e)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,q;i=0}e=a.gzindex<a.gzhead.name.length?255&a.gzhead.name.charCodeAt(a.gzindex++):0,wt(a,e)}while(0!==e);a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){let e,i=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i)),ct(t),0!==a.pending)return a.last_flush=-1,q;i=0}e=a.gzindex<a.gzhead.comment.length?255&a.gzhead.comment.charCodeAt(a.gzindex++):0,wt(a,e)}while(0!==e);a.gzhead.hcrc&&a.pending>i&&(t.adler=N(t.adler,a.pending_buf,a.pending-i,i))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(ct(t),0!==a.pending))return a.last_flush=-1,q;wt(a,255&t.adler),wt(a,t.adler>>8&255),t.adler=0}if(a.status=113,ct(t),0!==a.pending)return a.last_flush=-1,q}if(0!==t.avail_in||0!==a.lookahead||e!==P&&666!==a.status){let i=0===a.level?kt(a,e):a.strategy===at?((t,e)=>{let a;for(;;){if(0===t.lookahead&&(pt(t),0===t.lookahead)){if(e===P)return 1;break}if(t.match_length=0,a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):a.strategy===it?((t,e)=>{let a,i,n,s;const r=t.window;for(;;){if(t.lookahead<=258){if(pt(t),t.lookahead<=258&&e===P)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=t.strstart-1,i=r[n],i===r[++n]&&i===r[++n]&&i===r[++n])){s=t.strstart+258;do{}while(i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&i===r[++n]&&n<s);t.match_length=258-(s-n),t.match_length>t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=j(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=j(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(ut(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===X?(ut(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(ut(t,!1),0===t.strm.avail_out)?1:2})(a,e):zt[a.level].func(a,e);if(3!==i&&4!==i||(a.status=666),1===i||3===i)return 0===t.avail_out&&(a.last_flush=-1),q;if(2===i&&(e===Y?K(a):e!==W&&(H(a,0,0,!1),e===G&&(dt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),ct(t),0===t.avail_out))return a.last_flush=-1,q}return e!==X?q:a.wrap<=0?J:(2===a.wrap?(wt(a,255&t.adler),wt(a,t.adler>>8&255),wt(a,t.adler>>16&255),wt(a,t.adler>>24&255),wt(a,255&t.total_in),wt(a,t.total_in>>8&255),wt(a,t.total_in>>16&255),wt(a,t.total_in>>24&255)):(mt(a,t.adler>>>16),mt(a,65535&t.adler)),ct(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?q:J)},deflateEnd:t=>{if(Et(t))return Q;const e=t.state.status;return t.state=null,113===e?lt(t,V):q},deflateSetDictionary:(t,e)=>{let a=e.length;if(Et(t))return Q;const i=t.state,n=i.wrap;if(2===n||1===n&&42!==i.status||i.lookahead)return Q;if(1===n&&(t.adler=F(t.adler,e,a,0)),i.wrap=0,a>=i.w_size){0===n&&(dt(i.head),i.strstart=0,i.block_start=0,i.insert=0);let t=new Uint8Array(i.w_size);t.set(e.subarray(a-i.w_size,a),0),e=t,a=i.w_size}const s=t.avail_in,r=t.next_in,o=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,pt(i);i.lookahead>=3;){let t=i.strstart,e=i.lookahead-2;do{i.ins_h=ft(i,i.ins_h,i.window[t+3-1]),i.prev[t&i.w_mask]=i.head[i.ins_h],i.head[i.ins_h]=t,t++}while(--e);i.strstart=t,i.lookahead=2,pt(i)}return i.strstart+=i.lookahead,i.block_start=i.strstart,i.insert=i.lookahead,i.lookahead=0,i.match_length=i.prev_length=2,i.match_available=0,t.next_in=r,t.input=o,t.avail_in=s,i.wrap=n,q},deflateInfo:"pako deflate (from Nodeca project)"};const Dt=(t,e)=>Object.prototype.hasOwnProperty.call(t,e);var Tt=function(t){const e=Array.prototype.slice.call(arguments,1);for(;e.length;){const a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(const e in a)Dt(a,e)&&(t[e]=a[e])}}return t},Ot=t=>{let e=0;for(let a=0,i=t.length;a<i;a++)e+=t[a].length;const a=new Uint8Array(e);for(let e=0,i=0,n=t.length;e<n;e++){let n=t[e];a.set(n,i),i+=n.length}return a};let Ft=!0;try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(t){Ft=!1}const Lt=new Uint8Array(256);for(let t=0;t<256;t++)Lt[t]=t>=252?6:t>=248?5:t>=240?4:t>=224?3:t>=192?2:1;Lt[254]=Lt[254]=1;var Nt=t=>{if("function"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);let e,a,i,n,s,r=t.length,o=0;for(n=0;n<r;n++)a=t.charCodeAt(n),55296==(64512&a)&&n+1<r&&(i=t.charCodeAt(n+1),56320==(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),n++)),o+=a<128?1:a<2048?2:a<65536?3:4;for(e=new Uint8Array(o),s=0,n=0;s<o;n++)a=t.charCodeAt(n),55296==(64512&a)&&n+1<r&&(i=t.charCodeAt(n+1),56320==(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),n++)),a<128?e[s++]=a:a<2048?(e[s++]=192|a>>>6,e[s++]=128|63&a):a<65536?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},It=(t,e)=>{const a=e||t.length;if("function"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));let i,n;const s=new Array(2*a);for(n=0,i=0;i<a;){let e=t[i++];if(e<128){s[n++]=e;continue}let r=Lt[e];if(r>4)s[n++]=65533,i+=r-1;else{for(e&=2===r?31:3===r?15:7;r>1&&i<a;)e=e<<6|63&t[i++],r--;r>1?s[n++]=65533:e<65536?s[n++]=e:(e-=65536,s[n++]=55296|e>>10&1023,s[n++]=56320|1023&e)}}return((t,e)=>{if(e<65534&&t.subarray&&Ft)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));let a="";for(let i=0;i<e;i++)a+=String.fromCharCode(t[i]);return a})(s,n)},Bt=(t,e)=>{(e=e||t.length)>t.length&&(e=t.length);let a=e-1;for(;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Lt[t[a]]>e?a:e};var Ct=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0};const Ht=Object.prototype.toString,{Z_NO_FLUSH:Mt,Z_SYNC_FLUSH:jt,Z_FULL_FLUSH:Kt,Z_FINISH:Pt,Z_OK:Yt,Z_STREAM_END:Gt,Z_DEFAULT_COMPRESSION:Xt,Z_DEFAULT_STRATEGY:Wt,Z_DEFLATED:qt}=B;function Jt(t){this.options=Tt({level:Xt,method:qt,chunkSize:16384,windowBits:15,memLevel:8,strategy:Wt},t||{});let e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=St.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==Yt)throw new Error(I[a]);if(e.header&&St.deflateSetHeader(this.strm,e.header),e.dictionary){let t;if(t="string"==typeof e.dictionary?Nt(e.dictionary):"[object ArrayBuffer]"===Ht.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,a=St.deflateSetDictionary(this.strm,t),a!==Yt)throw new Error(I[a]);this._dict_set=!0}}function Qt(t,e){const a=new Jt(e);if(a.push(t,!0),a.err)throw a.msg||I[a.err];return a.result}Jt.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize;let n,s;if(this.ended)return!1;for(s=e===~~e?e:!0===e?Pt:Mt,"string"==typeof t?a.input=Nt(t):"[object ArrayBuffer]"===Ht.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;)if(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),(s===jt||s===Kt)&&a.avail_out<=6)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else{if(n=St.deflate(a,s),n===Gt)return a.next_out>0&&this.onData(a.output.subarray(0,a.next_out)),n=St.deflateEnd(this.strm),this.onEnd(n),this.ended=!0,n===Yt;if(0!==a.avail_out){if(s>0&&a.next_out>0)this.onData(a.output.subarray(0,a.next_out)),a.avail_out=0;else if(0===a.avail_in)break}else this.onData(a.output)}return!0},Jt.prototype.onData=function(t){this.chunks.push(t)},Jt.prototype.onEnd=function(t){t===Yt&&(this.result=Ot(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Vt={Deflate:Jt,deflate:Qt,deflateRaw:function(t,e){return(e=e||{}).raw=!0,Qt(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,Qt(t,e)},constants:B};var $t=function(t,e){let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z,A;const E=t.state;a=t.next_in,z=t.input,i=a+(t.avail_in-5),n=t.next_out,A=t.output,s=n-(e-t.avail_out),r=n+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,c=E.bits,u=E.lencode,w=E.distcode,m=(1<<E.lenbits)-1,b=(1<<E.distbits)-1;t:do{c<15&&(f+=z[a++]<<c,c+=8,f+=z[a++]<<c,c+=8),g=u[f&m];e:for(;;){if(p=g>>>24,f>>>=p,c-=p,p=g>>>16&255,0===p)A[n++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=u[(65535&g)+(f&(1<<p)-1)];continue e}if(32&p){E.mode=16191;break t}t.msg="invalid literal/length code",E.mode=16209;break t}k=65535&g,p&=15,p&&(c<p&&(f+=z[a++]<<c,c+=8),k+=f&(1<<p)-1,f>>>=p,c-=p),c<15&&(f+=z[a++]<<c,c+=8,f+=z[a++]<<c,c+=8),g=w[f&b];a:for(;;){if(p=g>>>24,f>>>=p,c-=p,p=g>>>16&255,!(16&p)){if(0==(64&p)){g=w[(65535&g)+(f&(1<<p)-1)];continue a}t.msg="invalid distance code",E.mode=16209;break t}if(v=65535&g,p&=15,c<p&&(f+=z[a++]<<c,c+=8,c<p&&(f+=z[a++]<<c,c+=8)),v+=f&(1<<p)-1,v>o){t.msg="invalid distance too far back",E.mode=16209;break t}if(f>>>=p,c-=p,p=n-s,v>p){if(p=v-p,p>h&&E.sane){t.msg="invalid distance too far back",E.mode=16209;break t}if(y=0,x=_,0===d){if(y+=l-p,p<k){k-=p;do{A[n++]=_[y++]}while(--p);y=n-v,x=A}}else if(d<p){if(y+=l+d-p,p-=d,p<k){k-=p;do{A[n++]=_[y++]}while(--p);if(y=0,d<k){p=d,k-=p;do{A[n++]=_[y++]}while(--p);y=n-v,x=A}}}else if(y+=d-p,p<k){k-=p;do{A[n++]=_[y++]}while(--p);y=n-v,x=A}for(;k>2;)A[n++]=x[y++],A[n++]=x[y++],A[n++]=x[y++],k-=3;k&&(A[n++]=x[y++],k>1&&(A[n++]=x[y++]))}else{y=n-v;do{A[n++]=A[y++],A[n++]=A[y++],A[n++]=A[y++],k-=3}while(k>2);k&&(A[n++]=A[y++],k>1&&(A[n++]=A[y++]))}break}}break}}while(a<i&&n<r);k=c>>3,a-=k,c-=k<<3,f&=(1<<c)-1,t.next_in=a,t.next_out=n,t.avail_in=a<i?i-a+5:5-(a-i),t.avail_out=n<r?r-n+257:257-(n-r),E.hold=f,E.bits=c};const te=new Uint16Array([3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0]),ee=new Uint8Array([16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78]),ae=new Uint16Array([1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0]),ie=new Uint8Array([16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64]);var ne=(t,e,a,i,n,s,r,o)=>{const l=o.bits;let h,d,_,f,c,u,w=0,m=0,b=0,g=0,p=0,k=0,v=0,y=0,x=0,z=0,A=null;const E=new Uint16Array(16),R=new Uint16Array(16);let Z,U,S,D=null;for(w=0;w<=15;w++)E[w]=0;for(m=0;m<i;m++)E[e[a+m]]++;for(p=l,g=15;g>=1&&0===E[g];g--);if(p>g&&(p=g),0===g)return n[s++]=20971520,n[s++]=20971520,o.bits=1,0;for(b=1;b<g&&0===E[b];b++);for(p<b&&(p=b),y=1,w=1;w<=15;w++)if(y<<=1,y-=E[w],y<0)return-1;if(y>0&&(0===t||1!==g))return-1;for(R[1]=0,w=1;w<15;w++)R[w+1]=R[w]+E[w];for(m=0;m<i;m++)0!==e[a+m]&&(r[R[e[a+m]]++]=m);if(0===t?(A=D=r,u=20):1===t?(A=te,D=ee,u=257):(A=ae,D=ie,u=0),z=0,m=0,w=b,c=s,k=p,v=0,_=-1,x=1<<p,f=x-1,1===t&&x>852||2===t&&x>592)return 1;for(;;){Z=w-v,r[m]+1<u?(U=0,S=r[m]):r[m]>=u?(U=D[r[m]-u],S=A[r[m]-u]):(U=96,S=0),h=1<<w-v,d=1<<k,b=d;do{d-=h,n[c+(z>>v)+d]=Z<<24|U<<16|S|0}while(0!==d);for(h=1<<w-1;z&h;)h>>=1;if(0!==h?(z&=h-1,z+=h):z=0,m++,0==--E[w]){if(w===g)break;w=e[a+r[m]]}if(w>p&&(z&f)!==_){for(0===v&&(v=p),c+=b,k=w-v,y=1<<k;k+v<g&&(y-=E[k+v],!(y<=0));)k++,y<<=1;if(x+=1<<k,1===t&&x>852||2===t&&x>592)return 1;_=z&f,n[_]=p<<24|k<<16|c-s|0}}return 0!==z&&(n[c+z]=w-v<<24|64<<16|0),o.bits=p,0};const{Z_FINISH:se,Z_BLOCK:re,Z_TREES:oe,Z_OK:le,Z_STREAM_END:he,Z_NEED_DICT:de,Z_STREAM_ERROR:_e,Z_DATA_ERROR:fe,Z_MEM_ERROR:ce,Z_BUF_ERROR:ue,Z_DEFLATED:we}=B,me=16209,be=t=>(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24);function ge(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}const pe=t=>{if(!t)return 1;const e=t.state;return!e||e.strm!==t||e.mode<16180||e.mode>16211?1:0},ke=t=>{if(pe(t))return _e;const e=t.state;return t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=16180,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,le},ve=t=>{if(pe(t))return _e;const e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,ke(t)},ye=(t,e)=>{let a;if(pe(t))return _e;const i=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?_e:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,ve(t))},xe=(t,e)=>{if(!t)return _e;const a=new ge;t.state=a,a.strm=t,a.window=null,a.mode=16180;const i=ye(t,e);return i!==le&&(t.state=null),i};let ze,Ae,Ee=!0;const Re=t=>{if(Ee){ze=new Int32Array(512),Ae=new Int32Array(32);let e=0;for(;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ne(1,t.lens,0,288,ze,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ne(2,t.lens,0,32,Ae,0,t.work,{bits:5}),Ee=!1}t.lencode=ze,t.lenbits=9,t.distcode=Ae,t.distbits=5},Ze=(t,e,a,i)=>{let n;const s=t.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new Uint8Array(s.wsize)),i>=s.wsize?(s.window.set(e.subarray(a-s.wsize,a),0),s.wnext=0,s.whave=s.wsize):(n=s.wsize-s.wnext,n>i&&(n=i),s.window.set(e.subarray(a-i,a-i+n),s.wnext),(i-=n)?(s.window.set(e.subarray(a-i,a),0),s.wnext=i,s.whave=s.wsize):(s.wnext+=n,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=n))),0};var Ue={inflateReset:ve,inflateReset2:ye,inflateResetKeep:ke,inflateInit:t=>xe(t,15),inflateInit2:xe,inflate:(t,e)=>{let a,i,n,s,r,o,l,h,d,_,f,c,u,w,m,b,g,p,k,v,y,x,z=0;const A=new Uint8Array(4);let E,R;const Z=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);if(pe(t)||!t.output||!t.input&&0!==t.avail_in)return _e;a=t.state,16191===a.mode&&(a.mode=16192),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,_=o,f=l,x=le;t:for(;;)switch(a.mode){case 16180:if(0===a.wrap){a.mode=16192;break}for(;d<16;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(2&a.wrap&&35615===h){0===a.wbits&&(a.wbits=15),a.check=0,A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg="incorrect header check",a.mode=me;break}if((15&h)!==we){t.msg="unknown compression method",a.mode=me;break}if(h>>>=4,d-=4,y=8+(15&h),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg="invalid window size",a.mode=me;break}a.dmax=1<<a.wbits,a.flags=0,t.adler=a.check=1,a.mode=512&h?16189:16191,h=0,d=0;break;case 16181:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(a.flags=h,(255&a.flags)!==we){t.msg="unknown compression method",a.mode=me;break}if(57344&a.flags){t.msg="unknown header flags set",a.mode=me;break}a.head&&(a.head.text=h>>8&1),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.head&&(a.head.time=h),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,A[2]=h>>>16&255,A[3]=h>>>24&255,a.check=N(a.check,A,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.head&&(a.head.xflags=255&h,a.head.os=h>>8),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.length=h,a.head&&(a.head.extra_len=h),512&a.flags&&4&a.wrap&&(A[0]=255&h,A[1]=h>>>8&255,a.check=N(a.check,A,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&(c=a.length,c>o&&(c=o),c&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(i.subarray(s,s+c),y)),512&a.flags&&4&a.wrap&&(a.check=N(a.check,i,c,s)),o-=c,s+=c,a.length-=c),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&c<o);if(512&a.flags&&4&a.wrap&&(a.check=N(a.check,i,c,s)),o-=c,s+=c,y)break t}else a.head&&(a.head.name=null);a.length=0,a.mode=16187;case 16187:if(4096&a.flags){if(0===o)break t;c=0;do{y=i[s+c++],a.head&&y&&a.length<65536&&(a.head.comment+=String.fromCharCode(y))}while(y&&c<o);if(512&a.flags&&4&a.wrap&&(a.check=N(a.check,i,c,s)),o-=c,s+=c,y)break t}else a.head&&(a.head.comment=null);a.mode=16188;case 16188:if(512&a.flags){for(;d<16;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(4&a.wrap&&h!==(65535&a.check)){t.msg="header crc mismatch",a.mode=me;break}h=0,d=0}a.head&&(a.head.hcrc=a.flags>>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=16191;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}t.adler=a.check=be(h),h=0,d=0,a.mode=16190;case 16190:if(0===a.havedict)return t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,de;t.adler=a.check=1,a.mode=16191;case 16191:if(e===re||e===oe)break t;case 16192:if(a.last){h>>>=7&d,d-=7&d,a.mode=16206;break}for(;d<3;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}switch(a.last=1&h,h>>>=1,d-=1,3&h){case 0:a.mode=16193;break;case 1:if(Re(a),a.mode=16199,e===oe){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg="invalid block type",a.mode=me}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if((65535&h)!=(h>>>16^65535)){t.msg="invalid stored block lengths",a.mode=me;break}if(a.length=65535&h,h=0,d=0,a.mode=16194,e===oe)break t;case 16194:a.mode=16195;case 16195:if(c=a.length,c){if(c>o&&(c=o),c>l&&(c=l),0===c)break t;n.set(i.subarray(s,s+c),r),o-=c,s+=c,l-=c,r+=c,a.length-=c;break}a.mode=16191;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(a.nlen=257+(31&h),h>>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=me;break}a.have=0,a.mode=16197;case 16197:for(;a.have<a.ncode;){for(;d<3;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.lens[Z[a.have++]]=7&h,h>>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,E={bits:a.lenbits},x=ne(0,a.lens,0,19,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid code lengths set",a.mode=me;break}a.have=0,a.mode=16198;case 16198:for(;a.have<a.nlen+a.ndist;){for(;z=a.lencode[h&(1<<a.lenbits)-1],m=z>>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(g<16)h>>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(R=m+2;d<R;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(h>>>=m,d-=m,0===a.have){t.msg="invalid bit length repeat",a.mode=me;break}y=a.lens[a.have-1],c=3+(3&h),h>>>=2,d-=2}else if(17===g){for(R=m+3;d<R;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}h>>>=m,d-=m,y=0,c=3+(7&h),h>>>=3,d-=3}else{for(R=m+7;d<R;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}h>>>=m,d-=m,y=0,c=11+(127&h),h>>>=7,d-=7}if(a.have+c>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=me;break}for(;c--;)a.lens[a.have++]=y}}if(a.mode===me)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=me;break}if(a.lenbits=9,E={bits:a.lenbits},x=ne(1,a.lens,0,a.nlen,a.lencode,0,a.work,E),a.lenbits=E.bits,x){t.msg="invalid literal/lengths set",a.mode=me;break}if(a.distbits=6,a.distcode=a.distdyn,E={bits:a.distbits},x=ne(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,E),a.distbits=E.bits,x){t.msg="invalid distances set",a.mode=me;break}if(a.mode=16199,e===oe)break t;case 16199:a.mode=16200;case 16200:if(o>=6&&l>=258){t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,$t(t,f),r=t.next_out,n=t.output,l=t.avail_out,s=t.next_in,i=t.input,o=t.avail_in,h=a.hold,d=a.bits,16191===a.mode&&(a.back=-1);break}for(a.back=0;z=a.lencode[h&(1<<a.lenbits)-1],m=z>>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(b&&0==(240&b)){for(p=m,k=b,v=g;z=a.lencode[v+((h&(1<<p+k)-1)>>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}h>>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=16191;break}if(64&b){t.msg="invalid literal/length code",a.mode=me;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(R=a.extra;d<R;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.length+=h&(1<<a.extra)-1,h>>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;z=a.distcode[h&(1<<a.distbits)-1],m=z>>>24,b=z>>>16&255,g=65535&z,!(m<=d);){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(0==(240&b)){for(p=m,k=b,v=g;z=a.distcode[v+((h&(1<<p+k)-1)>>p)],m=z>>>24,b=z>>>16&255,g=65535&z,!(p+m<=d);){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}h>>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg="invalid distance code",a.mode=me;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(R=a.extra;d<R;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}a.offset+=h&(1<<a.extra)-1,h>>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=me;break}a.mode=16204;case 16204:if(0===l)break t;if(c=f-l,a.offset>c){if(c=a.offset-c,c>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=me;break}c>a.wnext?(c-=a.wnext,u=a.wsize-c):u=a.wnext-c,c>a.length&&(c=a.length),w=a.window}else w=n,u=r-a.offset,c=a.length;c>l&&(c=l),l-=c,a.length-=c;do{n[r++]=w[u++]}while(--c);0===a.length&&(a.mode=16200);break;case 16205:if(0===l)break t;n[r++]=a.length,l--,a.mode=16200;break;case 16206:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=i[s++]<<d,d+=8}if(f-=l,t.total_out+=f,a.total+=f,4&a.wrap&&f&&(t.adler=a.check=a.flags?N(a.check,n,f,r-f):F(a.check,n,f,r-f)),f=l,4&a.wrap&&(a.flags?h:be(h))!==a.check){t.msg="incorrect data check",a.mode=me;break}h=0,d=0}a.mode=16207;case 16207:if(a.wrap&&a.flags){for(;d<32;){if(0===o)break t;o--,h+=i[s++]<<d,d+=8}if(4&a.wrap&&h!==(4294967295&a.total)){t.msg="incorrect length check",a.mode=me;break}h=0,d=0}a.mode=16208;case 16208:x=he;break t;case me:x=fe;break t;case 16210:return ce;default:return _e}return t.next_out=r,t.avail_out=l,t.next_in=s,t.avail_in=o,a.hold=h,a.bits=d,(a.wsize||f!==t.avail_out&&a.mode<me&&(a.mode<16206||e!==se))&&Ze(t,t.output,t.next_out,f-t.avail_out),_-=t.avail_in,f-=t.avail_out,t.total_in+=_,t.total_out+=f,a.total+=f,4&a.wrap&&f&&(t.adler=a.check=a.flags?N(a.check,n,f,t.next_out-f):F(a.check,n,f,t.next_out-f)),t.data_type=a.bits+(a.last?64:0)+(16191===a.mode?128:0)+(16199===a.mode||16194===a.mode?256:0),(0===_&&0===f||e===se)&&x===le&&(x=ue),x},inflateEnd:t=>{if(pe(t))return _e;let e=t.state;return e.window&&(e.window=null),t.state=null,le},inflateGetHeader:(t,e)=>{if(pe(t))return _e;const a=t.state;return 0==(2&a.wrap)?_e:(a.head=e,e.done=!1,le)},inflateSetDictionary:(t,e)=>{const a=e.length;let i,n,s;return pe(t)?_e:(i=t.state,0!==i.wrap&&16190!==i.mode?_e:16190===i.mode&&(n=1,n=F(n,e,a,0),n!==i.check)?fe:(s=Ze(t,e,a,a),s?(i.mode=16210,ce):(i.havedict=1,le)))},inflateInfo:"pako inflate (from Nodeca project)"};var Se=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1};const De=Object.prototype.toString,{Z_NO_FLUSH:Te,Z_FINISH:Oe,Z_OK:Fe,Z_STREAM_END:Le,Z_NEED_DICT:Ne,Z_STREAM_ERROR:Ie,Z_DATA_ERROR:Be,Z_MEM_ERROR:Ce}=B;function He(t){this.options=Tt({chunkSize:65536,windowBits:15,to:""},t||{});const e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new Ct,this.strm.avail_out=0;let a=Ue.inflateInit2(this.strm,e.windowBits);if(a!==Fe)throw new Error(I[a]);if(this.header=new Se,Ue.inflateGetHeader(this.strm,this.header),e.dictionary&&("string"==typeof e.dictionary?e.dictionary=Nt(e.dictionary):"[object ArrayBuffer]"===De.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Ue.inflateSetDictionary(this.strm,e.dictionary),a!==Fe)))throw new Error(I[a])}He.prototype.push=function(t,e){const a=this.strm,i=this.options.chunkSize,n=this.options.dictionary;let s,r,o;if(this.ended)return!1;for(r=e===~~e?e:!0===e?Oe:Te,"[object ArrayBuffer]"===De.call(t)?a.input=new Uint8Array(t):a.input=t,a.next_in=0,a.avail_in=a.input.length;;){for(0===a.avail_out&&(a.output=new Uint8Array(i),a.next_out=0,a.avail_out=i),s=Ue.inflate(a,r),s===Ne&&n&&(s=Ue.inflateSetDictionary(a,n),s===Fe?s=Ue.inflate(a,r):s===Be&&(s=Ne));a.avail_in>0&&s===Le&&a.state.wrap>0&&0!==t[a.next_in];)Ue.inflateReset(a),s=Ue.inflate(a,r);switch(s){case Ie:case Be:case Ne:case Ce:return this.onEnd(s),this.ended=!0,!1}if(o=a.avail_out,a.next_out&&(0===a.avail_out||s===Le))if("string"===this.options.to){let t=Bt(a.output,a.next_out),e=a.next_out-t,n=It(a.output,t);a.next_out=e,a.avail_out=i-e,e&&a.output.set(a.output.subarray(t,t+e),0),this.onData(n)}else this.onData(a.output.length===a.next_out?a.output:a.output.subarray(0,a.next_out));if(s!==Fe||0!==o){if(s===Le)return s=Ue.inflateEnd(this.strm),this.onEnd(s),this.ended=!0,!0;if(0===a.avail_in)break}}return!0},He.prototype.onData=function(t){this.chunks.push(t)},He.prototype.onEnd=function(t){t===Fe&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=Ot(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};const{Deflate:Me,deflate:je,deflateRaw:Ke,gzip:Pe}=Vt;var Ye=Me,Ge=je,Xe=B;const We=new class{constructor(){this._init()}clear(){this._init()}addEvent(t){if(!t)throw new Error("Adding invalid event");const e=this._hasEvents?",":"";this.deflate.push(e+t,Xe.Z_SYNC_FLUSH),this._hasEvents=!0}finish(){if(this.deflate.push("]",Xe.Z_FINISH),this.deflate.err)throw this.deflate.err;const t=this.deflate.result;return this._init(),t}_init(){this._hasEvents=!1,this.deflate=new Ye,this.deflate.push("[",Xe.Z_NO_FLUSH)}},qe={clear:()=>{We.clear()},addEvent:t=>We.addEvent(t),finish:()=>We.finish(),compress:t=>function(t){return Ge(t)}(t)};addEventListener("message",(function(t){const e=t.data.method,a=t.data.id,i=t.data.arg;if(e in qe&&"function"==typeof qe[e])try{const t=qe[e](i);postMessage({id:a,method:e,success:!0,response:t})}catch(t){postMessage({id:a,method:e,success:!1,response:t.message}),console.error(t)}})),postMessage({id:void 0,method:"init",success:!0,response:void 0});`;
+
+function e(){const e=new Blob([r]);return URL.createObjectURL(e)}
+
+/**
+ * Converts a timestamp to ms, if it was in s, or keeps it as ms.
+ */
+function timestampToMs(timestamp) {
+ const isMs = timestamp > 9999999999;
+ return isMs ? timestamp : timestamp * 1000;
+}
+
+/** This error indicates that the event buffer size exceeded the limit.. */
+class EventBufferSizeExceededError extends Error {
+ constructor() {
+ super(`Event buffer exceeded maximum size of ${REPLAY_MAX_EVENT_BUFFER_SIZE}.`);
+ }
+}
+
+/**
+ * A basic event buffer that does not do any compression.
+ * Used as fallback if the compression worker cannot be loaded or is disabled.
+ */
+class EventBufferArray {
+ /** All the events that are buffered to be sent. */
+
+ __init() {this._totalSize = 0;}
+
+ constructor() {EventBufferArray.prototype.__init.call(this);
+ this.events = [];
+ }
+
+ /** @inheritdoc */
+ get hasEvents() {
+ return this.events.length > 0;
+ }
+
+ /** @inheritdoc */
+ get type() {
+ return 'sync';
+ }
+
+ /** @inheritdoc */
+ destroy() {
+ this.events = [];
+ }
+
+ /** @inheritdoc */
+ async addEvent(event) {
+ const eventSize = JSON.stringify(event).length;
+ this._totalSize += eventSize;
+ if (this._totalSize > REPLAY_MAX_EVENT_BUFFER_SIZE) {
+ throw new EventBufferSizeExceededError();
+ }
+
+ this.events.push(event);
+ }
+
+ /** @inheritdoc */
+ finish() {
+ return new Promise(resolve => {
+ // Make a copy of the events array reference and immediately clear the
+ // events member so that we do not lose new events while uploading
+ // attachment.
+ const eventsRet = this.events;
+ this.clear();
+ resolve(JSON.stringify(eventsRet));
+ });
+ }
+
+ /** @inheritdoc */
+ clear() {
+ this.events = [];
+ this._totalSize = 0;
+ }
+
+ /** @inheritdoc */
+ getEarliestTimestamp() {
+ const timestamp = this.events.map(event => event.timestamp).sort()[0];
+
+ if (!timestamp) {
+ return null;
+ }
+
+ return timestampToMs(timestamp);
+ }
+}
+
+/**
+ * Event buffer that uses a web worker to compress events.
+ * Exported only for testing.
+ */
+class WorkerHandler {
+
+ constructor(worker) {
+ this._worker = worker;
+ this._id = 0;
+ }
+
+ /**
+ * Ensure the worker is ready (or not).
+ * This will either resolve when the worker is ready, or reject if an error occured.
+ */
+ ensureReady() {
+ // Ensure we only check once
+ if (this._ensureReadyPromise) {
+ return this._ensureReadyPromise;
+ }
+
+ this._ensureReadyPromise = new Promise((resolve, reject) => {
+ this._worker.addEventListener(
+ 'message',
+ ({ data }) => {
+ if ((data ).success) {
+ resolve();
+ } else {
+ reject();
+ }
+ },
+ { once: true },
+ );
+
+ this._worker.addEventListener(
+ 'error',
+ error => {
+ reject(error);
+ },
+ { once: true },
+ );
+ });
+
+ return this._ensureReadyPromise;
+ }
+
+ /**
+ * Destroy the worker.
+ */
+ destroy() {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Destroying compression worker');
+ this._worker.terminate();
+ }
+
+ /**
+ * Post message to worker and wait for response before resolving promise.
+ */
+ postMessage(method, arg) {
+ const id = this._getAndIncrementId();
+
+ return new Promise((resolve, reject) => {
+ const listener = ({ data }) => {
+ const response = data ;
+ if (response.method !== method) {
+ return;
+ }
+
+ // There can be multiple listeners for a single method, the id ensures
+ // that the response matches the caller.
+ if (response.id !== id) {
+ return;
+ }
+
+ // At this point, we'll always want to remove listener regardless of result status
+ this._worker.removeEventListener('message', listener);
+
+ if (!response.success) {
+ // TODO: Do some error handling, not sure what
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', response.response);
+
+ reject(new Error('Error in compression worker'));
+ return;
+ }
+
+ resolve(response.response );
+ };
+
+ // Note: we can't use `once` option because it's possible it needs to
+ // listen to multiple messages
+ this._worker.addEventListener('message', listener);
+ this._worker.postMessage({ id, method, arg });
+ });
+ }
+
+ /** Get the current ID and increment it for the next call. */
+ _getAndIncrementId() {
+ return this._id++;
+ }
+}
+
+/**
+ * Event buffer that uses a web worker to compress events.
+ * Exported only for testing.
+ */
+class EventBufferCompressionWorker {
+
+ __init() {this._totalSize = 0;}
+
+ constructor(worker) {EventBufferCompressionWorker.prototype.__init.call(this);
+ this._worker = new WorkerHandler(worker);
+ this._earliestTimestamp = null;
+ }
+
+ /** @inheritdoc */
+ get hasEvents() {
+ return !!this._earliestTimestamp;
+ }
+
+ /** @inheritdoc */
+ get type() {
+ return 'worker';
+ }
+
+ /**
+ * Ensure the worker is ready (or not).
+ * This will either resolve when the worker is ready, or reject if an error occured.
+ */
+ ensureReady() {
+ return this._worker.ensureReady();
+ }
+
+ /**
+ * Destroy the event buffer.
+ */
+ destroy() {
+ this._worker.destroy();
+ }
+
+ /**
+ * Add an event to the event buffer.
+ *
+ * Returns true if event was successfuly received and processed by worker.
+ */
+ addEvent(event) {
+ const timestamp = timestampToMs(event.timestamp);
+ if (!this._earliestTimestamp || timestamp < this._earliestTimestamp) {
+ this._earliestTimestamp = timestamp;
+ }
+
+ const data = JSON.stringify(event);
+ this._totalSize += data.length;
+
+ if (this._totalSize > REPLAY_MAX_EVENT_BUFFER_SIZE) {
+ return Promise.reject(new EventBufferSizeExceededError());
+ }
+
+ return this._sendEventToWorker(data);
+ }
+
+ /**
+ * Finish the event buffer and return the compressed data.
+ */
+ finish() {
+ return this._finishRequest();
+ }
+
+ /** @inheritdoc */
+ clear() {
+ this._earliestTimestamp = null;
+ this._totalSize = 0;
+ // We do not wait on this, as we assume the order of messages is consistent for the worker
+ void this._worker.postMessage('clear');
+ }
+
+ /** @inheritdoc */
+ getEarliestTimestamp() {
+ return this._earliestTimestamp;
+ }
+
+ /**
+ * Send the event to the worker.
+ */
+ _sendEventToWorker(data) {
+ return this._worker.postMessage('addEvent', data);
+ }
+
+ /**
+ * Finish the request and return the compressed data from the worker.
+ */
+ async _finishRequest() {
+ const response = await this._worker.postMessage('finish');
+
+ this._earliestTimestamp = null;
+ this._totalSize = 0;
+
+ return response;
+ }
+}
+
+/**
+ * This proxy will try to use the compression worker, and fall back to use the simple buffer if an error occurs there.
+ * This can happen e.g. if the worker cannot be loaded.
+ * Exported only for testing.
+ */
+class EventBufferProxy {
+
+ constructor(worker) {
+ this._fallback = new EventBufferArray();
+ this._compression = new EventBufferCompressionWorker(worker);
+ this._used = this._fallback;
+
+ this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
+ }
+
+ /** @inheritdoc */
+ get type() {
+ return this._used.type;
+ }
+
+ /** @inheritDoc */
+ get hasEvents() {
+ return this._used.hasEvents;
+ }
+
+ /** @inheritDoc */
+ destroy() {
+ this._fallback.destroy();
+ this._compression.destroy();
+ }
+
+ /** @inheritdoc */
+ clear() {
+ return this._used.clear();
+ }
+
+ /** @inheritdoc */
+ getEarliestTimestamp() {
+ return this._used.getEarliestTimestamp();
+ }
+
+ /**
+ * Add an event to the event buffer.
+ *
+ * Returns true if event was successfully added.
+ */
+ addEvent(event) {
+ return this._used.addEvent(event);
+ }
+
+ /** @inheritDoc */
+ async finish() {
+ // Ensure the worker is loaded, so the sent event is compressed
+ await this.ensureWorkerIsLoaded();
+
+ return this._used.finish();
+ }
+
+ /** Ensure the worker has loaded. */
+ ensureWorkerIsLoaded() {
+ return this._ensureWorkerIsLoadedPromise;
+ }
+
+ /** Actually check if the worker has been loaded. */
+ async _ensureWorkerIsLoaded() {
+ try {
+ await this._compression.ensureReady();
+ } catch (error) {
+ // If the worker fails to load, we fall back to the simple buffer.
+ // Nothing more to do from our side here
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Failed to load the compression worker, falling back to simple buffer');
+ return;
+ }
+
+ // Now we need to switch over the array buffer to the compression worker
+ await this._switchToCompressionWorker();
+ }
+
+ /** Switch the used buffer to the compression worker. */
+ async _switchToCompressionWorker() {
+ const { events } = this._fallback;
+
+ const addEventPromises = [];
+ for (const event of events) {
+ addEventPromises.push(this._compression.addEvent(event));
+ }
+
+ // We switch over to the new buffer immediately - any further events will be added
+ // after the previously buffered ones
+ this._used = this._compression;
+
+ // Wait for original events to be re-added before resolving
+ try {
+ await Promise.all(addEventPromises);
+ } catch (error) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('[Replay] Failed to add events when switching buffers.', error);
+ }
+ }
+}
+
+/**
+ * Create an event buffer for replays.
+ */
+function createEventBuffer({ useCompression }) {
+ // eslint-disable-next-line no-restricted-globals
+ if (useCompression && window.Worker) {
+ try {
+ const workerUrl = e();
+
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using compression worker');
+ const worker = new Worker(workerUrl);
+ return new EventBufferProxy(worker);
+ } catch (error) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Failed to create compression worker');
+ // Fall back to use simple event buffer array
+ }
+ }
+
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using simple buffer');
+ return new EventBufferArray();
+}
+
+/** If sessionStorage is available. */
+function hasSessionStorage() {
+ return 'sessionStorage' in WINDOW && !!WINDOW.sessionStorage;
+}
+
+/**
+ * Removes the session from Session Storage and unsets session in replay instance
+ */
+function clearSession(replay) {
+ deleteSession();
+ replay.session = undefined;
+}
+
+/**
+ * Deletes a session from storage
+ */
+function deleteSession() {
+ if (!hasSessionStorage()) {
+ return;
+ }
+
+ try {
+ WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
+ } catch (e) {
+ // Ignore potential SecurityError exceptions
+ }
+}
+
+/**
+ * Given an initial timestamp and an expiry duration, checks to see if current
+ * time should be considered as expired.
+ */
+function isExpired(
+ initialTime,
+ expiry,
+ targetTime = +new Date(),
+) {
+ // Always expired if < 0
+ if (initialTime === null || expiry === undefined || expiry < 0) {
+ return true;
+ }
+
+ // Never expires if == 0
+ if (expiry === 0) {
+ return false;
+ }
+
+ return initialTime + expiry <= targetTime;
+}
+
+/**
+ * Checks to see if session is expired
+ */
+function isSessionExpired(session, timeouts, targetTime = +new Date()) {
+ return (
+ // First, check that maximum session length has not been exceeded
+ isExpired(session.started, timeouts.maxSessionLife, targetTime) ||
+ // check that the idle timeout has not been exceeded (i.e. user has
+ // performed an action within the last `sessionIdleExpire` ms)
+ isExpired(session.lastActivity, timeouts.sessionIdleExpire, targetTime)
+ );
+}
+
+/**
+ * Given a sample rate, returns true if replay should be sampled.
+ *
+ * 1.0 = 100% sampling
+ * 0.0 = 0% sampling
+ */
+function isSampled(sampleRate) {
+ if (sampleRate === undefined) {
+ return false;
+ }
+
+ // Math.random() returns a number in range of 0 to 1 (inclusive of 0, but not 1)
+ return Math.random() < sampleRate;
+}
+
+/**
+ * Save a session to session storage.
+ */
+function saveSession(session) {
+ if (!hasSessionStorage()) {
+ return;
+ }
+
+ try {
+ WINDOW.sessionStorage.setItem(REPLAY_SESSION_KEY, JSON.stringify(session));
+ } catch (e) {
+ // Ignore potential SecurityError exceptions
+ }
+}
+
+/**
+ * Get a session with defaults & applied sampling.
+ */
+function makeSession(session) {
+ const now = Date.now();
+ const id = session.id || uuid4();
+ // Note that this means we cannot set a started/lastActivity of `0`, but this should not be relevant outside of tests.
+ const started = session.started || now;
+ const lastActivity = session.lastActivity || now;
+ const segmentId = session.segmentId || 0;
+ const sampled = session.sampled;
+
+ return {
+ id,
+ started,
+ lastActivity,
+ segmentId,
+ sampled,
+ shouldRefresh: true,
+ };
+}
+
+/**
+ * Get the sampled status for a session based on sample rates & current sampled status.
+ */
+function getSessionSampleType(sessionSampleRate, allowBuffering) {
+ return isSampled(sessionSampleRate) ? 'session' : allowBuffering ? 'buffer' : false;
+}
+
+/**
+ * Create a new session, which in its current implementation is a Sentry event
+ * that all replays will be saved to as attachments. Currently, we only expect
+ * one of these Sentry events per "replay session".
+ */
+function createSession({ sessionSampleRate, allowBuffering, stickySession = false }) {
+ const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
+ const session = makeSession({
+ sampled,
+ });
+
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Replay] Creating new session: ${session.id}`);
+
+ if (stickySession) {
+ saveSession(session);
+ }
+
+ return session;
+}
+
+/**
+ * Fetches a session from storage
+ */
+function fetchSession() {
+ if (!hasSessionStorage()) {
+ return null;
+ }
+
+ try {
+ // This can throw if cookies are disabled
+ const sessionStringFromStorage = WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY);
+
+ if (!sessionStringFromStorage) {
+ return null;
+ }
+
+ const sessionObj = JSON.parse(sessionStringFromStorage) ;
+
+ return makeSession(sessionObj);
+ } catch (e) {
+ return null;
+ }
+}
+
+/**
+ * Get or create a session
+ */
+function getSession({
+ timeouts,
+ currentSession,
+ stickySession,
+ sessionSampleRate,
+ allowBuffering,
+}) {
+ // If session exists and is passed, use it instead of always hitting session storage
+ const session = currentSession || (stickySession && fetchSession());
+
+ if (session) {
+ // If there is a session, check if it is valid (e.g. "last activity" time
+ // should be within the "session idle time", and "session started" time is
+ // within "max session time").
+ const isExpired = isSessionExpired(session, timeouts);
+
+ if (!isExpired || (allowBuffering && session.shouldRefresh)) {
+ return { type: 'saved', session };
+ } else if (!session.shouldRefresh) {
+ // This is the case if we have an error session that is completed
+ // (=triggered an error). Session will continue as session-based replay,
+ // and when this session is expired, it will not be renewed until user
+ // reloads.
+ const discardedSession = makeSession({ sampled: false });
+ return { type: 'new', session: discardedSession };
+ } else {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Session has expired');
+ }
+ // Otherwise continue to create a new session
+ }
+
+ const newSession = createSession({
+ stickySession,
+ sessionSampleRate,
+ allowBuffering,
+ });
+
+ return { type: 'new', session: newSession };
+}
+
+function isCustomEvent(event) {
+ return event.type === EventType.Custom;
+}
+
+/**
+ * Add an event to the event buffer.
+ * `isCheckout` is true if this is either the very first event, or an event triggered by `checkoutEveryNms`.
+ */
+async function addEvent(
+ replay,
+ event,
+ isCheckout,
+) {
+ if (!replay.eventBuffer) {
+ // This implies that `_isEnabled` is false
+ return null;
+ }
+
+ if (replay.isPaused()) {
+ // Do not add to event buffer when recording is paused
+ return null;
+ }
+
+ const timestampInMs = timestampToMs(event.timestamp);
+
+ // Throw out events that happen more than 5 minutes ago. This can happen if
+ // page has been left open and idle for a long period of time and user
+ // comes back to trigger a new session. The performance entries rely on
+ // `performance.timeOrigin`, which is when the page first opened.
+ if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
+ return null;
+ }
+
+ try {
+ if (isCheckout) {
+ replay.eventBuffer.clear();
+ }
+
+ const replayOptions = replay.getOptions();
+
+ const eventAfterPossibleCallback =
+ typeof replayOptions.beforeAddRecordingEvent === 'function' && isCustomEvent(event)
+ ? replayOptions.beforeAddRecordingEvent(event)
+ : event;
+
+ if (!eventAfterPossibleCallback) {
+ return;
+ }
+
+ return await replay.eventBuffer.addEvent(eventAfterPossibleCallback);
+ } catch (error) {
+ const reason = error && error instanceof EventBufferSizeExceededError ? 'addEventSizeExceeded' : 'addEvent';
+
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
+ await replay.stop(reason);
+
+ const client = getCurrentHub().getClient();
+
+ if (client) {
+ client.recordDroppedEvent('internal_sdk_error', 'replay');
+ }
+ }
+}
+
+/** If the event is an error event */
+function isErrorEvent(event) {
+ return !event.type;
+}
+
+/** If the event is a transaction event */
+function isTransactionEvent(event) {
+ return event.type === 'transaction';
+}
+
+/** If the event is an replay event */
+function isReplayEvent(event) {
+ return event.type === 'replay_event';
+}
+
+/**
+ * Returns a listener to be added to `client.on('afterSendErrorEvent, listener)`.
+ */
+function handleAfterSendEvent(replay) {
+ // Custom transports may still be returning `Promise<void>`, which means we cannot expect the status code to be available there
+ // TODO (v8): remove this check as it will no longer be necessary
+ const enforceStatusCode = isBaseTransportSend();
+
+ return (event, sendResponse) => {
+ if (!isErrorEvent(event) && !isTransactionEvent(event)) {
+ return;
+ }
+
+ const statusCode = sendResponse && sendResponse.statusCode;
+
+ // We only want to do stuff on successful error sending, otherwise you get error replays without errors attached
+ // If not using the base transport, we allow `undefined` response (as a custom transport may not implement this correctly yet)
+ // If we do use the base transport, we skip if we encountered an non-OK status code
+ if (enforceStatusCode && (!statusCode || statusCode < 200 || statusCode >= 300)) {
+ return;
+ }
+
+ // Collect traceIds in _context regardless of `recordingMode`
+ // In error mode, _context gets cleared on every checkout
+ if (isTransactionEvent(event) && event.contexts && event.contexts.trace && event.contexts.trace.trace_id) {
+ replay.getContext().traceIds.add(event.contexts.trace.trace_id );
+ return;
+ }
+
+ // Everything below is just for error events
+ if (!isErrorEvent(event)) {
+ return;
+ }
+
+ // Add error to list of errorIds of replay. This is ok to do even if not
+ // sampled because context will get reset at next checkout.
+ // XXX: There is also a race condition where it's possible to capture an
+ // error to Sentry before Replay SDK has loaded, but response returns after
+ // it was loaded, and this gets called.
+ if (event.event_id) {
+ replay.getContext().errorIds.add(event.event_id);
+ }
+
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
+ // Need to be very careful that this does not cause an infinite loop
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
+ setTimeout(() => {
+ // Capture current event buffer as new replay
+ void replay.sendBufferedReplayOrFlush();
+ });
+ }
+ };
+}
+
+function isBaseTransportSend() {
+ const client = getCurrentHub().getClient();
+ if (!client) {
+ return false;
+ }
+
+ const transport = client.getTransport();
+ if (!transport) {
+ return false;
+ }
+
+ return (
+ (transport.send ).__sentry__baseTransport__ || false
+ );
+}
+
+/**
+ * Returns true if we think the given event is an error originating inside of rrweb.
+ */
+function isRrwebError(event, hint) {
+ if (event.type || !event.exception || !event.exception.values || !event.exception.values.length) {
+ return false;
+ }
+
+ // @ts-ignore this may be set by rrweb when it finds errors
+ if (hint.originalException && hint.originalException.__rrweb__) {
+ return true;
+ }
+
+ // Check if any exception originates from rrweb
+ return event.exception.values.some(exception => {
+ if (!exception.stacktrace || !exception.stacktrace.frames || !exception.stacktrace.frames.length) {
+ return false;
+ }
+
+ return exception.stacktrace.frames.some(frame => frame.filename && frame.filename.includes('/rrweb/src/'));
+ });
+}
+
+/**
+ * Determine if event should be sampled (only applies in buffer mode).
+ * When an event is captured by `hanldleGlobalEvent`, when in buffer mode
+ * we determine if we want to sample the error or not.
+ */
+function shouldSampleForBufferEvent(replay, event) {
+ if (replay.recordingMode !== 'buffer') {
+ return false;
+ }
+
+ // ignore this error because otherwise we could loop indefinitely with
+ // trying to capture replay and failing
+ if (event.message === UNABLE_TO_SEND_REPLAY) {
+ return false;
+ }
+
+ // Require the event to be an error event & to have an exception
+ if (!event.exception || event.type) {
+ return false;
+ }
+
+ return isSampled(replay.getOptions().errorSampleRate);
+}
+
+/**
+ * Returns a listener to be added to `addGlobalEventProcessor(listener)`.
+ */
+function handleGlobalEventListener(
+ replay,
+ includeAfterSendEventHandling = false,
+) {
+ const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined;
+
+ return (event, hint) => {
+ if (isReplayEvent(event)) {
+ // Replays have separate set of breadcrumbs, do not include breadcrumbs
+ // from core SDK
+ delete event.breadcrumbs;
+ return event;
+ }
+
+ // We only want to handle errors & transactions, nothing else
+ if (!isErrorEvent(event) && !isTransactionEvent(event)) {
+ return event;
+ }
+
+ // Unless `captureExceptions` is enabled, we want to ignore errors coming from rrweb
+ // As there can be a bunch of stuff going wrong in internals there, that we don't want to bubble up to users
+ if (isRrwebError(event, hint) && !replay.getOptions()._experiments.captureExceptions) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Ignoring error from rrweb internals', event);
+ return null;
+ }
+
+ // When in buffer mode, we decide to sample here.
+ // Later, in `handleAfterSendEvent`, if the replayId is set, we know that we sampled
+ // And convert the buffer session to a full session
+ const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
+
+ // Tag errors if it has been sampled in buffer mode, or if it is session mode
+ // Only tag transactions if in session mode
+ const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === 'session';
+
+ if (shouldTagReplayId) {
+ event.tags = { ...event.tags, replayId: replay.getSessionId() };
+ }
+
+ // In cases where a custom client is used that does not support the new hooks (yet),
+ // we manually call this hook method here
+ if (afterSendHandler) {
+ // Pretend the error had a 200 response so we always capture it
+ afterSendHandler(event, { statusCode: 200 });
+ }
+
+ return event;
+ };
+}
+
+/**
+ * Create a "span" for each performance entry.
+ */
+function createPerformanceSpans(
+ replay,
+ entries,
+) {
+ return entries.map(({ type, start, end, name, data }) => {
+ const response = replay.throttledAddEvent({
+ type: EventType.Custom,
+ timestamp: start,
+ data: {
+ tag: 'performanceSpan',
+ payload: {
+ op: type,
+ description: name,
+ startTimestamp: start,
+ endTimestamp: end,
+ data,
+ },
+ },
+ });
+
+ // If response is a string, it means its either THROTTLED or SKIPPED
+ return typeof response === 'string' ? Promise.resolve(null) : response;
+ });
+}
+
+function handleHistory(handlerData) {
+ const { from, to } = handlerData;
+
+ const now = Date.now() / 1000;
+
+ return {
+ type: 'navigation.push',
+ start: now,
+ end: now,
+ name: to,
+ data: {
+ previous: from,
+ },
+ };
+}
+
+/**
+ * Returns a listener to be added to `addInstrumentationHandler('history', listener)`.
+ */
+function handleHistorySpanListener(replay) {
+ return (handlerData) => {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ const result = handleHistory(handlerData);
+
+ if (result === null) {
+ return;
+ }
+
+ // Need to collect visited URLs
+ replay.getContext().urls.push(result.name);
+ replay.triggerUserActivity();
+
+ replay.addUpdate(() => {
+ createPerformanceSpans(replay, [result]);
+ // Returning false to flush
+ return false;
+ });
+ };
+}
+
+/**
+ * Check whether a given request URL should be filtered out. This is so we
+ * don't log Sentry ingest requests.
+ */
+function shouldFilterRequest(replay, url) {
+ // If we enabled the `traceInternals` experiment, we want to trace everything
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && replay.getOptions()._experiments.traceInternals) {
+ return false;
+ }
+
+ return _isSentryRequest(url);
+}
+
+/**
+ * Checks wether a given URL belongs to the configured Sentry DSN.
+ */
+function _isSentryRequest(url) {
+ const client = getCurrentHub().getClient();
+ const dsn = client && client.getDsn();
+ return dsn ? url.includes(dsn.host) : false;
+}
+
+/** Add a performance entry breadcrumb */
+function addNetworkBreadcrumb(
+ replay,
+ result,
+) {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ if (result === null) {
+ return;
+ }
+
+ if (shouldFilterRequest(replay, result.name)) {
+ return;
+ }
+
+ replay.addUpdate(() => {
+ createPerformanceSpans(replay, [result]);
+ // Returning true will cause `addUpdate` to not flush
+ // We do not want network requests to cause a flush. This will prevent
+ // recurring/polling requests from keeping the replay session alive.
+ return true;
+ });
+}
+
+/** only exported for tests */
+function handleFetch(handlerData) {
+ const { startTimestamp, endTimestamp, fetchData, response } = handlerData;
+
+ if (!endTimestamp) {
+ return null;
+ }
+
+ // This is only used as a fallback, so we know the body sizes are never set here
+ const { method, url } = fetchData;
+
+ return {
+ type: 'resource.fetch',
+ start: startTimestamp / 1000,
+ end: endTimestamp / 1000,
+ name: url,
+ data: {
+ method,
+ statusCode: response ? (response ).status : undefined,
+ },
+ };
+}
+
+/**
+ * Returns a listener to be added to `addInstrumentationHandler('fetch', listener)`.
+ */
+function handleFetchSpanListener(replay) {
+ return (handlerData) => {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ const result = handleFetch(handlerData);
+
+ addNetworkBreadcrumb(replay, result);
+ };
+}
+
+/** only exported for tests */
+function handleXhr(handlerData) {
+ const { startTimestamp, endTimestamp, xhr } = handlerData;
+
+ const sentryXhrData = xhr[SENTRY_XHR_DATA_KEY];
+
+ if (!startTimestamp || !endTimestamp || !sentryXhrData) {
+ return null;
+ }
+
+ // This is only used as a fallback, so we know the body sizes are never set here
+ const { method, url, status_code: statusCode } = sentryXhrData;
+
+ if (url === undefined) {
+ return null;
+ }
+
+ return {
+ type: 'resource.xhr',
+ name: url,
+ start: startTimestamp / 1000,
+ end: endTimestamp / 1000,
+ data: {
+ method,
+ statusCode,
+ },
+ };
+}
+
+/**
+ * Returns a listener to be added to `addInstrumentationHandler('xhr', listener)`.
+ */
+function handleXhrSpanListener(replay) {
+ return (handlerData) => {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ const result = handleXhr(handlerData);
+
+ addNetworkBreadcrumb(replay, result);
+ };
+}
+
+const OBJ = 10;
+const OBJ_KEY = 11;
+const OBJ_KEY_STR = 12;
+const OBJ_VAL = 13;
+const OBJ_VAL_STR = 14;
+const OBJ_VAL_COMPLETED = 15;
+
+const ARR = 20;
+const ARR_VAL = 21;
+const ARR_VAL_STR = 22;
+const ARR_VAL_COMPLETED = 23;
+
+const ALLOWED_PRIMITIVES = ['true', 'false', 'null'];
+
+/**
+ * Complete an incomplete JSON string.
+ * This will ensure that the last element always has a `"~~"` to indicate it was truncated.
+ * For example, `[1,2,` will be completed to `[1,2,"~~"]`
+ * and `{"aa":"b` will be completed to `{"aa":"b~~"}`
+ */
+function completeJson(incompleteJson, stack) {
+ if (!stack.length) {
+ return incompleteJson;
+ }
+
+ let json = incompleteJson;
+
+ // Most checks are only needed for the last step in the stack
+ const lastPos = stack.length - 1;
+ const lastStep = stack[lastPos];
+
+ json = _fixLastStep(json, lastStep);
+
+ // Complete remaining steps - just add closing brackets
+ for (let i = lastPos; i >= 0; i--) {
+ const step = stack[i];
+
+ switch (step) {
+ case OBJ:
+ json = `${json}}`;
+ break;
+ case ARR:
+ json = `${json}]`;
+ break;
+ }
+ }
+
+ return json;
+}
+
+function _fixLastStep(json, lastStep) {
+ switch (lastStep) {
+ // Object cases
+ case OBJ:
+ return `${json}"~~":"~~"`;
+ case OBJ_KEY:
+ return `${json}:"~~"`;
+ case OBJ_KEY_STR:
+ return `${json}~~":"~~"`;
+ case OBJ_VAL:
+ return _maybeFixIncompleteObjValue(json);
+ case OBJ_VAL_STR:
+ return `${json}~~"`;
+ case OBJ_VAL_COMPLETED:
+ return `${json},"~~":"~~"`;
+
+ // Array cases
+ case ARR:
+ return `${json}"~~"`;
+ case ARR_VAL:
+ return _maybeFixIncompleteArrValue(json);
+ case ARR_VAL_STR:
+ return `${json}~~"`;
+ case ARR_VAL_COMPLETED:
+ return `${json},"~~"`;
+ }
+
+ return json;
+}
+
+function _maybeFixIncompleteArrValue(json) {
+ const pos = _findLastArrayDelimiter(json);
+
+ if (pos > -1) {
+ const part = json.slice(pos + 1);
+
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
+ return `${json},"~~"`;
+ }
+
+ // Everything else is replaced with `"~~"`
+ return `${json.slice(0, pos + 1)}"~~"`;
+ }
+
+ // fallback, this shouldn't happen, to be save
+ return json;
+}
+
+function _findLastArrayDelimiter(json) {
+ for (let i = json.length - 1; i >= 0; i--) {
+ const char = json[i];
+
+ if (char === ',' || char === '[') {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+function _maybeFixIncompleteObjValue(json) {
+ const startPos = json.lastIndexOf(':');
+
+ const part = json.slice(startPos + 1);
+
+ if (ALLOWED_PRIMITIVES.includes(part.trim())) {
+ return `${json},"~~":"~~"`;
+ }
+
+ // Everything else is replaced with `"~~"`
+ // This also means we do not have incomplete numbers, e.g `[1` is replaced with `["~~"]`
+ return `${json.slice(0, startPos + 1)}"~~"`;
+}
+
+/**
+ * Evaluate an (incomplete) JSON string.
+ */
+function evaluateJson(json) {
+ const stack = [];
+
+ for (let pos = 0; pos < json.length; pos++) {
+ _evaluateJsonPos(stack, json, pos);
+ }
+
+ return stack;
+}
+
+function _evaluateJsonPos(stack, json, pos) {
+ const curStep = stack[stack.length - 1];
+
+ const char = json[pos];
+
+ const whitespaceRegex = /\s/;
+
+ if (whitespaceRegex.test(char)) {
+ return;
+ }
+
+ if (char === '"' && !_isEscaped(json, pos)) {
+ _handleQuote(stack, curStep);
+ return;
+ }
+
+ switch (char) {
+ case '{':
+ _handleObj(stack, curStep);
+ break;
+ case '[':
+ _handleArr(stack, curStep);
+ break;
+ case ':':
+ _handleColon(stack, curStep);
+ break;
+ case ',':
+ _handleComma(stack, curStep);
+ break;
+ case '}':
+ _handleObjClose(stack, curStep);
+ break;
+ case ']':
+ _handleArrClose(stack, curStep);
+ break;
+ }
+}
+
+function _handleQuote(stack, curStep) {
+ // End of obj value
+ if (curStep === OBJ_VAL_STR) {
+ stack.pop();
+ stack.push(OBJ_VAL_COMPLETED);
+ return;
+ }
+
+ // End of arr value
+ if (curStep === ARR_VAL_STR) {
+ stack.pop();
+ stack.push(ARR_VAL_COMPLETED);
+ return;
+ }
+
+ // Start of obj value
+ if (curStep === OBJ_VAL) {
+ stack.push(OBJ_VAL_STR);
+ return;
+ }
+
+ // Start of arr value
+ if (curStep === ARR_VAL) {
+ stack.push(ARR_VAL_STR);
+ return;
+ }
+
+ // Start of obj key
+ if (curStep === OBJ) {
+ stack.push(OBJ_KEY_STR);
+ return;
+ }
+
+ // End of obj key
+ if (curStep === OBJ_KEY_STR) {
+ stack.pop();
+ stack.push(OBJ_KEY);
+ return;
+ }
+}
+
+function _handleObj(stack, curStep) {
+ // Initial object
+ if (!curStep) {
+ stack.push(OBJ);
+ return;
+ }
+
+ // New object as obj value
+ if (curStep === OBJ_VAL) {
+ stack.push(OBJ);
+ return;
+ }
+
+ // New object as array element
+ if (curStep === ARR_VAL) {
+ stack.push(OBJ);
+ }
+
+ // New object as first array element
+ if (curStep === ARR) {
+ stack.push(OBJ);
+ return;
+ }
+}
+
+function _handleArr(stack, curStep) {
+ // Initial array
+ if (!curStep) {
+ stack.push(ARR);
+ stack.push(ARR_VAL);
+ return;
+ }
+
+ // New array as obj value
+ if (curStep === OBJ_VAL) {
+ stack.push(ARR);
+ stack.push(ARR_VAL);
+ return;
+ }
+
+ // New array as array element
+ if (curStep === ARR_VAL) {
+ stack.push(ARR);
+ stack.push(ARR_VAL);
+ }
+
+ // New array as first array element
+ if (curStep === ARR) {
+ stack.push(ARR);
+ stack.push(ARR_VAL);
+ return;
+ }
+}
+
+function _handleColon(stack, curStep) {
+ if (curStep === OBJ_KEY) {
+ stack.pop();
+ stack.push(OBJ_VAL);
+ }
+}
+
+function _handleComma(stack, curStep) {
+ // Comma after obj value
+ if (curStep === OBJ_VAL) {
+ stack.pop();
+ return;
+ }
+ if (curStep === OBJ_VAL_COMPLETED) {
+ // Pop OBJ_VAL_COMPLETED & OBJ_VAL
+ stack.pop();
+ stack.pop();
+ return;
+ }
+
+ // Comma after arr value
+ if (curStep === ARR_VAL) {
+ // do nothing - basically we'd pop ARR_VAL but add it right back
+ return;
+ }
+
+ if (curStep === ARR_VAL_COMPLETED) {
+ // Pop ARR_VAL_COMPLETED
+ stack.pop();
+
+ // basically we'd pop ARR_VAL but add it right back
+ return;
+ }
+}
+
+function _handleObjClose(stack, curStep) {
+ // Empty object {}
+ if (curStep === OBJ) {
+ stack.pop();
+ }
+
+ // Object with element
+ if (curStep === OBJ_VAL) {
+ // Pop OBJ_VAL, OBJ
+ stack.pop();
+ stack.pop();
+ }
+
+ // Obj with element
+ if (curStep === OBJ_VAL_COMPLETED) {
+ // Pop OBJ_VAL_COMPLETED, OBJ_VAL, OBJ
+ stack.pop();
+ stack.pop();
+ stack.pop();
+ }
+
+ // if was obj value, complete it
+ if (stack[stack.length - 1] === OBJ_VAL) {
+ stack.push(OBJ_VAL_COMPLETED);
+ }
+
+ // if was arr value, complete it
+ if (stack[stack.length - 1] === ARR_VAL) {
+ stack.push(ARR_VAL_COMPLETED);
+ }
+}
+
+function _handleArrClose(stack, curStep) {
+ // Empty array []
+ if (curStep === ARR) {
+ stack.pop();
+ }
+
+ // Array with element
+ if (curStep === ARR_VAL) {
+ // Pop ARR_VAL, ARR
+ stack.pop();
+ stack.pop();
+ }
+
+ // Array with element
+ if (curStep === ARR_VAL_COMPLETED) {
+ // Pop ARR_VAL_COMPLETED, ARR_VAL, ARR
+ stack.pop();
+ stack.pop();
+ stack.pop();
+ }
+
+ // if was obj value, complete it
+ if (stack[stack.length - 1] === OBJ_VAL) {
+ stack.push(OBJ_VAL_COMPLETED);
+ }
+
+ // if was arr value, complete it
+ if (stack[stack.length - 1] === ARR_VAL) {
+ stack.push(ARR_VAL_COMPLETED);
+ }
+}
+
+function _isEscaped(str, pos) {
+ const previousChar = str[pos - 1];
+
+ return previousChar === '\\' && !_isEscaped(str, pos - 1);
+}
+
+/* eslint-disable max-lines */
+
+/**
+ * Takes an incomplete JSON string, and returns a hopefully valid JSON string.
+ * Note that this _can_ fail, so you should check the return value is valid JSON.
+ */
+function fixJson(incompleteJson) {
+ const stack = evaluateJson(incompleteJson);
+
+ return completeJson(incompleteJson, stack);
+}
+
+/** Get the size of a body. */
+function getBodySize(
+ body,
+ textEncoder,
+) {
+ if (!body) {
+ return undefined;
+ }
+
+ try {
+ if (typeof body === 'string') {
+ return textEncoder.encode(body).length;
+ }
+
+ if (body instanceof URLSearchParams) {
+ return textEncoder.encode(body.toString()).length;
+ }
+
+ if (body instanceof FormData) {
+ const formDataStr = _serializeFormData(body);
+ return textEncoder.encode(formDataStr).length;
+ }
+
+ if (body instanceof Blob) {
+ return body.size;
+ }
+
+ if (body instanceof ArrayBuffer) {
+ return body.byteLength;
+ }
+
+ // Currently unhandled types: ArrayBufferView, ReadableStream
+ } catch (e) {
+ // just return undefined
+ }
+
+ return undefined;
+}
+
+/** Convert a Content-Length header to number/undefined. */
+function parseContentLengthHeader(header) {
+ if (!header) {
+ return undefined;
+ }
+
+ const size = parseInt(header, 10);
+ return isNaN(size) ? undefined : size;
+}
+
+/** Get the string representation of a body. */
+function getBodyString(body) {
+ if (typeof body === 'string') {
+ return body;
+ }
+
+ if (body instanceof URLSearchParams) {
+ return body.toString();
+ }
+
+ if (body instanceof FormData) {
+ return _serializeFormData(body);
+ }
+
+ return undefined;
+}
+
+/** Convert ReplayNetworkRequestData to a PerformanceEntry. */
+function makeNetworkReplayBreadcrumb(
+ type,
+ data,
+) {
+ if (!data) {
+ return null;
+ }
+
+ const { startTimestamp, endTimestamp, url, method, statusCode, request, response } = data;
+
+ const result = {
+ type,
+ start: startTimestamp / 1000,
+ end: endTimestamp / 1000,
+ name: url,
+ data: dropUndefinedKeys({
+ method,
+ statusCode,
+ request,
+ response,
+ }),
+ };
+
+ return result;
+}
+
+/** Build the request or response part of a replay network breadcrumb that was skipped. */
+function buildSkippedNetworkRequestOrResponse(bodySize) {
+ return {
+ headers: {},
+ size: bodySize,
+ _meta: {
+ warnings: ['URL_SKIPPED'],
+ },
+ };
+}
+
+/** Build the request or response part of a replay network breadcrumb. */
+function buildNetworkRequestOrResponse(
+ headers,
+ bodySize,
+ body,
+) {
+ if (!bodySize && Object.keys(headers).length === 0) {
+ return undefined;
+ }
+
+ if (!bodySize) {
+ return {
+ headers,
+ };
+ }
+
+ if (!body) {
+ return {
+ headers,
+ size: bodySize,
+ };
+ }
+
+ const info = {
+ headers,
+ size: bodySize,
+ };
+
+ const { body: normalizedBody, warnings } = normalizeNetworkBody(body);
+ info.body = normalizedBody;
+ if (warnings.length > 0) {
+ info._meta = {
+ warnings,
+ };
+ }
+
+ return info;
+}
+
+/** Filter a set of headers */
+function getAllowedHeaders(headers, allowedHeaders) {
+ return Object.keys(headers).reduce((filteredHeaders, key) => {
+ const normalizedKey = key.toLowerCase();
+ // Avoid putting empty strings into the headers
+ if (allowedHeaders.includes(normalizedKey) && headers[key]) {
+ filteredHeaders[normalizedKey] = headers[key];
+ }
+ return filteredHeaders;
+ }, {});
+}
+
+function _serializeFormData(formData) {
+ // This is a bit simplified, but gives us a decent estimate
+ // This converts e.g. { name: 'Anne Smith', age: 13 } to 'name=Anne+Smith&age=13'
+ // @ts-ignore passing FormData to URLSearchParams actually works
+ return new URLSearchParams(formData).toString();
+}
+
+function normalizeNetworkBody(body)
+
+ {
+ if (!body || typeof body !== 'string') {
+ return {
+ body,
+ warnings: [],
+ };
+ }
+
+ const exceedsSizeLimit = body.length > NETWORK_BODY_MAX_SIZE;
+
+ if (_strIsProbablyJson(body)) {
+ try {
+ const json = exceedsSizeLimit ? fixJson(body.slice(0, NETWORK_BODY_MAX_SIZE)) : body;
+ const normalizedBody = JSON.parse(json);
+ return {
+ body: normalizedBody,
+ warnings: exceedsSizeLimit ? ['JSON_TRUNCATED'] : [],
+ };
+ } catch (e3) {
+ return {
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
+ warnings: exceedsSizeLimit ? ['INVALID_JSON', 'TEXT_TRUNCATED'] : ['INVALID_JSON'],
+ };
+ }
+ }
+
+ return {
+ body: exceedsSizeLimit ? `${body.slice(0, NETWORK_BODY_MAX_SIZE)}…` : body,
+ warnings: exceedsSizeLimit ? ['TEXT_TRUNCATED'] : [],
+ };
+}
+
+function _strIsProbablyJson(str) {
+ const first = str[0];
+ const last = str[str.length - 1];
+
+ // Simple check: If this does not start & end with {} or [], it's not JSON
+ return (first === '[' && last === ']') || (first === '{' && last === '}');
+}
+
+/** Match an URL against a list of strings/Regex. */
+function urlMatches(url, urls) {
+ const fullUrl = getFullUrl(url);
+
+ return stringMatchesSomePattern(fullUrl, urls);
+}
+
+/** exported for tests */
+function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
+ // Short circuit for common cases:
+ if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith(WINDOW.location.origin)) {
+ return url;
+ }
+ const fixedUrl = new URL(url, baseURI);
+
+ // If these do not match, we are not dealing with a relative URL, so just return it
+ if (fixedUrl.origin !== new URL(baseURI).origin) {
+ return url;
+ }
+
+ const fullUrl = fixedUrl.href;
+
+ // Remove trailing slashes, if they don't match the original URL
+ if (!url.endsWith('/') && fullUrl.endsWith('/')) {
+ return fullUrl.slice(0, -1);
+ }
+
+ return fullUrl;
+}
+
+/**
+ * Capture a fetch breadcrumb to a replay.
+ * This adds additional data (where approriate).
+ */
+async function captureFetchBreadcrumbToReplay(
+ breadcrumb,
+ hint,
+ options
+
+,
+) {
+ try {
+ const data = await _prepareFetchData(breadcrumb, hint, options);
+
+ // Create a replay performance entry from this breadcrumb
+ const result = makeNetworkReplayBreadcrumb('resource.fetch', data);
+ addNetworkBreadcrumb(options.replay, result);
+ } catch (error) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] Failed to capture fetch breadcrumb', error);
+ }
+}
+
+/**
+ * Enrich a breadcrumb with additional data.
+ * This has to be sync & mutate the given breadcrumb,
+ * as the breadcrumb is afterwards consumed by other handlers.
+ */
+function enrichFetchBreadcrumb(
+ breadcrumb,
+ hint,
+ options,
+) {
+ const { input, response } = hint;
+
+ const body = _getFetchRequestArgBody(input);
+ const reqSize = getBodySize(body, options.textEncoder);
+
+ const resSize = response ? parseContentLengthHeader(response.headers.get('content-length')) : undefined;
+
+ if (reqSize !== undefined) {
+ breadcrumb.data.request_body_size = reqSize;
+ }
+ if (resSize !== undefined) {
+ breadcrumb.data.response_body_size = resSize;
+ }
+}
+
+async function _prepareFetchData(
+ breadcrumb,
+ hint,
+ options
+
+,
+) {
+ const { startTimestamp, endTimestamp } = hint;
+
+ const {
+ url,
+ method,
+ status_code: statusCode = 0,
+ request_body_size: requestBodySize,
+ response_body_size: responseBodySize,
+ } = breadcrumb.data;
+
+ const captureDetails = urlMatches(url, options.networkDetailAllowUrls);
+
+ const request = captureDetails
+ ? _getRequestInfo(options, hint.input, requestBodySize)
+ : buildSkippedNetworkRequestOrResponse(requestBodySize);
+ const response = await _getResponseInfo(captureDetails, options, hint.response, responseBodySize);
+
+ return {
+ startTimestamp,
+ endTimestamp,
+ url,
+ method,
+ statusCode,
+ request,
+ response,
+ };
+}
+
+function _getRequestInfo(
+ { networkCaptureBodies, networkRequestHeaders },
+ input,
+ requestBodySize,
+) {
+ const headers = getRequestHeaders(input, networkRequestHeaders);
+
+ if (!networkCaptureBodies) {
+ return buildNetworkRequestOrResponse(headers, requestBodySize, undefined);
+ }
+
+ // We only want to transmit string or string-like bodies
+ const requestBody = _getFetchRequestArgBody(input);
+ const bodyStr = getBodyString(requestBody);
+ return buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
+}
+
+async function _getResponseInfo(
+ captureDetails,
+ {
+ networkCaptureBodies,
+ textEncoder,
+ networkResponseHeaders,
+ }
+
+,
+ response,
+ responseBodySize,
+) {
+ if (!captureDetails && responseBodySize !== undefined) {
+ return buildSkippedNetworkRequestOrResponse(responseBodySize);
+ }
+
+ const headers = getAllHeaders(response.headers, networkResponseHeaders);
+
+ if (!networkCaptureBodies && responseBodySize !== undefined) {
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
+ }
+
+ // Only clone the response if we need to
+ try {
+ // We have to clone this, as the body can only be read once
+ const res = response.clone();
+ const bodyText = await _parseFetchBody(res);
+
+ const size =
+ bodyText && bodyText.length && responseBodySize === undefined
+ ? getBodySize(bodyText, textEncoder)
+ : responseBodySize;
+
+ if (!captureDetails) {
+ return buildSkippedNetworkRequestOrResponse(size);
+ }
+
+ if (networkCaptureBodies) {
+ return buildNetworkRequestOrResponse(headers, size, bodyText);
+ }
+
+ return buildNetworkRequestOrResponse(headers, size, undefined);
+ } catch (e) {
+ // fallback
+ return buildNetworkRequestOrResponse(headers, responseBodySize, undefined);
+ }
+}
+
+async function _parseFetchBody(response) {
+ try {
+ return await response.text();
+ } catch (e2) {
+ return undefined;
+ }
+}
+
+function _getFetchRequestArgBody(fetchArgs = []) {
+ // We only support getting the body from the fetch options
+ if (fetchArgs.length !== 2 || typeof fetchArgs[1] !== 'object') {
+ return undefined;
+ }
+
+ return (fetchArgs[1] ).body;
+}
+
+function getAllHeaders(headers, allowedHeaders) {
+ const allHeaders = {};
+
+ allowedHeaders.forEach(header => {
+ if (headers.get(header)) {
+ allHeaders[header] = headers.get(header) ;
+ }
+ });
+
+ return allHeaders;
+}
+
+function getRequestHeaders(fetchArgs, allowedHeaders) {
+ if (fetchArgs.length === 1 && typeof fetchArgs[0] !== 'string') {
+ return getHeadersFromOptions(fetchArgs[0] , allowedHeaders);
+ }
+
+ if (fetchArgs.length === 2) {
+ return getHeadersFromOptions(fetchArgs[1] , allowedHeaders);
+ }
+
+ return {};
+}
+
+function getHeadersFromOptions(
+ input,
+ allowedHeaders,
+) {
+ if (!input) {
+ return {};
+ }
+
+ const headers = input.headers;
+
+ if (!headers) {
+ return {};
+ }
+
+ if (headers instanceof Headers) {
+ return getAllHeaders(headers, allowedHeaders);
+ }
+
+ // We do not support this, as it is not really documented (anymore?)
+ if (Array.isArray(headers)) {
+ return {};
+ }
+
+ return getAllowedHeaders(headers, allowedHeaders);
+}
+
+/**
+ * Capture an XHR breadcrumb to a replay.
+ * This adds additional data (where approriate).
+ */
+async function captureXhrBreadcrumbToReplay(
+ breadcrumb,
+ hint,
+ options,
+) {
+ try {
+ const data = _prepareXhrData(breadcrumb, hint, options);
+
+ // Create a replay performance entry from this breadcrumb
+ const result = makeNetworkReplayBreadcrumb('resource.xhr', data);
+ addNetworkBreadcrumb(options.replay, result);
+ } catch (error) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] Failed to capture fetch breadcrumb', error);
+ }
+}
+
+/**
+ * Enrich a breadcrumb with additional data.
+ * This has to be sync & mutate the given breadcrumb,
+ * as the breadcrumb is afterwards consumed by other handlers.
+ */
+function enrichXhrBreadcrumb(
+ breadcrumb,
+ hint,
+ options,
+) {
+ const { xhr, input } = hint;
+
+ const reqSize = getBodySize(input, options.textEncoder);
+ const resSize = xhr.getResponseHeader('content-length')
+ ? parseContentLengthHeader(xhr.getResponseHeader('content-length'))
+ : getBodySize(xhr.response, options.textEncoder);
+
+ if (reqSize !== undefined) {
+ breadcrumb.data.request_body_size = reqSize;
+ }
+ if (resSize !== undefined) {
+ breadcrumb.data.response_body_size = resSize;
+ }
+}
+
+function _prepareXhrData(
+ breadcrumb,
+ hint,
+ options,
+) {
+ const { startTimestamp, endTimestamp, input, xhr } = hint;
+
+ const {
+ url,
+ method,
+ status_code: statusCode = 0,
+ request_body_size: requestBodySize,
+ response_body_size: responseBodySize,
+ } = breadcrumb.data;
+
+ if (!url) {
+ return null;
+ }
+
+ if (!urlMatches(url, options.networkDetailAllowUrls)) {
+ const request = buildSkippedNetworkRequestOrResponse(requestBodySize);
+ const response = buildSkippedNetworkRequestOrResponse(responseBodySize);
+ return {
+ startTimestamp,
+ endTimestamp,
+ url,
+ method,
+ statusCode,
+ request,
+ response,
+ };
+ }
+
+ const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
+ const networkRequestHeaders = xhrInfo
+ ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders)
+ : {};
+ const networkResponseHeaders = getAllowedHeaders(getResponseHeaders(xhr), options.networkResponseHeaders);
+
+ const request = buildNetworkRequestOrResponse(
+ networkRequestHeaders,
+ requestBodySize,
+ options.networkCaptureBodies ? getBodyString(input) : undefined,
+ );
+ const response = buildNetworkRequestOrResponse(
+ networkResponseHeaders,
+ responseBodySize,
+ options.networkCaptureBodies ? hint.xhr.responseText : undefined,
+ );
+
+ return {
+ startTimestamp,
+ endTimestamp,
+ url,
+ method,
+ statusCode,
+ request,
+ response,
+ };
+}
+
+function getResponseHeaders(xhr) {
+ const headers = xhr.getAllResponseHeaders();
+
+ if (!headers) {
+ return {};
+ }
+
+ return headers.split('\r\n').reduce((acc, line) => {
+ const [key, value] = line.split(': ');
+ acc[key.toLowerCase()] = value;
+ return acc;
+ }, {});
+}
+
+/**
+ * This method does two things:
+ * - It enriches the regular XHR/fetch breadcrumbs with request/response size data
+ * - It captures the XHR/fetch breadcrumbs to the replay
+ * (enriching it with further data that is _not_ added to the regular breadcrumbs)
+ */
+function handleNetworkBreadcrumbs(replay) {
+ const client = getCurrentHub().getClient();
+
+ try {
+ const textEncoder = new TextEncoder();
+
+ const { networkDetailAllowUrls, networkCaptureBodies, networkRequestHeaders, networkResponseHeaders } =
+ replay.getOptions();
+
+ const options = {
+ replay,
+ textEncoder,
+ networkDetailAllowUrls,
+ networkCaptureBodies,
+ networkRequestHeaders,
+ networkResponseHeaders,
+ };
+
+ if (client && client.on) {
+ client.on('beforeAddBreadcrumb', (breadcrumb, hint) => beforeAddNetworkBreadcrumb(options, breadcrumb, hint));
+ } else {
+ // Fallback behavior
+ addInstrumentationHandler('fetch', handleFetchSpanListener(replay));
+ addInstrumentationHandler('xhr', handleXhrSpanListener(replay));
+ }
+ } catch (e2) {
+ // Do nothing
+ }
+}
+
+/** just exported for tests */
+function beforeAddNetworkBreadcrumb(
+ options,
+ breadcrumb,
+ hint,
+) {
+ if (!breadcrumb.data) {
+ return;
+ }
+
+ try {
+ if (_isXhrBreadcrumb(breadcrumb) && _isXhrHint(hint)) {
+ // This has to be sync, as we need to ensure the breadcrumb is enriched in the same tick
+ // Because the hook runs synchronously, and the breadcrumb is afterwards passed on
+ // So any async mutations to it will not be reflected in the final breadcrumb
+ enrichXhrBreadcrumb(breadcrumb, hint, options);
+
+ void captureXhrBreadcrumbToReplay(breadcrumb, hint, options);
+ }
+
+ if (_isFetchBreadcrumb(breadcrumb) && _isFetchHint(hint)) {
+ // This has to be sync, as we need to ensure the breadcrumb is enriched in the same tick
+ // Because the hook runs synchronously, and the breadcrumb is afterwards passed on
+ // So any async mutations to it will not be reflected in the final breadcrumb
+ enrichFetchBreadcrumb(breadcrumb, hint, options);
+
+ void captureFetchBreadcrumbToReplay(breadcrumb, hint, options);
+ }
+ } catch (e) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('Error when enriching network breadcrumb');
+ }
+}
+
+function _isXhrBreadcrumb(breadcrumb) {
+ return breadcrumb.category === 'xhr';
+}
+
+function _isFetchBreadcrumb(breadcrumb) {
+ return breadcrumb.category === 'fetch';
+}
+
+function _isXhrHint(hint) {
+ return hint && hint.xhr;
+}
+
+function _isFetchHint(hint) {
+ return hint && hint.response;
+}
+
+let _LAST_BREADCRUMB = null;
+
+function isBreadcrumbWithCategory(breadcrumb) {
+ return !!breadcrumb.category;
+}
+
+const handleScopeListener =
+ (replay) =>
+ (scope) => {
+ if (!replay.isEnabled()) {
+ return;
+ }
+
+ const result = handleScope(scope);
+
+ if (!result) {
+ return;
+ }
+
+ addBreadcrumbEvent(replay, result);
+ };
+
+/**
+ * An event handler to handle scope changes.
+ */
+function handleScope(scope) {
+ // TODO (v8): Remove this guard. This was put in place because we introduced
+ // Scope.getLastBreadcrumb mid-v7 which caused incompatibilities with older SDKs.
+ // For now, we'll just return null if the method doesn't exist but we should eventually
+ // get rid of this guard.
+ const newBreadcrumb = scope.getLastBreadcrumb && scope.getLastBreadcrumb();
+
+ // Listener can be called when breadcrumbs have not changed, so we store the
+ // reference to the last crumb and only return a crumb if it has changed
+ if (_LAST_BREADCRUMB === newBreadcrumb || !newBreadcrumb) {
+ return null;
+ }
+
+ _LAST_BREADCRUMB = newBreadcrumb;
+
+ if (
+ !isBreadcrumbWithCategory(newBreadcrumb) ||
+ ['fetch', 'xhr', 'sentry.event', 'sentry.transaction'].includes(newBreadcrumb.category) ||
+ newBreadcrumb.category.startsWith('ui.')
+ ) {
+ return null;
+ }
+
+ if (newBreadcrumb.category === 'console') {
+ return normalizeConsoleBreadcrumb(newBreadcrumb);
+ }
+
+ return createBreadcrumb(newBreadcrumb);
+}
+
+/** exported for tests only */
+function normalizeConsoleBreadcrumb(
+ breadcrumb,
+) {
+ const args = breadcrumb.data && breadcrumb.data.arguments;
+
+ if (!Array.isArray(args) || args.length === 0) {
+ return createBreadcrumb(breadcrumb);
+ }
+
+ let isTruncated = false;
+
+ // Avoid giant args captures
+ const normalizedArgs = args.map(arg => {
+ if (!arg) {
+ return arg;
+ }
+ if (typeof arg === 'string') {
+ if (arg.length > CONSOLE_ARG_MAX_SIZE) {
+ isTruncated = true;
+ return `${arg.slice(0, CONSOLE_ARG_MAX_SIZE)}…`;
+ }
+
+ return arg;
+ }
+ if (typeof arg === 'object') {
+ try {
+ const normalizedArg = normalize(arg, 7);
+ const stringified = JSON.stringify(normalizedArg);
+ if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
+ const fixedJson = fixJson(stringified.slice(0, CONSOLE_ARG_MAX_SIZE));
+ const json = JSON.parse(fixedJson);
+ // We only set this after JSON.parse() was successfull, so we know we didn't run into `catch`
+ isTruncated = true;
+ return json;
+ }
+ return normalizedArg;
+ } catch (e) {
+ // fall back to default
+ }
+ }
+
+ return arg;
+ });
+
+ return createBreadcrumb({
+ ...breadcrumb,
+ data: {
+ ...breadcrumb.data,
+ arguments: normalizedArgs,
+ ...(isTruncated ? { _meta: { warnings: ['CONSOLE_ARG_TRUNCATED'] } } : {}),
+ },
+ });
+}
+
+/**
+ * Add global listeners that cannot be removed.
+ */
+function addGlobalListeners(replay) {
+ // Listeners from core SDK //
+ const scope = getCurrentHub().getScope();
+ const client = getCurrentHub().getClient();
+
+ if (scope) {
+ scope.addScopeListener(handleScopeListener(replay));
+ }
+ addInstrumentationHandler('dom', handleDomListener(replay));
+ addInstrumentationHandler('history', handleHistorySpanListener(replay));
+ handleNetworkBreadcrumbs(replay);
+
+ // Tag all (non replay) events that get sent to Sentry with the current
+ // replay ID so that we can reference them later in the UI
+ addGlobalEventProcessor(handleGlobalEventListener(replay, !hasHooks(client)));
+
+ // If a custom client has no hooks yet, we continue to use the "old" implementation
+ if (hasHooks(client)) {
+ client.on('afterSendEvent', handleAfterSendEvent(replay));
+ client.on('createDsc', (dsc) => {
+ const replayId = replay.getSessionId();
+ // We do not want to set the DSC when in buffer mode, as that means the replay has not been sent (yet)
+ if (replayId && replay.isEnabled() && replay.recordingMode === 'session') {
+ dsc.replay_id = replayId;
+ }
+ });
+
+ client.on('startTransaction', transaction => {
+ replay.lastTransaction = transaction;
+ });
+
+ // We may be missing the initial startTransaction due to timing issues,
+ // so we capture it on finish again.
+ client.on('finishTransaction', transaction => {
+ replay.lastTransaction = transaction;
+ });
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function hasHooks(client) {
+ return !!(client && client.on);
+}
+
+/**
+ * Create a "span" for the total amount of memory being used by JS objects
+ * (including v8 internal objects).
+ */
+async function addMemoryEntry(replay) {
+ // window.performance.memory is a non-standard API and doesn't work on all browsers, so we try-catch this
+ try {
+ return Promise.all(
+ createPerformanceSpans(replay, [
+ // @ts-ignore memory doesn't exist on type Performance as the API is non-standard (we check that it exists above)
+ createMemoryEntry(WINDOW.performance.memory),
+ ]),
+ );
+ } catch (error) {
+ // Do nothing
+ return [];
+ }
+}
+
+function createMemoryEntry(memoryEntry) {
+ const { jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize } = memoryEntry;
+ // we don't want to use `getAbsoluteTime` because it adds the event time to the
+ // time origin, so we get the current timestamp instead
+ const time = Date.now() / 1000;
+ return {
+ type: 'memory',
+ name: 'memory',
+ start: time,
+ end: time,
+ data: {
+ memory: {
+ jsHeapSizeLimit,
+ totalJSHeapSize,
+ usedJSHeapSize,
+ },
+ },
+ };
+}
+
+// Map entryType -> function to normalize data for event
+// @ts-ignore TODO: entry type does not fit the create* functions entry type
+const ENTRY_TYPES
+
+ = {
+ // @ts-ignore TODO: entry type does not fit the create* functions entry type
+ resource: createResourceEntry,
+ paint: createPaintEntry,
+ // @ts-ignore TODO: entry type does not fit the create* functions entry type
+ navigation: createNavigationEntry,
+ // @ts-ignore TODO: entry type does not fit the create* functions entry type
+ ['largest-contentful-paint']: createLargestContentfulPaint,
+};
+
+/**
+ * Create replay performance entries from the browser performance entries.
+ */
+function createPerformanceEntries(
+ entries,
+) {
+ return entries.map(createPerformanceEntry).filter(Boolean) ;
+}
+
+function createPerformanceEntry(entry) {
+ if (ENTRY_TYPES[entry.entryType] === undefined) {
+ return null;
+ }
+
+ return ENTRY_TYPES[entry.entryType](entry);
+}
+
+function getAbsoluteTime(time) {
+ // browserPerformanceTimeOrigin can be undefined if `performance` or
+ // `performance.now` doesn't exist, but this is already checked by this integration
+ return ((browserPerformanceTimeOrigin || WINDOW.performance.timeOrigin) + time) / 1000;
+}
+
+function createPaintEntry(entry) {
+ const { duration, entryType, name, startTime } = entry;
+
+ const start = getAbsoluteTime(startTime);
+ return {
+ type: entryType,
+ name,
+ start,
+ end: start + duration,
+ data: undefined,
+ };
+}
+
+function createNavigationEntry(entry) {
+ const {
+ entryType,
+ name,
+ decodedBodySize,
+ duration,
+ domComplete,
+ encodedBodySize,
+ domContentLoadedEventStart,
+ domContentLoadedEventEnd,
+ domInteractive,
+ loadEventStart,
+ loadEventEnd,
+ redirectCount,
+ startTime,
+ transferSize,
+ type,
+ } = entry;
+
+ // Ignore entries with no duration, they do not seem to be useful and cause dupes
+ if (duration === 0) {
+ return null;
+ }
+
+ return {
+ type: `${entryType}.${type}`,
+ start: getAbsoluteTime(startTime),
+ end: getAbsoluteTime(domComplete),
+ name,
+ data: {
+ size: transferSize,
+ decodedBodySize,
+ encodedBodySize,
+ duration,
+ domInteractive,
+ domContentLoadedEventStart,
+ domContentLoadedEventEnd,
+ loadEventStart,
+ loadEventEnd,
+ domComplete,
+ redirectCount,
+ },
+ };
+}
+
+function createResourceEntry(
+ entry,
+) {
+ const {
+ entryType,
+ initiatorType,
+ name,
+ responseEnd,
+ startTime,
+ decodedBodySize,
+ encodedBodySize,
+ responseStatus,
+ transferSize,
+ } = entry;
+
+ // Core SDK handles these
+ if (['fetch', 'xmlhttprequest'].includes(initiatorType)) {
+ return null;
+ }
+
+ return {
+ type: `${entryType}.${initiatorType}`,
+ start: getAbsoluteTime(startTime),
+ end: getAbsoluteTime(responseEnd),
+ name,
+ data: {
+ size: transferSize,
+ statusCode: responseStatus,
+ decodedBodySize,
+ encodedBodySize,
+ },
+ };
+}
+
+function createLargestContentfulPaint(
+ entry,
+) {
+ const { entryType, startTime, size } = entry;
+
+ let startTimeOrNavigationActivation = 0;
+
+ if (WINDOW.performance) {
+ const navEntry = WINDOW.performance.getEntriesByType('navigation')[0]
+
+;
+
+ // See https://github.com/GoogleChrome/web-vitals/blob/9f11c4c6578fb4c5ee6fa4e32b9d1d756475f135/src/lib/getActivationStart.ts#L21
+ startTimeOrNavigationActivation = (navEntry && navEntry.activationStart) || 0;
+ }
+
+ // value is in ms
+ const value = Math.max(startTime - startTimeOrNavigationActivation, 0);
+ // LCP doesn't have a "duration", it just happens at a single point in time.
+ // But the UI expects both, so use end (in seconds) for both timestamps.
+ const end = getAbsoluteTime(startTimeOrNavigationActivation) + value / 1000;
+
+ return {
+ type: entryType,
+ name: entryType,
+ start: end,
+ end,
+ data: {
+ value, // LCP "duration" in ms
+ size,
+ // Not sure why this errors, Node should be correct (Argument of type 'Node' is not assignable to parameter of type 'INode')
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ nodeId: record.mirror.getId(entry.element ),
+ },
+ };
+}
+
+/**
+ * Heavily simplified debounce function based on lodash.debounce.
+ *
+ * This function takes a callback function (@param fun) and delays its invocation
+ * by @param wait milliseconds. Optionally, a maxWait can be specified in @param options,
+ * which ensures that the callback is invoked at least once after the specified max. wait time.
+ *
+ * @param func the function whose invocation is to be debounced
+ * @param wait the minimum time until the function is invoked after it was called once
+ * @param options the options object, which can contain the `maxWait` property
+ *
+ * @returns the debounced version of the function, which needs to be called at least once to start the
+ * debouncing process. Subsequent calls will reset the debouncing timer and, in case @paramfunc
+ * was already invoked in the meantime, return @param func's return value.
+ * The debounced function has two additional properties:
+ * - `flush`: Invokes the debounced function immediately and returns its return value
+ * - `cancel`: Cancels the debouncing process and resets the debouncing timer
+ */
+function debounce(func, wait, options) {
+ let callbackReturnValue;
+
+ let timerId;
+ let maxTimerId;
+
+ const maxWait = options && options.maxWait ? Math.max(options.maxWait, wait) : 0;
+
+ function invokeFunc() {
+ cancelTimers();
+ callbackReturnValue = func();
+ return callbackReturnValue;
+ }
+
+ function cancelTimers() {
+ timerId !== undefined && clearTimeout(timerId);
+ maxTimerId !== undefined && clearTimeout(maxTimerId);
+ timerId = maxTimerId = undefined;
+ }
+
+ function flush() {
+ if (timerId !== undefined || maxTimerId !== undefined) {
+ return invokeFunc();
+ }
+ return callbackReturnValue;
+ }
+
+ function debounced() {
+ if (timerId) {
+ clearTimeout(timerId);
+ }
+ timerId = setTimeout(invokeFunc, wait);
+
+ if (maxWait && maxTimerId === undefined) {
+ maxTimerId = setTimeout(invokeFunc, maxWait);
+ }
+
+ return callbackReturnValue;
+ }
+
+ debounced.cancel = cancelTimers;
+ debounced.flush = flush;
+ return debounced;
+}
+
+/**
+ * Handler for recording events.
+ *
+ * Adds to event buffer, and has varying flushing behaviors if the event was a checkout.
+ */
+function getHandleRecordingEmit(replay) {
+ let hadFirstEvent = false;
+
+ return (event, _isCheckout) => {
+ // If this is false, it means session is expired, create and a new session and wait for checkout
+ if (!replay.checkAndHandleExpiredSession()) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('[Replay] Received replay event after session expired.');
+
+ return;
+ }
+
+ // `_isCheckout` is only set when the checkout is due to `checkoutEveryNms`
+ // We also want to treat the first event as a checkout, so we handle this specifically here
+ const isCheckout = _isCheckout || !hadFirstEvent;
+ hadFirstEvent = true;
+
+ // The handler returns `true` if we do not want to trigger debounced flush, `false` if we want to debounce flush.
+ replay.addUpdate(() => {
+ // The session is always started immediately on pageload/init, but for
+ // error-only replays, it should reflect the most recent checkout
+ // when an error occurs. Clear any state that happens before this current
+ // checkout. This needs to happen before `addEvent()` which updates state
+ // dependent on this reset.
+ if (replay.recordingMode === 'buffer' && isCheckout) {
+ replay.setInitialState();
+ }
+
+ // We need to clear existing events on a checkout, otherwise they are
+ // incremental event updates and should be appended
+ void addEvent(replay, event, isCheckout);
+
+ // Different behavior for full snapshots (type=2), ignore other event types
+ // See https://github.com/rrweb-io/rrweb/blob/d8f9290ca496712aa1e7d472549480c4e7876594/packages/rrweb/src/types.ts#L16
+ if (!isCheckout) {
+ return false;
+ }
+
+ // Additionally, create a meta event that will capture certain SDK settings.
+ // In order to handle buffer mode, this needs to either be done when we
+ // receive checkout events or at flush time.
+ //
+ // `isCheckout` is always true, but want to be explicit that it should
+ // only be added for checkouts
+ void addSettingsEvent(replay, isCheckout);
+
+ // If there is a previousSessionId after a full snapshot occurs, then
+ // the replay session was started due to session expiration. The new session
+ // is started before triggering a new checkout and contains the id
+ // of the previous session. Do not immediately flush in this case
+ // to avoid capturing only the checkout and instead the replay will
+ // be captured if they perform any follow-up actions.
+ if (replay.session && replay.session.previousSessionId) {
+ return true;
+ }
+
+ // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer
+ // this should usually be the timestamp of the checkout event, but to be safe...
+ if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
+ const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
+ if (earliestEvent) {
+ replay.session.started = earliestEvent;
+
+ if (replay.getOptions().stickySession) {
+ saveSession(replay.session);
+ }
+ }
+ }
+
+ if (replay.recordingMode === 'session') {
+ // If the full snapshot is due to an initial load, we will not have
+ // a previous session ID. In this case, we want to buffer events
+ // for a set amount of time before flushing. This can help avoid
+ // capturing replays of users that immediately close the window.
+ void replay.flush();
+ }
+
+ return true;
+ });
+ };
+}
+
+/**
+ * Exported for tests
+ */
+function createOptionsEvent(replay) {
+ const options = replay.getOptions();
+ return {
+ type: EventType.Custom,
+ timestamp: Date.now(),
+ data: {
+ tag: 'options',
+ payload: {
+ sessionSampleRate: options.sessionSampleRate,
+ errorSampleRate: options.errorSampleRate,
+ useCompressionOption: options.useCompression,
+ blockAllMedia: options.blockAllMedia,
+ maskAllText: options.maskAllText,
+ maskAllInputs: options.maskAllInputs,
+ useCompression: replay.eventBuffer ? replay.eventBuffer.type === 'worker' : false,
+ networkDetailHasUrls: options.networkDetailAllowUrls.length > 0,
+ networkCaptureBodies: options.networkCaptureBodies,
+ networkRequestHasHeaders: options.networkRequestHeaders.length > 0,
+ networkResponseHasHeaders: options.networkResponseHeaders.length > 0,
+ },
+ },
+ };
+}
+
+/**
+ * Add a "meta" event that contains a simplified view on current configuration
+ * options. This should only be included on the first segment of a recording.
+ */
+function addSettingsEvent(replay, isCheckout) {
+ // Only need to add this event when sending the first segment
+ if (!isCheckout || !replay.session || replay.session.segmentId !== 0) {
+ return Promise.resolve(null);
+ }
+
+ return addEvent(replay, createOptionsEvent(replay), false);
+}
+
+/**
+ * Create a replay envelope ready to be sent.
+ * This includes both the replay event, as well as the recording data.
+ */
+function createReplayEnvelope(
+ replayEvent,
+ recordingData,
+ dsn,
+ tunnel,
+) {
+ return createEnvelope(
+ createEventEnvelopeHeaders(replayEvent, getSdkMetadataForEnvelopeHeader(replayEvent), tunnel, dsn),
+ [
+ [{ type: 'replay_event' }, replayEvent],
+ [
+ {
+ type: 'replay_recording',
+ // If string then we need to encode to UTF8, otherwise will have
+ // wrong size. TextEncoder has similar browser support to
+ // MutationObserver, although it does not accept IE11.
+ length:
+ typeof recordingData === 'string' ? new TextEncoder().encode(recordingData).length : recordingData.length,
+ },
+ recordingData,
+ ],
+ ],
+ );
+}
+
+/**
+ * Prepare the recording data ready to be sent.
+ */
+function prepareRecordingData({
+ recordingData,
+ headers,
+}
+
+) {
+ let payloadWithSequence;
+
+ // XXX: newline is needed to separate sequence id from events
+ const replayHeaders = `${JSON.stringify(headers)}
+`;
+
+ if (typeof recordingData === 'string') {
+ payloadWithSequence = `${replayHeaders}${recordingData}`;
+ } else {
+ const enc = new TextEncoder();
+ // XXX: newline is needed to separate sequence id from events
+ const sequence = enc.encode(replayHeaders);
+ // Merge the two Uint8Arrays
+ payloadWithSequence = new Uint8Array(sequence.length + recordingData.length);
+ payloadWithSequence.set(sequence);
+ payloadWithSequence.set(recordingData, sequence.length);
+ }
+
+ return payloadWithSequence;
+}
+
+/**
+ * Prepare a replay event & enrich it with the SDK metadata.
+ */
+async function prepareReplayEvent({
+ client,
+ scope,
+ replayId: event_id,
+ event,
+}
+
+) {
+ const integrations =
+ typeof client._integrations === 'object' && client._integrations !== null && !Array.isArray(client._integrations)
+ ? Object.keys(client._integrations)
+ : undefined;
+ const preparedEvent = (await prepareEvent(
+ client.getOptions(),
+ event,
+ { event_id, integrations },
+ scope,
+ )) ;
+
+ // If e.g. a global event processor returned null
+ if (!preparedEvent) {
+ return null;
+ }
+
+ // This normally happens in browser client "_prepareEvent"
+ // but since we do not use this private method from the client, but rather the plain import
+ // we need to do this manually.
+ preparedEvent.platform = preparedEvent.platform || 'javascript';
+
+ // extract the SDK name because `client._prepareEvent` doesn't add it to the event
+ const metadata = client.getSdkMetadata && client.getSdkMetadata();
+ const { name, version } = (metadata && metadata.sdk) || {};
+
+ preparedEvent.sdk = {
+ ...preparedEvent.sdk,
+ name: name || 'sentry.javascript.unknown',
+ version: version || '0.0.0',
+ };
+
+ return preparedEvent;
+}
+
+/**
+ * Send replay attachment using `fetch()`
+ */
+async function sendReplayRequest({
+ recordingData,
+ replayId,
+ segmentId: segment_id,
+ eventContext,
+ timestamp,
+ session,
+}) {
+ const preparedRecordingData = prepareRecordingData({
+ recordingData,
+ headers: {
+ segment_id,
+ },
+ });
+
+ const { urls, errorIds, traceIds, initialTimestamp } = eventContext;
+
+ const hub = getCurrentHub();
+ const client = hub.getClient();
+ const scope = hub.getScope();
+ const transport = client && client.getTransport();
+ const dsn = client && client.getDsn();
+
+ if (!client || !transport || !dsn || !session.sampled) {
+ return;
+ }
+
+ const baseEvent = {
+ type: REPLAY_EVENT_NAME,
+ replay_start_timestamp: initialTimestamp / 1000,
+ timestamp: timestamp / 1000,
+ error_ids: errorIds,
+ trace_ids: traceIds,
+ urls,
+ replay_id: replayId,
+ segment_id,
+ replay_type: session.sampled,
+ };
+
+ const replayEvent = await prepareReplayEvent({ scope, client, replayId, event: baseEvent });
+
+ if (!replayEvent) {
+ // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
+ client.recordDroppedEvent('event_processor', 'replay', baseEvent);
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('An event processor returned `null`, will not send event.');
+ return;
+ }
+
+ /*
+ For reference, the fully built event looks something like this:
+ {
+ "type": "replay_event",
+ "timestamp": 1670837008.634,
+ "error_ids": [
+ "errorId"
+ ],
+ "trace_ids": [
+ "traceId"
+ ],
+ "urls": [
+ "https://example.com"
+ ],
+ "replay_id": "eventId",
+ "segment_id": 3,
+ "replay_type": "error",
+ "platform": "javascript",
+ "event_id": "eventId",
+ "environment": "production",
+ "sdk": {
+ "integrations": [
+ "BrowserTracing",
+ "Replay"
+ ],
+ "name": "sentry.javascript.browser",
+ "version": "7.25.0"
+ },
+ "sdkProcessingMetadata": {},
+ "contexts": {
+ },
+ }
+ */
+
+ const envelope = createReplayEnvelope(replayEvent, preparedRecordingData, dsn, client.getOptions().tunnel);
+
+ let response;
+
+ try {
+ response = await transport.send(envelope);
+ } catch (err) {
+ const error = new Error(UNABLE_TO_SEND_REPLAY);
+
+ try {
+ // In case browsers don't allow this property to be writable
+ // @ts-ignore This needs lib es2022 and newer
+ error.cause = err;
+ } catch (e) {
+ // nothing to do
+ }
+ throw error;
+ }
+
+ // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
+ if (!response) {
+ return response;
+ }
+
+ // If the status code is invalid, we want to immediately stop & not retry
+ if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) {
+ throw new TransportStatusCodeError(response.statusCode);
+ }
+
+ return response;
+}
+
+/**
+ * This error indicates that the transport returned an invalid status code.
+ */
+class TransportStatusCodeError extends Error {
+ constructor(statusCode) {
+ super(`Transport returned status code ${statusCode}`);
+ }
+}
+
+/**
+ * Finalize and send the current replay event to Sentry
+ */
+async function sendReplay(
+ replayData,
+ retryConfig = {
+ count: 0,
+ interval: RETRY_BASE_INTERVAL,
+ },
+) {
+ const { recordingData, options } = replayData;
+
+ // short circuit if there's no events to upload (this shouldn't happen as _runFlush makes this check)
+ if (!recordingData.length) {
+ return;
+ }
+
+ try {
+ await sendReplayRequest(replayData);
+ return true;
+ } catch (err) {
+ if (err instanceof TransportStatusCodeError) {
+ throw err;
+ }
+
+ // Capture error for every failed replay
+ setContext('Replays', {
+ _retryCount: retryConfig.count,
+ });
+
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && options._experiments && options._experiments.captureExceptions) {
+ captureException(err);
+ }
+
+ // If an error happened here, it's likely that uploading the attachment
+ // failed, we'll can retry with the same events payload
+ if (retryConfig.count >= RETRY_MAX_COUNT) {
+ const error = new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`);
+
+ try {
+ // In case browsers don't allow this property to be writable
+ // @ts-ignore This needs lib es2022 and newer
+ error.cause = err;
+ } catch (e) {
+ // nothing to do
+ }
+
+ throw error;
+ }
+
+ // will retry in intervals of 5, 10, 30
+ retryConfig.interval *= ++retryConfig.count;
+
+ return new Promise((resolve, reject) => {
+ setTimeout(async () => {
+ try {
+ await sendReplay(replayData, retryConfig);
+ resolve(true);
+ } catch (err) {
+ reject(err);
+ }
+ }, retryConfig.interval);
+ });
+ }
+}
+
+const THROTTLED = '__THROTTLED';
+const SKIPPED = '__SKIPPED';
+
+/**
+ * Create a throttled function off a given function.
+ * When calling the throttled function, it will call the original function only
+ * if it hasn't been called more than `maxCount` times in the last `durationSeconds`.
+ *
+ * Returns `THROTTLED` if throttled for the first time, after that `SKIPPED`,
+ * or else the return value of the original function.
+ */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function throttle(
+ fn,
+ maxCount,
+ durationSeconds,
+) {
+ const counter = new Map();
+
+ const _cleanup = (now) => {
+ const threshold = now - durationSeconds;
+ counter.forEach((_value, key) => {
+ if (key < threshold) {
+ counter.delete(key);
+ }
+ });
+ };
+
+ const _getTotalCount = () => {
+ return [...counter.values()].reduce((a, b) => a + b, 0);
+ };
+
+ let isThrottled = false;
+
+ return (...rest) => {
+ // Date in second-precision, which we use as basis for the throttling
+ const now = Math.floor(Date.now() / 1000);
+
+ // First, make sure to delete any old entries
+ _cleanup(now);
+
+ // If already over limit, do nothing
+ if (_getTotalCount() >= maxCount) {
+ const wasThrottled = isThrottled;
+ isThrottled = true;
+ return wasThrottled ? SKIPPED : THROTTLED;
+ }
+
+ isThrottled = false;
+ const count = counter.get(now) || 0;
+ counter.set(now, count + 1);
+
+ return fn(...rest);
+ };
+}
+
+/* eslint-disable max-lines */ // TODO: We might want to split this file up
+
+/**
+ * The main replay container class, which holds all the state and methods for recording and sending replays.
+ */
+class ReplayContainer {
+ __init() {this.eventBuffer = null;}
+
+ /**
+ * List of PerformanceEntry from PerformanceObserver
+ */
+ __init2() {this.performanceEvents = [];}
+
+ /**
+ * Recording can happen in one of three modes:
+ * - session: Record the whole session, sending it continuously
+ * - buffer: Always keep the last 60s of recording, requires:
+ * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
+ * - or calling `flush()` to send the replay
+ */
+ __init3() {this.recordingMode = 'session';}
+
+ /**
+ * The current or last active transcation.
+ * This is only available when performance is enabled.
+ */
+
+ /**
+ * These are here so we can overwrite them in tests etc.
+ * @hidden
+ */
+ __init4() {this.timeouts = {
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
+ maxSessionLife: MAX_SESSION_LIFE,
+ }; }
+
+ /**
+ * Options to pass to `rrweb.record()`
+ */
+
+ __init5() {this._performanceObserver = null;}
+
+ __init6() {this._flushLock = null;}
+
+ /**
+ * Timestamp of the last user activity. This lives across sessions.
+ */
+ __init7() {this._lastActivity = Date.now();}
+
+ /**
+ * Is the integration currently active?
+ */
+ __init8() {this._isEnabled = false;}
+
+ /**
+ * Paused is a state where:
+ * - DOM Recording is not listening at all
+ * - Nothing will be added to event buffer (e.g. core SDK events)
+ */
+ __init9() {this._isPaused = false;}
+
+ /**
+ * Have we attached listeners to the core SDK?
+ * Note we have to track this as there is no way to remove instrumentation handlers.
+ */
+ __init10() {this._hasInitializedCoreListeners = false;}
+
+ /**
+ * Function to stop recording
+ */
+ __init11() {this._stopRecording = null;}
+
+ __init12() {this._context = {
+ errorIds: new Set(),
+ traceIds: new Set(),
+ urls: [],
+ initialTimestamp: Date.now(),
+ initialUrl: '',
+ };}
+
+ constructor({
+ options,
+ recordingOptions,
+ }
+
+) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);ReplayContainer.prototype.__init18.call(this);
+ this._recordingOptions = recordingOptions;
+ this._options = options;
+
+ this._debouncedFlush = debounce(() => this._flush(), this._options.flushMinDelay, {
+ maxWait: this._options.flushMaxDelay,
+ });
+
+ this._throttledAddEvent = throttle(
+ (event, isCheckout) => addEvent(this, event, isCheckout),
+ // Max 300 events...
+ 300,
+ // ... per 5s
+ 5,
+ );
+
+ const { slowClickTimeout, slowClickIgnoreSelectors } = this.getOptions();
+
+ const slowClickConfig = slowClickTimeout
+ ? {
+ threshold: Math.min(SLOW_CLICK_THRESHOLD, slowClickTimeout),
+ timeout: slowClickTimeout,
+ scrollTimeout: SLOW_CLICK_SCROLL_TIMEOUT,
+ ignoreSelector: slowClickIgnoreSelectors ? slowClickIgnoreSelectors.join(',') : '',
+ multiClickTimeout: MULTI_CLICK_TIMEOUT,
+ }
+ : undefined;
+
+ if (slowClickConfig) {
+ this.clickDetector = new ClickDetector(this, slowClickConfig);
+ }
+ }
+
+ /** Get the event context. */
+ getContext() {
+ return this._context;
+ }
+
+ /** If recording is currently enabled. */
+ isEnabled() {
+ return this._isEnabled;
+ }
+
+ /** If recording is currently paused. */
+ isPaused() {
+ return this._isPaused;
+ }
+
+ /** Get the replay integration options. */
+ getOptions() {
+ return this._options;
+ }
+
+ /**
+ * Initializes the plugin based on sampling configuration. Should not be
+ * called outside of constructor.
+ */
+ initializeSampling() {
+ const { errorSampleRate, sessionSampleRate } = this._options;
+
+ // If neither sample rate is > 0, then do nothing - user will need to call one of
+ // `start()` or `startBuffering` themselves.
+ if (errorSampleRate <= 0 && sessionSampleRate <= 0) {
+ return;
+ }
+
+ // Otherwise if there is _any_ sample rate set, try to load an existing
+ // session, or create a new one.
+ const isSessionSampled = this._loadAndCheckSession();
+
+ if (!isSessionSampled) {
+ // This should only occur if `errorSampleRate` is 0 and was unsampled for
+ // session-based replay. In this case there is nothing to do.
+ return;
+ }
+
+ if (!this.session) {
+ // This should not happen, something wrong has occurred
+ this._handleException(new Error('Unable to initialize and create session'));
+ return;
+ }
+
+ if (this.session.sampled && this.session.sampled !== 'session') {
+ // If not sampled as session-based, then recording mode will be `buffer`
+ // Note that we don't explicitly check if `sampled === 'buffer'` because we
+ // could have sessions from Session storage that are still `error` from
+ // prior SDK version.
+ this.recordingMode = 'buffer';
+ }
+
+ this._initializeRecording();
+ }
+
+ /**
+ * Start a replay regardless of sampling rate. Calling this will always
+ * create a new session. Will throw an error if replay is already in progress.
+ *
+ * Creates or loads a session, attaches listeners to varying events (DOM,
+ * _performanceObserver, Recording, Sentry SDK, etc)
+ */
+ start() {
+ if (this._isEnabled && this.recordingMode === 'session') {
+ throw new Error('Replay recording is already in progress');
+ }
+
+ if (this._isEnabled && this.recordingMode === 'buffer') {
+ throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
+ }
+
+ const previousSessionId = this.session && this.session.id;
+
+ const { session } = getSession({
+ timeouts: this.timeouts,
+ stickySession: Boolean(this._options.stickySession),
+ currentSession: this.session,
+ // This is intentional: create a new session-based replay when calling `start()`
+ sessionSampleRate: 1,
+ allowBuffering: false,
+ });
+
+ session.previousSessionId = previousSessionId;
+ this.session = session;
+
+ this._initializeRecording();
+ }
+
+ /**
+ * Start replay buffering. Buffers until `flush()` is called or, if
+ * `replaysOnErrorSampleRate` > 0, an error occurs.
+ */
+ startBuffering() {
+ if (this._isEnabled) {
+ throw new Error('Replay recording is already in progress');
+ }
+
+ const previousSessionId = this.session && this.session.id;
+
+ const { session } = getSession({
+ timeouts: this.timeouts,
+ stickySession: Boolean(this._options.stickySession),
+ currentSession: this.session,
+ sessionSampleRate: 0,
+ allowBuffering: true,
+ });
+
+ session.previousSessionId = previousSessionId;
+ this.session = session;
+
+ this.recordingMode = 'buffer';
+ this._initializeRecording();
+ }
+
+ /**
+ * Start recording.
+ *
+ * Note that this will cause a new DOM checkout
+ */
+ startRecording() {
+ try {
+ this._stopRecording = record({
+ ...this._recordingOptions,
+ // When running in error sampling mode, we need to overwrite `checkoutEveryNms`
+ // Without this, it would record forever, until an error happens, which we don't want
+ // instead, we'll always keep the last 60 seconds of replay before an error happened
+ ...(this.recordingMode === 'buffer' && { checkoutEveryNms: BUFFER_CHECKOUT_TIME }),
+ emit: getHandleRecordingEmit(this),
+ onMutation: this._onMutationHandler,
+ });
+ } catch (err) {
+ this._handleException(err);
+ }
+ }
+
+ /**
+ * Stops the recording, if it was running.
+ *
+ * Returns true if it was previously stopped, or is now stopped,
+ * otherwise false.
+ */
+ stopRecording() {
+ try {
+ if (this._stopRecording) {
+ this._stopRecording();
+ this._stopRecording = undefined;
+ }
+
+ return true;
+ } catch (err) {
+ this._handleException(err);
+ return false;
+ }
+ }
+
+ /**
+ * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
+ * does not support a teardown
+ */
+ async stop(reason) {
+ if (!this._isEnabled) {
+ return;
+ }
+
+ try {
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
+ const msg = `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`;
+
+ // When `traceInternals` is enabled, we want to log this to the console
+ // Else, use the regular debug output
+ // eslint-disable-next-line
+ const log = this.getOptions()._experiments.traceInternals ? console.warn : logger.log;
+ log(msg);
+ }
+
+ // We can't move `_isEnabled` after awaiting a flush, otherwise we can
+ // enter into an infinite loop when `stop()` is called while flushing.
+ this._isEnabled = false;
+ this._removeListeners();
+ this.stopRecording();
+
+ this._debouncedFlush.cancel();
+ // See comment above re: `_isEnabled`, we "force" a flush, ignoring the
+ // `_isEnabled` state of the plugin since it was disabled above.
+ if (this.recordingMode === 'session') {
+ await this._flush({ force: true });
+ }
+
+ // After flush, destroy event buffer
+ this.eventBuffer && this.eventBuffer.destroy();
+ this.eventBuffer = null;
+
+ // Clear session from session storage, note this means if a new session
+ // is started after, it will not have `previousSessionId`
+ clearSession(this);
+ } catch (err) {
+ this._handleException(err);
+ }
+ }
+
+ /**
+ * Pause some replay functionality. See comments for `_isPaused`.
+ * This differs from stop as this only stops DOM recording, it is
+ * not as thorough of a shutdown as `stop()`.
+ */
+ pause() {
+ this._isPaused = true;
+ this.stopRecording();
+ }
+
+ /**
+ * Resumes recording, see notes for `pause().
+ *
+ * Note that calling `startRecording()` here will cause a
+ * new DOM checkout.`
+ */
+ resume() {
+ if (!this._loadAndCheckSession()) {
+ return;
+ }
+
+ this._isPaused = false;
+ this.startRecording();
+ }
+
+ /**
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
+ * Unless `continueRecording` is false, the replay will continue to record and
+ * behave as a "session"-based replay.
+ *
+ * Otherwise, queue up a flush.
+ */
+ async sendBufferedReplayOrFlush({ continueRecording = true } = {}) {
+ if (this.recordingMode === 'session') {
+ return this.flushImmediate();
+ }
+
+ const activityTime = Date.now();
+
+ // Allow flush to complete before resuming as a session recording, otherwise
+ // the checkout from `startRecording` may be included in the payload.
+ // Prefer to keep the error replay as a separate (and smaller) segment
+ // than the session replay.
+ await this.flushImmediate();
+
+ const hasStoppedRecording = this.stopRecording();
+
+ if (!continueRecording || !hasStoppedRecording) {
+ return;
+ }
+
+ // Re-start recording, but in "session" recording mode
+
+ // Reset all "capture on error" configuration before
+ // starting a new recording
+ this.recordingMode = 'session';
+
+ // Once this session ends, we do not want to refresh it
+ if (this.session) {
+ this.session.shouldRefresh = false;
+
+ // It's possible that the session lifespan is > max session lifespan
+ // because we have been buffering beyond max session lifespan (we ignore
+ // expiration given that `shouldRefresh` is true). Since we flip
+ // `shouldRefresh`, the session could be considered expired due to
+ // lifespan, which is not what we want. Update session start date to be
+ // the current timestamp, so that session is not considered to be
+ // expired. This means that max replay duration can be MAX_SESSION_LIFE +
+ // (length of buffer), which we are ok with.
+ this._updateUserActivity(activityTime);
+ this._updateSessionActivity(activityTime);
+ this.session.started = activityTime;
+ this._maybeSaveSession();
+ }
+
+ this.startRecording();
+ }
+
+ /**
+ * We want to batch uploads of replay events. Save events only if
+ * `<flushMinDelay>` milliseconds have elapsed since the last event
+ * *OR* if `<flushMaxDelay>` milliseconds have elapsed.
+ *
+ * Accepts a callback to perform side-effects and returns true to stop batch
+ * processing and hand back control to caller.
+ */
+ addUpdate(cb) {
+ // We need to always run `cb` (e.g. in the case of `this.recordingMode == 'buffer'`)
+ const cbResult = cb();
+
+ // If this option is turned on then we will only want to call `flush`
+ // explicitly
+ if (this.recordingMode === 'buffer') {
+ return;
+ }
+
+ // If callback is true, we do not want to continue with flushing -- the
+ // caller will need to handle it.
+ if (cbResult === true) {
+ return;
+ }
+
+ // addUpdate is called quite frequently - use _debouncedFlush so that it
+ // respects the flush delays and does not flush immediately
+ this._debouncedFlush();
+ }
+
+ /**
+ * Updates the user activity timestamp and resumes recording. This should be
+ * called in an event handler for a user action that we consider as the user
+ * being "active" (e.g. a mouse click).
+ */
+ triggerUserActivity() {
+ this._updateUserActivity();
+
+ // This case means that recording was once stopped due to inactivity.
+ // Ensure that recording is resumed.
+ if (!this._stopRecording) {
+ // Create a new session, otherwise when the user action is flushed, it
+ // will get rejected due to an expired session.
+ if (!this._loadAndCheckSession()) {
+ return;
+ }
+
+ // Note: This will cause a new DOM checkout
+ this.resume();
+ return;
+ }
+
+ // Otherwise... recording was never suspended, continue as normalish
+ this.checkAndHandleExpiredSession();
+
+ this._updateSessionActivity();
+ }
+
+ /**
+ * Updates the user activity timestamp *without* resuming
+ * recording. Some user events (e.g. keydown) can be create
+ * low-value replays that only contain the keypress as a
+ * breadcrumb. Instead this would require other events to
+ * create a new replay after a session has expired.
+ */
+ updateUserActivity() {
+ this._updateUserActivity();
+ this._updateSessionActivity();
+ }
+
+ /**
+ * Only flush if `this.recordingMode === 'session'`
+ */
+ conditionalFlush() {
+ if (this.recordingMode === 'buffer') {
+ return Promise.resolve();
+ }
+
+ return this.flushImmediate();
+ }
+
+ /**
+ * Flush using debounce flush
+ */
+ flush() {
+ return this._debouncedFlush() ;
+ }
+
+ /**
+ * Always flush via `_debouncedFlush` so that we do not have flushes triggered
+ * from calling both `flush` and `_debouncedFlush`. Otherwise, there could be
+ * cases of mulitple flushes happening closely together.
+ */
+ flushImmediate() {
+ this._debouncedFlush();
+ // `.flush` is provided by the debounced function, analogously to lodash.debounce
+ return this._debouncedFlush.flush() ;
+ }
+
+ /**
+ * Cancels queued up flushes.
+ */
+ cancelFlush() {
+ this._debouncedFlush.cancel();
+ }
+
+ /** Get the current sesion (=replay) ID */
+ getSessionId() {
+ return this.session && this.session.id;
+ }
+
+ /**
+ * Checks if recording should be stopped due to user inactivity. Otherwise
+ * check if session is expired and create a new session if so. Triggers a new
+ * full snapshot on new session.
+ *
+ * Returns true if session is not expired, false otherwise.
+ * @hidden
+ */
+ checkAndHandleExpiredSession() {
+ const oldSessionId = this.getSessionId();
+
+ // Prevent starting a new session if the last user activity is older than
+ // SESSION_IDLE_PAUSE_DURATION. Otherwise non-user activity can trigger a new
+ // session+recording. This creates noisy replays that do not have much
+ // content in them.
+ if (
+ this._lastActivity &&
+ isExpired(this._lastActivity, this.timeouts.sessionIdlePause) &&
+ this.session &&
+ this.session.sampled === 'session'
+ ) {
+ // Pause recording only for session-based replays. Otherwise, resuming
+ // will create a new replay and will conflict with users who only choose
+ // to record error-based replays only. (e.g. the resumed replay will not
+ // contain a reference to an error)
+ this.pause();
+ return;
+ }
+
+ // --- There is recent user activity --- //
+ // This will create a new session if expired, based on expiry length
+ if (!this._loadAndCheckSession()) {
+ return;
+ }
+
+ // Session was expired if session ids do not match
+ const expired = oldSessionId !== this.getSessionId();
+
+ if (!expired) {
+ return true;
+ }
+
+ // Session is expired, trigger a full snapshot (which will create a new session)
+ this._triggerFullSnapshot();
+
+ return false;
+ }
+
+ /**
+ * Capture some initial state that can change throughout the lifespan of the
+ * replay. This is required because otherwise they would be captured at the
+ * first flush.
+ */
+ setInitialState() {
+ const urlPath = `${WINDOW.location.pathname}${WINDOW.location.hash}${WINDOW.location.search}`;
+ const url = `${WINDOW.location.origin}${urlPath}`;
+
+ this.performanceEvents = [];
+
+ // Reset _context as well
+ this._clearContext();
+
+ this._context.initialUrl = url;
+ this._context.initialTimestamp = Date.now();
+ this._context.urls.push(url);
+ }
+
+ /**
+ * Add a breadcrumb event, that may be throttled.
+ * If it was throttled, we add a custom breadcrumb to indicate that.
+ */
+ throttledAddEvent(
+ event,
+ isCheckout,
+ ) {
+ const res = this._throttledAddEvent(event, isCheckout);
+
+ // If this is THROTTLED, it means we have throttled the event for the first time
+ // In this case, we want to add a breadcrumb indicating that something was skipped
+ if (res === THROTTLED) {
+ const breadcrumb = createBreadcrumb({
+ category: 'replay.throttled',
+ });
+
+ this.addUpdate(() => {
+ void addEvent(this, {
+ type: EventType.Custom,
+ timestamp: breadcrumb.timestamp || 0,
+ data: {
+ tag: 'breadcrumb',
+ payload: breadcrumb,
+ metric: true,
+ },
+ });
+ });
+ }
+
+ return res;
+ }
+
+ /**
+ * This will get the parametrized route name of the current page.
+ * This is only available if performance is enabled, and if an instrumented router is used.
+ */
+ getCurrentRoute() {
+ const lastTransaction = this.lastTransaction || getCurrentHub().getScope().getTransaction();
+ if (!lastTransaction || !['route', 'custom'].includes(lastTransaction.metadata.source)) {
+ return undefined;
+ }
+
+ return lastTransaction.name;
+ }
+
+ /**
+ * Initialize and start all listeners to varying events (DOM,
+ * Performance Observer, Recording, Sentry SDK, etc)
+ */
+ _initializeRecording() {
+ this.setInitialState();
+
+ // this method is generally called on page load or manually - in both cases
+ // we should treat it as an activity
+ this._updateSessionActivity();
+
+ this.eventBuffer = createEventBuffer({
+ useCompression: this._options.useCompression,
+ });
+
+ this._removeListeners();
+ this._addListeners();
+
+ // Need to set as enabled before we start recording, as `record()` can trigger a flush with a new checkout
+ this._isEnabled = true;
+
+ this.startRecording();
+ }
+
+ /** A wrapper to conditionally capture exceptions. */
+ _handleException(error) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay]', error);
+
+ if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && this._options._experiments && this._options._experiments.captureExceptions) {
+ captureException(error);
+ }
+ }
+
+ /**
+ * Loads (or refreshes) the current session.
+ * Returns false if session is not recorded.
+ */
+ _loadAndCheckSession() {
+ const { type, session } = getSession({
+ timeouts: this.timeouts,
+ stickySession: Boolean(this._options.stickySession),
+ currentSession: this.session,
+ sessionSampleRate: this._options.sessionSampleRate,
+ allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
+ });
+
+ // If session was newly created (i.e. was not loaded from storage), then
+ // enable flag to create the root replay
+ if (type === 'new') {
+ this.setInitialState();
+ }
+
+ const currentSessionId = this.getSessionId();
+ if (session.id !== currentSessionId) {
+ session.previousSessionId = currentSessionId;
+ }
+
+ this.session = session;
+
+ if (!this.session.sampled) {
+ void this.stop('session unsampled');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds listeners to record events for the replay
+ */
+ _addListeners() {
+ try {
+ WINDOW.document.addEventListener('visibilitychange', this._handleVisibilityChange);
+ WINDOW.addEventListener('blur', this._handleWindowBlur);
+ WINDOW.addEventListener('focus', this._handleWindowFocus);
+ WINDOW.addEventListener('keydown', this._handleKeyboardEvent);
+
+ if (this.clickDetector) {
+ this.clickDetector.addListeners();
+ }
+
+ // There is no way to remove these listeners, so ensure they are only added once
+ if (!this._hasInitializedCoreListeners) {
+ addGlobalListeners(this);
+
+ this._hasInitializedCoreListeners = true;
+ }
+ } catch (err) {
+ this._handleException(err);
+ }
+
+ // PerformanceObserver //
+ if (!('PerformanceObserver' in WINDOW)) {
+ return;
+ }
+
+ this._performanceObserver = setupPerformanceObserver(this);
+ }
+
+ /**
+ * Cleans up listeners that were created in `_addListeners`
+ */
+ _removeListeners() {
+ try {
+ WINDOW.document.removeEventListener('visibilitychange', this._handleVisibilityChange);
+
+ WINDOW.removeEventListener('blur', this._handleWindowBlur);
+ WINDOW.removeEventListener('focus', this._handleWindowFocus);
+ WINDOW.removeEventListener('keydown', this._handleKeyboardEvent);
+
+ if (this.clickDetector) {
+ this.clickDetector.removeListeners();
+ }
+
+ if (this._performanceObserver) {
+ this._performanceObserver.disconnect();
+ this._performanceObserver = null;
+ }
+ } catch (err) {
+ this._handleException(err);
+ }
+ }
+
+ /**
+ * Handle when visibility of the page content changes. Opening a new tab will
+ * cause the state to change to hidden because of content of current page will
+ * be hidden. Likewise, moving a different window to cover the contents of the
+ * page will also trigger a change to a hidden state.
+ */
+ __init13() {this._handleVisibilityChange = () => {
+ if (WINDOW.document.visibilityState === 'visible') {
+ this._doChangeToForegroundTasks();
+ } else {
+ this._doChangeToBackgroundTasks();
+ }
+ };}
+
+ /**
+ * Handle when page is blurred
+ */
+ __init14() {this._handleWindowBlur = () => {
+ const breadcrumb = createBreadcrumb({
+ category: 'ui.blur',
+ });
+
+ // Do not count blur as a user action -- it's part of the process of them
+ // leaving the page
+ this._doChangeToBackgroundTasks(breadcrumb);
+ };}
+
+ /**
+ * Handle when page is focused
+ */
+ __init15() {this._handleWindowFocus = () => {
+ const breadcrumb = createBreadcrumb({
+ category: 'ui.focus',
+ });
+
+ // Do not count focus as a user action -- instead wait until they focus and
+ // interactive with page
+ this._doChangeToForegroundTasks(breadcrumb);
+ };}
+
+ /** Ensure page remains active when a key is pressed. */
+ __init16() {this._handleKeyboardEvent = (event) => {
+ handleKeyboardEvent(this, event);
+ };}
+
+ /**
+ * Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
+ */
+ _doChangeToBackgroundTasks(breadcrumb) {
+ if (!this.session) {
+ return;
+ }
+
+ const expired = isSessionExpired(this.session, this.timeouts);
+
+ if (breadcrumb && !expired) {
+ this._createCustomBreadcrumb(breadcrumb);
+ }
+
+ // Send replay when the page/tab becomes hidden. There is no reason to send
+ // replay if it becomes visible, since no actions we care about were done
+ // while it was hidden
+ void this.conditionalFlush();
+ }
+
+ /**
+ * Tasks to run when we consider a page to be visible (via focus and/or visibility)
+ */
+ _doChangeToForegroundTasks(breadcrumb) {
+ if (!this.session) {
+ return;
+ }
+
+ const isSessionActive = this.checkAndHandleExpiredSession();
+
+ if (!isSessionActive) {
+ // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
+ // ms, we will re-use the existing session, otherwise create a new
+ // session
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
+ return;
+ }
+
+ if (breadcrumb) {
+ this._createCustomBreadcrumb(breadcrumb);
+ }
+ }
+
+ /**
+ * Trigger rrweb to take a full snapshot which will cause this plugin to
+ * create a new Replay event.
+ */
+ _triggerFullSnapshot(checkout = true) {
+ try {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Taking full rrweb snapshot');
+ record.takeFullSnapshot(checkout);
+ } catch (err) {
+ this._handleException(err);
+ }
+ }
+
+ /**
+ * Update user activity (across session lifespans)
+ */
+ _updateUserActivity(_lastActivity = Date.now()) {
+ this._lastActivity = _lastActivity;
+ }
+
+ /**
+ * Updates the session's last activity timestamp
+ */
+ _updateSessionActivity(_lastActivity = Date.now()) {
+ if (this.session) {
+ this.session.lastActivity = _lastActivity;
+ this._maybeSaveSession();
+ }
+ }
+
+ /**
+ * Helper to create (and buffer) a replay breadcrumb from a core SDK breadcrumb
+ */
+ _createCustomBreadcrumb(breadcrumb) {
+ this.addUpdate(() => {
+ void this.throttledAddEvent({
+ type: EventType.Custom,
+ timestamp: breadcrumb.timestamp || 0,
+ data: {
+ tag: 'breadcrumb',
+ payload: breadcrumb,
+ },
+ });
+ });
+ }
+
+ /**
+ * Observed performance events are added to `this.performanceEvents`. These
+ * are included in the replay event before it is finished and sent to Sentry.
+ */
+ _addPerformanceEntries() {
+ // Copy and reset entries before processing
+ const entries = [...this.performanceEvents];
+ this.performanceEvents = [];
+
+ return Promise.all(createPerformanceSpans(this, createPerformanceEntries(entries)));
+ }
+
+ /**
+ * Clear _context
+ */
+ _clearContext() {
+ // XXX: `initialTimestamp` and `initialUrl` do not get cleared
+ this._context.errorIds.clear();
+ this._context.traceIds.clear();
+ this._context.urls = [];
+ }
+
+ /** Update the initial timestamp based on the buffer content. */
+ _updateInitialTimestampFromEventBuffer() {
+ const { session, eventBuffer } = this;
+ if (!session || !eventBuffer) {
+ return;
+ }
+
+ // we only ever update this on the initial segment
+ if (session.segmentId) {
+ return;
+ }
+
+ const earliestEvent = eventBuffer.getEarliestTimestamp();
+ if (earliestEvent && earliestEvent < this._context.initialTimestamp) {
+ this._context.initialTimestamp = earliestEvent;
+ }
+ }
+
+ /**
+ * Return and clear _context
+ */
+ _popEventContext() {
+ const _context = {
+ initialTimestamp: this._context.initialTimestamp,
+ initialUrl: this._context.initialUrl,
+ errorIds: Array.from(this._context.errorIds),
+ traceIds: Array.from(this._context.traceIds),
+ urls: this._context.urls,
+ };
+
+ this._clearContext();
+
+ return _context;
+ }
+
+ /**
+ * Flushes replay event buffer to Sentry.
+ *
+ * Performance events are only added right before flushing - this is
+ * due to the buffered performance observer events.
+ *
+ * Should never be called directly, only by `flush`
+ */
+ async _runFlush() {
+ if (!this.session || !this.eventBuffer) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] No session or eventBuffer found to flush.');
+ return;
+ }
+
+ await this._addPerformanceEntries();
+
+ // Check eventBuffer again, as it could have been stopped in the meanwhile
+ if (!this.eventBuffer || !this.eventBuffer.hasEvents) {
+ return;
+ }
+
+ // Only attach memory event if eventBuffer is not empty
+ await addMemoryEntry(this);
+
+ // Check eventBuffer again, as it could have been stopped in the meanwhile
+ if (!this.eventBuffer) {
+ return;
+ }
+
+ try {
+ // This uses the data from the eventBuffer, so we need to call this before `finish()
+ this._updateInitialTimestampFromEventBuffer();
+
+ // Note this empties the event buffer regardless of outcome of sending replay
+ const recordingData = await this.eventBuffer.finish();
+
+ // NOTE: Copy values from instance members, as it's possible they could
+ // change before the flush finishes.
+ const replayId = this.session.id;
+ const eventContext = this._popEventContext();
+ // Always increment segmentId regardless of outcome of sending replay
+ const segmentId = this.session.segmentId++;
+ this._maybeSaveSession();
+
+ await sendReplay({
+ replayId,
+ recordingData,
+ segmentId,
+ eventContext,
+ session: this.session,
+ options: this.getOptions(),
+ timestamp: Date.now(),
+ });
+ } catch (err) {
+ this._handleException(err);
+
+ // This means we retried 3 times and all of them failed,
+ // or we ran into a problem we don't want to retry, like rate limiting.
+ // In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
+ void this.stop('sendReplay');
+
+ const client = getCurrentHub().getClient();
+
+ if (client) {
+ client.recordDroppedEvent('send_error', 'replay');
+ }
+ }
+ }
+
+ /**
+ * Flush recording data to Sentry. Creates a lock so that only a single flush
+ * can be active at a time. Do not call this directly.
+ */
+ __init17() {this._flush = async ({
+ force = false,
+ }
+
+ = {}) => {
+ if (!this._isEnabled && !force) {
+ // This can happen if e.g. the replay was stopped because of exceeding the retry limit
+ return;
+ }
+
+ if (!this.checkAndHandleExpiredSession()) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] Attempting to finish replay event after session expired.');
+ return;
+ }
+
+ if (!this.session) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error('[Replay] No session found to flush.');
+ return;
+ }
+
+ // A flush is about to happen, cancel any queued flushes
+ this._debouncedFlush.cancel();
+
+ // this._flushLock acts as a lock so that future calls to `_flush()`
+ // will be blocked until this promise resolves
+ if (!this._flushLock) {
+ this._flushLock = this._runFlush();
+ await this._flushLock;
+ this._flushLock = null;
+ return;
+ }
+
+ // Wait for previous flush to finish, then call the debounced `_flush()`.
+ // It's possible there are other flush requests queued and waiting for it
+ // to resolve. We want to reduce all outstanding requests (as well as any
+ // new flush requests that occur within a second of the locked flush
+ // completing) into a single flush.
+
+ try {
+ await this._flushLock;
+ } catch (err) {
+ (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(err);
+ } finally {
+ this._debouncedFlush();
+ }
+ };}
+
+ /** Save the session, if it is sticky */
+ _maybeSaveSession() {
+ if (this.session && this._options.stickySession) {
+ saveSession(this.session);
+ }
+ }
+
+ /** Handler for rrweb.record.onMutation */
+ __init18() {this._onMutationHandler = (mutations) => {
+ const count = mutations.length;
+
+ const mutationLimit = this._options.mutationLimit;
+ const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
+ const overMutationLimit = mutationLimit && count > mutationLimit;
+
+ // Create a breadcrumb if a lot of mutations happen at the same time
+ // We can show this in the UI as an information with potential performance improvements
+ if (count > mutationBreadcrumbLimit || overMutationLimit) {
+ const breadcrumb = createBreadcrumb({
+ category: 'replay.mutations',
+ data: {
+ count,
+ limit: overMutationLimit,
+ },
+ });
+ this._createCustomBreadcrumb(breadcrumb);
+ }
+
+ // Stop replay if over the mutation limit
+ if (overMutationLimit) {
+ void this.stop('mutationLimit');
+ return false;
+ }
+
+ // `true` means we use the regular mutation handling by rrweb
+ return true;
+ };}
+}
+
+function getOption(
+ selectors,
+ defaultSelectors,
+ deprecatedClassOption,
+ deprecatedSelectorOption,
+) {
+ const deprecatedSelectors = typeof deprecatedSelectorOption === 'string' ? deprecatedSelectorOption.split(',') : [];
+
+ const allSelectors = [
+ ...selectors,
+ // @deprecated
+ ...deprecatedSelectors,
+
+ // sentry defaults
+ ...defaultSelectors,
+ ];
+
+ // @deprecated
+ if (typeof deprecatedClassOption !== 'undefined') {
+ // NOTE: No support for RegExp
+ if (typeof deprecatedClassOption === 'string') {
+ allSelectors.push(`.${deprecatedClassOption}`);
+ }
+
+ // eslint-disable-next-line no-console
+ console.warn(
+ '[Replay] You are using a deprecated configuration item for privacy. Read the documentation on how to use the new privacy configuration.',
+ );
+ }
+
+ return allSelectors.join(',');
+}
+
+/**
+ * Returns privacy related configuration for use in rrweb
+ */
+function getPrivacyOptions({
+ mask,
+ unmask,
+ block,
+ unblock,
+ ignore,
+
+ // eslint-disable-next-line deprecation/deprecation
+ blockClass,
+ // eslint-disable-next-line deprecation/deprecation
+ blockSelector,
+ // eslint-disable-next-line deprecation/deprecation
+ maskTextClass,
+ // eslint-disable-next-line deprecation/deprecation
+ maskTextSelector,
+ // eslint-disable-next-line deprecation/deprecation
+ ignoreClass,
+}) {
+ const defaultBlockedElements = ['base[href="/"]'];
+
+ const maskSelector = getOption(mask, ['.sentry-mask', '[data-sentry-mask]'], maskTextClass, maskTextSelector);
+ const unmaskSelector = getOption(unmask, ['.sentry-unmask', '[data-sentry-unmask]']);
+
+ const options = {
+ // We are making the decision to make text and input selectors the same
+ maskTextSelector: maskSelector,
+ unmaskTextSelector: unmaskSelector,
+ maskInputSelector: maskSelector,
+ unmaskInputSelector: unmaskSelector,
+
+ blockSelector: getOption(
+ block,
+ ['.sentry-block', '[data-sentry-block]', ...defaultBlockedElements],
+ blockClass,
+ blockSelector,
+ ),
+ unblockSelector: getOption(unblock, ['.sentry-unblock', '[data-sentry-unblock]']),
+ ignoreSelector: getOption(ignore, ['.sentry-ignore', '[data-sentry-ignore]', 'input[type="file"]'], ignoreClass),
+ };
+
+ if (blockClass instanceof RegExp) {
+ options.blockClass = blockClass;
+ }
+
+ if (maskTextClass instanceof RegExp) {
+ options.maskTextClass = maskTextClass;
+ }
+
+ return options;
+}
+
+/**
+ * Returns true if we are in the browser.
+ */
+function isBrowser() {
+ // eslint-disable-next-line no-restricted-globals
+ return typeof window !== 'undefined' && (!isNodeEnv() || isElectronNodeRenderer());
+}
+
+// Electron renderers with nodeIntegration enabled are detected as Node.js so we specifically test for them
+function isElectronNodeRenderer() {
+ return typeof process !== 'undefined' && (process ).type === 'renderer';
+}
+
+const MEDIA_SELECTORS =
+ 'img,image,svg,video,object,picture,embed,map,audio,link[rel="icon"],link[rel="apple-touch-icon"]';
+
+const DEFAULT_NETWORK_HEADERS = ['content-length', 'content-type', 'accept'];
+
+let _initialized = false;
+
+/**
+ * The main replay integration class, to be passed to `init({ integrations: [] })`.
+ */
+class Replay {
+ /**
+ * @inheritDoc
+ */
+ static __initStatic() {this.id = 'Replay';}
+
+ /**
+ * @inheritDoc
+ */
+ __init() {this.name = Replay.id;}
+
+ /**
+ * Options to pass to `rrweb.record()`
+ */
+
+ /**
+ * Initial options passed to the replay integration, merged with default values.
+ * Note: `sessionSampleRate` and `errorSampleRate` are not required here, as they
+ * can only be finally set when setupOnce() is called.
+ *
+ * @private
+ */
+
+ constructor({
+ flushMinDelay = DEFAULT_FLUSH_MIN_DELAY,
+ flushMaxDelay = DEFAULT_FLUSH_MAX_DELAY,
+ stickySession = true,
+ useCompression = true,
+ _experiments = {},
+ sessionSampleRate,
+ errorSampleRate,
+ maskAllText = true,
+ maskAllInputs = true,
+ blockAllMedia = true,
+
+ mutationBreadcrumbLimit = 750,
+ mutationLimit = 10000,
+
+ slowClickTimeout = 7000,
+ slowClickIgnoreSelectors = [],
+
+ networkDetailAllowUrls = [],
+ networkCaptureBodies = true,
+ networkRequestHeaders = [],
+ networkResponseHeaders = [],
+
+ mask = [],
+ unmask = [],
+ block = [],
+ unblock = [],
+ ignore = [],
+ maskFn,
+
+ beforeAddRecordingEvent,
+
+ // eslint-disable-next-line deprecation/deprecation
+ blockClass,
+ // eslint-disable-next-line deprecation/deprecation
+ blockSelector,
+ // eslint-disable-next-line deprecation/deprecation
+ maskInputOptions,
+ // eslint-disable-next-line deprecation/deprecation
+ maskTextClass,
+ // eslint-disable-next-line deprecation/deprecation
+ maskTextSelector,
+ // eslint-disable-next-line deprecation/deprecation
+ ignoreClass,
+ } = {}) {Replay.prototype.__init.call(this);
+ this._recordingOptions = {
+ maskAllInputs,
+ maskAllText,
+ maskInputOptions: { ...(maskInputOptions || {}), password: true },
+ maskTextFn: maskFn,
+ maskInputFn: maskFn,
+
+ ...getPrivacyOptions({
+ mask,
+ unmask,
+ block,
+ unblock,
+ ignore,
+ blockClass,
+ blockSelector,
+ maskTextClass,
+ maskTextSelector,
+ ignoreClass,
+ }),
+
+ // Our defaults
+ slimDOMOptions: 'all',
+ inlineStylesheet: true,
+ // Disable inline images as it will increase segment/replay size
+ inlineImages: false,
+ // collect fonts, but be aware that `sentry.io` needs to be an allowed
+ // origin for playback
+ collectFonts: true,
+ };
+
+ this._initialOptions = {
+ flushMinDelay,
+ flushMaxDelay,
+ stickySession,
+ sessionSampleRate,
+ errorSampleRate,
+ useCompression,
+ blockAllMedia,
+ maskAllInputs,
+ maskAllText,
+ mutationBreadcrumbLimit,
+ mutationLimit,
+ slowClickTimeout,
+ slowClickIgnoreSelectors,
+ networkDetailAllowUrls,
+ networkCaptureBodies,
+ networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
+ networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
+ beforeAddRecordingEvent,
+
+ _experiments,
+ };
+
+ if (typeof sessionSampleRate === 'number') {
+ // eslint-disable-next-line
+ console.warn(
+ `[Replay] You are passing \`sessionSampleRate\` to the Replay integration.
+This option is deprecated and will be removed soon.
+Instead, configure \`replaysSessionSampleRate\` directly in the SDK init options, e.g.:
+Sentry.init({ replaysSessionSampleRate: ${sessionSampleRate} })`,
+ );
+
+ this._initialOptions.sessionSampleRate = sessionSampleRate;
+ }
+
+ if (typeof errorSampleRate === 'number') {
+ // eslint-disable-next-line
+ console.warn(
+ `[Replay] You are passing \`errorSampleRate\` to the Replay integration.
+This option is deprecated and will be removed soon.
+Instead, configure \`replaysOnErrorSampleRate\` directly in the SDK init options, e.g.:
+Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
+ );
+
+ this._initialOptions.errorSampleRate = errorSampleRate;
+ }
+
+ if (this._initialOptions.blockAllMedia) {
+ // `blockAllMedia` is a more user friendly option to configure blocking
+ // embedded media elements
+ this._recordingOptions.blockSelector = !this._recordingOptions.blockSelector
+ ? MEDIA_SELECTORS
+ : `${this._recordingOptions.blockSelector},${MEDIA_SELECTORS}`;
+ }
+
+ if (this._isInitialized && isBrowser()) {
+ throw new Error('Multiple Sentry Session Replay instances are not supported');
+ }
+
+ this._isInitialized = true;
+ }
+
+ /** If replay has already been initialized */
+ get _isInitialized() {
+ return _initialized;
+ }
+
+ /** Update _isInitialized */
+ set _isInitialized(value) {
+ _initialized = value;
+ }
+
+ /**
+ * Setup and initialize replay container
+ */
+ setupOnce() {
+ if (!isBrowser()) {
+ return;
+ }
+
+ this._setup();
+
+ // Once upon a time, we tried to create a transaction in `setupOnce` and it would
+ // potentially create a transaction before some native SDK integrations have run
+ // and applied their own global event processor. An example is:
+ // https://github.com/getsentry/sentry-javascript/blob/b47ceafbdac7f8b99093ce6023726ad4687edc48/packages/browser/src/integrations/useragent.ts
+ //
+ // So we call `this._initialize()` in next event loop as a workaround to wait for other
+ // global event processors to finish. This is no longer needed, but keeping it
+ // here to avoid any future issues.
+ setTimeout(() => this._initialize());
+ }
+
+ /**
+ * Start a replay regardless of sampling rate. Calling this will always
+ * create a new session. Will throw an error if replay is already in progress.
+ *
+ * Creates or loads a session, attaches listeners to varying events (DOM,
+ * PerformanceObserver, Recording, Sentry SDK, etc)
+ */
+ start() {
+ if (!this._replay) {
+ return;
+ }
+
+ this._replay.start();
+ }
+
+ /**
+ * Start replay buffering. Buffers until `flush()` is called or, if
+ * `replaysOnErrorSampleRate` > 0, until an error occurs.
+ */
+ startBuffering() {
+ if (!this._replay) {
+ return;
+ }
+
+ this._replay.startBuffering();
+ }
+
+ /**
+ * Currently, this needs to be manually called (e.g. for tests). Sentry SDK
+ * does not support a teardown
+ */
+ stop() {
+ if (!this._replay) {
+ return Promise.resolve();
+ }
+
+ return this._replay.stop();
+ }
+
+ /**
+ * If not in "session" recording mode, flush event buffer which will create a new replay.
+ * Unless `continueRecording` is false, the replay will continue to record and
+ * behave as a "session"-based replay.
+ *
+ * Otherwise, queue up a flush.
+ */
+ flush(options) {
+ if (!this._replay || !this._replay.isEnabled()) {
+ return Promise.resolve();
+ }
+
+ return this._replay.sendBufferedReplayOrFlush(options);
+ }
+
+ /**
+ * Get the current session ID.
+ */
+ getReplayId() {
+ if (!this._replay || !this._replay.isEnabled()) {
+ return;
+ }
+
+ return this._replay.getSessionId();
+ }
+ /**
+ * Initializes replay.
+ */
+ _initialize() {
+ if (!this._replay) {
+ return;
+ }
+
+ this._replay.initializeSampling();
+ }
+
+ /** Setup the integration. */
+ _setup() {
+ // Client is not available in constructor, so we need to wait until setupOnce
+ const finalOptions = loadReplayOptionsFromClient(this._initialOptions);
+
+ this._replay = new ReplayContainer({
+ options: finalOptions,
+ recordingOptions: this._recordingOptions,
+ });
+ }
+} Replay.__initStatic();
+
+/** Parse Replay-related options from SDK options */
+function loadReplayOptionsFromClient(initialOptions) {
+ const client = getCurrentHub().getClient();
+ const opt = client && (client.getOptions() );
+
+ const finalOptions = { sessionSampleRate: 0, errorSampleRate: 0, ...dropUndefinedKeys(initialOptions) };
+
+ if (!opt) {
+ // eslint-disable-next-line no-console
+ console.warn('SDK client is not available.');
+ return finalOptions;
+ }
+
+ if (
+ initialOptions.sessionSampleRate == null && // TODO remove once deprecated rates are removed
+ initialOptions.errorSampleRate == null && // TODO remove once deprecated rates are removed
+ opt.replaysSessionSampleRate == null &&
+ opt.replaysOnErrorSampleRate == null
+ ) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'Replay is disabled because neither `replaysSessionSampleRate` nor `replaysOnErrorSampleRate` are set.',
+ );
+ }
+
+ if (typeof opt.replaysSessionSampleRate === 'number') {
+ finalOptions.sessionSampleRate = opt.replaysSessionSampleRate;
+ }
+
+ if (typeof opt.replaysOnErrorSampleRate === 'number') {
+ finalOptions.errorSampleRate = opt.replaysOnErrorSampleRate;
+ }
+
+ return finalOptions;
+}
+
+function _getMergedNetworkHeaders(headers) {
+ return [...DEFAULT_NETWORK_HEADERS, ...headers.map(header => header.toLowerCase())];
+}
+
+export { Replay };
+//# sourceMappingURL=index.js.map