@lookit/lookit-initjspsych
Version:
This package overloads jsPsych's init function.
20,126 lines • 689 kB
JavaScript
var chsInitJsPsych = (function (jspsychModule, Api, chsTemplates) {
'use strict';
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var jspsychModule__namespace = /*#__PURE__*/_interopNamespaceDefault(jspsychModule);
class UndefinedTypeError extends Error {
constructor(object) {
super(
`A trial object in the timeline has an undefined type. Maybe the type name is misspelled, or the plugin you want to use is not supported. Object: ${JSON.stringify(object)}.`
);
}
}
class UndefinedTimelineError extends Error {
constructor(el) {
super(
`An element in the timeline is not structured correctly or is missing required information. It may be a timeline node with a timeline array that is the wrong type or missing/undefined, or a trial object with a missing type. Element: ${JSON.stringify(el)}`
);
}
}
class NoJsPsychInstanceError extends Error {
constructor() {
super("No jsPsych instance available for on_data_update.");
this.name = "NoJsPsychInstanceError";
}
}
const DEBUG_BUILD$3 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
const GLOBAL_OBJ = globalThis;
const SDK_VERSION = "10.73.0" ;
function getMainCarrier() {
getSentryCarrier(GLOBAL_OBJ);
return GLOBAL_OBJ;
}
function getSentryCarrier(carrier) {
const __SENTRY__ = carrier.__SENTRY__ = carrier.__SENTRY__ || {};
__SENTRY__.version = __SENTRY__.version || SDK_VERSION;
return __SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {};
}
function getGlobalSingleton(name, creator, obj = GLOBAL_OBJ) {
const __SENTRY__ = obj.__SENTRY__ = obj.__SENTRY__ || {};
const carrier = __SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {};
return carrier[name] || (carrier[name] = creator());
}
const CONSOLE_LEVELS$1 = [
"debug",
"info",
"warn",
"error",
"log",
"assert",
"trace"
];
const PREFIX$1 = "Sentry Logger ";
const originalConsoleMethods = {};
function consoleSandbox(callback) {
if (!("console" in GLOBAL_OBJ)) {
return callback();
}
const console = GLOBAL_OBJ.console;
const wrappedFuncs = {};
const wrappedLevels = Object.keys(originalConsoleMethods);
wrappedLevels.forEach((level) => {
const originalConsoleMethod = originalConsoleMethods[level];
wrappedFuncs[level] = console[level];
console[level] = originalConsoleMethod;
});
try {
return callback();
} finally {
wrappedLevels.forEach((level) => {
console[level] = wrappedFuncs[level];
});
}
}
function enable() {
_getLoggerSettings().enabled = true;
}
function disable() {
_getLoggerSettings().enabled = false;
}
function isEnabled$1() {
return _getLoggerSettings().enabled;
}
function log(...args) {
_maybeLog("log", ...args);
}
function warn(...args) {
_maybeLog("warn", ...args);
}
function error(...args) {
_maybeLog("error", ...args);
}
function _maybeLog(level, ...args) {
if (!DEBUG_BUILD$3) {
return;
}
if (isEnabled$1()) {
consoleSandbox(() => {
GLOBAL_OBJ.console[level](`${PREFIX$1}[${level}]:`, ...args);
});
}
}
function _getLoggerSettings() {
if (!DEBUG_BUILD$3) {
return { enabled: false };
}
return getGlobalSingleton("loggerSettings", () => ({ enabled: false }));
}
const debug$1 = {
/** Enable logging. */
enable,
/** Disable logging. */
disable,
/** Check if logging is enabled. */
isEnabled: isEnabled$1,
/** Log a message. */
log,
/** Log a warning. */
warn,
/** Log an error. */
error
};
const STACKTRACE_FRAME_LIMIT = 50;
const UNKNOWN_FUNCTION = "?";
const WEBPACK_ERROR_REGEXP = /\(error: (.*)\)/;
const STRIP_FRAME_REGEXP = /captureMessage|captureException/;
function createStackParser(...parsers) {
const sortedParsers = parsers.sort((a, b) => a[0] - b[0]).map((p) => p[1]);
return (stack, skipFirstLines = 0, framesToPop = 0) => {
const frames = [];
const lines = stack.split("\n");
for (let i = skipFirstLines; i < lines.length; i++) {
let line = lines[i];
if (line.length > 1024) {
line = line.slice(0, 1024);
}
const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, "$1") : line;
if (cleanedLine.includes("Error: ")) {
continue;
}
for (const parser of sortedParsers) {
const frame = parser(cleanedLine);
if (frame) {
frames.push(frame);
break;
}
}
if (frames.length >= STACKTRACE_FRAME_LIMIT + framesToPop) {
break;
}
}
return stripSentryFramesAndReverse(frames.slice(framesToPop));
};
}
function stackParserFromStackParserOptions(stackParser) {
if (Array.isArray(stackParser)) {
return createStackParser(...stackParser);
}
return stackParser;
}
function stripSentryFramesAndReverse(stack) {
if (!stack.length) {
return [];
}
const localStack = Array.from(stack);
if (/sentryWrapped/.test(getLastStackFrame(localStack).function || "")) {
localStack.pop();
}
localStack.reverse();
if (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || "")) {
localStack.pop();
if (STRIP_FRAME_REGEXP.test(getLastStackFrame(localStack).function || "")) {
localStack.pop();
}
}
return localStack.slice(0, STACKTRACE_FRAME_LIMIT).map((frame) => ({
...frame,
filename: frame.filename || getLastStackFrame(localStack).filename,
function: frame.function || UNKNOWN_FUNCTION
}));
}
function getLastStackFrame(arr) {
return arr[arr.length - 1] || {};
}
const defaultFunctionName = "<anonymous>";
function getFunctionName(fn) {
try {
if (!fn || typeof fn !== "function") {
return defaultFunctionName;
}
return fn.name || defaultFunctionName;
} catch {
return defaultFunctionName;
}
}
function getFramesFromEvent(event) {
const exception = event.exception;
if (exception) {
const frames = [];
try {
exception.values.forEach((value) => {
if (value.stacktrace.frames) {
frames.push(...value.stacktrace.frames);
}
});
return frames;
} catch {
return void 0;
}
}
return void 0;
}
const handlers$2 = {};
const instrumented$1 = {};
function addHandler$1(type, handler) {
handlers$2[type] = handlers$2[type] || [];
handlers$2[type].push(handler);
return () => {
const typeHandlers = handlers$2[type];
if (typeHandlers) {
const index = typeHandlers.indexOf(handler);
if (index !== -1) {
typeHandlers.splice(index, 1);
}
}
};
}
function maybeInstrument(type, instrumentFn) {
if (!instrumented$1[type]) {
instrumented$1[type] = true;
try {
instrumentFn();
} catch (e) {
DEBUG_BUILD$3 && debug$1.error(`Error while instrumenting ${type}`, e);
}
}
}
function triggerHandlers$1(type, data) {
const typeHandlers = type && handlers$2[type];
if (!typeHandlers) {
return;
}
for (const handler of typeHandlers) {
try {
handler(data);
} catch (e) {
DEBUG_BUILD$3 && debug$1.error(
`Error while triggering instrumentation handler.
Type: ${type}
Name: ${getFunctionName(handler)}
Error:`,
e
);
}
}
}
let _oldOnErrorHandler = null;
function addGlobalErrorInstrumentationHandler(handler) {
const type = "error";
addHandler$1(type, handler);
maybeInstrument(type, instrumentError);
}
function instrumentError() {
_oldOnErrorHandler = GLOBAL_OBJ.onerror;
GLOBAL_OBJ.onerror = function(msg, url, line, column, error) {
const handlerData = {
column,
error,
line,
msg,
url
};
triggerHandlers$1("error", handlerData);
if (_oldOnErrorHandler) {
return _oldOnErrorHandler.apply(this, arguments);
}
return false;
};
GLOBAL_OBJ.onerror.__SENTRY_INSTRUMENTED__ = true;
}
let _oldOnUnhandledRejectionHandler = null;
function addGlobalUnhandledRejectionInstrumentationHandler(handler) {
const type = "unhandledrejection";
addHandler$1(type, handler);
maybeInstrument(type, instrumentUnhandledRejection);
}
function instrumentUnhandledRejection() {
_oldOnUnhandledRejectionHandler = GLOBAL_OBJ.onunhandledrejection;
GLOBAL_OBJ.onunhandledrejection = function(e) {
const handlerData = e;
triggerHandlers$1("unhandledrejection", handlerData);
if (_oldOnUnhandledRejectionHandler) {
return _oldOnUnhandledRejectionHandler.apply(this, arguments);
}
return true;
};
GLOBAL_OBJ.onunhandledrejection.__SENTRY_INSTRUMENTED__ = true;
}
const objectToString = Object.prototype.toString;
function isError(wat) {
switch (objectToString.call(wat)) {
case "[object Error]":
case "[object Exception]":
case "[object DOMException]":
case "[object WebAssembly.Exception]":
return true;
default:
return isInstanceOf(wat, Error);
}
}
function isBuiltin(wat, className) {
return objectToString.call(wat) === `[object ${className}]`;
}
function isErrorEvent$2(wat) {
return isBuiltin(wat, "ErrorEvent");
}
function isDOMError(wat) {
return isBuiltin(wat, "DOMError");
}
function isDOMException(wat) {
return isBuiltin(wat, "DOMException");
}
function isString(wat) {
return isBuiltin(wat, "String");
}
function isParameterizedString(wat) {
return typeof wat === "object" && wat !== null && "__sentry_template_string__" in wat && "__sentry_template_values__" in wat;
}
function isPrimitive(wat) {
return wat === null || isParameterizedString(wat) || typeof wat !== "object" && typeof wat !== "function";
}
function isPlainObject(wat) {
return isBuiltin(wat, "Object");
}
function isObjectLike(wat) {
return typeof wat === "object" && wat !== null;
}
function isEvent(wat) {
return typeof Event !== "undefined" && isInstanceOf(wat, Event);
}
function isRegExp(wat) {
return isBuiltin(wat, "RegExp");
}
function isThenable(wat) {
return Boolean(wat?.then && typeof wat.then === "function");
}
function isInstanceOf(wat, base) {
try {
return wat instanceof base;
} catch {
return false;
}
}
function isRequest(request) {
return typeof Request !== "undefined" && isInstanceOf(request, Request);
}
function fill(source, name, replacementFactory) {
if (!(name in source)) {
return;
}
const original = source[name];
if (typeof original !== "function") {
return;
}
const wrapped = replacementFactory(original);
if (typeof wrapped === "function") {
markFunctionWrapped(wrapped, original);
}
try {
source[name] = wrapped;
} catch {
DEBUG_BUILD$3 && debug$1.log(`Failed to replace method "${name}" in object`, source);
}
}
function addNonEnumerableProperty(obj, name, value) {
try {
Object.defineProperty(obj, name, {
// enumerable: false, // the default, so we can save on bundle size by not explicitly setting it
value,
writable: true,
configurable: true
});
} catch {
DEBUG_BUILD$3 && debug$1.log(`Failed to add non-enumerable property "${String(name)}" to object`, obj);
}
}
function markFunctionWrapped(wrapped, original) {
try {
const proto = original.prototype || {};
wrapped.prototype = original.prototype = proto;
addNonEnumerableProperty(wrapped, "__sentry_original__", original);
} catch {
}
}
function getOriginalFunction(func) {
return func.__sentry_original__;
}
function convertToPlainObject(value) {
if (isError(value)) {
return {
message: value.message,
name: value.name,
stack: value.stack,
...getOwnProperties(value)
};
}
if (isEvent(value)) {
const { type, target, currentTarget, detail } = value;
return {
type,
target,
currentTarget,
...detail ? { detail } : {},
...getOwnProperties(value)
};
}
return value;
}
function getOwnProperties(obj) {
if (isObjectLike(obj)) {
return Object.fromEntries(Object.entries(obj));
}
return {};
}
function extractExceptionKeysForMessage(exception) {
const keys = Object.keys(convertToPlainObject(exception));
keys.sort();
return !keys[0] ? "[object has no keys]" : keys.join(", ");
}
let RESOLVED_RUNNER;
function withRandomSafeContext(cb) {
if (RESOLVED_RUNNER !== void 0) {
return RESOLVED_RUNNER ? RESOLVED_RUNNER(cb) : cb();
}
const sym = /* @__PURE__ */ Symbol.for("__SENTRY_SAFE_RANDOM_ID_WRAPPER__");
const globalWithSymbol = GLOBAL_OBJ;
if (sym in globalWithSymbol && typeof globalWithSymbol[sym] === "function") {
RESOLVED_RUNNER = globalWithSymbol[sym];
return RESOLVED_RUNNER(cb);
}
RESOLVED_RUNNER = null;
return cb();
}
function safeMathRandom() {
return withRandomSafeContext(() => Math.random());
}
function safeDateNow() {
return withRandomSafeContext(() => Date.now());
}
const SENTRY_SKIP_NORMALIZATION = /* @__PURE__ */ Symbol.for("sentry.skipNormalization");
const SENTRY_OVERRIDE_NORMALIZATION_DEPTH = /* @__PURE__ */ Symbol.for("sentry.overrideNormalizationDepth");
function hasSkipNormalizationHint(value) {
return Boolean(value[SENTRY_SKIP_NORMALIZATION]);
}
function getNormalizationDepthOverrideHint(value) {
const v = value[SENTRY_OVERRIDE_NORMALIZATION_DEPTH];
return typeof v === "number" ? v : void 0;
}
let stringifier;
function setNormalizeStringifier(newStringifier) {
stringifier = newStringifier;
}
function normalize(input, depth = 100, maxProperties = Infinity) {
try {
return visit("", input, depth, maxProperties);
} catch (err) {
return { ERROR: `**non-serializable** (${err})` };
}
}
function normalizeToSize(object, depth = 3, maxSize = 100 * 1024) {
const normalized = normalize(object, depth);
if (jsonSize(normalized) > maxSize) {
return normalizeToSize(object, depth - 1, maxSize);
}
return normalized;
}
function visit(key, value, depth = Infinity, maxProperties = Infinity, memo = memoBuilder()) {
const [memoize, unmemoize] = memo;
if (value == null || // this matches null and undefined -> eqeq not eqeqeq
["boolean", "string"].includes(typeof value) || typeof value === "number" && Number.isFinite(value)) {
return value;
}
const stringified = stringifyValue(key, value);
if (!stringified.startsWith("[object ")) {
return stringified;
}
if (hasSkipNormalizationHint(value)) {
return value;
}
const overrideDepth = getNormalizationDepthOverrideHint(value);
const remainingDepth = overrideDepth !== void 0 ? overrideDepth : depth;
if (remainingDepth === 0) {
return stringified.replace("object ", "");
}
if (memoize(value)) {
return "[Circular ~]";
}
const valueWithToJSON = value;
if (valueWithToJSON && typeof valueWithToJSON.toJSON === "function") {
try {
const jsonValue = valueWithToJSON.toJSON();
return visit("", jsonValue, remainingDepth - 1, maxProperties, memo);
} catch {
}
}
const normalized = Array.isArray(value) ? [] : {};
let numAdded = 0;
const visitable = convertToPlainObject(value);
for (const visitKey in visitable) {
if (!Object.prototype.hasOwnProperty.call(visitable, visitKey)) {
continue;
}
if (numAdded >= maxProperties) {
normalized[visitKey] = "[MaxProperties ~]";
break;
}
const visitValue = visitable[visitKey];
normalized[visitKey] = visit(visitKey, visitValue, remainingDepth - 1, maxProperties, memo);
numAdded++;
}
unmemoize(value);
return normalized;
}
function stringifyValue(key, value) {
try {
if (stringifier) {
const stringified = stringifier(value);
if (stringified) {
return stringified;
}
}
if (typeof global !== "undefined" && value === global) {
return "[Global]";
}
if (typeof value === "number" && !Number.isFinite(value)) {
return `[${value}]`;
}
if (typeof value === "function") {
return `[Function: ${getFunctionName(value)}]`;
}
if (typeof value === "symbol") {
return `[${String(value)}]`;
}
if (typeof value === "bigint") {
return `[BigInt: ${String(value)}]`;
}
const objName = getConstructorName$1(value);
return `[object ${objName}]`;
} catch (err) {
return `**non-serializable** (${err})`;
}
}
function getConstructorName$1(value) {
const prototype = Object.getPrototypeOf(value);
return prototype?.constructor ? prototype.constructor.name : "null prototype";
}
function utf8Length(value) {
return ~-encodeURI(value).split(/%..|./).length;
}
function jsonSize(value) {
return utf8Length(JSON.stringify(value));
}
function memoBuilder() {
const inner = /* @__PURE__ */ new WeakSet();
function memoize(obj) {
if (inner.has(obj)) {
return true;
}
inner.add(obj);
return false;
}
function unmemoize(obj) {
inner.delete(obj);
}
return [memoize, unmemoize];
}
function truncate(str, max = 0) {
if (typeof str !== "string" || max === 0) {
return str;
}
return str.length <= max ? str : `${str.slice(0, max)}...`;
}
function safeJoin(input, delimiter) {
if (!Array.isArray(input)) {
return "";
}
const output = [];
for (let i = 0; i < input.length; i++) {
const value = input[i];
if (isPrimitive(value)) {
output.push(String(value));
} else if (value instanceof Error) {
output.push(value.message ? `${value.name}: ${value.message}` : value.name);
} else {
output.push(stringifyValue(void 0, value));
}
}
return output.join(delimiter);
}
function isMatchingPattern(value, pattern, requireExactStringMatch = false) {
if (!isString(value)) {
return false;
}
if (isRegExp(pattern)) {
return pattern.test(value);
}
if (isString(pattern)) {
return requireExactStringMatch ? value === pattern : value.includes(pattern);
}
if (typeof pattern === "function") {
return pattern(value);
}
return false;
}
function stringMatchesSomePattern(testString, patterns = [], requireExactStringMatch = false) {
for (const pattern of patterns) {
if (isMatchingPattern(testString, pattern, requireExactStringMatch)) {
return true;
}
}
return false;
}
function getCrypto() {
const gbl = GLOBAL_OBJ;
return gbl.crypto || gbl.msCrypto;
}
let emptyUuid;
function getRandomByte() {
return safeMathRandom() * 16;
}
function uuid4(crypto = getCrypto()) {
try {
if (crypto?.randomUUID) {
return withRandomSafeContext(() => crypto.randomUUID()).replace(/-/g, "");
}
} catch {
}
if (!emptyUuid) {
emptyUuid = "10000000100040008000" + 1e11;
}
return emptyUuid.replace(
/[018]/g,
(c) => (
// eslint-disable-next-line no-bitwise
(c ^ (getRandomByte() & 15) >> c / 4).toString(16)
)
);
}
function getFirstException(event) {
return event.exception?.values?.[0];
}
function getEventDescription(event) {
const { message, event_id: eventId } = event;
if (message) {
return message;
}
const firstException = getFirstException(event);
if (firstException) {
if (firstException.type && firstException.value) {
return `${firstException.type}: ${firstException.value}`;
}
return firstException.type || firstException.value || eventId || "<unknown>";
}
return eventId || "<unknown>";
}
function addExceptionTypeValue(event, value, type) {
const exception = event.exception = event.exception || {};
const values = exception.values = exception.values || [];
const firstException = values[0] = values[0] || {};
if (!firstException.value) {
firstException.value = value || "";
}
if (!firstException.type) {
firstException.type = type || "Error";
}
}
function addExceptionMechanism(event, newMechanism) {
const firstException = getFirstException(event);
if (!firstException) {
return;
}
const defaultMechanism = { type: "generic", handled: true };
const currentMechanism = firstException.mechanism;
firstException.mechanism = { ...defaultMechanism, ...currentMechanism, ...newMechanism };
if (newMechanism && "data" in newMechanism) {
const mergedData = { ...currentMechanism?.data, ...newMechanism.data };
firstException.mechanism.data = mergedData;
}
}
function checkOrSetAlreadyCaught(exception) {
if (isAlreadyCaptured(exception)) {
return true;
}
try {
addNonEnumerableProperty(exception, "__sentry_captured__", true);
} catch {
}
return false;
}
function isAlreadyCaptured(exception) {
try {
return exception.__sentry_captured__;
} catch {
}
}
const ONE_SECOND_IN_MS = 1e3;
function dateTimestampInSeconds() {
return safeDateNow() / ONE_SECOND_IN_MS;
}
function createUnixTimestampInSecondsFunc() {
const { performance } = GLOBAL_OBJ;
if (!performance?.now || !performance.timeOrigin) {
return dateTimestampInSeconds;
}
const timeOrigin = performance.timeOrigin;
return () => {
return (timeOrigin + withRandomSafeContext(() => performance.now())) / ONE_SECOND_IN_MS;
};
}
let _cachedTimestampInSeconds;
function timestampInSeconds() {
const func = _cachedTimestampInSeconds ?? (_cachedTimestampInSeconds = createUnixTimestampInSecondsFunc());
return func();
}
let cachedTimeOrigin = null;
function getBrowserTimeOrigin() {
const { performance } = GLOBAL_OBJ;
if (!performance?.now) {
return void 0;
}
const threshold = 3e5;
const performanceNow = withRandomSafeContext(() => performance.now());
const dateNow = safeDateNow();
const timeOrigin = performance.timeOrigin;
if (typeof timeOrigin === "number") {
const timeOriginDelta = Math.abs(timeOrigin + performanceNow - dateNow);
if (timeOriginDelta < threshold) {
return timeOrigin;
}
}
const navigationStart = performance.timing?.navigationStart;
if (typeof navigationStart === "number") {
const navigationStartDelta = Math.abs(navigationStart + performanceNow - dateNow);
if (navigationStartDelta < threshold) {
return navigationStart;
}
}
return dateNow - performanceNow;
}
function browserPerformanceTimeOrigin() {
if (cachedTimeOrigin === null) {
cachedTimeOrigin = getBrowserTimeOrigin();
}
return cachedTimeOrigin;
}
function makeSession$1(context) {
const startingTime = timestampInSeconds();
const session = {
sid: uuid4(),
init: true,
timestamp: startingTime,
started: startingTime,
duration: 0,
status: "ok",
errors: 0,
ignoreDuration: false,
toJSON: () => sessionToJSON(session)
};
if (context) {
updateSession(session, context);
}
return session;
}
function updateSession(session, context = {}) {
if (context.user) {
if (!session.ipAddress && context.user.ip_address) {
session.ipAddress = context.user.ip_address;
}
if (!session.did && !context.did) {
session.did = context.user.id || context.user.email || context.user.username;
}
}
session.timestamp = context.timestamp || timestampInSeconds();
if (context.abnormal_mechanism) {
session.abnormal_mechanism = context.abnormal_mechanism;
}
if (context.ignoreDuration) {
session.ignoreDuration = context.ignoreDuration;
}
if (context.sid) {
session.sid = context.sid.length === 32 ? context.sid : uuid4();
}
if (context.init !== void 0) {
session.init = context.init;
}
if (!session.did && context.did) {
session.did = `${context.did}`;
}
if (typeof context.started === "number") {
session.started = context.started;
}
if (session.ignoreDuration) {
session.duration = void 0;
} else if (typeof context.duration === "number") {
session.duration = context.duration;
} else {
const duration = session.timestamp - session.started;
session.duration = duration >= 0 ? duration : 0;
}
if (context.release) {
session.release = context.release;
}
if (context.environment) {
session.environment = context.environment;
}
if (!session.ipAddress && context.ipAddress) {
session.ipAddress = context.ipAddress;
}
if (!session.userAgent && context.userAgent) {
session.userAgent = context.userAgent;
}
if (typeof context.errors === "number") {
session.errors = context.errors;
}
if (context.status) {
session.status = context.status;
}
}
function closeSession(session, status) {
let context = {};
if (status) {
context = { status };
} else if (session.status === "ok") {
context = { status: "exited" };
}
updateSession(session, context);
}
function sessionToJSON(session) {
return {
sid: `${session.sid}`,
init: session.init,
// Make sure that sec is converted to ms for date constructor
started: new Date(session.started * 1e3).toISOString(),
timestamp: new Date(session.timestamp * 1e3).toISOString(),
status: session.status,
errors: session.errors,
did: typeof session.did === "number" || typeof session.did === "string" ? `${session.did}` : void 0,
duration: session.duration,
abnormal_mechanism: session.abnormal_mechanism,
attrs: {
release: session.release,
environment: session.environment,
ip_address: session.ipAddress,
user_agent: session.userAgent
}
};
}
function merge(initialObj, mergeObj, levels = 2) {
if (!mergeObj || typeof mergeObj !== "object" || levels <= 0) {
return mergeObj;
}
if (initialObj && Object.keys(mergeObj).length === 0) {
return initialObj;
}
const output = { ...initialObj };
for (const key in mergeObj) {
if (Object.prototype.hasOwnProperty.call(mergeObj, key)) {
output[key] = merge(output[key], mergeObj[key], levels - 1);
}
}
return output;
}
function generateTraceId() {
return uuid4();
}
function generateSpanId() {
return uuid4().substring(16);
}
function makeWeakRef(value) {
try {
const WeakRefImpl = GLOBAL_OBJ.WeakRef;
if (typeof WeakRefImpl === "function") {
return new WeakRefImpl(value);
}
} catch {
}
return value;
}
function derefWeakRef(ref) {
if (!ref) {
return void 0;
}
if (typeof ref === "object" && "deref" in ref && typeof ref.deref === "function") {
try {
return ref.deref();
} catch {
return void 0;
}
}
return ref;
}
const SCOPE_SPAN_FIELD = "_sentrySpan";
function _setSpanForScope(scope, span) {
if (span) {
addNonEnumerableProperty(scope, SCOPE_SPAN_FIELD, makeWeakRef(span));
} else {
delete scope[SCOPE_SPAN_FIELD];
}
}
function _getSpanForScope(scope) {
return derefWeakRef(scope[SCOPE_SPAN_FIELD]);
}
const DEFAULT_MAX_BREADCRUMBS = 100;
class Scope {
// NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
constructor() {
this._notifyingListeners = false;
this._scopeListeners = [];
this._eventProcessors = [];
this._breadcrumbs = [];
this._attachments = [];
this._user = {};
this._tags = {};
this._attributes = {};
this._extra = {};
this._contexts = {};
this._sdkProcessingMetadata = {};
this._propagationContext = {
traceId: generateTraceId(),
sampleRand: safeMathRandom()
};
}
/**
* Clone all data from this scope into a new scope.
*/
clone() {
const newScope = new Scope();
newScope._breadcrumbs = [...this._breadcrumbs];
newScope._tags = { ...this._tags };
newScope._attributes = { ...this._attributes };
newScope._extra = { ...this._extra };
newScope._contexts = { ...this._contexts };
if (this._contexts.flags) {
newScope._contexts.flags = {
values: [...this._contexts.flags.values]
};
}
newScope._user = this._user;
newScope._level = this._level;
newScope._session = this._session;
newScope._transactionName = this._transactionName;
newScope._fingerprint = this._fingerprint;
newScope._eventProcessors = [...this._eventProcessors];
newScope._attachments = [...this._attachments];
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
newScope._propagationContext = { ...this._propagationContext };
newScope._client = this._client;
newScope._lastEventId = this._lastEventId;
newScope._conversationId = this._conversationId;
_setSpanForScope(newScope, _getSpanForScope(this));
return newScope;
}
/**
* Update the client assigned to this scope.
* Note that not every scope will have a client assigned - isolation scopes & the global scope will generally not have a client,
* as well as manually created scopes.
*/
setClient(client) {
this._client = client;
}
/**
* Set the ID of the last captured error event.
* This is generally only captured on the isolation scope.
*/
setLastEventId(lastEventId) {
this._lastEventId = lastEventId;
}
/**
* Get the client assigned to this scope.
*/
getClient() {
return this._client;
}
/**
* Get the ID of the last captured error event.
* This is generally only available on the isolation scope.
*/
lastEventId() {
return this._lastEventId;
}
/**
* @inheritDoc
*/
addScopeListener(callback) {
this._scopeListeners.push(callback);
}
/**
* Add an event processor that will be called before an event is sent.
*/
addEventProcessor(callback) {
this._eventProcessors.push(callback);
return this;
}
/**
* Set the user for this scope.
* Set to `null` to unset the user.
*/
setUser(user) {
this._user = user || {
email: void 0,
id: void 0,
ip_address: void 0,
username: void 0
};
if (this._session) {
updateSession(this._session, { user });
}
this._notifyScopeListeners();
return this;
}
/**
* Get the user from this scope.
*/
getUser() {
return this._user;
}
/**
* Set the conversation ID for this scope.
* Set to `null` to unset the conversation ID.
*/
setConversationId(conversationId) {
this._conversationId = conversationId || void 0;
this._notifyScopeListeners();
return this;
}
/**
* Set an object that will be merged into existing tags on the scope,
* and will be sent as tags data with the event.
*/
setTags(tags) {
this._tags = {
...this._tags,
...tags
};
this._notifyScopeListeners();
return this;
}
/**
* Set a single tag that will be sent as tags data with the event.
*/
setTag(key, value) {
return this.setTags({ [key]: value });
}
/**
* Sets attributes onto the scope.
*
* These attributes are applied to logs, metrics and streamed spans.
*
* Supported attribute value types are `string`, `number`, `boolean`, `string[]`, `number[]` and `boolean[]`.
*
* @param newAttributes - The attributes to set on the scope, as key-value pairs.
*
* @example
* ```typescript
* scope.setAttributes({
* is_admin: true,
* payment_selection: 'credit_card',
* render_duration: 150,
* });
* ```
*/
setAttributes(newAttributes) {
this._attributes = {
...this._attributes,
...newAttributes
};
this._notifyScopeListeners();
return this;
}
/**
* Sets an attribute onto the scope.
*
* These attributes are applied to logs, metrics and streamed spans.
*
* Supported attribute value types are `string`, `number`, `boolean`, `string[]`, `number[]` and `boolean[]`.
*
* @param key - The attribute key.
* @param value - The attribute value.
*
* @example
* ```typescript
* scope.setAttribute('is_admin', true);
* scope.setAttribute('render_duration', 150);
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setAttribute(key, value) {
return this.setAttributes({ [key]: value });
}
/**
* Removes the attribute with the given key from the scope.
*
* @param key - The attribute key.
*
* @example
* ```typescript
* scope.removeAttribute('is_admin');
* ```
*/
removeAttribute(key) {
if (key in this._attributes) {
delete this._attributes[key];
this._notifyScopeListeners();
}
return this;
}
/**
* Set an object that will be merged into existing extra on the scope,
* and will be sent as extra data with the event.
*/
setExtras(extras) {
this._extra = {
...this._extra,
...extras
};
this._notifyScopeListeners();
return this;
}
/**
* Set a single key:value extra entry that will be sent as extra data with the event.
*/
setExtra(key, extra) {
this._extra = { ...this._extra, [key]: extra };
this._notifyScopeListeners();
return this;
}
/**
* Sets the fingerprint on the scope to send with the events.
* @param {string[]} fingerprint Fingerprint to group events in Sentry.
*/
setFingerprint(fingerprint) {
this._fingerprint = fingerprint;
this._notifyScopeListeners();
return this;
}
/**
* Sets the level on the scope for future events.
*/
setLevel(level) {
this._level = level;
this._notifyScopeListeners();
return this;
}
/**
* Sets the transaction name on the scope so that the name of e.g. taken server route or
* the page location is attached to future events.
*
* IMPORTANT: Calling this function does NOT change the name of the currently active
* root span. If you want to change the name of the active root span, use
* `Sentry.updateSpanName(rootSpan, 'new name')` instead.
*
* By default, the SDK updates the scope's transaction name automatically on sensible
* occasions, such as a page navigation or when handling a new request on the server.
*/
setTransactionName(name) {
this._transactionName = name;
this._notifyScopeListeners();
return this;
}
/**
* Sets context data with the given name.
* Data passed as context will be normalized. You can also pass `null` to unset the context.
* Note that context data will not be merged - calling `setContext` will overwrite an existing context with the same key.
*/
setContext(key, context) {
if (context === null) {
delete this._contexts[key];
} else {
this._contexts[key] = context;
}
this._notifyScopeListeners();
return this;
}
/**
* Set the session for the scope.
*/
setSession(session) {
if (!session) {
delete this._session;
} else {
this._session = session;
}
this._notifyScopeListeners();
return this;
}
/**
* Get the session from the scope.
*/
getSession() {
return this._session;
}
/**
* Updates the scope with provided data. Can work in three variations:
* - plain object containing updatable attributes
* - Scope instance that'll extract the attributes from
* - callback function that'll receive the current scope as an argument and allow for modifications
*/
update(captureContext) {
if (!captureContext) {
return this;
}
const scopeToMerge = typeof captureContext === "function" ? captureContext(this) : captureContext;
const scopeInstance = scopeToMerge instanceof Scope ? scopeToMerge.getScopeData() : isPlainObject(scopeToMerge) ? captureContext : void 0;
const {
tags,
attributes,
extra,
user,
contexts,
level,
fingerprint = [],
propagationContext,
conversationId
} = scopeInstance || {};
this._tags = { ...this._tags, ...tags };
this._attributes = { ...this._attributes, ...attributes };
this._extra = { ...this._extra, ...extra };
this._contexts = { ...this._contexts, ...contexts };
if (user && Object.keys(user).length) {
this._user = user;
}
if (level) {
this._level = level;
}
if (fingerprint.length) {
this._fingerprint = fingerprint;
}
if (propagationContext) {
this._propagationContext = propagationContext;
}
if (conversationId) {
this._conversationId = conversationId;
}
return this;
}
/**
* Clears the current scope and resets its properties.
* Note: The client will not be cleared.
*
* @deprecated This method will be removed in v11. To reset scope state, re-initialize the SDK or run
* your code in a fresh scope via `withScope` instead.
*/
clear() {
this._breadcrumbs = [];
this._tags = {};
this._attributes = {};
this._extra = {};
this._user = {};
this._contexts = {};
this._level = void 0;
this._transactionName = void 0;
this._fingerprint = void 0;
this._session = void 0;
this._conversationId = void 0;
_setSpanForScope(this, void 0);
this._attachments = [];
this.setPropagationContext({
traceId: generateTraceId(),
sampleRand: safeMathRandom()
});
this._notifyScopeListeners();
return this;
}
/**
* Adds a breadcrumb to the scope.
* By default, the last 100 breadcrumbs are kept.
*/
addBreadcrumb(breadcrumb, maxBreadcrumbs) {
const maxCrumbs = typeof maxBreadcrumbs === "number" ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
if (maxCrumbs <= 0) {
return this;
}
const mergedBreadcrumb = {
timestamp: dateTimestampInSeconds(),
...breadcrumb,
// Breadcrumb messages can theoretically be infinitely large and they're held in memory so we truncate them not to leak (too much) memory
message: breadcrumb.message ? truncate(breadcrumb.message, 2048) : breadcrumb.message
};
this._breadcrumbs.push(mergedBreadcrumb);
if (this._breadcrumbs.length > maxCrumbs) {
this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs);
this._client?.recordDroppedEvent("buffer_overflow", "log_item");
}
this._notifyScopeListeners();
return this;
}
/**
* Get the last breadcrumb of the scope.
*/
getLastBreadcrumb() {
return this._breadcrumbs[this._breadcrumbs.length - 1];
}
/**
* Clear all breadcrumbs from the scope.
*/
clearBreadcrumbs() {
this._breadcrumbs = [];
this._notifyScopeListeners();
return this;
}
/**
* Add an attachment to the scope.
*/
addAttachment(attachment) {
this._attachments.push(attachment);
return this;
}
/**
* Clear all attachments from the scope.
*/
clearAttachments() {
this._attachments = [];
return this;
}
/**
* Get the data of this scope, which should be applied to an event during processing.
*/
getScopeData() {
return {
breadcrumbs: this._breadcrumbs,
attachments: this._attachments,
contexts: this._contexts,
tags: this._tags,
attributes: this._attributes,
extra: this._extra,
user: this._user,
level: this._level,
fingerprint: this._fingerprint || [],
eventProcessors: this._eventProcessors,
propagationContext: this._propagationContext,
sdkProcessingMetadata: this._sdkProcessingMetadata,
transactionName: this._transactionName,
span: _getSpanForScope(this),
conversationId: this._conversationId
};
}
/**
* Add data which will be accessible during event processing but won't get sent to Sentry.
*/
setSDKProcessingMetadata(newData) {
this._sdkProcessingMetadata = merge(this._sdkProcessingMetadata, newData, 2);
return this;
}
/**
* Add propagation context to the scope, used for distributed tracing
*/
setPropagationContext(context) {
this._propagationContext = context;
return this;
}
/**
* Get propagation context from the scope, used for distributed tracing
*/
getPropagationContext() {
return this._propagationContext;
}
/**
* Capture an exception for this scope.
*
* @returns {string} The id of the captured Sentry event.
*/
captureException(exception, hint) {
const eventId = hint?.event_id || uuid4();
if (!this._client) {
DEBUG_BUILD$3 && debug$1.warn("No client configured on scope - will not capture exception!");
return eventId;
}
const syntheticException = new Error("Sentry syntheticException");
this._client.captureException(
exception,
{
originalException: exception,
syntheticException,
...hint,
event_id: eventId
},
this
);
return eventId;
}
/**
* Capture a message for this scope.
*
* @returns {string} The id of the captured message.
*/
captureMessage(message, level, hint) {
const eventId = hint?.event_id || uuid4();
if (!this._client) {
DEBUG_BUILD$3 && debug$1.warn("No client configured on scope - will not capture message!");
return eventId;
}
const syntheticException = hint?.syntheticException ?? new Error(message);
this._client.captureMessage(
message,
level,
{
originalException: message,
syntheticException,
...hint,
event_id: eventId
},
this
);
return eventId;
}
/**
* Capture a Sentry event for this scope.
*
* @returns {string} The id of the captured event.
*/
captureEvent(event, hint) {
const eventId = event.event_id || hint?.event_id || uuid4();
if (!this._client) {
DEBUG_BUILD$3 && debug$1.warn("No client configured on scope - will not capture event!");
return eventId;
}
this._client.captureEvent(event, { ...hint, event_id: eventId }, this);
return eventId;
}
/**
* This will be called on every set call.
*/
_notifyScopeListeners() {
if (!this._notifyingListeners) {
this._notifyingListeners = true;
this._scopeListeners.forEach((callback) => {
callback(this);
});
this._notifyingListeners = false;
}
}
}
function getDefaultCurrentScope() {
return getGlobalSingleton("defaultCurrentScope", () => new Scope());
}
function getDefaultIsolationScope() {
return getGlobalSingleton("defaultIsolationScope", () => new Scope());
}
const isActualPromise = (p) => p instanceof Promise && !p[kChainedCopy];
const kChainedCopy = /* @__PURE__ */ Symbol("chained PromiseLike");
const chainAndCopyPromiseLike = (original, onSuccess, onError) => {
const chained = original.then(
(value) => {
onSuccess(value);
return value;
},
(err) => {
onError(err);
throw err;
}
);
return isActualPromise(chained) && isActualPromise(original) ? chained : copyProps(original, chained);
};
const copyProps = (original, chained) => {
if (!chained) return original;
let mutated = false;
for (const key in original) {
if (key in chained) continue;
mutated = true;
const value = original[key];
if (typeof value === "function") {
Object.defineProperty(chained, key, {
value: (...args) => value.apply(original, args),
enumerable: true,
configurable: true,
writable: true
});
} else {
chained[key] = value;
}
}
if (mutated) Object.assign(chained, { [kChainedCopy]: true });
return chained;
};
class AsyncContextStack {
constructor(scope, isolationScope) {
let assignedScope;
if (!scope) {
assignedScope = new Scope();
} else {
assignedScope = scope;
}
let assignedIsolationScope;
if (!isolationScope) {
assignedIsolationScope = new Scope();
} else {
assignedIsolationScope = isolationScope;
}
this._stack = [{ scope: assignedScope }];
this._isolationScope = assignedIsolationScope;
}
/**
* Fork a scope for the stack.
*/
withScope(callback) {
const scope = this._pushScope();
let maybePromiseResult;
try {
maybePromiseResult = callback(scope);
} catch (e) {
this._popScope();
throw e;
}
if (isThenable(maybePromiseResult)) {
return chainAndCopyPromiseLike(
maybePromiseResult,
() => this._popScope(),
() => this._popScope()
);
}
this._popScope();
return maybePromiseResult;
}
/**
* Get the client of the stack.
*/
getClient() {
return this.getStackTop().client;
}
/**
* Returns the scope of the top stack.
*/
getScope() {
return this.getStackTop().scope;
}
/**
* Get the isolation scope for the stack.
*/
getIsolationScope() {
return this._isolationScope;
}
/**
* Returns the topmost scope layer in the order domain > local > process.
*/
getStackTop() {
return this._stack[this._stack.length - 1];
}
/**
* Push a scope to the stack.
*/
_pushScope() {
const scope = this.getScope().clone();
this._stack.push({
client: this.getClient(),
scope
});
return scope;
}
/**
* Pop a scope from the stack.
*/
_popScope() {
if (this._stack.length <= 1) return false;
return !!this._stack.pop();
}
}
function getAsyncContextStack() {
const registry = getMainCarrier();
const sentry = getSentryCarrier(registry);
return sentry.stack = sentry.stack || new AsyncContextStack(getDefaultCurrentScope(), getDefaultIsolationScope());
}
function withScope$1(callback) {
return getAsyncContextStack().withScope(callback);
}
function withSetScope(scope, callback) {
const stack = getAsyncContextStack();
return stack.withScope(() => {
stack.getStackTop().scope = scope;
return callback(scope);
});
}
function withIsolationScope(callback) {
return getAsyncContextStack().withScope(() => {
return callback(getAsyncContextStack().getIsolationScope());
});
}
function getStackAsyncContextStrategy() {
return {
withIsolationScope,
withScope: withScope$1,
withSetScope,
withSetIsolationScope: (_isolationScope, callback) => {
return withIsolationScope(callback);
},
getCurrentScope: () => getAsyncContextStack().getScope(),
getIsolationScope: () => getAsyncContextStack().getIsolationScope()
};
}
function getAsyncContextStrategy(carrier) {
const sentry = getSentryCarrier(carrier);
if (sentry.acs) {
return sentry.acs;
}
return getStackAsyncContextStrategy();
}
function isAttributeObject(maybeObj) {
return typeof maybeObj === "object" && maybeObj != null && !Array.isArray(maybeObj) && Object.keys(maybeObj).includes("value");
}
function attributeValueToTypedAttributeValue(rawValue, useFallback) {
const { value, unit } = isAttributeObject(rawValue) ? rawValue : { value: rawValue, unit: void 0 };
const attributeValue = getTypedAttributeValue(value);
const checkedUnit = unit && typeof unit === "string" ? { unit } : {};
if (attributeValue) {
return { ...attributeValue, ...checkedUnit };
}
if (!useFallback || useFallback === "skip-undefined" && value === void 0) {
return;
}
let stringValue = "";
try {
stringValue = JSON.stringify(value) ?? "";
} catch {
}
return {
value: stringValue,
type: "string",
...checkedUnit
};
}
function serializeAttributes(attributes, fallback = false) {
const serializedAttributes = {};
for (const [key, value] of Object.entries(attributes ?? {})) {
const typedValue = attributeValueToTypedAttributeValue(value, fallback);
if (typedValue) {
serializedAttributes[key] = typedValue;
}
}
return serializedAttributes;
}
function getTypedAttributeValue(value) {
if (Array.isArray(value)) {
return { value, type: "array" };
}
const primitiveType = typeof value === "string" ? "string" : typeof value === "boolean" ? "boolean" : typeof value === "number" && !Number.isNaN(value) ? Number.isInteger(value) ? "integer" : "double" : null;
if (primitiveType) {
return { value, type: primitiveType };
}
}
let _externalPropagationContextProvider;
function hasExternalPropagationContext() {
return _externalPropagationContextProvider !== void 0;
}
function getCurrentScope() {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
return acs.getCurrentScope();
}
function getIsolationScope() {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
return acs.getIsolationScope();
}
function getGlobalScope() {
return getGlobalSingleton("globalScope", () => new Scope());
}
function withScope(...rest) {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
if (rest.length === 2) {
const [scope, callback] = rest;
if (!scope) {
return acs.withScope(callback);
}
return acs.withSetScope(scope, callback);
}
return acs.withScope(rest[0]);
}
function getClient() {
return getCurrentScope().getClient();
}
function getTraceContextFromScope(scope) {
const propagationContext = scope.getPropagationContext();
const { traceId, parentSpanId, propagationSpanId } = propagationContext;
const traceContext = {
trace_id: traceId,
span_id: propagationSpanId || generateSpanId()
};
if (parentSpanId) {
traceContext.parent_span_id = parentSpanId;
}
return traceContext;
}
const SEMANTIC_ATTRIBUTE_SENTRY_SOURCE = "sentry.source";
const SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE = "sentry.sample_rate";
const SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE = "sentry.previous_trace_sample_rate";
const SEMANTIC_ATTRIBUTE_SENTRY_OP = "sentry.op";
const SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN = "sentry.origin";
const SEMANTIC_ATTRIBUTE_SENTRY_STATUS_MESSAGE = "sentry.status.message";
const SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON = "sentry.idle_span_finish_reason";
const SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT = "sentry.measurement_unit";
const SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE = "sentry.measurement_value";
const SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME = "sentry.custom_span_name";
const SEMANTIC_ATTRIBUTE_PROFILE_ID = "sentry.profile_id";
const SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME = "sentry.exclusive_time";
const SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE = "sentry.link.type";
const GEN_AI_CONVERSATION_ID_ATTRIBUTE = "gen_ai.conversation.id";
const SPAN_STATUS_UNSET = 0;
const SPAN_STATUS_OK = 1;
const SPAN_STATUS_ERROR = 2;
function getSpanStatusFromHttpCode(httpStatus) {
if (httpStatus < 400 && httpStatus >= 100) {
return { code: SPAN_STATUS_OK };
}
if (httpStatus >= 400 && httpStatus < 500) {
switch (httpStatus) {
case 401:
return { code: SPAN_STATUS_ERROR, message: "unauthenticated" };
case 403:
return { code: SPAN_STATUS_ERROR, message: "permission_denied" };
case 404:
return { code: SPAN_STATUS_ERROR, message: "not_found" };
case 409:
return { code: SPAN_STATUS_ERROR, message: "already_exists" };
case 413:
return { code: SPAN_STATUS_ERROR, message: "failed_precondition" };
case 429:
return { code: SPAN_STATUS_ERROR, message: "resource_exhausted" };
case 499:
return { code: SPAN_STATUS_ERROR, message: "cancelled" };
default:
return { code: SPAN_STATUS_ERROR, message: "invalid_argument" };
}
}
if (httpStatus >= 500 && httpStatus < 600) {
switch (httpStatus) {
case 501:
return { code: SPAN_STATUS_ERROR, message: "unimplemented" };
case 503:
return { code: SPAN_STATUS_ERROR, message: "unavailable" };
case 504:
return { code: SPAN_STATUS_ERROR, message: "deadline_exceeded" };
default:
return { code: SPAN_STATUS_ERROR, message: "internal_error" };
}
}
return { code: SPAN_STATUS_ERROR, message: "internal_error" };
}
function setHttpStatus(span, httpStatus) {
span.setAttribute("http.response.status_code", httpStatus);
const spanStatus = getSpanStatusFromHttpCode(httpStatus);
if (spanStatus.message !== "unknown_error") {
span.setStatus(spanStatus);
}
}
const SCOPE_ON_START_SPAN_FIELD = "_sentryScope";
const ISOLATION_SCOPE_ON_START_SPAN_FIELD = "_sentryIsolationScope";
const OTEL_SOURCE_INFERENCE_SPAN_FIELD = /* @__PURE__ */ Symbol.for("sentry.otelSourceInference");
const OTEL_SOURCE_EXPLICITLY_SET_SPAN_FIELD = /* @__PURE__ */ Symbol.for("sentry.otelSourceExplicitlySet");
const TRACER_PROVIDER_SPAN_FIELD = /* @__PURE__ */ Symbol.for("sentry.tracerProviderSpan");
function setCapturedScopesOnSpan(span, scope, isolationScope) {
if (span) {
addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, makeWeakRef(isolationScope));
addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope);
}
}
function getCapturedScopesOnSpan(span) {
const spanWithScopes = span;
return {
scope: spanWithScopes[SCOPE_ON_START_SPAN_FIELD],
isolationScope: derefWeakRef(spanWithScopes[ISOLATION_SCOPE_ON_START_SPAN_FIELD])
};
}
function spanShouldInferOtelSource(span) {
return span[OTEL_SOURCE_INFERENCE_SPAN_FIELD] === true;
}
function markSpanSourceAsExplicit(span) {
addNonEnumerableProperty(span, OTEL_SOURCE_EXPLICITLY_SET_SPAN_FIELD, true);
}
function spanIsTracerProviderSpan(span) {
return span[TRACER_PROVIDER_SPAN_FIELD] === true;
}
const SENTRY_BAGGAGE_KEY_PREFIX = "sentry-";
const MAX_BAGGAGE_STRING_LENGTH = 8192;
function baggageHeaderToDynamicSamplingContext(baggageHeader) {
const baggageObject = parseBaggageHeader(baggageHeader);
if (!baggageObject) {
return void 0;
}
const dynamicSamplingContext = Object.entries(baggageObject).reduce((acc, [key, value]) => {
if (key.startsWith(SENTRY_BAGGAGE_KEY_PREFIX)) {
const nonPrefixedKey = key.slice(SENTRY_BAGGAGE_KEY_PREFIX.length);
acc[nonPrefixedKey] = value;
}
return acc;
}, {});
if (Object.keys(dynamicSamplingContext).length > 0) {
return dynamicSamplingContext;
} else {
return void 0;
}
}
function dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext) {
if (!dynamicSamplingContext) {
return void 0;
}
const sentryPrefixedDSC = Object.entries(dynamicSamplingContext).reduce(
(acc, [dscKey, dscValue]) => {
if (dscValue) {
acc[`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`] = dscValue;
}
return acc;
},
{}
);
return objectToBaggageHeader(sentryPrefixedDSC);
}
function parseBaggageHeader(baggageHeader) {
if (!baggageHeader || !isString(baggageHeader) && !Array.isArray(baggageHeader)) {
return void 0;
}
if (Array.isArray(baggageHeader)) {
return baggageHeader.reduce((acc, curr) => {
const currBaggageObject = baggageHeaderToObject(curr);
Object.entries(currBaggageObject).forEach(([key, value]) => {
acc[key] = value;
});
return acc;
}, {});
}
return baggageHeaderToObject(baggageHeader);
}
function baggageHeaderToObject(baggageHeader) {
return baggageHeader.split(",").map((baggageEntry) => {
const eqIdx = baggageEntry.indexOf("=");
if (eqIdx === -1) {
return [];
}
const key = baggageEntry.slice(0, eqIdx);
const value = baggageEntry.slice(eqIdx + 1);
return [key, value].map((keyOrValue) => {
try {
return decodeURIComponent(keyOrValue.trim());
} catch {
return;
}
});
}).reduce((acc, [key, value]) => {
if (key && value) {
acc[key] = value;
}
return acc;
}, {});
}
function objectToBaggageHeader(object) {
if (Object.keys(object).length === 0) {
return void 0;
}
return Object.entries(object).reduce((baggageHeader, [objectKey, objectValue], currentIndex) => {
const baggageEntry = `${encodeURIComponent(objectKey)}=${encodeURIComponent(objectValue)}`;
const newBaggageHeader = currentIndex === 0 ? baggageEntry : `${baggageHeader},${baggageEntry}`;
if (newBaggageHeader.length > MAX_BAGGAGE_STRING_LENGTH) {
DEBUG_BUILD$3 && debug$1.warn(
`Not adding key: ${objectKey} with val: ${objectValue} to baggage header due to exceeding baggage size limits.`
);
return baggageHeader;
} else {
return newBaggageHeader;
}
}, "");
}
const ORG_ID_REGEX = /^o(\d+)\./;
const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+)?)?@)((?:\[[:.%\w]+\]|[\w.-]+))(?::(\d+))?\/(.+)/;
function isValidProtocol(protocol) {
return protocol === "http" || protocol === "https";
}
function dsnToString(dsn, withPassword = false) {
const { host, path, pass, port, projectId, protocol, publicKey } = dsn;
return `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ""}@${host}${port ? `:${port}` : ""}/${path ? `${path}/` : path}${projectId}`;
}
function dsnFromString(str) {
const match = DSN_REGEX.exec(str);
if (!match) {
consoleSandbox(() => {
console.error(`Invalid Sentry Dsn: ${str}`);
});
return void 0;
}
const [protocol, publicKey, pass = "", host = "", port = "", lastPath = ""] = match.slice(1);
let path = "";
let projectId = lastPath;
const split = projectId.split("/");
if (split.length > 1) {
path = split.slice(0, -1).join("/");
projectId = split.pop();
}
if (projectId) {
const projectMatch = projectId.match(/^\d+/);
if (projectMatch) {
projectId = projectMatch[0];
}
}
return dsnFromComponents({ host, pass, path, projectId, port, protocol, publicKey });
}
function dsnFromComponents(components) {
return {
protocol: components.protocol,
publicKey: components.publicKey || "",
pass: components.pass || "",
host: components.host,
port: components.port || "",
path: components.path || "",
projectId: components.projectId
};
}
function validateDsn(dsn) {
if (!DEBUG_BUILD$3) {
return true;
}
const { port, projectId, protocol } = dsn;
const requiredComponents = ["protocol", "publicKey", "host", "projectId"];
const hasMissingRequiredComponent = requiredComponents.find((component) => {
if (!dsn[component]) {
debug$1.error(`Invalid Sentry Dsn: ${component} missing`);
return true;
}
return false;
});
if (hasMissingRequiredComponent) {
return false;
}
if (!projectId.match(/^\d+$/)) {
debug$1.error(`Invalid Sentry Dsn: Invalid projectId ${projectId}`);
return false;
}
if (!isValidProtocol(protocol)) {
debug$1.error(`Invalid Sentry Dsn: Invalid protocol ${protocol}`);
return false;
}
if (port && isNaN(parseInt(port, 10))) {
debug$1.error(`Invalid Sentry Dsn: Invalid port ${port}`);
return false;
}
return true;
}
function extractOrgIdFromDsnHost(host) {
const match = host.match(ORG_ID_REGEX);
return match?.[1];
}
function extractOrgIdFromClient(client) {
const options = client.getOptions();
const { host } = client.getDsn() || {};
let org_id;
if (options.orgId) {
org_id = String(options.orgId);
} else if (host) {
org_id = extractOrgIdFromDsnHost(host);
}
return org_id;
}
function makeDsn(from) {
const components = typeof from === "string" ? dsnFromString(from) : dsnFromComponents(from);
if (!components || !validateDsn(components)) {
return void 0;
}
return components;
}
function parseSampleRate(sampleRate) {
if (typeof sampleRate === "boolean") {
return Number(sampleRate);
}
const rate = typeof sampleRate === "string" ? parseFloat(sampleRate) : sampleRate;
if (typeof rate !== "number" || isNaN(rate) || rate < 0 || rate > 1) {
return void 0;
}
return rate;
}
const TRACEPARENT_REGEXP = new RegExp(
"^[ \\t]*([0-9a-f]{32})?-?([0-9a-f]{16})?-?([01])?[ \\t]*$"
// whitespace
);
function extractTraceparentData(traceparent) {
if (!traceparent) {
return void 0;
}
const matches = traceparent.match(TRACEPARENT_REGEXP);
if (!matches) {
return void 0;
}
let parentSampled;
if (matches[3] === "1") {
parentSampled = true;
} else if (matches[3] === "0") {
parentSampled = false;
}
return {
traceId: matches[1],
parentSampled,
parentSpanId: matches[2]
};
}
function propagationContextFromHeaders(sentryTrace, baggage) {
const traceparentData = extractTraceparentData(sentryTrace);
const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggage);
if (!traceparentData?.traceId) {
return {
traceId: generateTraceId(),
sampleRand: safeMathRandom()
};
}
const sampleRand = getSampleRandFromTraceparentAndDsc(traceparentData, dynamicSamplingContext);
if (dynamicSamplingContext) {
dynamicSamplingContext.sample_rand = sampleRand.toString();
}
const { traceId, parentSpanId, parentSampled } = traceparentData;
return {
traceId,
parentSpanId,
sampled: parentSampled,
dsc: dynamicSamplingContext || {},
// If we have traceparent data but no DSC it means we are not head of trace and we must freeze it
sampleRand
};
}
function generateSentryTraceHeader(traceId = generateTraceId(), spanId = generateSpanId(), sampled) {
let sampledString = "";
if (sampled !== void 0) {
sampledString = sampled ? "-1" : "-0";
}
return `${traceId}-${spanId}${sampledString}`;
}
function generateTraceparentHeader(traceId = generateTraceId(), spanId = generateSpanId(), sampled) {
return `00-${traceId}-${spanId}-${sampled ? "01" : "00"}`;
}
function getSampleRandFromTraceparentAndDsc(traceparentData, dsc) {
const parsedSampleRand = parseSampleRate(dsc?.sample_rand);
if (parsedSampleRand !== void 0) {
return parsedSampleRand;
}
const parsedSampleRate = parseSampleRate(dsc?.sample_rate);
if (parsedSampleRate && traceparentData?.parentSampled !== void 0) {
return traceparentData.parentSampled ? (
// Returns a sample rand with positive sampling decision [0, sampleRate)
safeMathRandom() * parsedSampleRate
) : (
// Returns a sample rand with negative sampling decision [sampleRate, 1)
parsedSampleRate + safeMathRandom() * (1 - parsedSampleRate)
);
} else {
return safeMathRandom();
}
}
const TRACE_FLAG_NONE = 0;
const TRACE_FLAG_SAMPLED = 1;
let hasShownSpanDropWarning = false;
function spanToTransactionTraceContext(span) {
const { spanId: span_id, traceId: trace_id } = span.spanContext();
const { data, op, parent_span_id, status, origin, links } = spanToJSON(span);
return {
parent_span_id,
span_id,
trace_id,
data,
op,
status,
origin,
links
};
}
function spanToTraceContext(span) {
const { spanId, traceId: trace_id, isRemote } = span.spanContext();
const parent_span_id = isRemote ? spanId : spanToJSON(span).parent_span_id;
const scope = getCapturedScopesOnSpan(span).scope;
const span_id = isRemote ? scope?.getPropagationContext().propagationSpanId || generateSpanId() : spanId;
return {
parent_span_id,
span_id,
trace_id
};
}
function spanToTraceHeader(span) {
const { traceId, spanId } = span.spanContext();
const sampled = spanIsSampled(span);
return generateSentryTraceHeader(traceId, spanId, sampled);
}
function spanToTraceparentHeader(span) {
const { traceId, spanId } = span.spanContext();
const sampled = spanIsSampled(span);
return generateTraceparentHeader(traceId, spanId, sampled);
}
function convertSpanLinksForEnvelope(links) {
if (links && links.length > 0) {
return links.map(({ context: { spanId, traceId, traceFlags, ...restContext }, attributes }) => ({
span_id: spanId,
trace_id: traceId,
sampled: traceFlags === TRACE_FLAG_SAMPLED,
attributes,
...restContext
}));
} else {
return void 0;
}
}
function getStreamedSpanLinks(links) {
if (links?.length) {
return links.map(({ context: { spanId, traceId, traceFlags }, attributes }) => ({
span_id: spanId,
trace_id: traceId,
sampled: traceFlags === TRACE_FLAG_SAMPLED,
attributes
}));
} else {
return void 0;
}
}
function spanTimeInputToSeconds(input) {
if (typeof input === "number") {
return ensureTimestampInSeconds(input);
}
if (Array.isArray(input)) {
return input[0] + input[1] / 1e9;
}
if (input instanceof Date) {
return ensureTimestampInSeconds(input.getTime());
}
return timestampInSeconds();
}
function ensureTimestampInSeconds(timestamp) {
const isMs = timestamp > 9999999999;
return isMs ? timestamp / 1e3 : timestamp;
}
function spanToJSON(span) {
if (spanIsSentrySpan(span)) {
return span.getSpanJSON();
}
const { spanId: span_id, traceId: trace_id } = span.spanContext();
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
const { attributes, startTime, name, endTime, status, links } = span;
return {
span_id,
trace_id,
data: attributes,
description: name,
parent_span_id: getOtelParentSpanId(span),
start_timestamp: spanTimeInputToSeconds(startTime),
// This is [0,0] by default in OTEL, in which case we want to interpret this as no end time
timestamp: spanTimeInputToSeconds(endTime) || void 0,
status: getStatusMessage(status),
op: attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
origin: attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN],
links: convertSpanLinksForEnvelope(links)
};
}
return {
span_id,
trace_id,
start_timestamp: 0,
data: {}
};
}
function spanToStreamedSpanJSON(span) {
if (spanIsSentrySpan(span)) {
return span.getStreamedSpanJSON();
}
const { spanId: span_id, traceId: trace_id } = span.spanContext();
if (spanIsOpenTelemetrySdkTraceBaseSpan(span)) {
const { attributes, startTime, name, endTime, status, links } = span;
return {
name,
span_id,
trace_id,
parent_span_id: getOtelParentSpanId(span),
start_timestamp: spanTimeInputToSeconds(startTime),
end_timestamp: spanTimeInputToSeconds(endTime),
is_segment: span === INTERNAL_getSegmentSpan(span),
status: getSimpleStatus(status),
attributes: addStatusMessageAttribute(attributes, status),
links: getStreamedSpanLinks(links)
};
}
return {
span_id,
trace_id,
start_timestamp: 0,
name: "",
end_timestamp: 0,
status: "ok",
is_segment: span === INTERNAL_getSegmentSpan(span)
};
}
function getOtelParentSpanId(span) {
return "parentSpanId" in span ? span.parentSpanId : "parentSpanContext" in span ? span.parentSpanContext?.spanId : void 0;
}
function streamedSpanJsonToSerializedSpan(spanJson) {
return {
...spanJson,
attributes: serializeAttributes(spanJson.attributes),
links: spanJson.links?.map((link) => ({
...link,
attributes: serializeAttributes(link.attributes)
}))
};
}
function spanIsOpenTelemetrySdkTraceBaseSpan(span) {
const castSpan = span;
return !!castSpan.attributes && !!castSpan.startTime && !!castSpan.name && !!castSpan.endTime && !!castSpan.status;
}
function spanIsSentrySpan(span) {
return typeof span.getSpanJSON === "function";
}
function spanIsSampled(span) {
const { traceFlags } = span.spanContext();
return traceFlags === TRACE_FLAG_SAMPLED;
}
function getStatusMessage(status) {
if (!status || status.code === SPAN_STATUS_UNSET) {
return void 0;
}
if (status.code === SPAN_STATUS_OK) {
return "ok";
}
return status.message || "internal_error";
}
function getSimpleStatus(status) {
return !status || status.code === SPAN_STATUS_OK || status.code === SPAN_STATUS_UNSET || status.message === "cancelled" ? "ok" : "error";
}
function addStatusMessageAttribute(attributes, status) {
const statusMessage = getSimpleStatus(status) === "error" ? status?.message : void 0;
return {
...statusMessage && { [SEMANTIC_ATTRIBUTE_SENTRY_STATUS_MESSAGE]: statusMessage },
...attributes
};
}
const CHILD_SPANS_FIELD = "_sentryChildSpans";
const ROOT_SPAN_FIELD = "_sentryRootSpan";
function addChildSpanToSpan(span, childSpan) {
const rootSpan = span[ROOT_SPAN_FIELD] || span;
addNonEnumerableProperty(childSpan, ROOT_SPAN_FIELD, rootSpan);
if (!spanIsSampled(span)) {
return;
}
if (!span.isRecording() && !rootSpan.isRecording()) {
return;
}
if (span[CHILD_SPANS_FIELD]) {
span[CHILD_SPANS_FIELD].add(childSpan);
} else {
addNonEnumerableProperty(span, CHILD_SPANS_FIELD, /* @__PURE__ */ new Set([childSpan]));
}
}
function removeChildSpanFromSpan(span, childSpan) {
if (span[CHILD_SPANS_FIELD]) {
span[CHILD_SPANS_FIELD].delete(childSpan);
}
}
function getSpanDescendants(span) {
const resultSet = /* @__PURE__ */ new Set();
function addSpanChildren(span2) {
if (resultSet.has(span2)) {
return;
} else if (spanIsSampled(span2)) {
resultSet.add(span2);
const childSpans = span2[CHILD_SPANS_FIELD] ? Array.from(span2[CHILD_SPANS_FIELD]) : [];
for (const childSpan of childSpans) {
addSpanChildren(childSpan);
}
}
}
addSpanChildren(span);
return Array.from(resultSet);
}
const getRootSpan = INTERNAL_getSegmentSpan;
function INTERNAL_getSegmentSpan(span) {
return span[ROOT_SPAN_FIELD] || span;
}
function getActiveSpan() {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
if (acs.getActiveSpan) {
return acs.getActiveSpan();
}
return _getSpanForScope(getCurrentScope());
}
function showSpanDropWarning() {
if (!hasShownSpanDropWarning) {
consoleSandbox(() => {
console.warn(
"[Sentry] Returning null from `beforeSendSpan` is disallowed. To drop certain spans, configure the respective integrations directly or use `ignoreSpans`."
);
});
hasShownSpanDropWarning = true;
}
}
let errorsInstrumented = false;
function registerSpanErrorInstrumentation() {
if (errorsInstrumented) {
return;
}
function errorCallback() {
const activeSpan = getActiveSpan();
const rootSpan = activeSpan && getRootSpan(activeSpan);
if (rootSpan) {
const message = "internal_error";
DEBUG_BUILD$3 && debug$1.log(`[Tracing] Root span: ${message} -> Global error occurred`);
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message });
}
}
errorsInstrumented = true;
addGlobalErrorInstrumentationHandler(errorCallback);
addGlobalUnhandledRejectionInstrumentationHandler(errorCallback);
}
function hasSpansEnabled(maybeOptions) {
if (typeof __SENTRY_TRACING__ === "boolean" && !__SENTRY_TRACING__) {
return false;
}
const options = maybeOptions || getClient()?.getOptions();
return !!options && // Note: This check is `!= null`, meaning "nullish". `0` is not "nullish", `undefined` and `null` are. (This comment was brought to you by 15 minutes of questioning life)
(options.tracesSampleRate != null || !!options.tracesSampler);
}
function logIgnoredSpan(droppedSpan) {
debug$1.log(`Ignoring span ${droppedSpan.op} - ${droppedSpan.description} because it matches \`ignoreSpans\`.`);
}
function shouldIgnoreSpan(span, ignoreSpans) {
if (!ignoreSpans?.length) {
return false;
}
for (const pattern of ignoreSpans) {
if (isStringOrRegExp(pattern)) {
if (span.description && isMatchingPattern(span.description, pattern)) {
DEBUG_BUILD$3 && logIgnoredSpan(span);
return true;
}
continue;
}
const hasAttributes = !!pattern.attributes && Object.keys(pattern.attributes).length > 0;
if (!pattern.name && !pattern.op && !hasAttributes) {
continue;
}
const nameMatches = pattern.name ? span.description && isMatchingPattern(span.description, pattern.name) : true;
const opMatches = pattern.op ? span.op && isMatchingPattern(span.op, pattern.op) : true;
const attrsMatch = pattern.attributes ? Object.entries(pattern.attributes).every(
([key, valuePattern]) => _matchesAttributeValue(span.attributes?.[key], valuePattern)
) : true;
if (nameMatches && opMatches && attrsMatch) {
DEBUG_BUILD$3 && logIgnoredSpan(span);
return true;
}
}
return false;
}
function _matchesAttributeValue(actual, pat) {
if (typeof actual === "string" && (typeof pat === "string" || pat instanceof RegExp)) {
return isMatchingPattern(actual, pat);
}
if (Array.isArray(actual) && Array.isArray(pat)) {
return actual.length === pat.length && actual.every((v, i) => v === pat[i]);
}
return actual === pat;
}
function reparentChildSpans(spans, dropSpan) {
const droppedSpanParentId = dropSpan.parent_span_id;
const droppedSpanId = dropSpan.span_id;
if (!droppedSpanParentId) {
return;
}
for (const span of spans) {
if (span.parent_span_id === droppedSpanId) {
span.parent_span_id = droppedSpanParentId;
}
}
}
function isStringOrRegExp(value) {
return typeof value === "string" || value instanceof RegExp;
}
const NON_RECORDING_SPAN_FIELD = /* @__PURE__ */ Symbol.for("sentry.nonRecordingSpan");
class SentryNonRecordingSpan {
constructor(spanContext = {}) {
this._traceId = spanContext.traceId || generateTraceId();
this._spanId = spanContext.spanId || generateSpanId();
this.dropReason = spanContext.dropReason;
addNonEnumerableProperty(this, NON_RECORDING_SPAN_FIELD, true);
}
/** @inheritdoc */
spanContext() {
return {
spanId: this._spanId,
traceId: this._traceId,
traceFlags: TRACE_FLAG_NONE
};
}
/** @inheritdoc */
end(_timestamp) {
}
/** @inheritdoc */
setAttribute(_key, _value) {
return this;
}
/** @inheritdoc */
setAttributes(_values) {
return this;
}
/** @inheritdoc */
setStatus(_status) {
return this;
}
/** @inheritdoc */
updateName(_name) {
return this;
}
/** @inheritdoc */
isRecording() {
return false;
}
/** @inheritdoc */
addEvent(_name, _attributesOrStartTime, _startTime) {
return this;
}
/** @inheritDoc */
addLink(_link) {
return this;
}
/** @inheritDoc */
addLinks(_links) {
return this;
}
/**
* This should generally not be used,
* but we need it for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
recordException(_exception, _time) {
}
}
function spanIsNonRecordingSpan(span) {
return !!span && span[NON_RECORDING_SPAN_FIELD] === true;
}
const DEFAULT_ENVIRONMENT = "production";
const FROZEN_DSC_FIELD = "_frozenDsc";
function freezeDscOnSpan(span, dsc) {
const spanWithMaybeDsc = span;
addNonEnumerableProperty(spanWithMaybeDsc, FROZEN_DSC_FIELD, dsc);
}
function getDynamicSamplingContextFromClient(trace_id, client) {
const options = client.getOptions();
const { publicKey: public_key } = client.getDsn() || {};
const dsc = {
environment: options.environment || DEFAULT_ENVIRONMENT,
release: options.release,
public_key,
trace_id,
org_id: extractOrgIdFromClient(client)
};
client.emit("createDsc", dsc);
return dsc;
}
function getDynamicSamplingContextFromScope(client, scope) {
const propagationContext = scope.getPropagationContext();
return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client);
}
function getDynamicSamplingContextFromSpan(span) {
const client = getClient();
if (!client) {
return {};
}
const rootSpan = getRootSpan(span);
const rootSpanJson = spanToJSON(rootSpan);
const rootSpanAttributes = rootSpanJson.data;
const traceState = rootSpan.spanContext().traceState;
const rootSpanSampleRate = traceState?.get("sentry.sample_rate") ?? rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ?? rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE];
function applyLocalSampleRateToDsc(dsc2) {
if (typeof rootSpanSampleRate === "number" || typeof rootSpanSampleRate === "string") {
dsc2.sample_rate = `${rootSpanSampleRate}`;
}
return dsc2;
}
const frozenDsc = rootSpan[FROZEN_DSC_FIELD];
if (frozenDsc) {
return applyLocalSampleRateToDsc(frozenDsc);
}
const isNonRecordingRoot = spanIsNonRecordingSpan(rootSpan);
const isIgnoredRoot = isNonRecordingRoot && rootSpan.dropReason === "ignored";
if (isNonRecordingRoot && (!hasSpansEnabled(client.getOptions()) || isIgnoredRoot)) {
const capturedScope = getCapturedScopesOnSpan(rootSpan).scope;
if (capturedScope) {
const dsc2 = { ...getDynamicSamplingContextFromScope(client, capturedScope) };
if (isIgnoredRoot) {
dsc2.sampled = "false";
}
return applyLocalSampleRateToDsc(dsc2);
}
}
const traceStateDsc = traceState?.get("sentry.dsc");
const dscOnTraceState = traceStateDsc && baggageHeaderToDynamicSamplingContext(traceStateDsc);
if (dscOnTraceState) {
return applyLocalSampleRateToDsc(dscOnTraceState);
}
const dsc = getDynamicSamplingContextFromClient(span.spanContext().traceId, client);
const source = rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] ?? rootSpanAttributes["sentry.segment.name.source"];
const name = rootSpanJson.description;
if (source !== "url" && name) {
dsc.transaction = name;
}
if (hasSpansEnabled()) {
dsc.sampled = String(spanIsSampled(rootSpan));
dsc.sample_rand = // In OTEL we store the sample rand on the trace state because we cannot access scopes for NonRecordingSpans
// The Sentry OTEL SpanSampler takes care of writing the sample rand on the root span
traceState?.get("sentry.sample_rand") ?? // On all other platforms we can actually get the scopes from a root span (we use this as a fallback)
getCapturedScopesOnSpan(rootSpan).scope?.getPropagationContext().sampleRand.toString();
}
applyLocalSampleRateToDsc(dsc);
client.emit("createDsc", dsc, rootSpan);
return dsc;
}
function isStreamedBeforeSendSpanCallback(callback) {
return !!callback && typeof callback === "function" && "_streamed" in callback && !!callback._streamed;
}
function createEnvelope(headers, items = []) {
return [headers, items];
}
function addItemToEnvelope(envelope, newItem) {
const [headers, items] = envelope;
return [headers, [...items, newItem]];
}
function forEachEnvelopeItem(envelope, callback) {
const envelopeItems = envelope[1];
for (const envelopeItem of envelopeItems) {
const envelopeItemType = envelopeItem[0].type;
const result = callback(envelopeItem, envelopeItemType);
if (result) {
return true;
}
}
return false;
}
function envelopeContainsItemType(envelope, types) {
return forEachEnvelopeItem(envelope, (_, type) => types.includes(type));
}
function encodeUTF8(input) {
const carrier = getSentryCarrier(GLOBAL_OBJ);
return carrier.encodePolyfill ? carrier.encodePolyfill(input) : new TextEncoder().encode(input);
}
function serializeEnvelope(envelope) {
const [envHeaders, items] = envelope;
let parts = JSON.stringify(envHeaders);
function append(next) {
if (typeof parts === "string") {
parts = typeof next === "string" ? parts + next : [encodeUTF8(parts), next];
} else {
parts.push(typeof next === "string" ? encodeUTF8(next) : next);
}
}
for (const item of items) {
const [itemHeaders, payload] = item;
append(`
${JSON.stringify(itemHeaders)}
`);
if (typeof payload === "string" || payload instanceof Uint8Array) {
append(payload);
} else {
let stringifiedPayload;
try {
stringifiedPayload = JSON.stringify(payload);
} catch {
stringifiedPayload = JSON.stringify(normalize(payload));
}
append(stringifiedPayload);
}
}
return typeof parts === "string" ? parts : concatBuffers(parts);
}
function concatBuffers(buffers) {
const totalLength = buffers.reduce((acc, buf) => acc + buf.length, 0);
const merged = new Uint8Array(totalLength);
let offset = 0;
for (const buffer of buffers) {
merged.set(buffer, offset);
offset += buffer.length;
}
return merged;
}
function createSpanEnvelopeItem(spanJson) {
const spanHeaders = {
type: "span"
};
return [spanHeaders, spanJson];
}
function createAttachmentEnvelopeItem(attachment) {
const buffer = typeof attachment.data === "string" ? encodeUTF8(attachment.data) : attachment.data;
return [
{
type: "attachment",
length: buffer.length,
filename: attachment.filename,
content_type: attachment.contentType,
attachment_type: attachment.attachmentType
},
buffer
];
}
const DATA_CATEGORY_OVERRIDES = {
sessions: "session",
event: "error",
client_report: "internal",
user_report: "default",
profile_chunk: "profile",
replay_event: "replay",
replay_recording: "replay",
check_in: "monitor",
raw_security: "security",
log: "log_item",
trace_metric: "metric"
};
function _isOverriddenType(type) {
return type in DATA_CATEGORY_OVERRIDES;
}
function envelopeItemTypeToDataCategory(type) {
return _isOverriddenType(type) ? DATA_CATEGORY_OVERRIDES[type] : type;
}
function getSdkMetadataForEnvelopeHeader(metadataOrEvent) {
if (!metadataOrEvent?.sdk) {
return;
}
const { name, version } = metadataOrEvent.sdk;
return { name, version };
}
function createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn) {
const dynamicSamplingContext = event.sdkProcessingMetadata?.dynamicSamplingContext;
return {
event_id: event.event_id,
sent_at: new Date(safeDateNow()).toISOString(),
...sdkInfo && { sdk: sdkInfo },
...!!tunnel && dsn && { dsn: dsnToString(dsn) },
...dynamicSamplingContext && {
trace: dynamicSamplingContext
}
};
}
function _enhanceEventWithSdkInfo(event, newSdkInfo) {
if (!newSdkInfo) {
return event;
}
const eventSdkInfo = event.sdk || {};
event.sdk = {
...eventSdkInfo,
name: eventSdkInfo.name || newSdkInfo.name,
version: eventSdkInfo.version || newSdkInfo.version,
integrations: [...event.sdk?.integrations || [], ...newSdkInfo.integrations || []],
packages: [...event.sdk?.packages || [], ...newSdkInfo.packages || []],
settings: event.sdk?.settings || newSdkInfo.settings ? {
...event.sdk?.settings,
...newSdkInfo.settings
} : void 0
};
return event;
}
function createSessionEnvelope(session, dsn, metadata, tunnel) {
const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata);
const envelopeHeaders = {
sent_at: new Date(safeDateNow()).toISOString(),
...sdkInfo && { sdk: sdkInfo },
...!!tunnel && dsn && { dsn: dsnToString(dsn) }
};
const envelopeItem = "aggregates" in session ? [{ type: "sessions" }, session] : [{ type: "session" }, session.toJSON()];
return createEnvelope(envelopeHeaders, [envelopeItem]);
}
function createEventEnvelope(event, dsn, metadata, tunnel) {
const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata);
const eventType = event.type && event.type !== "replay_event" ? event.type : "event";
_enhanceEventWithSdkInfo(event, metadata?.sdk);
const envelopeHeaders = createEventEnvelopeHeaders(event, sdkInfo, tunnel, dsn);
delete event.sdkProcessingMetadata;
const eventItem = [{ type: eventType }, event];
return createEnvelope(envelopeHeaders, [eventItem]);
}
function createSpanEnvelope(spans, client) {
function dscHasRequiredProps(dsc2) {
return !!dsc2.trace_id && !!dsc2.public_key;
}
const dsc = getDynamicSamplingContextFromSpan(spans[0]);
const dsn = client?.getDsn();
const tunnel = client?.getOptions().tunnel;
const headers = {
sent_at: new Date(safeDateNow()).toISOString(),
...dscHasRequiredProps(dsc) && { trace: dsc },
...!!tunnel && dsn && { dsn: dsnToString(dsn) }
};
const { beforeSendSpan, ignoreSpans } = client?.getOptions() || {};
const filteredSpans = ignoreSpans?.length ? spans.filter((span) => {
const json = spanToJSON(span);
return !shouldIgnoreSpan({ description: json.description, op: json.op, attributes: json.data }, ignoreSpans);
}) : spans;
const droppedSpans = spans.length - filteredSpans.length;
if (droppedSpans) {
client?.recordDroppedEvent("before_send", "span", droppedSpans);
}
const convertToSpanJSON = beforeSendSpan ? (span) => {
const spanJson = spanToJSON(span);
const processedSpan = !isStreamedBeforeSendSpanCallback(beforeSendSpan) ? beforeSendSpan(spanJson) : spanJson;
if (!processedSpan) {
showSpanDropWarning();
return spanJson;
}
return processedSpan;
} : spanToJSON;
const items = [];
for (const span of filteredSpans) {
const spanJson = convertToSpanJSON(span);
if (spanJson) {
items.push(createSpanEnvelopeItem(spanJson));
}
}
return createEnvelope(headers, items);
}
function logSpanStart(span) {
if (!DEBUG_BUILD$3) return;
const { description = "< unknown name >", op = "< unknown op >", parent_span_id: parentSpanId } = spanToJSON(span);
const { spanId } = span.spanContext();
const sampled = spanIsSampled(span);
const rootSpan = getRootSpan(span);
const isRootSpan = rootSpan === span;
const header = `[Tracing] Starting ${sampled ? "sampled" : "unsampled"} ${isRootSpan ? "root " : ""}span`;
const infoParts = [`op: ${op}`, `name: ${description}`, `ID: ${spanId}`];
if (parentSpanId) {
infoParts.push(`parent ID: ${parentSpanId}`);
}
if (!isRootSpan) {
const { op: op2, description: description2 } = spanToJSON(rootSpan);
infoParts.push(`root ID: ${rootSpan.spanContext().spanId}`);
if (op2) {
infoParts.push(`root op: ${op2}`);
}
if (description2) {
infoParts.push(`root description: ${description2}`);
}
}
debug$1.log(`${header}
${infoParts.join("\n ")}`);
}
function logSpanEnd(span) {
if (!DEBUG_BUILD$3) return;
const { description = "< unknown name >", op = "< unknown op >" } = spanToJSON(span);
const { spanId } = span.spanContext();
const rootSpan = getRootSpan(span);
const isRootSpan = rootSpan === span;
const msg = `[Tracing] Finishing "${op}" ${isRootSpan ? "root " : ""}span "${description}" with ID ${spanId}`;
debug$1.log(msg);
}
function setMeasurement(name, value, unit, activeSpan = getActiveSpan()) {
const rootSpan = activeSpan && getRootSpan(activeSpan);
if (rootSpan) {
DEBUG_BUILD$3 && debug$1.log(`[Measurement] Setting measurement on root span: ${name} = ${value} ${unit}`);
rootSpan.addEvent(name, {
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: value,
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: unit
});
}
}
function timedEventsToMeasurements(events) {
if (!events || events.length === 0) {
return void 0;
}
const measurements = {};
events.forEach((event) => {
const attributes = event.attributes || {};
const unit = attributes[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT];
const value = attributes[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE];
if (typeof unit === "string" && typeof value === "number") {
measurements[event.name] = { value, unit };
}
});
return measurements;
}
function getSegmentSpanCaptureStrategy() {
return getSentryCarrier(getMainCarrier()).segmentSpanCaptureStrategy;
}
function hasSpanStreamingEnabled(client) {
return client.getOptions().traceLifecycle === "stream";
}
const MAX_SPAN_COUNT = 1e3;
class SentrySpan {
/**
* You should never call the constructor manually, always use `Sentry.startSpan()`
* or other span methods.
* @internal
* @hideconstructor
* @hidden
*/
constructor(spanContext = {}) {
this._traceId = spanContext.traceId || generateTraceId();
this._spanId = spanContext.spanId || generateSpanId();
this._startTime = spanContext.startTimestamp || timestampInSeconds();
this._links = spanContext.links;
this._attributes = {};
this.setAttributes({
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "manual",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: spanContext.op,
...spanContext.attributes
});
this._name = spanContext.name;
if (spanContext.parentSpanId) {
this._parentSpanId = spanContext.parentSpanId;
}
if ("sampled" in spanContext) {
this._sampled = spanContext.sampled;
}
if (spanContext.endTimestamp) {
this._endTime = spanContext.endTimestamp;
}
this._events = [];
this._isStandaloneSpan = spanContext.isStandalone;
if (this._endTime) {
this._onSpanEnded();
}
}
/** @inheritDoc */
addLink(link) {
if (this._frozen) {
return this;
}
if (this._links) {
this._links.push(link);
} else {
this._links = [link];
}
return this;
}
/** @inheritDoc */
addLinks(links) {
if (this._frozen) {
return this;
}
if (this._links) {
this._links.push(...links);
} else {
this._links = links;
}
return this;
}
/**
* This should generally not be used,
* but it is needed for being compliant with the OTEL Span interface.
*
* @hidden
* @internal
*/
recordException(_exception, _time) {
}
/** @inheritdoc */
spanContext() {
const { _spanId: spanId, _traceId: traceId, _sampled: sampled } = this;
return {
spanId,
traceId,
traceFlags: sampled ? TRACE_FLAG_SAMPLED : TRACE_FLAG_NONE
};
}
/** @inheritdoc */
setAttribute(key, value) {
if (this._frozen) {
return this;
}
if (value === void 0) {
delete this._attributes[key];
} else {
this._attributes[key] = value;
}
if (key === SEMANTIC_ATTRIBUTE_SENTRY_SOURCE && value !== void 0 && spanShouldInferOtelSource(this)) {
markSpanSourceAsExplicit(this);
}
return this;
}
/** @inheritdoc */
setAttributes(attributes) {
Object.keys(attributes).forEach((key) => this.setAttribute(key, attributes[key]));
return this;
}
/**
* This should generally not be used,
* but we need it for browser tracing where we want to adjust the start time afterwards.
* USE THIS WITH CAUTION!
*
* @hidden
* @internal
*/
updateStartTime(timeInput) {
if (this._frozen) {
return;
}
this._startTime = spanTimeInputToSeconds(timeInput);
}
/**
* @inheritDoc
*/
setStatus(value) {
if (this._frozen) {
return this;
}
this._status = value;
return this;
}
/**
* @inheritDoc
*/
updateName(name) {
if (this._frozen) {
return this;
}
this._name = name;
if (!spanShouldInferOtelSource(this)) {
this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, "custom");
}
return this;
}
/** @inheritdoc */
end(endTimestamp) {
if (this._endTime) {
this._frozen = spanIsTracerProviderSpan(this);
return;
}
this._endTime = spanTimeInputToSeconds(endTimestamp);
logSpanEnd(this);
this._onSpanEnded();
this._frozen = spanIsTracerProviderSpan(this);
}
/**
* Get JSON representation of this span.
*
* @hidden
* @internal This method is purely for internal purposes and should not be used outside
* of SDK code. If you need to get a JSON representation of a span,
* use `spanToJSON(span)` instead.
*/
getSpanJSON() {
return {
data: this._attributes,
description: this._name,
op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP],
parent_span_id: this._parentSpanId,
span_id: this._spanId,
start_timestamp: this._startTime,
status: getStatusMessage(this._status),
timestamp: this._endTime,
trace_id: this._traceId,
origin: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN],
profile_id: this._attributes[SEMANTIC_ATTRIBUTE_PROFILE_ID],
exclusive_time: this._attributes[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME],
measurements: timedEventsToMeasurements(this._events),
is_segment: this._isStandaloneSpan && getRootSpan(this) === this || void 0,
segment_id: this._isStandaloneSpan ? getRootSpan(this).spanContext().spanId : void 0,
links: convertSpanLinksForEnvelope(this._links)
};
}
/**
* Get {@link StreamedSpanJSON} representation of this span.
*
* @hidden
* @internal This method is purely for internal purposes and should not be used outside
* of SDK code. If you need to get a JSON representation of a span,
* use `spanToStreamedSpanJSON(span)` instead.
*/
getStreamedSpanJSON() {
return {
name: this._name ?? "",
span_id: this._spanId,
trace_id: this._traceId,
parent_span_id: this._parentSpanId,
start_timestamp: this._startTime,
// just in case _endTime is not set, we use the start time (i.e. duration 0)
end_timestamp: this._endTime ?? this._startTime,
is_segment: this._isStandaloneSpan || this === getRootSpan(this),
status: getSimpleStatus(this._status),
attributes: addStatusMessageAttribute(this._attributes, this._status),
links: getStreamedSpanLinks(this._links)
};
}
/** @inheritdoc */
isRecording() {
return !this._endTime && !!this._sampled;
}
/**
* @inheritdoc
*/
addEvent(name, attributesOrStartTime, startTime) {
if (this._frozen) {
return this;
}
DEBUG_BUILD$3 && debug$1.log("[Tracing] Adding an event to span:", name);
const time = isSpanTimeInput(attributesOrStartTime) ? attributesOrStartTime : startTime || timestampInSeconds();
const attributes = isSpanTimeInput(attributesOrStartTime) ? {} : attributesOrStartTime || {};
const event = {
name,
time: spanTimeInputToSeconds(time),
attributes
};
this._events.push(event);
return this;
}
/**
* This method should generally not be used,
* but for now we need a way to publicly check if the `_isStandaloneSpan` flag is set.
* USE THIS WITH CAUTION!
* @internal
* @hidden
* @experimental
*/
isStandaloneSpan() {
return !!this._isStandaloneSpan;
}
/** Emit `spanEnd` when the span is ended. */
_onSpanEnded() {
const client = getClient();
if (client) {
client.emit("spanEnd", this);
if (!this._isStandaloneSpan) {
client.emit("afterSpanEnd", this);
}
}
const rootSpan = getRootSpan(this);
const isSegmentSpan = this._isStandaloneSpan || this === rootSpan;
if (this._isStandaloneSpan) {
if (this._sampled) {
sendSpanEnvelope(createSpanEnvelope([this], client));
} else {
DEBUG_BUILD$3 && debug$1.log("[Tracing] Discarding standalone span because its trace was not chosen to be sampled.");
if (client) {
client.recordDroppedEvent("sample_rate", "span");
}
}
return;
}
if (!isSegmentSpan) {
const strategy2 = getSegmentSpanCaptureStrategy();
if (strategy2) {
const scope2 = getCapturedScopesOnSpan(this).scope || getCurrentScope();
strategy2.onChildSpanEnded(this, rootSpan, (options) => this._convertSpanToTransaction(options), scope2);
}
return;
}
if (client && hasSpanStreamingEnabled(client)) {
client.emit("afterSegmentSpanEnd", this);
return;
}
const scope = getCapturedScopesOnSpan(this).scope || getCurrentScope();
const strategy = getSegmentSpanCaptureStrategy();
if (strategy) {
strategy.onSegmentSpanEnded((options) => this._convertSpanToTransaction(options), scope);
} else {
const transactionEvent = this._convertSpanToTransaction();
if (transactionEvent) {
scope.captureEvent(transactionEvent);
}
}
}
/**
* Finish the transaction & prepare the event to send to Sentry.
*/
_convertSpanToTransaction(options = {}) {
if (!isFullFinishedSpan(spanToJSON(this))) {
return void 0;
}
if (!this._name) {
DEBUG_BUILD$3 && debug$1.warn("Transaction has no name, falling back to `<unlabeled transaction>`.");
this._name = "<unlabeled transaction>";
}
const { scope: capturedSpanScope, isolationScope: capturedSpanIsolationScope } = getCapturedScopesOnSpan(this);
const normalizedRequest = capturedSpanScope?.getScopeData().sdkProcessingMetadata?.normalizedRequest;
if (this._sampled !== true) {
return void 0;
}
options.onSpanCaptured?.(this);
const spans = [];
for (const descendant of getSpanDescendants(this)) {
if (descendant === this || isStandaloneSpan(descendant) || options.isSpanAlreadyCaptured?.(descendant)) {
continue;
}
const spanJSON = spanToJSON(descendant);
if (!isFullFinishedSpan(spanJSON)) {
continue;
}
options.onSpanCaptured?.(descendant);
spans.push(spanJSON);
}
const source = this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
delete this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
let hasGenAiSpans = false;
spans.forEach((span) => {
delete span.data[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
if (span.op?.startsWith("gen_ai.")) {
hasGenAiSpans = true;
}
});
const transaction = {
contexts: {
trace: spanToTransactionTraceContext(this)
},
spans: (
// spans.sort() mutates the array, but `spans` is already a copy so we can safely do this here
// we do not use spans anymore after this point
spans.length > MAX_SPAN_COUNT ? spans.sort((a, b) => a.start_timestamp - b.start_timestamp).slice(0, MAX_SPAN_COUNT) : spans
),
start_timestamp: this._startTime,
timestamp: this._endTime,
transaction: this._name,
type: "transaction",
sdkProcessingMetadata: {
capturedSpanScope,
capturedSpanIsolationScope,
dynamicSamplingContext: getDynamicSamplingContextFromSpan(this),
hasGenAiSpans
},
request: normalizedRequest,
...source && {
transaction_info: {
source
}
}
};
const measurements = timedEventsToMeasurements(this._events);
const hasMeasurements = measurements && Object.keys(measurements).length;
if (hasMeasurements) {
DEBUG_BUILD$3 && debug$1.log(
"[Measurements] Adding measurements to transaction event",
JSON.stringify(measurements, void 0, 2)
);
transaction.measurements = measurements;
}
return transaction;
}
}
function isSpanTimeInput(value) {
return value && typeof value === "number" || value instanceof Date || Array.isArray(value);
}
function isFullFinishedSpan(input) {
return !!input.start_timestamp && !!input.timestamp && !!input.span_id && !!input.trace_id;
}
function isStandaloneSpan(span) {
return span instanceof SentrySpan && span.isStandaloneSpan();
}
function sendSpanEnvelope(envelope) {
const client = getClient();
if (!client) {
return;
}
const spanItems = envelope[1];
if (!spanItems || spanItems.length === 0) {
client.recordDroppedEvent("before_send", "span");
return;
}
client.sendEnvelope(envelope);
}
function sampleSpan(options, samplingContext, sampleRand) {
if (!hasSpansEnabled(options)) {
return [false];
}
let localSampleRateWasApplied = void 0;
let sampleRate;
if (typeof options.tracesSampler === "function") {
sampleRate = options.tracesSampler({
...samplingContext,
inheritOrSampleWith: (fallbackSampleRate) => {
if (typeof samplingContext.parentSampleRate === "number") {
return samplingContext.parentSampleRate;
}
if (typeof samplingContext.parentSampled === "boolean") {
return Number(samplingContext.parentSampled);
}
return fallbackSampleRate;
}
});
localSampleRateWasApplied = true;
} else if (samplingContext.parentSampled !== void 0) {
sampleRate = samplingContext.parentSampled;
} else if (typeof options.tracesSampleRate !== "undefined") {
sampleRate = options.tracesSampleRate;
localSampleRateWasApplied = true;
}
const parsedSampleRate = parseSampleRate(sampleRate);
if (parsedSampleRate === void 0) {
DEBUG_BUILD$3 && debug$1.warn(
`[Tracing] Discarding root span because of invalid sample rate. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
sampleRate
)} of type ${JSON.stringify(typeof sampleRate)}.`
);
return [false];
}
if (!parsedSampleRate) {
DEBUG_BUILD$3 && debug$1.log(
`[Tracing] Discarding transaction because ${typeof options.tracesSampler === "function" ? "tracesSampler returned 0 or false" : "a negative sampling decision was inherited or tracesSampleRate is set to 0"}`
);
return [false, parsedSampleRate, localSampleRateWasApplied];
}
const shouldSample = sampleRand < parsedSampleRate;
if (!shouldSample) {
DEBUG_BUILD$3 && debug$1.log(
`[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
sampleRate
)})`
);
}
return [shouldSample, parsedSampleRate, localSampleRateWasApplied];
}
const SUPPRESS_TRACING_KEY = "__SENTRY_SUPPRESS_TRACING__";
function startInactiveSpan(options) {
const acs = getAcs();
if (acs.startInactiveSpan) {
return acs.startInactiveSpan(options);
}
return _startInactiveSpanImpl(options);
}
function _startInactiveSpanImpl(options) {
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan } = options;
const wrapper = options.scope ? (callback) => withScope(options.scope, callback) : customParentSpan !== void 0 ? (callback) => withActiveSpan(customParentSpan, callback) : (callback) => callback();
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope, customParentSpan);
const client = getClient();
const missingRequiredParent = options.onlyIfParent && !parentSpan;
if (missingRequiredParent) {
return startMissingRequiredParentSpan(scope, client);
}
return createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
});
});
}
function withActiveSpan(span, callback) {
const acs = getAcs();
if (acs.withActiveSpan) {
return acs.withActiveSpan(span, callback);
}
return withScope((scope) => {
_setSpanForScope(scope, span || void 0);
return callback(scope);
});
}
function isTracingSuppressed(scope = getCurrentScope()) {
const acs = getAcs();
if (acs.isTracingSuppressed) {
return acs.isTracingSuppressed(scope);
}
return scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] === true;
}
function startMissingRequiredParentSpan(scope, client) {
client?.recordDroppedEvent("no_parent_span", "span");
const span = new SentryNonRecordingSpan({ traceId: scope.getPropagationContext().traceId });
setCapturedScopesOnSpan(span, scope, getIsolationScope());
return span;
}
function createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
}) {
const isolationScope = getIsolationScope();
if (!hasSpansEnabled()) {
const scopePropagationContext = { ...isolationScope.getPropagationContext(), ...scope.getPropagationContext() };
const traceId = parentSpan ? parentSpan.spanContext().traceId : scopePropagationContext.traceId;
const span2 = new SentryNonRecordingSpan({ traceId });
if (parentSpan && !forceTransaction) {
addChildSpanToSpan(parentSpan, span2);
}
setCapturedScopesOnSpan(span2, scope, isolationScope);
return span2;
}
const client = getClient();
if (_shouldIgnoreStreamedSpan(client, spanArguments)) {
if (!isTracingSuppressed(scope)) {
client?.recordDroppedEvent("ignored", "span");
}
const ignoredSpan = new SentryNonRecordingSpan({
dropReason: "ignored",
traceId: parentSpan?.spanContext().traceId ?? scope.getPropagationContext().traceId
});
if (parentSpan && !forceTransaction) {
addChildSpanToSpan(parentSpan, ignoredSpan);
}
setCapturedScopesOnSpan(ignoredSpan, scope, isolationScope);
return ignoredSpan;
}
let span;
if (parentSpan && !forceTransaction) {
span = _startChildSpan(parentSpan, scope, spanArguments, isolationScope);
addChildSpanToSpan(parentSpan, span);
} else if (parentSpan) {
const dsc = getDynamicSamplingContextFromSpan(parentSpan);
const { traceId, spanId: parentSpanId } = parentSpan.spanContext();
const parentSampled = spanIsSampled(parentSpan);
span = _startRootSpan(
{
traceId,
parentSpanId,
...spanArguments
},
scope,
isolationScope,
parentSampled
);
freezeDscOnSpan(span, dsc);
} else {
const {
traceId,
dsc,
parentSpanId,
sampled: parentSampled
} = {
...isolationScope.getPropagationContext(),
...scope.getPropagationContext()
};
span = _startRootSpan(
{
traceId,
parentSpanId,
...spanArguments
},
scope,
isolationScope,
parentSampled
);
if (dsc) {
freezeDscOnSpan(span, dsc);
}
}
logSpanStart(span);
return span;
}
function parseSentrySpanArguments(options) {
const exp = options.experimental || {};
const initialCtx = {
isStandalone: exp.standalone,
...options
};
if (options.startTime) {
const ctx = { ...initialCtx };
ctx.startTimestamp = spanTimeInputToSeconds(options.startTime);
delete ctx.startTime;
return ctx;
}
return initialCtx;
}
function getAcs() {
const carrier = getMainCarrier();
return getAsyncContextStrategy(carrier);
}
function _startRootSpan(spanArguments, scope, isolationScope, parentSampled) {
const client = getClient();
const options = client?.getOptions() || {};
const { name = "" } = spanArguments;
const mutableSpanSamplingData = { spanAttributes: { ...spanArguments.attributes }, spanName: name, parentSampled };
client?.emit("beforeSampling", mutableSpanSamplingData, { decision: false });
const finalParentSampled = mutableSpanSamplingData.parentSampled ?? parentSampled;
const finalAttributes = mutableSpanSamplingData.spanAttributes;
const currentPropagationContext = scope.getPropagationContext();
const _isTracingSuppressed = isTracingSuppressed(scope);
const [sampled, sampleRate, localSampleRateWasApplied] = _isTracingSuppressed ? [false] : sampleSpan(
options,
{
name,
parentSampled: finalParentSampled,
attributes: finalAttributes,
normalizedRequest: isolationScope.getScopeData().sdkProcessingMetadata.normalizedRequest,
parentSampleRate: parseSampleRate(currentPropagationContext.dsc?.sample_rate)
},
currentPropagationContext.sampleRand
);
const rootSpan = new SentrySpan({
...spanArguments,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "custom",
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: sampleRate !== void 0 && localSampleRateWasApplied ? sampleRate : void 0,
...finalAttributes
},
sampled
});
if (!sampled && client && !_isTracingSuppressed) {
DEBUG_BUILD$3 && debug$1.log("[Tracing] Discarding root span because its trace was not chosen to be sampled.");
client.recordDroppedEvent("sample_rate", hasSpanStreamingEnabled(client) ? "span" : "transaction");
}
setCapturedScopesOnSpan(rootSpan, scope, isolationScope);
if (client) {
client.emit("spanStart", rootSpan);
}
return rootSpan;
}
function _startChildSpan(parentSpan, scope, spanArguments, isolationScope) {
const { spanId, traceId } = parentSpan.spanContext();
const _isTracingSuppressed = isTracingSuppressed(scope);
const sampled = _isTracingSuppressed ? false : spanIsSampled(parentSpan);
const childSpan = sampled ? new SentrySpan({
...spanArguments,
parentSpanId: spanId,
traceId,
sampled
}) : new SentryNonRecordingSpan({ traceId });
addChildSpanToSpan(parentSpan, childSpan);
setCapturedScopesOnSpan(childSpan, scope, isolationScope);
const client = getClient();
if (!client) {
return childSpan;
}
if (hasSpanStreamingEnabled(client) && spanIsNonRecordingSpan(childSpan)) {
if (spanIsNonRecordingSpan(parentSpan) && parentSpan.dropReason) {
childSpan.dropReason = parentSpan.dropReason;
client.recordDroppedEvent(parentSpan.dropReason, "span");
} else if (!_isTracingSuppressed) {
childSpan.dropReason = "sample_rate";
client.recordDroppedEvent("sample_rate", "span");
}
}
client.emit("spanStart", childSpan);
if (spanArguments.endTimestamp) {
client.emit("spanEnd", childSpan);
client.emit("afterSpanEnd", childSpan);
}
return childSpan;
}
function getParentSpan(scope, customParentSpan) {
if (customParentSpan) {
return customParentSpan;
}
if (customParentSpan === null) {
return void 0;
}
const span = _getSpanForScope(scope);
if (!span) {
return void 0;
}
const client = getClient();
const options = client ? client.getOptions() : {};
if (options.parentSpanIsAlwaysRootSpan) {
return getRootSpan(span);
}
return span;
}
function _shouldIgnoreStreamedSpan(client, spanArguments) {
const ignoreSpans = client?.getOptions().ignoreSpans;
if (!client || !hasSpanStreamingEnabled(client) || !ignoreSpans?.length) {
return false;
}
return shouldIgnoreSpan(
{
description: spanArguments.name || "",
op: spanArguments.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] || spanArguments.op,
attributes: spanArguments.attributes
},
ignoreSpans
);
}
function spanIsIgnored(span) {
return spanIsNonRecordingSpan(span) && span.dropReason === "ignored";
}
const TRACING_DEFAULTS = {
idleTimeout: 1e3,
finalTimeout: 3e4,
childSpanTimeout: 15e3
};
const FINISH_REASON_HEARTBEAT_FAILED = "heartbeatFailed";
const FINISH_REASON_IDLE_TIMEOUT = "idleTimeout";
const FINISH_REASON_FINAL_TIMEOUT = "finalTimeout";
const FINISH_REASON_EXTERNAL_FINISH = "externalFinish";
function startIdleSpan(startSpanOptions, options = {}) {
const activities = /* @__PURE__ */ new Map();
let _finished = false;
let _idleTimeoutID;
let _childSpanTimeoutID;
let _finishReason = FINISH_REASON_EXTERNAL_FINISH;
let _autoFinishAllowed = !options.disableAutoFinish;
const _cleanupHooks = [];
const {
idleTimeout = TRACING_DEFAULTS.idleTimeout,
finalTimeout = TRACING_DEFAULTS.finalTimeout,
childSpanTimeout = TRACING_DEFAULTS.childSpanTimeout,
beforeSpanEnd,
trimIdleSpanEndTimestamp = true
} = options;
const client = getClient();
const scope = getCurrentScope();
if (!client || !hasSpansEnabled()) {
const span2 = new SentryNonRecordingSpan({ traceId: scope.getPropagationContext().traceId });
setCapturedScopesOnSpan(span2, scope, getIsolationScope());
return span2;
}
const previousActiveSpan = getActiveSpan();
const span = _startIdleSpan(startSpanOptions);
span.end = new Proxy(span.end, {
apply(target, thisArg, args) {
if (beforeSpanEnd) {
beforeSpanEnd(span);
}
if (spanIsNonRecordingSpan(thisArg)) {
return;
}
const [definedEndTimestamp, ...rest] = args;
const timestamp = definedEndTimestamp || timestampInSeconds();
const spanEndTimestamp = spanTimeInputToSeconds(timestamp);
const spans = getSpanDescendants(span).filter((child) => child !== span);
const spanJson = spanToJSON(span);
if (!spans.length || !trimIdleSpanEndTimestamp) {
onIdleSpanEnded(spanEndTimestamp);
return Reflect.apply(target, thisArg, [spanEndTimestamp, ...rest]);
}
const ignoreSpans = client.getOptions().ignoreSpans;
const latestSpanEndTimestamp = spans?.reduce((acc, current) => {
const currentSpanJson = spanToJSON(current);
if (!currentSpanJson.timestamp) {
return acc;
}
if (ignoreSpans && shouldIgnoreSpan(
{ description: currentSpanJson.description, op: currentSpanJson.op, attributes: currentSpanJson.data },
ignoreSpans
)) {
return acc;
}
return acc ? Math.max(acc, currentSpanJson.timestamp) : currentSpanJson.timestamp;
}, void 0);
const spanStartTimestamp = spanJson.start_timestamp;
const endTimestamp = Math.min(
spanStartTimestamp ? spanStartTimestamp + finalTimeout / 1e3 : Infinity,
Math.max(spanStartTimestamp || -Infinity, Math.min(spanEndTimestamp, latestSpanEndTimestamp || Infinity))
);
onIdleSpanEnded(endTimestamp);
return Reflect.apply(target, thisArg, [endTimestamp, ...rest]);
}
});
function _cancelIdleTimeout() {
if (_idleTimeoutID) {
clearTimeout(_idleTimeoutID);
_idleTimeoutID = void 0;
}
}
function _cancelChildSpanTimeout() {
if (_childSpanTimeoutID) {
clearTimeout(_childSpanTimeoutID);
_childSpanTimeoutID = void 0;
}
}
function _restartIdleTimeout(endTimestamp) {
_cancelIdleTimeout();
_idleTimeoutID = setTimeout(() => {
if (!_finished && activities.size === 0 && _autoFinishAllowed) {
_finishReason = FINISH_REASON_IDLE_TIMEOUT;
span.end(endTimestamp);
}
}, idleTimeout);
}
function _restartChildSpanTimeout(endTimestamp) {
_cancelChildSpanTimeout();
_childSpanTimeoutID = setTimeout(() => {
if (!_finished && _autoFinishAllowed) {
_finishReason = FINISH_REASON_HEARTBEAT_FAILED;
span.end(endTimestamp);
}
}, childSpanTimeout);
}
function _pushActivity(spanId) {
_cancelIdleTimeout();
activities.set(spanId, true);
const endTimestamp = timestampInSeconds();
_restartChildSpanTimeout(endTimestamp + childSpanTimeout / 1e3);
}
function _popActivity(spanId) {
if (activities.has(spanId)) {
activities.delete(spanId);
}
if (activities.size === 0) {
const endTimestamp = timestampInSeconds();
_restartIdleTimeout(endTimestamp + idleTimeout / 1e3);
_cancelChildSpanTimeout();
}
}
function onIdleSpanEnded(endTimestamp) {
_finished = true;
activities.clear();
_cleanupHooks.forEach((cleanup) => cleanup());
_setSpanForScope(scope, previousActiveSpan);
const spanJSON = spanToJSON(span);
const { start_timestamp: startTimestamp } = spanJSON;
if (!startTimestamp) {
return;
}
const attributes = spanJSON.data;
if (!attributes[SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON]) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, _finishReason);
}
const currentStatus = spanJSON.status;
if (!currentStatus || currentStatus === "unknown") {
span.setStatus({ code: SPAN_STATUS_OK });
}
debug$1.log(`[Tracing] Idle span "${spanJSON.op}" finished`);
const childSpans = getSpanDescendants(span).filter((child) => child !== span);
let discardedSpans = 0;
childSpans.forEach((childSpan) => {
if (childSpan.isRecording()) {
childSpan.setStatus({ code: SPAN_STATUS_ERROR, message: "cancelled" });
childSpan.end(endTimestamp);
DEBUG_BUILD$3 && debug$1.log("[Tracing] Cancelling span since span ended early", JSON.stringify(childSpan, void 0, 2));
}
const childSpanJSON = spanToJSON(childSpan);
const { timestamp: childEndTimestamp = 0, start_timestamp: childStartTimestamp = 0 } = childSpanJSON;
const spanStartedBeforeIdleSpanEnd = childStartTimestamp <= endTimestamp;
const timeoutWithMarginOfError = (finalTimeout + idleTimeout) / 1e3;
const spanEndedBeforeFinalTimeout = childEndTimestamp - childStartTimestamp <= timeoutWithMarginOfError;
if (DEBUG_BUILD$3) {
const stringifiedSpan = JSON.stringify(childSpan, void 0, 2);
if (!spanStartedBeforeIdleSpanEnd) {
debug$1.log("[Tracing] Discarding span since it happened after idle span was finished", stringifiedSpan);
} else if (!spanEndedBeforeFinalTimeout) {
debug$1.log("[Tracing] Discarding span since it finished after idle span final timeout", stringifiedSpan);
}
}
if (!spanEndedBeforeFinalTimeout || !spanStartedBeforeIdleSpanEnd) {
removeChildSpanFromSpan(span, childSpan);
discardedSpans++;
}
});
if (discardedSpans > 0) {
span.setAttribute("sentry.idle_span_discarded_spans", discardedSpans);
}
}
_cleanupHooks.push(
client.on("spanStart", (startedSpan) => {
if (_finished || startedSpan === span || !!spanToJSON(startedSpan).timestamp || startedSpan instanceof SentrySpan && startedSpan.isStandaloneSpan()) {
return;
}
const allSpans = getSpanDescendants(span);
if (allSpans.includes(startedSpan)) {
_pushActivity(startedSpan.spanContext().spanId);
}
})
);
_cleanupHooks.push(
client.on("spanEnd", (endedSpan) => {
if (_finished) {
return;
}
_popActivity(endedSpan.spanContext().spanId);
})
);
_cleanupHooks.push(
client.on("idleSpanEnableAutoFinish", (spanToAllowAutoFinish) => {
if (spanToAllowAutoFinish === span) {
_autoFinishAllowed = true;
_restartIdleTimeout();
if (activities.size) {
_restartChildSpanTimeout();
}
}
})
);
if (!options.disableAutoFinish) {
_restartIdleTimeout();
}
setTimeout(() => {
if (!_finished) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: "deadline_exceeded" });
_finishReason = FINISH_REASON_FINAL_TIMEOUT;
span.end();
}
}, finalTimeout);
return span;
}
function _startIdleSpan(options) {
const span = startInactiveSpan(options);
_setSpanForScope(getCurrentScope(), span);
DEBUG_BUILD$3 && debug$1.log("[Tracing] Started span is an idle span");
return span;
}
function debounce$1(func, wait, options) {
let callbackReturnValue;
let timerId;
let maxTimerId;
const maxWait = options?.maxWait ? Math.max(options.maxWait, wait) : 0;
const setTimeoutImpl = options?.setTimeoutImpl || setTimeout;
function invokeFunc() {
cancelTimers();
callbackReturnValue = func();
return callbackReturnValue;
}
function cancelTimers() {
timerId !== void 0 && clearTimeout(timerId);
maxTimerId !== void 0 && clearTimeout(maxTimerId);
timerId = maxTimerId = void 0;
}
function flush() {
if (timerId !== void 0 || maxTimerId !== void 0) {
return invokeFunc();
}
return callbackReturnValue;
}
function debounced() {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeoutImpl(invokeFunc, wait);
if (maxWait && maxTimerId === void 0) {
maxTimerId = setTimeoutImpl(invokeFunc, maxWait);
}
return callbackReturnValue;
}
debounced.cancel = cancelTimers;
debounced.flush = flush;
return debounced;
}
function applyScopeDataToEvent(event, data) {
const { fingerprint, span, breadcrumbs, sdkProcessingMetadata } = data;
applyDataToEvent(event, data);
if (span) {
applySpanToEvent(event, span);
}
applyFingerprintToEvent(event, fingerprint);
applyBreadcrumbsToEvent(event, breadcrumbs);
applySdkMetadataToEvent(event, sdkProcessingMetadata);
}
function mergeScopeData(data, mergeData) {
const {
extra,
tags,
attributes,
user,
contexts,
level,
sdkProcessingMetadata,
breadcrumbs,
fingerprint,
eventProcessors,
attachments,
propagationContext,
transactionName,
span
} = mergeData;
mergeAndOverwriteScopeData(data, "extra", extra);
mergeAndOverwriteScopeData(data, "tags", tags);
mergeAndOverwriteScopeData(data, "attributes", attributes);
mergeAndOverwriteScopeData(data, "user", user);
mergeAndOverwriteScopeData(data, "contexts", contexts);
data.sdkProcessingMetadata = merge(data.sdkProcessingMetadata, sdkProcessingMetadata, 2);
if (level) {
data.level = level;
}
if (transactionName) {
data.transactionName = transactionName;
}
if (span) {
data.span = span;
}
if (breadcrumbs.length) {
data.breadcrumbs = [...data.breadcrumbs, ...breadcrumbs];
}
if (fingerprint.length) {
data.fingerprint = [...data.fingerprint, ...fingerprint];
}
if (eventProcessors.length) {
data.eventProcessors = [...data.eventProcessors, ...eventProcessors];
}
if (attachments.length) {
data.attachments = [...data.attachments, ...attachments];
}
data.propagationContext = { ...data.propagationContext, ...propagationContext };
}
function mergeAndOverwriteScopeData(data, prop, mergeVal) {
data[prop] = merge(data[prop], mergeVal, 1);
}
function getCombinedScopeData(isolationScope, currentScope) {
const scopeData = getGlobalScope().getScopeData();
isolationScope && mergeScopeData(scopeData, isolationScope.getScopeData());
currentScope && mergeScopeData(scopeData, currentScope.getScopeData());
return scopeData;
}
function applyDataToEvent(event, data) {
const { extra, tags, user, contexts, level, transactionName } = data;
if (Object.keys(extra).length) {
event.extra = { ...extra, ...event.extra };
}
if (Object.keys(tags).length) {
event.tags = { ...tags, ...event.tags };
}
if (Object.keys(user).length) {
event.user = { ...user, ...event.user };
}
if (Object.keys(contexts).length) {
event.contexts = { ...contexts, ...event.contexts };
}
if (level) {
event.level = level;
}
if (transactionName && event.type !== "transaction") {
event.transaction = transactionName;
}
}
function applyBreadcrumbsToEvent(event, breadcrumbs) {
const mergedBreadcrumbs = [...event.breadcrumbs || [], ...breadcrumbs];
event.breadcrumbs = mergedBreadcrumbs.length ? mergedBreadcrumbs : void 0;
}
function applySdkMetadataToEvent(event, sdkProcessingMetadata) {
event.sdkProcessingMetadata = {
...event.sdkProcessingMetadata,
...sdkProcessingMetadata
};
}
function applySpanToEvent(event, span) {
event.contexts = {
trace: spanToTraceContext(span),
...event.contexts
};
event.sdkProcessingMetadata = {
dynamicSamplingContext: getDynamicSamplingContextFromSpan(span),
...event.sdkProcessingMetadata
};
const rootSpan = getRootSpan(span);
const transactionName = spanToJSON(rootSpan).description;
if (transactionName && !event.transaction && event.type === "transaction") {
event.transaction = transactionName;
}
}
function applyFingerprintToEvent(event, fingerprint) {
event.fingerprint = event.fingerprint ? Array.isArray(event.fingerprint) ? event.fingerprint : [event.fingerprint] : [];
if (fingerprint) {
event.fingerprint = event.fingerprint.concat(fingerprint);
}
if (!event.fingerprint.length) {
delete event.fingerprint;
}
}
const ws="http.url",Lc="sentry.segment.name",Xc="sentry.transaction",Yu="url.full",Vu="url.path";
function safeSetSpanJSONAttributes(spanJSON, newAttributes) {
const originalAttributes = spanJSON.attributes ?? (spanJSON.attributes = {});
Object.entries(newAttributes).forEach(([key, value]) => {
if (value != null && !(key in originalAttributes)) {
originalAttributes[key] = value;
}
});
}
const STATE_PENDING = 0;
const STATE_RESOLVED = 1;
const STATE_REJECTED = 2;
function resolvedSyncPromise(value) {
return new SyncPromise((resolve) => {
resolve(value);
});
}
function rejectedSyncPromise(reason) {
return new SyncPromise((_, reject) => {
reject(reason);
});
}
class SyncPromise {
constructor(executor) {
this._state = STATE_PENDING;
this._handlers = [];
this._runExecutor(executor);
}
/** @inheritdoc */
then(onfulfilled, onrejected) {
return new SyncPromise((resolve, reject) => {
this._handlers.push([
false,
(result) => {
if (!onfulfilled) {
resolve(result);
} else {
try {
resolve(onfulfilled(result));
} catch (e) {
reject(e);
}
}
},
(reason) => {
if (!onrejected) {
reject(reason);
} else {
try {
resolve(onrejected(reason));
} catch (e) {
reject(e);
}
}
}
]);
this._executeHandlers();
});
}
/** @inheritdoc */
catch(onrejected) {
return this.then((val) => val, onrejected);
}
/** @inheritdoc */
finally(onfinally) {
return new SyncPromise((resolve, reject) => {
let val;
let isRejected;
return this.then(
(value) => {
isRejected = false;
val = value;
if (onfinally) {
onfinally();
}
},
(reason) => {
isRejected = true;
val = reason;
if (onfinally) {
onfinally();
}
}
).then(() => {
if (isRejected) {
reject(val);
return;
}
resolve(val);
});
});
}
/** Excute the resolve/reject handlers. */
_executeHandlers() {
if (this._state === STATE_PENDING) {
return;
}
const cachedHandlers = this._handlers.slice();
this._handlers = [];
cachedHandlers.forEach((handler) => {
if (handler[0]) {
return;
}
if (this._state === STATE_RESOLVED) {
handler[1](this._value);
}
if (this._state === STATE_REJECTED) {
handler[2](this._value);
}
handler[0] = true;
});
}
/** Run the executor for the SyncPromise. */
_runExecutor(executor) {
const setResult = (state, value) => {
if (this._state !== STATE_PENDING) {
return;
}
if (isThenable(value)) {
void value.then(resolve, reject);
return;
}
this._state = state;
this._value = value;
this._executeHandlers();
};
const resolve = (value) => {
setResult(STATE_RESOLVED, value);
};
const reject = (reason) => {
setResult(STATE_REJECTED, reason);
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
}
function notifyEventProcessors(processors, event, hint, index = 0) {
try {
const result = _notifyEventProcessors(event, hint, processors, index);
return isThenable(result) ? result : resolvedSyncPromise(result);
} catch (error) {
return rejectedSyncPromise(error);
}
}
function _notifyEventProcessors(event, hint, processors, index) {
const processor = processors[index];
if (!event || !processor) {
return event;
}
const result = processor({ ...event }, hint);
DEBUG_BUILD$3 && result === null && debug$1.log(`Event processor "${processor.id || "?"}" dropped event`);
if (isThenable(result)) {
return result.then((final) => _notifyEventProcessors(final, hint, processors, index + 1));
}
return _notifyEventProcessors(result, hint, processors, index + 1);
}
let parsedStackResults;
let lastSentryKeysCount;
let lastNativeKeysCount;
let cachedFilenameDebugIds;
function getFilenameToDebugIdMap(stackParser) {
const sentryDebugIdMap = GLOBAL_OBJ._sentryDebugIds;
const nativeDebugIdMap = GLOBAL_OBJ._debugIds;
if (!sentryDebugIdMap && !nativeDebugIdMap) {
return {};
}
const sentryDebugIdKeys = sentryDebugIdMap ? Object.keys(sentryDebugIdMap) : [];
const nativeDebugIdKeys = nativeDebugIdMap ? Object.keys(nativeDebugIdMap) : [];
if (cachedFilenameDebugIds && sentryDebugIdKeys.length === lastSentryKeysCount && nativeDebugIdKeys.length === lastNativeKeysCount) {
return cachedFilenameDebugIds;
}
lastSentryKeysCount = sentryDebugIdKeys.length;
lastNativeKeysCount = nativeDebugIdKeys.length;
cachedFilenameDebugIds = {};
if (!parsedStackResults) {
parsedStackResults = {};
}
const processDebugIds = (debugIdKeys, debugIdMap) => {
for (const key of debugIdKeys) {
const debugId = debugIdMap[key];
const result = parsedStackResults?.[key];
if (result && cachedFilenameDebugIds && debugId) {
cachedFilenameDebugIds[result[0]] = debugId;
if (parsedStackResults) {
parsedStackResults[key] = [result[0], debugId];
}
} else if (debugId) {
const parsedStack = stackParser(key);
for (let i = parsedStack.length - 1; i >= 0; i--) {
const stackFrame = parsedStack[i];
const filename = stackFrame?.filename;
if (filename && cachedFilenameDebugIds && parsedStackResults) {
cachedFilenameDebugIds[filename] = debugId;
parsedStackResults[key] = [filename, debugId];
break;
}
}
}
}
};
if (sentryDebugIdMap) {
processDebugIds(sentryDebugIdKeys, sentryDebugIdMap);
}
if (nativeDebugIdMap) {
processDebugIds(nativeDebugIdKeys, nativeDebugIdMap);
}
return cachedFilenameDebugIds;
}
function prepareEvent(options, event, hint, scope, client, isolationScope) {
const { normalizeDepth = 3, normalizeMaxBreadth = 1e3 } = options;
const prepared = {
...event,
event_id: event.event_id || hint.event_id || uuid4(),
timestamp: event.timestamp || dateTimestampInSeconds()
};
const integrations = hint.integrations || options.integrations.map((i) => i.name);
applyClientOptions(prepared, options);
applyIntegrationsMetadata(prepared, integrations);
if (client) {
client.emit("applyFrameMetadata", event);
}
if (event.type === void 0) {
applyDebugIds(prepared, options.stackParser);
}
const finalScope = getFinalScope(scope, hint.captureContext);
if (hint.mechanism) {
addExceptionMechanism(prepared, hint.mechanism);
}
const clientEventProcessors = client ? client.getEventProcessors() : [];
const data = getCombinedScopeData(isolationScope, finalScope);
const attachments = [...hint.attachments || [], ...data.attachments];
if (attachments.length) {
hint.attachments = attachments;
}
applyScopeDataToEvent(prepared, data);
const eventProcessors = [
...clientEventProcessors,
// Run scope event processors _after_ all other processors
...data.eventProcessors
];
const isInternalException = hint.data && hint.data.__sentry__ === true;
const result = isInternalException ? resolvedSyncPromise(prepared) : notifyEventProcessors(eventProcessors, prepared, hint);
return result.then((evt) => {
if (evt) {
applyDebugMeta(evt);
}
if (typeof normalizeDepth === "number" && normalizeDepth > 0) {
return normalizeEvent(evt, normalizeDepth, normalizeMaxBreadth);
}
return evt;
});
}
function applyClientOptions(event, options) {
const { environment, release, dist, maxValueLength } = options;
event.environment = event.environment || environment || DEFAULT_ENVIRONMENT;
if (!event.release && release) {
event.release = release;
}
if (!event.dist && dist) {
event.dist = dist;
}
const request = event.request;
if (request?.url && maxValueLength) {
request.url = truncate(request.url, maxValueLength);
}
if (maxValueLength) {
event.exception?.values?.forEach((exception) => {
if (exception.value) {
exception.value = truncate(exception.value, maxValueLength);
}
});
}
}
function applyDebugIds(event, stackParser) {
const filenameDebugIdMap = getFilenameToDebugIdMap(stackParser);
event.exception?.values?.forEach((exception) => {
exception.stacktrace?.frames?.forEach((frame) => {
if (frame.filename) {
frame.debug_id = filenameDebugIdMap[frame.filename];
}
});
});
}
function applyDebugMeta(event) {
const filenameDebugIdMap = {};
event.exception?.values?.forEach((exception) => {
exception.stacktrace?.frames?.forEach((frame) => {
if (frame.debug_id) {
if (frame.abs_path) {
filenameDebugIdMap[frame.abs_path] = frame.debug_id;
} else if (frame.filename) {
filenameDebugIdMap[frame.filename] = frame.debug_id;
}
delete frame.debug_id;
}
});
});
if (Object.keys(filenameDebugIdMap).length === 0) {
return;
}
event.debug_meta = event.debug_meta || {};
event.debug_meta.images = event.debug_meta.images || [];
const images = event.debug_meta.images;
Object.entries(filenameDebugIdMap).forEach(([filename, debug_id]) => {
images.push({
type: "sourcemap",
code_file: filename,
debug_id
});
});
}
function applyIntegrationsMetadata(event, integrationNames) {
if (integrationNames.length > 0) {
event.sdk = event.sdk || {};
event.sdk.integrations = [...event.sdk.integrations || [], ...integrationNames];
}
}
function normalizeEvent(event, depth, maxBreadth) {
if (!event) {
return null;
}
const normalized = {
...event,
...event.breadcrumbs && {
breadcrumbs: event.breadcrumbs.map((b) => ({
...b,
...b.data && {
data: normalize(b.data, depth, maxBreadth)
}
}))
},
...event.user && {
user: normalize(event.user, depth, maxBreadth)
},
...event.contexts && {
contexts: normalize(event.contexts, depth, maxBreadth)
},
...event.extra && {
extra: normalize(event.extra, depth, maxBreadth)
}
};
if (event.contexts?.trace && normalized.contexts) {
normalized.contexts.trace = event.contexts.trace;
if (event.contexts.trace.data) {
normalized.contexts.trace.data = normalize(event.contexts.trace.data, depth, maxBreadth);
}
}
if (event.spans) {
normalized.spans = event.spans.map((span) => {
return {
...span,
...span.data && {
data: normalize(span.data, depth, maxBreadth)
}
};
});
}
if (event.contexts?.flags && normalized.contexts) {
normalized.contexts.flags = normalize(event.contexts.flags, 3, maxBreadth);
}
return normalized;
}
function getFinalScope(scope, captureContext) {
if (!captureContext) {
return scope;
}
const finalScope = scope ? scope.clone() : new Scope();
finalScope.update(captureContext);
return finalScope;
}
function parseEventHintOrCaptureContext(hint) {
if (!hint) {
return void 0;
}
if (hintIsScopeOrFunction(hint)) {
return { captureContext: hint };
}
if (hintIsScopeContext(hint)) {
return {
captureContext: hint
};
}
return hint;
}
function hintIsScopeOrFunction(hint) {
return hint instanceof Scope || typeof hint === "function";
}
const captureContextKeys = [
"user",
"level",
"extra",
"contexts",
"tags",
"fingerprint",
"propagationContext"
];
function hintIsScopeContext(hint) {
return Object.keys(hint).some((key) => captureContextKeys.includes(key));
}
function captureException(exception, hint) {
return getCurrentScope().captureException(exception, parseEventHintOrCaptureContext(hint));
}
function captureEvent(event, hint) {
return getCurrentScope().captureEvent(event, hint);
}
function setContext(name, context) {
getIsolationScope().setContext(name, context);
}
function setTag(key, value) {
getIsolationScope().setTag(key, value);
}
function isEnabled() {
const client = getClient();
return client?.getOptions().enabled !== false && !!client?.getTransport();
}
function addEventProcessor(callback) {
getIsolationScope().addEventProcessor(callback);
}
function startSession(context) {
const isolationScope = getIsolationScope();
const { user } = getCombinedScopeData(isolationScope, getCurrentScope());
const { userAgent } = GLOBAL_OBJ.navigator || {};
const session = makeSession$1({
user,
...userAgent && { userAgent },
...context
});
const currentSession = isolationScope.getSession();
if (currentSession?.status === "ok") {
updateSession(currentSession, { status: "exited" });
}
endSession();
isolationScope.setSession(session);
return session;
}
function endSession() {
const isolationScope = getIsolationScope();
const currentScope = getCurrentScope();
const session = currentScope.getSession() || isolationScope.getSession();
if (session) {
closeSession(session);
}
_sendSessionUpdate();
isolationScope.setSession();
}
function _sendSessionUpdate() {
const isolationScope = getIsolationScope();
const client = getClient();
const session = isolationScope.getSession();
if (session && client) {
client.captureSession(session);
}
}
function captureSession(end = false) {
if (end) {
endSession();
return;
}
_sendSessionUpdate();
}
function safeUnref(timer) {
if (typeof timer === "object" && typeof timer.unref === "function") {
timer.unref();
}
return timer;
}
const SENTRY_API_VERSION = "7";
function getBaseApiEndpoint(dsn) {
const protocol = dsn.protocol ? `${dsn.protocol}:` : "";
const port = dsn.port ? `:${dsn.port}` : "";
return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ""}/api/`;
}
function _getIngestEndpoint(dsn) {
return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/envelope/`;
}
function _encodedAuth(dsn, sdkInfo) {
const params = {
sentry_version: SENTRY_API_VERSION
};
if (dsn.publicKey) {
params.sentry_key = dsn.publicKey;
}
if (sdkInfo) {
params.sentry_client = `${sdkInfo.name}/${sdkInfo.version}`;
}
return new URLSearchParams(params).toString();
}
function getEnvelopeEndpointWithUrlEncodedAuth(dsn, tunnel, sdkInfo) {
return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn, sdkInfo)}`;
}
const installedIntegrations = [];
function filterDuplicates(integrations) {
const integrationsByName = {};
integrations.forEach((currentInstance) => {
const { name } = currentInstance;
const existingInstance = integrationsByName[name];
if (existingInstance && !existingInstance.isDefaultInstance && currentInstance.isDefaultInstance) {
return;
}
integrationsByName[name] = currentInstance;
});
return Object.values(integrationsByName);
}
function getIntegrationsToSetup(options) {
const defaultIntegrations = options.defaultIntegrations || [];
const userIntegrations = options.integrations;
defaultIntegrations.forEach((integration) => {
integration.isDefaultInstance = true;
});
let integrations;
if (Array.isArray(userIntegrations)) {
integrations = [...defaultIntegrations, ...userIntegrations];
} else if (typeof userIntegrations === "function") {
const resolvedUserIntegrations = userIntegrations(defaultIntegrations);
integrations = Array.isArray(resolvedUserIntegrations) ? resolvedUserIntegrations : [resolvedUserIntegrations];
} else {
integrations = defaultIntegrations;
}
return filterDuplicates(integrations);
}
function setupIntegrations(client, integrations) {
const integrationIndex = {};
integrations.forEach((integration) => {
if (integration?.beforeSetup) {
integration.beforeSetup(client);
}
});
integrations.forEach((integration) => {
if (integration) {
setupIntegration(client, integration, integrationIndex);
}
});
return integrationIndex;
}
function afterSetupIntegrations(client, integrations) {
for (const integration of integrations) {
if (integration?.afterAllSetup) {
integration.afterAllSetup(client);
}
}
}
function setupIntegration(client, integration, integrationIndex) {
if (integrationIndex[integration.name]) {
DEBUG_BUILD$3 && debug$1.log(`Integration skipped because it was already installed: ${integration.name}`);
return;
}
integrationIndex[integration.name] = integration;
if (!installedIntegrations.includes(integration.name) && typeof integration.setupOnce === "function") {
integration.setupOnce();
installedIntegrations.push(integration.name);
}
if (integration.setup && typeof integration.setup === "function") {
integration.setup(client);
}
if (typeof integration.preprocessEvent === "function") {
const callback = integration.preprocessEvent.bind(integration);
client.on("preprocessEvent", (event, hint) => callback(event, hint, client));
}
if (typeof integration.processEvent === "function") {
const callback = integration.processEvent.bind(integration);
const processor = Object.assign((event, hint) => callback(event, hint, client), {
id: integration.name
});
client.addEventProcessor(processor);
}
["processSpan", "processSegmentSpan"].forEach((hook) => {
const callback = integration[hook];
if (typeof callback === "function") {
client.on(hook, (span) => callback.call(integration, span, client));
}
});
DEBUG_BUILD$3 && debug$1.log(`Integration installed: ${integration.name}`);
}
function defineIntegration(fn) {
return fn;
}
function isBrowserBundle() {
return typeof __SENTRY_BROWSER_BUNDLE__ !== "undefined" && !!__SENTRY_BROWSER_BUNDLE__;
}
function getSDKSource() {
/*! __SENTRY_SDK_SOURCE__ */
return "npm";
}
function isNodeEnv() {
return !isBrowserBundle() && Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
}
function isBrowser() {
return typeof window !== "undefined" && (!isNodeEnv() || isElectronNodeRenderer());
}
function isElectronNodeRenderer() {
const process = GLOBAL_OBJ.process;
return process?.type === "renderer";
}
function createLogContainerEnvelopeItem(items, inferUserData) {
const inferSetting = inferUserData ? "auto" : "never";
return [
{
type: "log",
item_count: items.length,
content_type: "application/vnd.sentry.items.log+json"
},
{
version: 2,
...isBrowser() && {
ingest_settings: { infer_ip: inferSetting, infer_user_agent: inferSetting }
},
items
}
];
}
function createLogEnvelope(logs, metadata, tunnel, dsn, inferUserData) {
const headers = {};
if (metadata?.sdk) {
headers.sdk = {
name: metadata.sdk.name,
version: metadata.sdk.version
};
}
if (!!tunnel && !!dsn) {
headers.dsn = dsnToString(dsn);
}
return createEnvelope(headers, [createLogContainerEnvelopeItem(logs, inferUserData)]);
}
function _INTERNAL_flushLogsBuffer(client, maybeLogBuffer) {
const logBuffer = maybeLogBuffer ?? _INTERNAL_getLogBuffer(client) ?? [];
if (logBuffer.length === 0) {
return;
}
const clientOptions = client.getOptions();
const envelope = createLogEnvelope(
logBuffer,
clientOptions._metadata,
clientOptions.tunnel,
client.getDsn(),
client.getDataCollectionOptions().userInfo
);
_getBufferMap$1().set(client, []);
client.emit("flushLogs");
client.sendEnvelope(envelope);
}
function _INTERNAL_getLogBuffer(client) {
return _getBufferMap$1().get(client);
}
function _getBufferMap$1() {
return getGlobalSingleton("clientToLogBufferMap", () => /* @__PURE__ */ new WeakMap());
}
function createMetricContainerEnvelopeItem(items, inferUserData) {
const inferSetting = inferUserData ? "auto" : "never";
return [
{
type: "trace_metric",
item_count: items.length,
content_type: "application/vnd.sentry.items.trace-metric+json"
},
{
version: 2,
...isBrowser() && {
ingest_settings: { infer_ip: inferSetting, infer_user_agent: inferSetting }
},
items
}
];
}
function createMetricEnvelope(metrics, metadata, tunnel, dsn, inferUserData) {
const headers = {};
if (metadata?.sdk) {
headers.sdk = {
name: metadata.sdk.name,
version: metadata.sdk.version
};
}
if (!!tunnel && !!dsn) {
headers.dsn = dsnToString(dsn);
}
return createEnvelope(headers, [createMetricContainerEnvelopeItem(metrics, inferUserData)]);
}
function _INTERNAL_flushMetricsBuffer(client, maybeMetricBuffer) {
const metricBuffer = maybeMetricBuffer ?? _INTERNAL_getMetricBuffer(client) ?? [];
if (metricBuffer.length === 0) {
return;
}
const clientOptions = client.getOptions();
const envelope = createMetricEnvelope(
metricBuffer,
clientOptions._metadata,
clientOptions.tunnel,
client.getDsn(),
client.getDataCollectionOptions().userInfo
);
_getBufferMap().set(client, []);
client.emit("flushMetrics");
client.sendEnvelope(envelope);
}
function _INTERNAL_getMetricBuffer(client) {
return _getBufferMap().get(client);
}
function _getBufferMap() {
return getGlobalSingleton("clientToMetricBufferMap", () => /* @__PURE__ */ new WeakMap());
}
function spanJsonToSerializedStreamedSpan(span) {
const streamedSpan = {
trace_id: span.trace_id,
span_id: span.span_id,
parent_span_id: span.parent_span_id,
name: span.description || "",
start_timestamp: span.start_timestamp,
end_timestamp: span.timestamp || span.start_timestamp,
status: !span.status || span.status === "ok" || span.status === "cancelled" ? "ok" : "error",
is_segment: false,
attributes: { ...span.data },
links: span.links
};
return streamedSpanJsonToSerializedSpan(streamedSpan);
}
function extractGenAiSpansFromEvent(event, client) {
if (event.type !== "transaction" || !event.spans?.length || !event.sdkProcessingMetadata?.hasGenAiSpans || client.getOptions().streamGenAiSpans === false || hasSpanStreamingEnabled(client)) {
return void 0;
}
const genAiSpans = [];
const remainingSpans = [];
for (const span of event.spans) {
if (span.op?.startsWith("gen_ai.")) {
genAiSpans.push(spanJsonToSerializedStreamedSpan(span));
} else {
remainingSpans.push(span);
}
}
if (genAiSpans.length === 0) {
return void 0;
}
event.spans = remainingSpans;
const inferSetting = client.getDataCollectionOptions().userInfo ? "auto" : "never";
return [
{ type: "span", item_count: genAiSpans.length, content_type: "application/vnd.sentry.items.span.v2+json" },
{
version: 2,
...isBrowser() && {
ingest_settings: { infer_ip: inferSetting, infer_user_agent: inferSetting }
},
items: genAiSpans
}
];
}
const SENTRY_BUFFER_FULL_ERROR = /* @__PURE__ */ Symbol.for("SentryBufferFullError");
function makePromiseBuffer(limit = 100) {
const buffer = /* @__PURE__ */ new Set();
function isReady() {
return buffer.size < limit;
}
function remove(task) {
buffer.delete(task);
}
function add(taskProducer) {
if (!isReady()) {
return rejectedSyncPromise(SENTRY_BUFFER_FULL_ERROR);
}
const task = taskProducer();
buffer.add(task);
void task.then(
() => remove(task),
() => remove(task)
);
return task;
}
function drain(timeout) {
if (!buffer.size) {
return resolvedSyncPromise(true);
}
const drainPromise = Promise.allSettled(Array.from(buffer)).then(() => true);
if (!timeout) {
return drainPromise;
}
const promises = [
drainPromise,
new Promise((resolve) => safeUnref(setTimeout(() => resolve(false), timeout)))
];
return Promise.race(promises);
}
return {
get $() {
return Array.from(buffer);
},
add,
drain
};
}
const DEFAULT_RETRY_AFTER = 60 * 1e3;
function parseRetryAfterHeader(header, now = safeDateNow()) {
const headerDelay = parseInt(`${header}`, 10);
if (!isNaN(headerDelay)) {
return headerDelay * 1e3;
}
const headerDate = Date.parse(`${header}`);
if (!isNaN(headerDate)) {
return headerDate - now;
}
return DEFAULT_RETRY_AFTER;
}
function disabledUntil(limits, dataCategory) {
return limits[dataCategory] || limits.all || 0;
}
function isRateLimited(limits, dataCategory, now = safeDateNow()) {
return disabledUntil(limits, dataCategory) > now;
}
function updateRateLimits(limits, { statusCode, headers }, now = safeDateNow()) {
const updatedRateLimits = {
...limits
};
const rateLimitHeader = headers?.["x-sentry-rate-limits"];
const retryAfterHeader = headers?.["retry-after"];
if (rateLimitHeader) {
for (const limit of rateLimitHeader.trim().split(",")) {
const [retryAfter, categories, , , namespaces] = limit.split(":", 5);
const headerDelay = parseInt(retryAfter, 10);
const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1e3;
if (!categories) {
updatedRateLimits.all = now + delay;
} else {
for (const category of categories.split(";")) {
if (category === "metric_bucket") {
if (!namespaces || namespaces.split(";").includes("custom")) {
updatedRateLimits[category] = now + delay;
}
} else {
updatedRateLimits[category] = now + delay;
}
}
}
}
} else if (retryAfterHeader) {
updatedRateLimits.all = now + parseRetryAfterHeader(retryAfterHeader, now);
} else if (statusCode === 429) {
updatedRateLimits.all = now + 60 * 1e3;
}
return updatedRateLimits;
}
const DEFAULT_TRANSPORT_BUFFER_SIZE = 64;
function createTransport(options, makeRequest, buffer = makePromiseBuffer(
options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE
)) {
let rateLimits = {};
const flush = (timeout) => buffer.drain(timeout);
function send(envelope) {
const filteredEnvelopeItems = [];
forEachEnvelopeItem(envelope, (item, type) => {
const dataCategory = envelopeItemTypeToDataCategory(type);
if (isRateLimited(rateLimits, dataCategory)) {
options.recordDroppedEvent("ratelimit_backoff", dataCategory);
} else {
filteredEnvelopeItems.push(item);
}
});
if (filteredEnvelopeItems.length === 0) {
return Promise.resolve({});
}
const filteredEnvelope = createEnvelope(envelope[0], filteredEnvelopeItems);
const recordEnvelopeLoss = (reason) => {
if (envelopeContainsItemType(filteredEnvelope, ["client_report"])) {
DEBUG_BUILD$3 && debug$1.warn(`Dropping client report. Will not send outcomes (reason: ${reason}).`);
return;
}
forEachEnvelopeItem(filteredEnvelope, (item, type) => {
options.recordDroppedEvent(reason, envelopeItemTypeToDataCategory(type));
});
};
const requestTask = () => makeRequest({ body: serializeEnvelope(filteredEnvelope) }).then(
(response) => {
if (response.statusCode === 413) {
DEBUG_BUILD$3 && debug$1.error(
"Sentry responded with status code 413. Envelope was discarded due to exceeding size limits."
);
recordEnvelopeLoss("send_error");
return response;
}
if (DEBUG_BUILD$3 && response.statusCode !== void 0 && (response.statusCode < 200 || response.statusCode >= 300)) {
debug$1.warn(`Sentry responded with status code ${response.statusCode} to sent event.`);
}
rateLimits = updateRateLimits(rateLimits, response);
return response;
},
(error) => {
recordEnvelopeLoss("network_error");
DEBUG_BUILD$3 && debug$1.error("Encountered error running transport request:", error);
throw error;
}
);
return buffer.add(requestTask).then(
(result) => result,
(error) => {
if (error === SENTRY_BUFFER_FULL_ERROR) {
DEBUG_BUILD$3 && debug$1.error("Skipped sending event because buffer is full.");
recordEnvelopeLoss("queue_overflow");
return Promise.resolve({});
} else {
throw error;
}
}
);
}
return {
send,
flush
};
}
function createClientReportEnvelope(discarded_events, dsn, timestamp) {
const clientReportItem = [
{ type: "client_report" },
{
timestamp: timestamp || dateTimestampInSeconds(),
discarded_events
}
];
return createEnvelope(dsn ? { dsn } : {}, [clientReportItem]);
}
function getPossibleEventMessages(event) {
const possibleMessages = [];
if (event.message) {
possibleMessages.push(event.message);
}
try {
const lastException = event.exception.values[event.exception.values.length - 1];
if (lastException?.value) {
possibleMessages.push(lastException.value);
if (lastException.type) {
possibleMessages.push(`${lastException.type}: ${lastException.value}`);
}
}
} catch {
}
return possibleMessages;
}
function convertTransactionEventToSpanJson(event) {
const { trace_id, parent_span_id, span_id, status, origin, data, op } = event.contexts?.trace ?? {};
return {
data: data ?? {},
description: event.transaction,
op,
parent_span_id,
span_id: span_id ?? "",
start_timestamp: event.start_timestamp ?? 0,
status,
timestamp: event.timestamp,
trace_id: trace_id ?? "",
origin,
profile_id: data?.[SEMANTIC_ATTRIBUTE_PROFILE_ID],
exclusive_time: data?.[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME],
measurements: event.measurements,
is_segment: true
};
}
function convertSpanJsonToTransactionEvent(span) {
return {
type: "transaction",
timestamp: span.timestamp,
start_timestamp: span.start_timestamp,
transaction: span.description,
contexts: {
trace: {
trace_id: span.trace_id,
span_id: span.span_id,
parent_span_id: span.parent_span_id,
op: span.op,
status: span.status,
origin: span.origin,
data: {
...span.data,
...span.profile_id && { [SEMANTIC_ATTRIBUTE_PROFILE_ID]: span.profile_id },
...span.exclusive_time && { [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: span.exclusive_time }
}
}
},
measurements: span.measurements
};
}
const PII_HEADER_SNIPPETS = ["forwarded", "-ip", "remote-", "via", "-user"];
function defaultPiiToCollectionOptions(sendDefaultPii) {
return sendDefaultPii === true ? {
userInfo: true,
cookies: true,
httpHeaders: { request: true, response: true },
httpBodies: ["incomingRequest", "outgoingRequest", "incomingResponse", "outgoingResponse"],
urlQueryParams: true,
graphQL: { document: true, variables: true },
genAI: { inputs: true, outputs: true },
databaseQueryData: true,
stackFrameVariables: true,
frameContextLines: 7
// default should be 5, but ContextLines integration uses 7
} : {
userInfo: false,
cookies: { deny: PII_HEADER_SNIPPETS },
httpHeaders: { request: { deny: PII_HEADER_SNIPPETS }, response: { deny: PII_HEADER_SNIPPETS } },
httpBodies: [],
urlQueryParams: { deny: PII_HEADER_SNIPPETS },
// The GraphQL document has literal values redacted at collection time, so it was historically
// always attached regardless of `sendDefaultPii`; keep it on to preserve that behavior.
graphQL: { document: true, variables: true },
genAI: { inputs: false, outputs: false },
// Database query values were only sent with `sendDefaultPii: true` (e.g. Supabase gated on it),
// so map the legacy "off" state to `false`.
databaseQueryData: false,
stackFrameVariables: true,
frameContextLines: 7
// default should be 5, but ContextLines integration uses 7
};
}
const DEFAULTS = {
userInfo: true,
cookies: true,
httpHeaders: { request: true, response: true },
httpBodies: ["incomingRequest", "outgoingRequest", "incomingResponse", "outgoingResponse"],
urlQueryParams: true,
graphQL: { document: true, variables: true },
genAI: { inputs: true, outputs: true },
databaseQueryData: true,
stackFrameVariables: true,
frameContextLines: 5
};
function resolveDataCollectionOptions(options) {
const base = options.dataCollection != null ? DEFAULTS : defaultPiiToCollectionOptions(options.sendDefaultPii);
const dc = options.dataCollection ?? {};
return {
userInfo: dc.userInfo ?? base.userInfo,
cookies: dc.cookies ?? base.cookies,
httpHeaders: {
request: dc.httpHeaders?.request ?? base.httpHeaders.request,
response: dc.httpHeaders?.response ?? base.httpHeaders.response
},
httpBodies: dc.httpBodies ?? base.httpBodies,
// oxlint-disable-next-line typescript/no-deprecated
urlQueryParams: dc.urlQueryParams ?? dc.queryParams ?? base.urlQueryParams,
graphQL: {
document: dc.graphQL?.document ?? base.graphQL.document,
variables: dc.graphQL?.variables ?? base.graphQL.variables
},
genAI: {
inputs: dc.genAI?.inputs ?? base.genAI.inputs,
outputs: dc.genAI?.outputs ?? base.genAI.outputs
},
databaseQueryData: dc.databaseQueryData ?? base.databaseQueryData,
stackFrameVariables: dc.stackFrameVariables ?? base.stackFrameVariables,
frameContextLines: dc.frameContextLines ?? base.frameContextLines
};
}
const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured.";
const MISSING_RELEASE_FOR_SESSION_ERROR = "Discarded session because of missing or non-string release";
const INTERNAL_ERROR_SYMBOL = /* @__PURE__ */ Symbol.for("SentryInternalError");
const DO_NOT_SEND_EVENT_SYMBOL = /* @__PURE__ */ Symbol.for("SentryDoNotSendEventError");
const DEFAULT_FLUSH_INTERVAL = 5e3;
function _makeInternalError(message) {
return {
message,
[INTERNAL_ERROR_SYMBOL]: true
};
}
function _makeDoNotSendEventError(message) {
return {
message,
[DO_NOT_SEND_EVENT_SYMBOL]: true
};
}
function _isInternalError(error) {
return isObjectLike(error) && INTERNAL_ERROR_SYMBOL in error;
}
function _isDoNotSendEventError(error) {
return isObjectLike(error) && DO_NOT_SEND_EVENT_SYMBOL in error;
}
function setupWeightBasedFlushing(client, afterCaptureHook, flushHook, estimateSizeFn, flushFn) {
let weight = 0;
let flushTimeout;
let isTimerActive = false;
client.on(flushHook, () => {
weight = 0;
clearTimeout(flushTimeout);
isTimerActive = false;
});
client.on(afterCaptureHook, (item) => {
weight += estimateSizeFn(item);
if (weight >= 8e5) {
flushFn(client);
} else if (!isTimerActive) {
const flushInterval = client.getOptions()._flushInterval ?? DEFAULT_FLUSH_INTERVAL;
if (flushInterval > 0) {
isTimerActive = true;
flushTimeout = safeUnref(
setTimeout(() => {
flushFn(client);
}, flushInterval)
);
}
}
});
client.on("flush", () => {
flushFn(client);
});
}
class Client {
/**
* Initializes this client instance.
*
* @param options Options for the client.
*/
constructor(options) {
this._options = options;
this._integrations = {};
this._numProcessing = 0;
this._outcomes = {};
this._hooks = {};
this._eventProcessors = [];
this._promiseBuffer = makePromiseBuffer(options.transportOptions?.bufferSize ?? DEFAULT_TRANSPORT_BUFFER_SIZE);
this._dataCollection = resolveDataCollectionOptions(options);
if (options.dsn) {
this._dsn = makeDsn(options.dsn);
} else {
DEBUG_BUILD$3 && debug$1.warn("No DSN provided, client will not send events.");
}
if (this._dsn) {
const url = getEnvelopeEndpointWithUrlEncodedAuth(
this._dsn,
options.tunnel,
options._metadata ? options._metadata.sdk : void 0
);
this._transport = options.transport({
tunnel: this._options.tunnel,
recordDroppedEvent: this.recordDroppedEvent.bind(this),
...options.transportOptions,
url
});
}
this._options.enableLogs = this._options.enableLogs ?? this._options._experiments?.enableLogs ?? true;
if (this._options.enableLogs) {
setupWeightBasedFlushing(this, "afterCaptureLog", "flushLogs", estimateLogSizeInBytes, _INTERNAL_flushLogsBuffer);
}
const enableMetrics = this._options.enableMetrics ?? this._options._experiments?.enableMetrics ?? true;
if (enableMetrics) {
setupWeightBasedFlushing(
this,
"afterCaptureMetric",
"flushMetrics",
estimateMetricSizeInBytes,
_INTERNAL_flushMetricsBuffer
);
}
}
/**
* Captures an exception event and sends it to Sentry.
*
* Unlike `captureException` exported from every SDK, this method requires that you pass it the current scope.
*/
captureException(exception, hint, scope) {
const eventId = uuid4();
if (checkOrSetAlreadyCaught(exception)) {
DEBUG_BUILD$3 && debug$1.log(ALREADY_SEEN_ERROR);
return eventId;
}
const hintWithEventId = {
event_id: eventId,
...hint
};
this._process(
() => this.eventFromException(exception, hintWithEventId).then((event) => this._captureEvent(event, hintWithEventId, scope)).then((res) => res),
"error"
);
return hintWithEventId.event_id;
}
/**
* Captures a message event and sends it to Sentry.
*
* Unlike `captureMessage` exported from every SDK, this method requires that you pass it the current scope.
*/
captureMessage(message, level, hint, currentScope) {
const hintWithEventId = {
event_id: uuid4(),
...hint
};
const eventMessage = isParameterizedString(message) ? message : String(message);
const isMessage = isPrimitive(message);
const promisedEvent = isMessage ? this.eventFromMessage(eventMessage, level, hintWithEventId) : this.eventFromException(message, hintWithEventId);
this._process(
() => promisedEvent.then((event) => this._captureEvent(event, hintWithEventId, currentScope)),
isMessage ? "unknown" : "error"
);
return hintWithEventId.event_id;
}
/**
* Captures a manually created event and sends it to Sentry.
*
* Unlike `captureEvent` exported from every SDK, this method requires that you pass it the current scope.
*/
captureEvent(event, hint, currentScope) {
const eventId = uuid4();
if (hint?.originalException && checkOrSetAlreadyCaught(hint.originalException)) {
DEBUG_BUILD$3 && debug$1.log(ALREADY_SEEN_ERROR);
return eventId;
}
const hintWithEventId = {
event_id: eventId,
...hint
};
const sdkProcessingMetadata = event.sdkProcessingMetadata || {};
const capturedSpanScope = sdkProcessingMetadata.capturedSpanScope;
const capturedSpanIsolationScope = sdkProcessingMetadata.capturedSpanIsolationScope;
const dataCategory = getDataCategoryByType(event.type);
this._process(
() => this._captureEvent(event, hintWithEventId, capturedSpanScope || currentScope, capturedSpanIsolationScope),
dataCategory
);
return hintWithEventId.event_id;
}
/**
* Captures a session.
*/
captureSession(session) {
this.sendSession(session);
updateSession(session, { init: false });
}
/**
* Get the current Dsn.
*/
getDsn() {
return this._dsn;
}
/**
* Get the current options.
*/
getOptions() {
return this._options;
}
/**
* Get the resolved data collection configuration.
*/
getDataCollectionOptions() {
return this._dataCollection;
}
/**
* Get the SDK metadata.
* @see SdkMetadata
*/
getSdkMetadata() {
return this._options._metadata;
}
/**
* Returns the transport that is used by the client.
* Please note that the transport gets lazy initialized so it will only be there once the first event has been sent.
*/
getTransport() {
return this._transport;
}
/**
* Wait for all events to be sent or the timeout to expire, whichever comes first.
*
* @param timeout Maximum time in ms the client should wait for events to be flushed. Omitting this parameter will
* cause the client to wait until all events are sent before resolving the promise.
* @returns A promise that will resolve with `true` if all events are sent before the timeout, or `false` if there are
* still events in the queue when the timeout is reached.
*/
// @ts-expect-error - PromiseLike is a subset of Promise
async flush(timeout) {
const transport = this._transport;
this.emit("flush");
if (!transport) {
return true;
}
const clientFinished = await this._isClientDoneProcessing(timeout);
const transportFlushed = await transport.flush(timeout);
return clientFinished && transportFlushed;
}
/**
* Flush the event queue and set the client to `enabled = false`. See {@link Client.flush}.
*
* @param {number} timeout Maximum time in ms the client should wait before shutting down. Omitting this parameter will cause
* the client to wait until all events are sent before disabling itself.
* @returns {Promise<boolean>} A promise which resolves to `true` if the flush completes successfully before the timeout, or `false` if
* it doesn't.
*/
// @ts-expect-error - PromiseLike is a subset of Promise
async close(timeout) {
const result = await this.flush(timeout);
this.getOptions().enabled = false;
this.emit("close");
return result;
}
/**
* Get all installed event processors.
*/
getEventProcessors() {
return this._eventProcessors;
}
/**
* Adds an event processor that applies to any event processed by this client.
*/
addEventProcessor(eventProcessor) {
this._eventProcessors.push(eventProcessor);
}
/**
* Initialize this client.
* Call this after the client was set on a scope.
*/
init() {
if (this._isEnabled() || // Force integrations to be setup even if no DSN was set when we have
// Spotlight enabled. This is particularly important for browser as we
// don't support the `spotlight` option there and rely on the users
// adding the `spotlightBrowserIntegration()` to their integrations which
// wouldn't get initialized with the check below when there's no DSN set.
this._options.integrations.some(({ name }) => name.startsWith("Spotlight"))) {
this._setupIntegrations();
}
}
/**
* Gets an installed integration by its name.
*
* @returns {Integration|undefined} The installed integration or `undefined` if no integration with that `name` was installed.
*/
getIntegrationByName(integrationName) {
return this._integrations[integrationName];
}
/**
* Returns the names of all installed integrations.
*/
getIntegrationNames() {
return Object.keys(this._integrations);
}
/**
* Add an integration to the client.
* This can be used to e.g. lazy load integrations.
* In most cases, this should not be necessary,
* and you're better off just passing the integrations via `integrations: []` at initialization time.
* However, if you find the need to conditionally load & add an integration, you can use `addIntegration` to do so.
*/
addIntegration(integration) {
const isAlreadyInstalled = this._integrations[integration.name];
if (!isAlreadyInstalled && integration.beforeSetup) {
integration.beforeSetup(this);
}
setupIntegration(this, integration, this._integrations);
if (!isAlreadyInstalled) {
afterSetupIntegrations(this, [integration]);
}
}
/**
* Send a fully prepared event to Sentry.
*/
sendEvent(event, hint = {}) {
this.emit("beforeSendEvent", event, hint);
const genAiSpanItem = extractGenAiSpansFromEvent(event, this);
let env = createEventEnvelope(event, this._dsn, this._options._metadata, this._options.tunnel);
for (const attachment of hint.attachments || []) {
env = addItemToEnvelope(env, createAttachmentEnvelopeItem(attachment));
}
if (genAiSpanItem) {
env = addItemToEnvelope(env, genAiSpanItem);
}
this.sendEnvelope(env).then((sendResponse) => this.emit("afterSendEvent", event, sendResponse));
}
/**
* Send a session or session aggregrates to Sentry.
*/
sendSession(session) {
const { release: clientReleaseOption, environment: clientEnvironmentOption = DEFAULT_ENVIRONMENT } = this._options;
if ("aggregates" in session) {
const sessionAttrs = session.attrs || {};
if (!sessionAttrs.release && !clientReleaseOption) {
DEBUG_BUILD$3 && debug$1.warn(MISSING_RELEASE_FOR_SESSION_ERROR);
return;
}
sessionAttrs.release = sessionAttrs.release || clientReleaseOption;
sessionAttrs.environment = sessionAttrs.environment || clientEnvironmentOption;
session.attrs = sessionAttrs;
} else {
if (!session.release && !clientReleaseOption) {
DEBUG_BUILD$3 && debug$1.warn(MISSING_RELEASE_FOR_SESSION_ERROR);
return;
}
session.release = session.release || clientReleaseOption;
session.environment = session.environment || clientEnvironmentOption;
}
this.emit("beforeSendSession", session);
const env = createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel);
this.sendEnvelope(env);
}
/**
* Record on the client that an event got dropped (ie, an event that will not be sent to Sentry).
*/
recordDroppedEvent(reason, category, count = 1) {
if (this._options.sendClientReports) {
const key = `${reason}:${category}`;
DEBUG_BUILD$3 && debug$1.log(`Recording outcome: "${key}"${count > 1 ? ` (${count} times)` : ""}`);
this._outcomes[key] = (this._outcomes[key] || 0) + count;
}
}
/**
* Register a hook on this client.
*/
on(hook, callback) {
const hookCallbacks = this._hooks[hook] = this._hooks[hook] || /* @__PURE__ */ new Set();
const uniqueCallback = (...args) => callback(...args);
hookCallbacks.add(uniqueCallback);
return () => {
hookCallbacks.delete(uniqueCallback);
};
}
/**
* Emit a hook that was previously registered via `on()`.
*/
emit(hook, ...rest) {
const callbacks = this._hooks[hook];
if (callbacks) {
callbacks.forEach((callback) => callback(...rest));
}
}
/**
* Send an envelope to Sentry.
*/
// @ts-expect-error - PromiseLike is a subset of Promise
async sendEnvelope(envelope) {
this.emit("beforeEnvelope", envelope);
if (this._isEnabled() && this._transport) {
try {
return await this._transport.send(envelope);
} catch (reason) {
DEBUG_BUILD$3 && debug$1.error("Error while sending envelope:", reason);
return {};
}
}
DEBUG_BUILD$3 && debug$1.error("Transport disabled");
return {};
}
/**
* Register a cleanup function to be called when the client is disposed.
* This is useful for integrations that need to clean up global state.
*
* NOTE: This is a no-op in the base `Client` class. Subclasses like `ServerRuntimeClient`
* override this method to actually register and execute cleanup callbacks.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
registerCleanup(callback) {
}
/**
* Disposes of the client and releases all resources.
*
* Subclasses should override this method to clean up their own resources, including invoking
* any callbacks registered via {@link Client.registerCleanup}. The base implementation is a
* no-op and does NOT execute registered cleanup callbacks.
*
* After calling dispose(), the client should not be used anymore.
*/
dispose() {
}
/* eslint-enable @typescript-eslint/unified-signatures */
/** Setup integrations for this client. */
_setupIntegrations() {
const { integrations } = this._options;
this._integrations = setupIntegrations(this, integrations);
afterSetupIntegrations(this, integrations);
}
/** Updates existing session based on the provided event */
_updateSessionFromEvent(session, event) {
let crashed = event.level === "fatal";
let errored = false;
const exceptions = event.exception?.values;
if (exceptions) {
errored = true;
crashed = false;
for (const ex of exceptions) {
if (ex.mechanism?.handled === false) {
crashed = true;
break;
}
}
}
const sessionNonTerminal = session.status === "ok";
const shouldUpdateAndSend = sessionNonTerminal && session.errors === 0 || sessionNonTerminal && crashed;
if (shouldUpdateAndSend) {
updateSession(session, {
...crashed && { status: "crashed" },
errors: session.errors || Number(errored || crashed)
});
this.captureSession(session);
}
}
/**
* Determine if the client is finished processing. Returns a promise because it will wait `timeout` ms before saying
* "no" (resolving to `false`) in order to give the client a chance to potentially finish first.
*
* @param timeout The time, in ms, after which to resolve to `false` if the client is still busy. Passing `0` (or not
* passing anything) will make the promise wait as long as it takes for processing to finish before resolving to
* `true`.
* @returns A promise which will resolve to `true` if processing is already done or finishes before the timeout, and
* `false` otherwise
*/
async _isClientDoneProcessing(timeout) {
let ticked = 0;
while (!timeout || ticked < timeout) {
await new Promise((resolve) => setTimeout(resolve, 1));
if (!this._numProcessing) {
return true;
}
ticked++;
}
return false;
}
/** Determines whether this SDK is enabled and a transport is present. */
_isEnabled() {
return this.getOptions().enabled !== false && this._transport !== void 0;
}
/**
* Adds common information to events.
*
* The information includes release and environment from `options`,
* breadcrumbs and context (extra, tags and user) from the scope.
*
* Information that is already present in the event is never overwritten. For
* nested objects, such as the context, keys are merged.
*
* @param event The original event.
* @param hint May contain additional information about the original exception.
* @param currentScope A scope containing event metadata.
* @returns A new event with more information.
*/
_prepareEvent(event, hint, currentScope, isolationScope) {
const options = this.getOptions();
const integrations = this.getIntegrationNames();
if (!hint.integrations && integrations.length) {
hint.integrations = integrations;
}
this.emit("preprocessEvent", event, hint);
if (!event.type) {
isolationScope.setLastEventId(event.event_id || hint.event_id);
}
return prepareEvent(options, event, hint, currentScope, this, isolationScope).then((evt) => {
if (evt === null) {
return evt;
}
this.emit("postprocessEvent", evt, hint);
evt.contexts = {
trace: { ...evt.contexts?.trace, ...getTraceContextFromScope(currentScope) },
...evt.contexts
};
const dynamicSamplingContext = getDynamicSamplingContextFromScope(this, currentScope);
evt.sdkProcessingMetadata = {
dynamicSamplingContext,
...evt.sdkProcessingMetadata
};
return evt;
});
}
/**
* Processes the event and logs an error in case of rejection
* @param event
* @param hint
* @param scope
*/
_captureEvent(event, hint = {}, currentScope = getCurrentScope(), isolationScope = getIsolationScope()) {
if (DEBUG_BUILD$3 && isErrorEvent$1(event)) {
debug$1.log(`Captured error event \`${getPossibleEventMessages(event)[0] || "<unknown>"}\``);
}
return this._processEvent(event, hint, currentScope, isolationScope).then(
(finalEvent) => {
return finalEvent.event_id;
},
(reason) => {
if (DEBUG_BUILD$3) {
if (_isDoNotSendEventError(reason)) {
debug$1.log(reason.message);
} else if (_isInternalError(reason)) {
debug$1.warn(reason.message);
} else {
debug$1.warn(reason);
}
}
return void 0;
}
);
}
/**
* Processes an event (either error or message) and sends it to Sentry.
*
* This also adds breadcrumbs and context information to the event. However,
* platform specific meta data (such as the User's IP address) must be added
* by the SDK implementor.
*
*
* @param event The event to send to Sentry.
* @param hint May contain additional information about the original exception.
* @param currentScope A scope containing event metadata.
* @returns A SyncPromise that resolves with the event or rejects in case event was/will not be send.
*/
_processEvent(event, hint, currentScope, isolationScope) {
const options = this.getOptions();
const { sampleRate } = options;
const isTransaction = isTransactionEvent$1(event);
const isError = isErrorEvent$1(event);
const eventType = event.type || "error";
const beforeSendLabel = `before send for type \`${eventType}\``;
const parsedSampleRate = typeof sampleRate === "undefined" ? void 0 : parseSampleRate(sampleRate);
const dataCategory = getDataCategoryByType(event.type);
return this._prepareEvent(event, hint, currentScope, isolationScope).then((prepared) => {
if (prepared === null) {
this.recordDroppedEvent("event_processor", dataCategory);
throw _makeDoNotSendEventError("An event processor returned `null`, will not send event.");
}
const isInternalException = hint.data?.__sentry__ === true;
if (isInternalException) {
return prepared;
}
const result = processBeforeSend(this, options, prepared, hint);
return _validateBeforeSendResult(result, beforeSendLabel);
}).then((processedEvent) => {
if (processedEvent === null) {
this.recordDroppedEvent("before_send", dataCategory);
if (isTransaction) {
const spans = event.spans || [];
const spanCount = 1 + spans.length;
this.recordDroppedEvent("before_send", "span", spanCount);
}
throw _makeDoNotSendEventError(`${beforeSendLabel} returned \`null\`, will not send event.`);
}
const session = currentScope.getSession() || isolationScope.getSession();
if (isError && session) {
this._updateSessionFromEvent(session, processedEvent);
}
if (isError && typeof parsedSampleRate === "number" && safeMathRandom() > parsedSampleRate) {
this.recordDroppedEvent("sample_rate", "error");
throw _makeDoNotSendEventError(
`Discarding event because it's not included in the random sample (sampling rate = ${sampleRate})`
);
}
if (isTransaction) {
const spanCountBefore = processedEvent.sdkProcessingMetadata?.spanCountBeforeProcessing || 0;
const spanCountAfter = processedEvent.spans ? processedEvent.spans.length : 0;
const droppedSpanCount = spanCountBefore - spanCountAfter;
if (droppedSpanCount > 0) {
this.recordDroppedEvent("before_send", "span", droppedSpanCount);
}
}
const transactionInfo = processedEvent.transaction_info;
if (isTransaction && transactionInfo && processedEvent.transaction !== event.transaction) {
const source = "custom";
processedEvent.transaction_info = {
...transactionInfo,
source
};
}
this.sendEvent(processedEvent, hint);
return processedEvent;
}).then(null, (reason) => {
if (_isDoNotSendEventError(reason) || _isInternalError(reason)) {
throw reason;
}
this.captureException(reason, {
mechanism: {
handled: false,
type: "internal"
},
data: {
__sentry__: true
},
originalException: reason
});
throw _makeInternalError(
`Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.
Reason: ${reason}`
);
});
}
/**
* Occupies the client with processing and event
*/
_process(taskProducer, dataCategory) {
this._numProcessing++;
void this._promiseBuffer.add(taskProducer).then(
(value) => {
this._numProcessing--;
return value;
},
(reason) => {
this._numProcessing--;
if (reason === SENTRY_BUFFER_FULL_ERROR) {
this.recordDroppedEvent("queue_overflow", dataCategory);
}
return reason;
}
);
}
/**
* Clears outcomes on this client and returns them.
*/
_clearOutcomes() {
const outcomes = this._outcomes;
this._outcomes = {};
return Object.entries(outcomes).map(([key, quantity]) => {
const [reason, category] = key.split(":");
return {
reason,
category,
quantity
};
});
}
/**
* Sends client reports as an envelope.
*/
_flushOutcomes() {
DEBUG_BUILD$3 && debug$1.log("Flushing outcomes...");
const outcomes = this._clearOutcomes();
if (outcomes.length === 0) {
DEBUG_BUILD$3 && debug$1.log("No outcomes to send");
return;
}
if (!this._dsn) {
DEBUG_BUILD$3 && debug$1.log("No dsn provided, will not send outcomes");
return;
}
DEBUG_BUILD$3 && debug$1.log("Sending outcomes:", outcomes);
const envelope = createClientReportEnvelope(outcomes, this._options.tunnel && dsnToString(this._dsn));
this.sendEnvelope(envelope);
}
}
function getDataCategoryByType(type) {
return type === "replay_event" ? "replay" : type || "error";
}
function _validateBeforeSendResult(beforeSendResult, beforeSendLabel) {
const invalidValueError = `${beforeSendLabel} must return \`null\` or a valid event.`;
if (isThenable(beforeSendResult)) {
return beforeSendResult.then(
(event) => {
if (!isPlainObject(event) && event !== null) {
throw _makeInternalError(invalidValueError);
}
return event;
},
(e) => {
throw _makeInternalError(`${beforeSendLabel} rejected with ${e}`);
}
);
} else if (!isPlainObject(beforeSendResult) && beforeSendResult !== null) {
throw _makeInternalError(invalidValueError);
}
return beforeSendResult;
}
function processBeforeSend(client, options, event, hint) {
const { beforeSend, beforeSendTransaction, ignoreSpans } = options;
const beforeSendSpan = !isStreamedBeforeSendSpanCallback(options.beforeSendSpan) && options.beforeSendSpan;
let processedEvent = event;
if (isErrorEvent$1(processedEvent) && beforeSend) {
return beforeSend(processedEvent, hint);
}
if (isTransactionEvent$1(processedEvent)) {
if (beforeSendSpan || ignoreSpans) {
const rootSpanJson = convertTransactionEventToSpanJson(processedEvent);
if (ignoreSpans?.length && shouldIgnoreSpan(
{ description: rootSpanJson.description, op: rootSpanJson.op, attributes: rootSpanJson.data },
ignoreSpans
)) {
return null;
}
if (beforeSendSpan) {
const processedRootSpanJson = beforeSendSpan(rootSpanJson);
if (!processedRootSpanJson) {
showSpanDropWarning();
} else {
processedEvent = merge(event, convertSpanJsonToTransactionEvent(processedRootSpanJson));
}
}
if (processedEvent.spans) {
const processedSpans = [];
const initialSpans = processedEvent.spans;
for (const span of initialSpans) {
if (ignoreSpans?.length && shouldIgnoreSpan({ description: span.description, op: span.op, attributes: span.data }, ignoreSpans)) {
reparentChildSpans(initialSpans, span);
continue;
}
if (beforeSendSpan) {
const processedSpan = beforeSendSpan(span);
if (!processedSpan) {
showSpanDropWarning();
processedSpans.push(span);
} else {
processedSpans.push(processedSpan);
}
} else {
processedSpans.push(span);
}
}
const droppedSpans = processedEvent.spans.length - processedSpans.length;
if (droppedSpans) {
client.recordDroppedEvent("before_send", "span", droppedSpans);
}
processedEvent.spans = processedSpans;
}
}
if (beforeSendTransaction) {
if (processedEvent.spans) {
const spanCountBefore = processedEvent.spans.length;
processedEvent.sdkProcessingMetadata = {
...event.sdkProcessingMetadata,
spanCountBeforeProcessing: spanCountBefore
};
}
return beforeSendTransaction(processedEvent, hint);
}
}
return processedEvent;
}
function isErrorEvent$1(event) {
return event.type === void 0;
}
function isTransactionEvent$1(event) {
return event.type === "transaction";
}
function estimateMetricSizeInBytes(metric) {
let weight = 0;
if (metric.name) {
weight += metric.name.length * 2;
}
weight += 8;
return weight + estimateAttributesSizeInBytes(metric.attributes);
}
function estimateLogSizeInBytes(log) {
let weight = 0;
if (log.message) {
weight += log.message.length * 2;
}
return weight + estimateAttributesSizeInBytes(log.attributes);
}
function estimateAttributesSizeInBytes(attributes) {
if (!attributes) {
return 0;
}
let weight = 0;
Object.values(attributes).forEach((value) => {
if (Array.isArray(value)) {
weight += value.length * estimatePrimitiveSizeInBytes(value[0]);
} else if (isPrimitive(value)) {
weight += estimatePrimitiveSizeInBytes(value);
} else {
weight += 100;
}
});
return weight;
}
function estimatePrimitiveSizeInBytes(value) {
if (typeof value === "string") {
return value.length * 2;
} else if (typeof value === "number") {
return 8;
} else if (typeof value === "boolean") {
return 4;
}
return 0;
}
function initAndBind(clientClass, options) {
if (options.debug === true) {
if (DEBUG_BUILD$3) {
debug$1.enable();
} else {
consoleSandbox(() => {
console.warn("[Sentry] Cannot initialize SDK with `debug` option using a non-debug bundle.");
});
}
}
const scope = getCurrentScope();
scope.update(options.initialScope);
const client = new clientClass(options);
setCurrentClient(client);
client.init();
return client;
}
function setCurrentClient(client) {
getCurrentScope().setClient(client);
}
const DEFAULT_BASE_URL = "thismessage:/";
function isURLObjectRelative(url) {
return "isRelative" in url;
}
function parseStringToURLObject(url, urlBase) {
const isRelative = url.indexOf("://") <= 0 && url.indexOf("//") !== 0;
const base = urlBase ?? (isRelative ? DEFAULT_BASE_URL : void 0);
try {
if ("canParse" in URL && !URL.canParse(url, base)) {
return void 0;
}
const fullUrlObject = new URL(url, base);
if (isRelative) {
return {
isRelative,
pathname: fullUrlObject.pathname,
search: fullUrlObject.search,
hash: fullUrlObject.hash
};
}
return fullUrlObject;
} catch {
}
return void 0;
}
function getSanitizedUrlStringFromUrlObject(url) {
if (isURLObjectRelative(url)) {
return url.pathname;
}
const newUrl = new URL(url);
newUrl.search = "";
newUrl.hash = "";
if (["80", "443"].includes(newUrl.port)) {
newUrl.port = "";
}
if (newUrl.password) {
newUrl.password = "%filtered%";
}
if (newUrl.username) {
newUrl.username = "%filtered%";
}
return newUrl.toString();
}
function parseUrl(url) {
if (!url) {
return {};
}
const match = url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);
if (!match) {
return {};
}
const query = match[6] || "";
const fragment = match[8] || "";
return {
host: match[4],
path: match[5],
protocol: match[2],
search: query,
hash: fragment,
relative: match[5] + query + fragment
// everything minus origin
};
}
function stripUrlQueryAndFragment(urlPath) {
return urlPath.split(/[?#]/, 1)[0];
}
function stripDataUrlContent(url, includeDataPrefix = true) {
if (url.startsWith("data:")) {
const match = url.match(/^data:([^;,]+)/);
const mimeType = match ? match[1] : "text/plain";
const isBase64 = url.includes(";base64,");
const dataStart = url.indexOf(",");
let dataPrefix = "";
if (includeDataPrefix && dataStart !== -1) {
const data = url.slice(dataStart + 1);
dataPrefix = data.length > 10 ? `${data.slice(0, 10)}... [truncated]` : data;
}
return `data:${mimeType}${isBase64 ? ",base64" : ""}${dataPrefix ? `,${dataPrefix}` : ""}`;
}
return url;
}
function isSentryRequestUrl(url, client) {
const dsn = client?.getDsn();
const tunnel = client?.getOptions().tunnel;
return checkDsn(url, dsn) || checkTunnel(url, tunnel);
}
function checkTunnel(url, tunnel) {
if (!tunnel) {
return false;
}
return removeTrailingSlash(url) === removeTrailingSlash(tunnel);
}
function checkDsn(url, dsn) {
const urlParts = parseStringToURLObject(url);
if (!urlParts || isURLObjectRelative(urlParts)) {
return false;
}
if (!dsn) {
return false;
}
return hostnameMatchesDsnHost(urlParts.hostname, dsn.host) && /(^|&|\?)sentry_key=/.test(urlParts.search);
}
function hostnameMatchesDsnHost(hostname, dsnHost) {
return hostname === dsnHost || dsnHost.length > 0 && hostname.endsWith(`.${dsnHost}`);
}
function removeTrailingSlash(str) {
return str[str.length - 1] === "/" ? str.slice(0, -1) : str;
}
function addAutoIpAddressToSession(session) {
if ("aggregates" in session) {
if (session.attrs?.["ip_address"] === void 0) {
session.attrs = {
...session.attrs,
ip_address: "{{auto}}"
};
}
} else {
if (session.ipAddress === void 0) {
session.ipAddress = "{{auto}}";
}
}
}
function applySdkMetadata(options, name, names = [name], source = "npm") {
const sdk = (options._metadata = options._metadata || {}).sdk = options._metadata.sdk || {};
if (!sdk.name) {
sdk.name = `sentry.javascript.${name}`;
sdk.packages = names.map((name2) => ({
name: `${source}:@sentry/${name2}`,
version: SDK_VERSION
}));
sdk.version = SDK_VERSION;
}
}
function getTraceData(options = {}) {
const client = options.client || getClient();
if (!isEnabled() || !client) {
return {};
}
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
if (acs.getTraceData) {
return acs.getTraceData(options);
}
const scope = options.scope || getCurrentScope();
const span = options.span || getActiveSpan();
const isTwpPlaceholder = spanIsNonRecordingSpan(span) && !hasSpansEnabled(client.getOptions());
if (!span && hasExternalPropagationContext()) {
return {};
}
const sentryTrace = span && !isTwpPlaceholder ? spanToTraceHeader(span) : scopeToTraceHeader(scope);
const dsc = span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScope(client, scope);
const baggage = dynamicSamplingContextToSentryBaggageHeader(dsc);
const isValidSentryTraceHeader = TRACEPARENT_REGEXP.test(sentryTrace);
if (!isValidSentryTraceHeader) {
debug$1.warn("Invalid sentry-trace data. Cannot generate trace data");
return {};
}
const traceData = {
"sentry-trace": sentryTrace,
baggage
};
if (options.propagateTraceparent) {
traceData.traceparent = span && !isTwpPlaceholder ? spanToTraceparentHeader(span) : scopeToTraceparentHeader(scope);
}
return traceData;
}
function scopeToTraceHeader(scope) {
const { traceId, sampled, propagationSpanId } = scope.getPropagationContext();
return generateSentryTraceHeader(traceId, propagationSpanId, sampled);
}
function scopeToTraceparentHeader(scope) {
const { traceId, sampled, propagationSpanId } = scope.getPropagationContext();
return generateTraceparentHeader(traceId, propagationSpanId, sampled);
}
const DEFAULT_BREADCRUMBS = 100;
function addBreadcrumb(breadcrumb, hint) {
const client = getClient();
const isolationScope = getIsolationScope();
if (!client) return;
const { beforeBreadcrumb = null, maxBreadcrumbs = DEFAULT_BREADCRUMBS } = client.getOptions();
if (maxBreadcrumbs <= 0) return;
const timestamp = dateTimestampInSeconds();
const mergedBreadcrumb = { timestamp, ...breadcrumb };
const finalBreadcrumb = beforeBreadcrumb ? consoleSandbox(() => beforeBreadcrumb(mergedBreadcrumb, hint)) : mergedBreadcrumb;
if (finalBreadcrumb === null) return;
if (client.emit) {
client.emit("beforeAddBreadcrumb", finalBreadcrumb, hint);
}
isolationScope.addBreadcrumb(finalBreadcrumb, maxBreadcrumbs);
}
const INTEGRATION_NAME$8 = "FunctionToString";
const SETUP_CLIENTS = /* @__PURE__ */ new WeakMap();
const _functionToStringIntegration = (() => {
return {
name: INTEGRATION_NAME$8,
setupOnce() {
const originalFunctionToString = Function.prototype.toString;
try {
Function.prototype.toString = function(...args) {
const originalFunction = getOriginalFunction(this);
let unwrappedFunction;
try {
if (SETUP_CLIENTS.has(getClient()) && originalFunction !== void 0) {
unwrappedFunction = originalFunction;
}
} catch {
}
return originalFunctionToString.apply(unwrappedFunction ?? this, args);
};
} catch {
}
},
setup(client) {
SETUP_CLIENTS.set(client, true);
}
};
});
const functionToStringIntegration = defineIntegration(_functionToStringIntegration);
const DEFAULT_IGNORE_ERRORS = [
/^Script error\.?$/,
/^Javascript error: Script error\.? on line 0$/,
/^ResizeObserver loop completed with undelivered notifications.$/,
// The browser logs this when a ResizeObserver handler takes a bit longer. Usually this is not an actual issue though. It indicates slowness.
/^Cannot redefine property: googletag$/,
// This is thrown when google tag manager is used in combination with an ad blocker
/^Can't find variable: gmo$/,
// Error from Google Search App https://issuetracker.google.com/issues/396043331
/^undefined is not an object \(evaluating 'a\.[A-Z]'\)$/,
// Random error that happens but not actionable or noticeable to end-users.
/can't redefine non-configurable property "solana"/,
// Probably a browser extension or custom browser (Brave) throwing this error
/vv\(\)\.getRestrictions is not a function/,
// Error thrown by GTM, seemingly not affecting end-users
/Can't find variable: _AutofillCallbackHandler/,
// Unactionable error in instagram webview https://developers.facebook.com/community/threads/320013549791141/
/Object Not Found Matching Id:\d+, MethodName:simulateEvent/,
// unactionable error from CEFSharp, a .NET library that embeds chromium in .NET apps
/^Java exception was raised during method invocation$/
// error from Facebook Mobile browser (https://github.com/getsentry/sentry-javascript/issues/15065)
];
const INTEGRATION_NAME$7 = "EventFilters";
const eventFiltersIntegration = defineIntegration((options = {}) => {
let mergedOptions;
return {
name: INTEGRATION_NAME$7,
setup(client) {
const clientOptions = client.getOptions();
mergedOptions = _mergeOptions(options, clientOptions);
},
processEvent(event, _hint, client) {
if (!mergedOptions) {
const clientOptions = client.getOptions();
mergedOptions = _mergeOptions(options, clientOptions);
}
return _shouldDropEvent$1(event, mergedOptions) ? null : event;
}
};
});
const inboundFiltersIntegration = defineIntegration(((options = {}) => {
return {
...eventFiltersIntegration(options),
name: "InboundFilters"
};
}));
function _mergeOptions(internalOptions = {}, clientOptions = {}) {
return {
allowUrls: [...internalOptions.allowUrls || [], ...clientOptions.allowUrls || []],
denyUrls: [...internalOptions.denyUrls || [], ...clientOptions.denyUrls || []],
ignoreErrors: [
...internalOptions.ignoreErrors || [],
...clientOptions.ignoreErrors || [],
...internalOptions.disableErrorDefaults ? [] : DEFAULT_IGNORE_ERRORS
],
ignoreTransactions: [...internalOptions.ignoreTransactions || [], ...clientOptions.ignoreTransactions || []]
};
}
function _shouldDropEvent$1(event, options) {
if (!event.type) {
if (_isIgnoredError(event, options.ignoreErrors)) {
DEBUG_BUILD$3 && debug$1.warn(
`Event dropped due to being matched by \`ignoreErrors\` option.
Event: ${getEventDescription(event)}`
);
return true;
}
if (_isUselessError(event)) {
DEBUG_BUILD$3 && debug$1.warn(
`Event dropped due to not having an error message, error type or stacktrace.
Event: ${getEventDescription(
event
)}`
);
return true;
}
if (_isDeniedUrl(event, options.denyUrls)) {
DEBUG_BUILD$3 && debug$1.warn(
`Event dropped due to being matched by \`denyUrls\` option.
Event: ${getEventDescription(
event
)}.
Url: ${_getEventFilterUrl(event)}`
);
return true;
}
if (!_isAllowedUrl(event, options.allowUrls)) {
DEBUG_BUILD$3 && debug$1.warn(
`Event dropped due to not being matched by \`allowUrls\` option.
Event: ${getEventDescription(
event
)}.
Url: ${_getEventFilterUrl(event)}`
);
return true;
}
} else if (event.type === "transaction") {
if (_isIgnoredTransaction(event, options.ignoreTransactions)) {
DEBUG_BUILD$3 && debug$1.warn(
`Event dropped due to being matched by \`ignoreTransactions\` option.
Event: ${getEventDescription(event)}`
);
return true;
}
}
return false;
}
function _isIgnoredError(event, ignoreErrors) {
if (!ignoreErrors?.length) {
return false;
}
return getPossibleEventMessages(event).some((message) => stringMatchesSomePattern(message, ignoreErrors));
}
function _isIgnoredTransaction(event, ignoreTransactions) {
if (!ignoreTransactions?.length) {
return false;
}
const name = event.transaction;
return name ? stringMatchesSomePattern(name, ignoreTransactions) : false;
}
function _isDeniedUrl(event, denyUrls) {
if (!denyUrls?.length) {
return false;
}
const url = _getEventFilterUrl(event);
return !url ? false : stringMatchesSomePattern(url, denyUrls);
}
function _isAllowedUrl(event, allowUrls) {
if (!allowUrls?.length) {
return true;
}
const url = _getEventFilterUrl(event);
return !url ? true : stringMatchesSomePattern(url, allowUrls);
}
function _getLastValidUrl(frames = []) {
for (let i = frames.length - 1; i >= 0; i--) {
const frame = frames[i];
if (frame && frame.filename !== "<anonymous>" && frame.filename !== "[native code]") {
return frame.filename || null;
}
}
return null;
}
function _getEventFilterUrl(event) {
try {
const rootException = [...event.exception?.values ?? []].reverse().find((value) => value.mechanism?.parent_id === void 0 && value.stacktrace?.frames?.length);
const frames = rootException?.stacktrace?.frames;
return frames ? _getLastValidUrl(frames) : null;
} catch {
DEBUG_BUILD$3 && debug$1.error(`Cannot extract url for event ${getEventDescription(event)}`);
return null;
}
}
function _isUselessError(event) {
if (!event.exception?.values?.length) {
return false;
}
return (
// No top-level message
!event.message && // There are no exception values that have a stacktrace, a non-generic-Error type or value
!event.exception.values.some((value) => value.stacktrace || value.type && value.type !== "Error" || value.value)
);
}
function applyAggregateErrorsToEvent(exceptionFromErrorImplementation, parser, key, limit, event, hint) {
if (!event.exception?.values || !hint || !isError(hint.originalException)) {
return;
}
const originalException = event.exception.values.length > 0 ? event.exception.values[event.exception.values.length - 1] : void 0;
if (originalException) {
event.exception.values = aggregateExceptionsFromError(
exceptionFromErrorImplementation,
parser,
limit,
hint.originalException,
key,
event.exception.values,
originalException,
0
);
}
}
function aggregateExceptionsFromError(exceptionFromErrorImplementation, parser, limit, error, key, prevExceptions, exception, exceptionId) {
if (prevExceptions.length >= limit + 1) {
return prevExceptions;
}
let newExceptions = [...prevExceptions];
if (isError(error[key])) {
applyExceptionGroupFieldsForParentException(exception, exceptionId, error);
const newException = exceptionFromErrorImplementation(parser, error[key]);
const newExceptionId = newExceptions.length;
applyExceptionGroupFieldsForChildException(newException, key, newExceptionId, exceptionId);
newExceptions = aggregateExceptionsFromError(
exceptionFromErrorImplementation,
parser,
limit,
error[key],
key,
[newException, ...newExceptions],
newException,
newExceptionId
);
}
if (isExceptionGroup(error)) {
error.errors.forEach((childError, i) => {
if (isError(childError)) {
applyExceptionGroupFieldsForParentException(exception, exceptionId, error);
const newException = exceptionFromErrorImplementation(parser, childError);
const newExceptionId = newExceptions.length;
applyExceptionGroupFieldsForChildException(newException, `errors[${i}]`, newExceptionId, exceptionId);
newExceptions = aggregateExceptionsFromError(
exceptionFromErrorImplementation,
parser,
limit,
childError,
key,
[newException, ...newExceptions],
newException,
newExceptionId
);
}
});
}
return newExceptions;
}
function isExceptionGroup(error) {
return Array.isArray(error.errors);
}
function applyExceptionGroupFieldsForParentException(exception, exceptionId, error) {
exception.mechanism = {
handled: true,
type: "auto.core.linked_errors",
...isExceptionGroup(error) && { is_exception_group: true },
...exception.mechanism,
exception_id: exceptionId
};
}
function applyExceptionGroupFieldsForChildException(exception, source, exceptionId, parentId) {
exception.mechanism = {
handled: true,
...exception.mechanism,
type: "chained",
source,
exception_id: exceptionId,
parent_id: parentId
};
}
function hasSentryFetchUrlHost(error) {
return isError(error) && "__sentry_fetch_url_host__" in error && typeof error.__sentry_fetch_url_host__ === "string";
}
function _enhanceErrorWithSentryInfo(error) {
if (hasSentryFetchUrlHost(error)) {
return `${error.message} (${error.__sentry_fetch_url_host__})`;
}
return error.message;
}
const _filter = /* @__PURE__ */ new Set([]);
function addConsoleInstrumentationHandler(handler) {
const type = "console";
const removeHandler = addHandler$1(type, handler);
maybeInstrument(type, instrumentConsole);
return removeHandler;
}
const instrumentedLevels = /* @__PURE__ */ new Set();
function instrumentConsole() {
if (!("console" in GLOBAL_OBJ)) {
return;
}
CONSOLE_LEVELS$1.forEach(function(level) {
if (instrumentedLevels.has(level) || !(level in GLOBAL_OBJ.console)) {
return;
}
instrumentedLevels.add(level);
fill(GLOBAL_OBJ.console, level, function(originalConsoleMethod) {
originalConsoleMethods[level] = originalConsoleMethod;
return function(...args) {
const firstArg = args[0];
const log = originalConsoleMethods[level];
const isFiltered = _filter.size && typeof firstArg === "string" && stringMatchesSomePattern(firstArg, _filter);
if (!isFiltered) {
triggerHandlers$1("console", { args, level });
}
if (!isFiltered || DEBUG_BUILD$3 && debug$1.isEnabled()) {
log?.apply(GLOBAL_OBJ.console, args);
}
};
});
});
}
function severityLevelFromString(level) {
return level === "warn" ? "warning" : ["fatal", "error", "warning", "log", "info", "debug"].includes(level) ? level : "log";
}
const INTEGRATION_NAME$6 = "Dedupe";
const _dedupeIntegration = (() => {
let previousEvent;
return {
name: INTEGRATION_NAME$6,
processEvent(currentEvent) {
if (currentEvent.type) {
return currentEvent;
}
try {
if (_shouldDropEvent(currentEvent, previousEvent)) {
DEBUG_BUILD$3 && debug$1.warn("Event dropped due to being a duplicate of previously captured event.");
return null;
}
} catch {
}
return previousEvent = currentEvent;
}
};
});
const dedupeIntegration = defineIntegration(_dedupeIntegration);
function _shouldDropEvent(currentEvent, previousEvent) {
if (!previousEvent) {
return false;
}
if (_isSameMessageEvent(currentEvent, previousEvent)) {
return true;
}
if (_isSameExceptionEvent(currentEvent, previousEvent)) {
return true;
}
return false;
}
function _isSameMessageEvent(currentEvent, previousEvent) {
const currentMessage = currentEvent.message;
const previousMessage = previousEvent.message;
if (!currentMessage && !previousMessage) {
return false;
}
if (currentMessage && !previousMessage || !currentMessage && previousMessage) {
return false;
}
if (currentMessage !== previousMessage) {
return false;
}
if (!_isSameFingerprint(currentEvent, previousEvent)) {
return false;
}
if (!_isSameStacktrace(currentEvent, previousEvent)) {
return false;
}
return true;
}
function _isSameExceptionEvent(currentEvent, previousEvent) {
const previousException = _getExceptionFromEvent(previousEvent);
const currentException = _getExceptionFromEvent(currentEvent);
if (!previousException || !currentException) {
return false;
}
if (previousException.type !== currentException.type || previousException.value !== currentException.value) {
return false;
}
if (!_isSameFingerprint(currentEvent, previousEvent)) {
return false;
}
if (!_isSameStacktrace(currentEvent, previousEvent)) {
return false;
}
return true;
}
function _isSameStacktrace(currentEvent, previousEvent) {
let currentFrames = getFramesFromEvent(currentEvent);
let previousFrames = getFramesFromEvent(previousEvent);
if (!currentFrames && !previousFrames) {
return true;
}
if (currentFrames && !previousFrames || !currentFrames && previousFrames) {
return false;
}
currentFrames = currentFrames;
previousFrames = previousFrames;
if (previousFrames.length !== currentFrames.length) {
return false;
}
for (let i = 0; i < previousFrames.length; i++) {
const frameA = previousFrames[i];
const frameB = currentFrames[i];
if (frameA.filename !== frameB.filename || frameA.lineno !== frameB.lineno || frameA.colno !== frameB.colno || frameA.function !== frameB.function) {
return false;
}
}
return true;
}
function _isSameFingerprint(currentEvent, previousEvent) {
let currentFingerprint = currentEvent.fingerprint;
let previousFingerprint = previousEvent.fingerprint;
if (!currentFingerprint && !previousFingerprint) {
return true;
}
if (currentFingerprint && !previousFingerprint || !currentFingerprint && previousFingerprint) {
return false;
}
currentFingerprint = currentFingerprint;
previousFingerprint = previousFingerprint;
try {
return !!(currentFingerprint.join("") === previousFingerprint.join(""));
} catch {
return false;
}
}
function _getExceptionFromEvent(event) {
return event.exception?.values?.[0];
}
const INTEGRATION_NAME$5 = "ConversationId";
const _conversationIdIntegration = (() => {
return {
name: INTEGRATION_NAME$5,
setup(client) {
client.on("spanStart", (span) => {
const scopeData = getCurrentScope().getScopeData();
const isolationScopeData = getIsolationScope().getScopeData();
const conversationId = scopeData.conversationId || isolationScopeData.conversationId;
if (conversationId) {
const { op, data: attributes, description: name } = spanToJSON(span);
if (!op?.startsWith("gen_ai.") && !attributes["ai.operationId"] && !name?.startsWith("ai.")) {
return;
}
span.setAttribute(GEN_AI_CONVERSATION_ID_ATTRIBUTE, conversationId);
}
});
}
};
});
const conversationIdIntegration = defineIntegration(_conversationIdIntegration);
function instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeaders, spans, spanOriginOrOptions) {
if (!handlerData.fetchData) {
return void 0;
}
const { method, url } = handlerData.fetchData;
const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url);
if (handlerData.endTimestamp) {
const spanId = handlerData.fetchData.__span;
if (!spanId) return;
const span2 = spans[spanId];
if (span2) {
if (shouldCreateSpanResult) {
endSpan(span2, handlerData);
_callOnRequestSpanEnd(span2, handlerData, spanOriginOrOptions);
}
delete spans[spanId];
}
return void 0;
}
const { spanOrigin = "auto.http.browser", propagateTraceparent = false } = typeof spanOriginOrOptions === "object" ? spanOriginOrOptions : { spanOrigin: spanOriginOrOptions };
const client = getClient();
const hasParent = !!getActiveSpan();
const shouldEmitSpan = hasParent || !!client && hasSpanStreamingEnabled(client);
const span = shouldCreateSpanResult && shouldEmitSpan ? startInactiveSpan(getSpanStartOptions(url, method, spanOrigin)) : new SentryNonRecordingSpan();
const spanForTraceHeaders = spanIsIgnored(span) && hasParent ? void 0 : span;
if (shouldCreateSpanResult && !shouldEmitSpan) {
client?.recordDroppedEvent("no_parent_span", "span");
}
handlerData.fetchData.__span = span.spanContext().spanId;
spans[span.spanContext().spanId] = span;
if (shouldAttachHeaders(handlerData.fetchData.url)) {
const request = handlerData.args[0];
const options = { ...handlerData.args[1] || {} };
const headers = _INTERNAL_getTracingHeadersForFetchRequest(
request,
options,
// If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction),
// we do not want to use the span as base for the trace headers,
// which means that the headers will be generated from the scope and the sampling decision is deferred
hasSpansEnabled() && shouldEmitSpan ? spanForTraceHeaders : void 0,
propagateTraceparent
);
if (headers) {
handlerData.args[1] = options;
options.headers = headers;
}
}
if (client) {
const fetchHint = {
input: handlerData.args,
response: handlerData.response,
startTimestamp: handlerData.startTimestamp,
endTimestamp: handlerData.endTimestamp
};
client.emit("beforeOutgoingRequestSpan", span, fetchHint);
}
return span;
}
function _callOnRequestSpanEnd(span, handlerData, spanOriginOrOptions) {
const onRequestSpanEnd = typeof spanOriginOrOptions === "object" && spanOriginOrOptions !== null ? spanOriginOrOptions.onRequestSpanEnd : void 0;
onRequestSpanEnd?.(span, {
headers: handlerData.response?.headers,
error: handlerData.error
});
}
function _INTERNAL_getTracingHeadersForFetchRequest(request, fetchOptionsObj, span, propagateTraceparent) {
const traceHeaders = getTraceData({ span, propagateTraceparent });
const sentryTrace = traceHeaders["sentry-trace"];
const baggage = traceHeaders.baggage;
const traceparent = traceHeaders.traceparent;
if (!sentryTrace) {
return void 0;
}
const originalHeaders = fetchOptionsObj.headers || (isRequest(request) ? request.headers : void 0);
if (!originalHeaders) {
return {
"sentry-trace": sentryTrace,
...baggage && { baggage },
...traceparent && { traceparent }
};
} else if (isHeaders(originalHeaders)) {
const newHeaders = new Headers(originalHeaders);
if (!newHeaders.get("sentry-trace")) {
newHeaders.set("sentry-trace", sentryTrace);
}
if (propagateTraceparent && traceparent && !newHeaders.get("traceparent")) {
newHeaders.set("traceparent", traceparent);
}
if (baggage) {
const prevBaggageHeader = newHeaders.get("baggage");
if (!prevBaggageHeader) {
newHeaders.set("baggage", baggage);
} else if (!baggageHeaderHasSentryBaggageValues(prevBaggageHeader)) {
newHeaders.set("baggage", `${prevBaggageHeader},${baggage}`);
}
}
return newHeaders;
} else if (isHeadersInitTupleArray(originalHeaders)) {
const newHeaders = [...originalHeaders];
if (!newHeaders.find((header) => header[0] === "sentry-trace")) {
newHeaders.push(["sentry-trace", sentryTrace]);
}
if (propagateTraceparent && traceparent && !newHeaders.find((header) => header[0] === "traceparent")) {
newHeaders.push(["traceparent", traceparent]);
}
const prevBaggageHeaderWithSentryValues = originalHeaders.find(
(header) => header[0] === "baggage" && typeof header[1] === "string" && baggageHeaderHasSentryBaggageValues(header[1])
);
if (baggage && !prevBaggageHeaderWithSentryValues) {
newHeaders.push(["baggage", baggage]);
}
return newHeaders;
} else {
const existingSentryTraceHeader = "sentry-trace" in originalHeaders ? originalHeaders["sentry-trace"] : void 0;
const existingTraceparentHeader = "traceparent" in originalHeaders ? originalHeaders.traceparent : void 0;
const existingBaggageHeader = "baggage" in originalHeaders ? originalHeaders.baggage : void 0;
const newBaggageHeaders = existingBaggageHeader ? Array.isArray(existingBaggageHeader) ? [...existingBaggageHeader] : [existingBaggageHeader] : [];
const prevBaggageHeaderWithSentryValues = existingBaggageHeader && (Array.isArray(existingBaggageHeader) ? existingBaggageHeader.find((headerItem) => baggageHeaderHasSentryBaggageValues(headerItem)) : baggageHeaderHasSentryBaggageValues(existingBaggageHeader));
if (baggage && !prevBaggageHeaderWithSentryValues) {
newBaggageHeaders.push(baggage);
}
const newHeaders = Object.assign({}, originalHeaders, {
"sentry-trace": existingSentryTraceHeader ?? sentryTrace,
...newBaggageHeaders.length > 0 && { baggage: newBaggageHeaders.join(",") }
});
if (propagateTraceparent && traceparent && !existingTraceparentHeader) {
newHeaders.traceparent = traceparent;
}
return newHeaders;
}
}
function endSpan(span, handlerData) {
if (handlerData.response) {
setHttpStatus(span, handlerData.response.status);
const contentLength = handlerData.response?.headers?.get("content-length");
if (contentLength) {
const contentLengthNum = parseInt(contentLength);
if (contentLengthNum > 0) {
span.setAttribute("http.response_content_length", contentLengthNum);
}
}
} else if (handlerData.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
}
span.end();
}
function baggageHeaderHasSentryBaggageValues(baggageHeader) {
if (typeof baggageHeader !== "string") {
return false;
}
return baggageHeader.split(",").some((baggageEntry) => baggageEntry.trim().startsWith(SENTRY_BAGGAGE_KEY_PREFIX));
}
function isHeaders(headers) {
return typeof Headers !== "undefined" && isInstanceOf(headers, Headers);
}
function isHeadersInitTupleArray(headers) {
if (!Array.isArray(headers)) {
return false;
}
return headers.every(
(item) => Array.isArray(item) && item.length === 2 && typeof item[0] === "string"
);
}
function getSpanStartOptions(url, method, spanOrigin) {
if (url.startsWith("data:")) {
const sanitizedUrl2 = stripDataUrlContent(url);
return {
name: `${method} ${sanitizedUrl2}`,
attributes: getFetchSpanAttributes(url, void 0, method, spanOrigin)
};
}
const parsedUrl = parseStringToURLObject(url);
const sanitizedUrl = parsedUrl ? getSanitizedUrlStringFromUrlObject(parsedUrl) : url;
return {
name: `${method} ${sanitizedUrl}`,
attributes: getFetchSpanAttributes(url, parsedUrl, method, spanOrigin)
};
}
function getFetchSpanAttributes(url, parsedUrl, method, spanOrigin) {
const attributes = {
url: stripDataUrlContent(url),
type: "fetch",
"http.method": method,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin,
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "http.client"
};
if (parsedUrl) {
if (!isURLObjectRelative(parsedUrl)) {
attributes[ws] = stripDataUrlContent(parsedUrl.href);
attributes[Yu] = stripDataUrlContent(parsedUrl.href);
attributes["server.address"] = parsedUrl.host;
}
if (parsedUrl.search) {
attributes["http.query"] = parsedUrl.search;
}
if (parsedUrl.hash) {
attributes["http.fragment"] = parsedUrl.hash;
}
}
return attributes;
}
function getBreadcrumbLogLevelFromHttpStatusCode(statusCode) {
if (statusCode === void 0) {
return void 0;
} else if (statusCode >= 400 && statusCode < 500) {
return "warning";
} else if (statusCode >= 500) {
return "error";
} else {
return void 0;
}
}
const WINDOW$4 = GLOBAL_OBJ;
function supportsHistory() {
return "history" in WINDOW$4 && !!WINDOW$4.history;
}
function _isFetchSupported() {
if (!("fetch" in WINDOW$4)) {
return false;
}
try {
new Headers();
new Request("data:,");
new Response();
return true;
} catch {
return false;
}
}
function isNativeFunction(func) {
return func && /^function\s+\w+\(\)\s+\{\s+\[native code\]\s+\}$/.test(func.toString());
}
function supportsNativeFetch() {
if (typeof EdgeRuntime === "string") {
return true;
}
if (!_isFetchSupported()) {
return false;
}
if (isNativeFunction(WINDOW$4.fetch)) {
return true;
}
let result = false;
const doc = WINDOW$4.document;
if (doc && typeof doc.createElement === "function") {
try {
const sandbox = doc.createElement("iframe");
sandbox.hidden = true;
doc.head.appendChild(sandbox);
if (sandbox.contentWindow?.fetch) {
result = isNativeFunction(sandbox.contentWindow.fetch);
}
doc.head.removeChild(sandbox);
} catch (err) {
DEBUG_BUILD$3 && debug$1.warn("Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ", err);
}
}
return result;
}
function addFetchInstrumentationHandler(handler, skipNativeFetchCheck) {
const type = "fetch";
const removeHandler = addHandler$1(type, handler);
maybeInstrument(type, () => instrumentFetch(void 0, skipNativeFetchCheck));
return removeHandler;
}
function addFetchEndInstrumentationHandler(handler) {
const type = "fetch-body-resolved";
const removeHandler = addHandler$1(type, handler);
maybeInstrument(type, () => instrumentFetch(streamHandler));
return removeHandler;
}
function instrumentFetch(onFetchResolved, skipNativeFetchCheck = false) {
if (skipNativeFetchCheck && !supportsNativeFetch()) {
return;
}
fill(GLOBAL_OBJ, "fetch", function(originalFetch) {
return function(...args) {
const virtualError = new Error();
const { method, url } = parseFetchArgs(args);
const handlerData = {
args,
fetchData: {
method,
url
},
startTimestamp: timestampInSeconds() * 1e3,
// // Adding the error to be able to fingerprint the failed fetch event in HttpClient instrumentation
virtualError,
headers: getHeadersFromFetchArgs(args)
};
if (!onFetchResolved) {
triggerHandlers$1("fetch", {
...handlerData
});
}
return originalFetch.apply(GLOBAL_OBJ, args).then(
async (response) => {
if (onFetchResolved) {
onFetchResolved(response);
} else {
triggerHandlers$1("fetch", {
...handlerData,
endTimestamp: timestampInSeconds() * 1e3,
response
});
}
return response;
},
(error) => {
triggerHandlers$1("fetch", {
...handlerData,
endTimestamp: timestampInSeconds() * 1e3,
error
});
if (isError(error) && error.stack === void 0) {
error.stack = virtualError.stack;
addNonEnumerableProperty(error, "framesToPop", 1);
}
const client = getClient();
const enhanceOption = client?.getOptions().enhanceFetchErrorMessages ?? "always";
const shouldEnhance = enhanceOption !== false;
if (shouldEnhance && isError(error) && error.name === "TypeError" && (error.message === "Failed to fetch" || error.message === "Load failed" || error.message === "NetworkError when attempting to fetch resource.")) {
try {
const url2 = new URL(handlerData.fetchData.url);
const hostname = url2.host;
if (enhanceOption === "always") {
error.message = `${error.message} (${hostname})`;
} else {
addNonEnumerableProperty(error, "__sentry_fetch_url_host__", hostname);
}
} catch {
}
}
throw error;
}
);
};
});
}
async function resolveResponse(res, onFinishedResolving) {
if (res?.body) {
const body = res.body;
const responseReader = body.getReader();
const maxFetchDurationTimeout = setTimeout(
() => {
body.cancel().then(null, () => {
});
},
90 * 1e3
// 90s
);
let readingActive = true;
while (readingActive) {
let chunkTimeout;
try {
chunkTimeout = setTimeout(() => {
body.cancel().then(null, () => {
});
}, 5e3);
const { done } = await responseReader.read();
clearTimeout(chunkTimeout);
if (done) {
onFinishedResolving();
readingActive = false;
}
} catch {
readingActive = false;
} finally {
clearTimeout(chunkTimeout);
}
}
clearTimeout(maxFetchDurationTimeout);
responseReader.releaseLock();
body.cancel().then(null, () => {
});
}
}
function streamHandler(response) {
let clonedResponseForResolving;
try {
clonedResponseForResolving = response.clone();
} catch {
return;
}
resolveResponse(clonedResponseForResolving, () => {
triggerHandlers$1("fetch-body-resolved", {
endTimestamp: timestampInSeconds() * 1e3,
response
});
});
}
function hasProp(obj, prop) {
return isObjectLike(obj) && !!obj[prop];
}
function getUrlFromResource(resource) {
if (typeof resource === "string") {
return resource;
}
if (!resource) {
return "";
}
if (hasProp(resource, "url")) {
return resource.url;
}
if (resource.toString) {
return resource.toString();
}
return "";
}
function parseFetchArgs(fetchArgs) {
if (fetchArgs.length === 0) {
return { method: "GET", url: "" };
}
if (fetchArgs.length === 2) {
const [resource, options] = fetchArgs;
return {
url: getUrlFromResource(resource),
method: hasProp(options, "method") ? String(options.method).toUpperCase() : (
// Request object as first argument
isRequest(resource) && hasProp(resource, "method") ? String(resource.method).toUpperCase() : "GET"
)
};
}
const arg = fetchArgs[0];
return {
url: getUrlFromResource(arg),
method: hasProp(arg, "method") ? String(arg.method).toUpperCase() : "GET"
};
}
function getHeadersFromFetchArgs(fetchArgs) {
const [requestArgument, optionsArgument] = fetchArgs;
try {
if (typeof optionsArgument === "object" && optionsArgument !== null && "headers" in optionsArgument && optionsArgument.headers) {
return new Headers(optionsArgument.headers);
}
if (isRequest(requestArgument)) {
return new Headers(requestArgument.headers);
}
} catch {
}
return;
}
const WINDOW$3 = GLOBAL_OBJ;
function getLocationHref() {
try {
return WINDOW$3.document.location.href;
} catch {
return "";
}
}
function getComponentName(elem, maxTraverseHeight = 5) {
if (!WINDOW$3.HTMLElement) {
return null;
}
let currentElem = elem;
for (let i = 0; i < maxTraverseHeight; i++) {
if (!currentElem) {
return null;
}
if (currentElem instanceof HTMLElement) {
if (currentElem.dataset["sentryComponent"]) {
return currentElem.dataset["sentryComponent"];
}
if (currentElem.dataset["sentryElement"]) {
return currentElem.dataset["sentryElement"];
}
}
currentElem = currentElem.parentNode;
}
return null;
}
const WINDOW$2 = GLOBAL_OBJ;
let ignoreOnError = 0;
function shouldIgnoreOnError() {
return ignoreOnError > 0;
}
function ignoreNextOnError() {
ignoreOnError++;
setTimeout(() => {
ignoreOnError--;
});
}
function wrap(fn, options = {}) {
function isFunction(fn2) {
return typeof fn2 === "function";
}
if (!isFunction(fn)) {
return fn;
}
try {
const hasOwnWrapper = Object.prototype.hasOwnProperty.call(fn, "__sentry_wrapped__");
if (hasOwnWrapper) {
const wrapper = fn.__sentry_wrapped__;
if (typeof wrapper === "function") {
return wrapper;
} else {
return fn;
}
}
if (getOriginalFunction(fn)) {
return fn;
}
} catch {
return fn;
}
const sentryWrapped = function(...args) {
GLOBAL_OBJ._sentryWrappedDepth = (GLOBAL_OBJ._sentryWrappedDepth || 0) + 1;
try {
const wrappedArguments = args.map((arg) => wrap(arg, options));
return fn.apply(this, wrappedArguments);
} catch (ex) {
ignoreNextOnError();
withScope((scope) => {
scope.addEventProcessor((event) => {
if (options.mechanism) {
addExceptionTypeValue(event, void 0, void 0);
addExceptionMechanism(event, options.mechanism);
}
event.extra = {
...event.extra,
arguments: args
};
return event;
});
captureException(ex);
});
throw ex;
} finally {
GLOBAL_OBJ._sentryWrappedDepth = (GLOBAL_OBJ._sentryWrappedDepth || 0) - 1;
}
};
try {
for (const property in fn) {
if (Object.prototype.hasOwnProperty.call(fn, property)) {
sentryWrapped[property] = fn[property];
}
}
} catch {
}
markFunctionWrapped(sentryWrapped, fn);
addNonEnumerableProperty(fn, "__sentry_wrapped__", sentryWrapped);
try {
const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, "name");
if (descriptor.configurable) {
Object.defineProperty(sentryWrapped, "name", {
get() {
return fn.name;
}
});
}
} catch {
}
return sentryWrapped;
}
function getHttpRequestData() {
const url = getLocationHref();
const { referrer } = WINDOW$2.document || {};
const { userAgent } = WINDOW$2.navigator || {};
const headers = {
...referrer && { Referer: referrer },
...userAgent && { "User-Agent": userAgent }
};
const request = {
url,
headers
};
return request;
}
function exceptionFromError(stackParser, ex) {
const frames = parseStackFrames(stackParser, ex);
const exception = {
type: extractType(ex),
value: extractMessage(ex)
};
if (frames.length) {
exception.stacktrace = { frames };
}
if (exception.type === void 0 && exception.value === "") {
exception.value = "Unrecoverable error caught";
}
return exception;
}
function eventFromPlainObject(stackParser, exception, syntheticException, isUnhandledRejection) {
const client = getClient();
const normalizeDepth = client?.getOptions().normalizeDepth;
const errorFromProp = getErrorPropertyFromObject(exception);
const extra = {
__serialized__: normalizeToSize(exception, normalizeDepth)
};
if (errorFromProp) {
return {
exception: {
values: [exceptionFromError(stackParser, errorFromProp)]
},
extra
};
}
const event = {
exception: {
values: [
{
type: isEvent(exception) ? exception.constructor.name : isUnhandledRejection ? "UnhandledRejection" : "Error",
value: getNonErrorObjectExceptionValue(exception, { isUnhandledRejection })
}
]
},
extra
};
if (syntheticException) {
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
event.exception.values[0].stacktrace = { frames };
}
}
return event;
}
function eventFromError(stackParser, ex) {
return {
exception: {
values: [exceptionFromError(stackParser, ex)]
}
};
}
function parseStackFrames(stackParser, ex) {
const stacktrace = ex.stacktrace || ex.stack || "";
const skipLines = getSkipFirstStackStringLines(ex);
const framesToPop = getPopFirstTopFrames(ex);
try {
return stackParser(stacktrace, skipLines, framesToPop);
} catch {
}
return [];
}
const reactMinifiedRegexp = /Minified React error #\d+;/i;
function getSkipFirstStackStringLines(ex) {
if (ex && reactMinifiedRegexp.test(ex.message)) {
return 1;
}
return 0;
}
function getPopFirstTopFrames(ex) {
if (typeof ex.framesToPop === "number") {
return ex.framesToPop;
}
return 0;
}
function isWebAssemblyException(exception) {
if (typeof WebAssembly !== "undefined" && typeof WebAssembly.Exception !== "undefined") {
return exception instanceof WebAssembly.Exception;
} else {
return false;
}
}
function extractType(ex) {
const name = ex?.name;
if (!name && isWebAssemblyException(ex)) {
const hasTypeInMessage = ex.message && Array.isArray(ex.message) && ex.message.length == 2;
return hasTypeInMessage ? ex.message[0] : "WebAssembly.Exception";
}
return name;
}
function extractMessage(ex) {
const message = ex?.message;
if (isWebAssemblyException(ex)) {
if (Array.isArray(ex.message) && ex.message.length == 2) {
return ex.message[1];
}
return "wasm exception";
}
if (!message) {
return "No error message";
}
if (message.error && typeof message.error.message === "string") {
return _enhanceErrorWithSentryInfo(message.error);
}
return _enhanceErrorWithSentryInfo(ex);
}
function eventFromException(stackParser, exception, hint, attachStacktrace) {
const syntheticException = hint?.syntheticException || void 0;
const event = eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace);
addExceptionMechanism(event);
event.level = "error";
if (hint?.event_id) {
event.event_id = hint.event_id;
}
return resolvedSyncPromise(event);
}
function eventFromMessage(stackParser, message, level = "info", hint, attachStacktrace) {
const syntheticException = hint?.syntheticException || void 0;
const event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
event.level = level;
if (hint?.event_id) {
event.event_id = hint.event_id;
}
return resolvedSyncPromise(event);
}
function eventFromUnknownInput(stackParser, exception, syntheticException, attachStacktrace, isUnhandledRejection) {
let event;
if (isErrorEvent$2(exception) && exception.error) {
const errorEvent = exception;
return eventFromError(stackParser, errorEvent.error);
}
if (isDOMError(exception) || isDOMException(exception)) {
const domException = exception;
if ("stack" in exception) {
event = eventFromError(stackParser, exception);
const firstException = event.exception?.values?.[0];
if (attachStacktrace && syntheticException && firstException && !firstException.stacktrace) {
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
firstException.stacktrace = { frames };
addExceptionMechanism(event, { synthetic: true });
}
}
} else {
const name = domException.name || (isDOMError(domException) ? "DOMError" : "DOMException");
const message = domException.message ? `${name}: ${domException.message}` : name;
event = eventFromString(stackParser, message, syntheticException, attachStacktrace);
addExceptionTypeValue(event, message);
}
if ("code" in domException) {
event.tags = { ...event.tags, "DOMException.code": `${domException.code}` };
}
return event;
}
if (isError(exception)) {
return eventFromError(stackParser, exception);
}
if (isPlainObject(exception) || isEvent(exception)) {
const objectException = exception;
event = eventFromPlainObject(stackParser, objectException, syntheticException, isUnhandledRejection);
addExceptionMechanism(event, {
synthetic: true
});
return event;
}
event = eventFromString(stackParser, exception, syntheticException, attachStacktrace);
addExceptionTypeValue(event, `${exception}`, void 0);
addExceptionMechanism(event, {
synthetic: true
});
return event;
}
function eventFromString(stackParser, message, syntheticException, attachStacktrace) {
const event = {};
if (attachStacktrace && syntheticException) {
const frames = parseStackFrames(stackParser, syntheticException);
if (frames.length) {
event.exception = {
values: [{ value: message, stacktrace: { frames } }]
};
}
addExceptionMechanism(event, { synthetic: true });
}
if (isParameterizedString(message)) {
const { __sentry_template_string__, __sentry_template_values__ } = message;
event.logentry = {
message: __sentry_template_string__,
params: __sentry_template_values__
};
return event;
}
event.message = message;
return event;
}
function getNonErrorObjectExceptionValue(exception, { isUnhandledRejection }) {
const keys = extractExceptionKeysForMessage(exception);
const captureType = isUnhandledRejection ? "promise rejection" : "exception";
if (isErrorEvent$2(exception)) {
return `Event \`ErrorEvent\` captured as ${captureType} with message \`${exception.message}\``;
}
if (isEvent(exception)) {
const className = getObjectClassName(exception);
return `Event \`${className}\` (type=${exception.type}) captured as ${captureType}`;
}
return `Object captured as ${captureType} with keys: ${keys}`;
}
function getObjectClassName(obj) {
try {
const prototype = Object.getPrototypeOf(obj);
return prototype ? prototype.constructor.name : void 0;
} catch {
}
}
function getErrorPropertyFromObject(obj) {
return Object.values(obj).find(isError);
}
class BrowserClient extends Client {
/**
* Creates a new Browser SDK instance.
*
* @param options Configuration options for this SDK.
*/
constructor(options) {
const opts = applyDefaultOptions(options);
const sdkSource = WINDOW$2.SENTRY_SDK_SOURCE || getSDKSource();
applySdkMetadata(opts, "browser", ["browser"], sdkSource);
super(opts);
const { userInfo } = this.getDataCollectionOptions();
if (opts._metadata?.sdk) {
opts._metadata.sdk.settings = {
// Only allow IP inferral by Relay if the user opted in via dataCollection
infer_ip: userInfo ? "auto" : "never",
// purposefully allowing already passed settings to override the default
...opts._metadata.sdk.settings
};
}
const { sendClientReports } = this._options;
if (WINDOW$2.document) {
WINDOW$2.document.addEventListener("visibilitychange", () => {
if (WINDOW$2.document.visibilityState === "hidden") {
if (sendClientReports) {
this._flushOutcomes();
}
queueMicrotask(() => {
void this.flush();
});
}
});
}
if (userInfo) {
this.on("beforeSendSession", addAutoIpAddressToSession);
}
}
/**
* @inheritDoc
*/
eventFromException(exception, hint) {
return eventFromException(this._options.stackParser, exception, hint, this._options.attachStacktrace);
}
/**
* @inheritDoc
*/
eventFromMessage(message, level = "info", hint) {
return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace);
}
/**
* @inheritDoc
*/
_prepareEvent(event, hint, currentScope, isolationScope) {
event.platform = event.platform || "javascript";
return super._prepareEvent(event, hint, currentScope, isolationScope);
}
}
function applyDefaultOptions(optionsArg) {
return {
release: typeof __SENTRY_RELEASE__ === "string" ? __SENTRY_RELEASE__ : WINDOW$2.SENTRY_RELEASE?.id,
// This supports the variable that sentry-webpack-plugin injects
sendClientReports: true,
// We default this to true, as it is the safer scenario
parentSpanIsAlwaysRootSpan: true,
...optionsArg
};
}
const DEBUG_BUILD$2 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
const WINDOW$1 = GLOBAL_OBJ;
const getRating = (value, thresholds) => {
if (value > thresholds[1]) {
return "poor";
}
if (value > thresholds[0]) {
return "needs-improvement";
}
return "good";
};
const bindReporter = (callback, metric, thresholds, reportAllChanges) => {
let prevValue;
let delta;
return (forceReport) => {
if (metric.value >= 0) {
if (forceReport || reportAllChanges) {
delta = metric.value - (prevValue ?? 0);
if (delta || prevValue === void 0) {
prevValue = metric.value;
metric.delta = delta;
metric.rating = getRating(metric.value, thresholds);
callback(metric);
}
}
}
};
};
const getNavigationEntry = (checkResponseStart = true) => {
const navigationEntry = WINDOW$1.performance?.getEntriesByType?.("navigation")[0];
if (
// sentry-specific change:
// We don't want to check for responseStart for our own use of `getNavigationEntry`
!checkResponseStart || navigationEntry && navigationEntry.responseStart > 0 && navigationEntry.responseStart < performance.now()
) {
return navigationEntry;
}
};
const getActivationStart = () => {
const navEntry = getNavigationEntry();
return navEntry?.activationStart ?? 0;
};
function addPageListener(type, listener, options) {
if (WINDOW$1.document) {
WINDOW$1.addEventListener(type, listener, options);
}
}
function removePageListener(type, listener, options) {
if (WINDOW$1.document) {
WINDOW$1.removeEventListener(type, listener, options);
}
}
let firstHiddenTime = -1;
const onHiddenFunctions = /* @__PURE__ */ new Set();
const initHiddenTime = () => {
return WINDOW$1.document?.visibilityState === "hidden" && !WINDOW$1.document?.prerendering ? 0 : Infinity;
};
const onVisibilityUpdate = (event) => {
if (isPageHidden(event) && firstHiddenTime > -1) {
if (event.type === "visibilitychange" || event.type === "pagehide") {
for (const onHiddenFunction of onHiddenFunctions) {
onHiddenFunction();
}
}
if (!isFinite(firstHiddenTime)) {
firstHiddenTime = event.type === "visibilitychange" ? event.timeStamp : 0;
removePageListener("prerenderingchange", onVisibilityUpdate, true);
}
}
};
const getVisibilityWatcher = () => {
if (WINDOW$1.document && firstHiddenTime < 0) {
const activationStart = getActivationStart();
const firstVisibilityStateHiddenTime = !WINDOW$1.document.prerendering ? globalThis.performance.getEntriesByType("visibility-state").filter((e) => e.name === "hidden" && e.startTime > activationStart)[0]?.startTime : void 0;
firstHiddenTime = firstVisibilityStateHiddenTime ?? initHiddenTime();
addPageListener("visibilitychange", onVisibilityUpdate, true);
addPageListener("pagehide", onVisibilityUpdate, true);
addPageListener("prerenderingchange", onVisibilityUpdate, true);
}
return {
get firstHiddenTime() {
return firstHiddenTime;
},
onHidden(cb) {
onHiddenFunctions.add(cb);
}
};
};
function isPageHidden(event) {
return event.type === "pagehide" || WINDOW$1.document?.visibilityState === "hidden";
}
const generateUniqueID = () => {
return `v5-${Date.now()}-${Math.floor(Math.random() * (9e12 - 1)) + 1e12}`;
};
const initMetric = (name, value = -1) => {
const navEntry = getNavigationEntry();
let navigationType = "navigate";
if (navEntry) {
if (WINDOW$1.document?.prerendering || getActivationStart() > 0) {
navigationType = "prerender";
} else if (WINDOW$1.document?.wasDiscarded) {
navigationType = "restore";
} else if (navEntry.type) {
navigationType = navEntry.type.replace(/_/g, "-");
}
}
const entries = [];
return {
name,
value,
rating: "good",
// If needed, will be updated when reported. `const` to keep the type from widening to `string`.
delta: 0,
entries,
id: generateUniqueID(),
navigationType
};
};
const instanceMap = /* @__PURE__ */ new WeakMap();
function initUnique(identityObj, ClassObj) {
try {
if (!instanceMap.get(identityObj)) {
instanceMap.set(identityObj, new ClassObj());
}
return instanceMap.get(identityObj);
} catch (_e) {
return new ClassObj();
}
}
class LayoutShiftManager {
constructor() {
// oxlint-disable-next-line sdk/no-class-field-initializers
this._sessionValue = 0;
// oxlint-disable-next-line sdk/no-class-field-initializers
this._sessionEntries = [];
}
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility
_processEntry(entry) {
if (entry.hadRecentInput) return;
const firstSessionEntry = this._sessionEntries[0];
const lastSessionEntry = this._sessionEntries[this._sessionEntries.length - 1];
if (this._sessionValue && firstSessionEntry && lastSessionEntry && entry.startTime - lastSessionEntry.startTime < 1e3 && entry.startTime - firstSessionEntry.startTime < 5e3) {
this._sessionValue += entry.value;
this._sessionEntries.push(entry);
} else {
this._sessionValue = entry.value;
this._sessionEntries = [entry];
}
this._onAfterProcessingUnexpectedShift?.(entry);
}
}
const observe = (type, callback, opts = {}) => {
try {
if (PerformanceObserver.supportedEntryTypes.includes(type)) {
const po = new PerformanceObserver((list) => {
Promise.resolve().then(() => {
callback(list.getEntries());
});
});
po.observe({ type, buffered: true, ...opts });
return po;
}
} catch {
}
return;
};
const runOnce = (cb) => {
let called = false;
return () => {
if (!called) {
cb();
called = true;
}
};
};
const whenActivated = (callback) => {
if (WINDOW$1.document?.prerendering) {
addEventListener("prerenderingchange", () => callback(), true);
} else {
callback();
}
};
const FCPThresholds = [1800, 3e3];
const onFCP = (onReport, opts = {}) => {
whenActivated(() => {
const visibilityWatcher = getVisibilityWatcher();
const metric = initMetric("FCP");
let report;
const handleEntries = (entries) => {
for (const entry of entries) {
if (entry.name === "first-contentful-paint") {
po.disconnect();
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
metric.value = Math.max(entry.startTime - getActivationStart(), 0);
metric.entries.push(entry);
report(true);
}
}
}
};
const po = observe("paint", handleEntries);
if (po) {
report = bindReporter(onReport, metric, FCPThresholds, opts.reportAllChanges);
}
});
};
const CLSThresholds = [0.1, 0.25];
const onCLS = (onReport, opts = {}) => {
onFCP(
runOnce(() => {
const metric = initMetric("CLS", 0);
let report;
const visibilityWatcher = getVisibilityWatcher();
const layoutShiftManager = initUnique(opts, LayoutShiftManager);
const handleEntries = (entries) => {
for (const entry of entries) {
layoutShiftManager._processEntry(entry);
}
if (layoutShiftManager._sessionValue > metric.value) {
metric.value = layoutShiftManager._sessionValue;
metric.entries = layoutShiftManager._sessionEntries;
report();
}
};
const po = observe("layout-shift", handleEntries);
if (po) {
report = bindReporter(onReport, metric, CLSThresholds, opts.reportAllChanges);
visibilityWatcher.onHidden(() => {
handleEntries(po.takeRecords());
report(true);
});
WINDOW$1?.setTimeout?.(report);
}
})
);
};
let interactionCountEstimate = 0;
let minKnownInteractionId = Infinity;
let maxKnownInteractionId = 0;
const updateEstimate = (entries) => {
entries.forEach((e) => {
if (e.interactionId) {
minKnownInteractionId = Math.min(minKnownInteractionId, e.interactionId);
maxKnownInteractionId = Math.max(maxKnownInteractionId, e.interactionId);
interactionCountEstimate = maxKnownInteractionId ? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1 : 0;
}
});
};
let po;
const getInteractionCount = () => {
return po ? interactionCountEstimate : performance.interactionCount || 0;
};
const initInteractionCountPolyfill = () => {
if ("interactionCount" in performance || po) return;
po = observe("event", updateEstimate, {
type: "event",
buffered: true,
durationThreshold: 0
});
};
const MAX_INTERACTIONS_TO_CONSIDER = 10;
let prevInteractionCount = 0;
const getInteractionCountForNavigation = () => {
return getInteractionCount() - prevInteractionCount;
};
class InteractionManager {
constructor() {
/**
* A list of longest interactions on the page (by latency) sorted so the
* longest one is first. The list is at most MAX_INTERACTIONS_TO_CONSIDER
* long.
*/
// oxlint-disable-next-line sdk/no-class-field-initializers
this._longestInteractionList = [];
/**
* A mapping of longest interactions by their interaction ID.
* This is used for faster lookup.
*/
// oxlint-disable-next-line sdk/no-class-field-initializers
this._longestInteractionMap = /* @__PURE__ */ new Map();
}
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility, jsdoc/require-jsdoc
_resetInteractions() {
prevInteractionCount = getInteractionCount();
this._longestInteractionList.length = 0;
this._longestInteractionMap.clear();
}
/**
* Returns the estimated p98 longest interaction based on the stored
* interaction candidates and the interaction count for the current page.
*/
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility
_estimateP98LongestInteraction() {
const candidateInteractionIndex = Math.min(
this._longestInteractionList.length - 1,
Math.floor(getInteractionCountForNavigation() / 50)
);
return this._longestInteractionList[candidateInteractionIndex];
}
/**
* Takes a performance entry and adds it to the list of worst interactions
* if its duration is long enough to make it among the worst. If the
* entry is part of an existing interaction, it is merged and the latency
* and entries list is updated as needed.
*/
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility
_processEntry(entry) {
this._onBeforeProcessingEntry?.(entry);
if (!(entry.interactionId || entry.entryType === "first-input")) return;
const minLongestInteraction = this._longestInteractionList.at(-1);
let interaction = this._longestInteractionMap.get(entry.interactionId);
if (interaction || this._longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || // If the above conditions are false, `minLongestInteraction` will be set.
entry.duration > minLongestInteraction._latency) {
if (interaction) {
if (entry.duration > interaction._latency) {
interaction.entries = [entry];
interaction._latency = entry.duration;
} else if (entry.duration === interaction._latency && entry.startTime === interaction.entries[0].startTime) {
interaction.entries.push(entry);
}
} else {
interaction = {
id: entry.interactionId,
entries: [entry],
_latency: entry.duration
};
this._longestInteractionMap.set(interaction.id, interaction);
this._longestInteractionList.push(interaction);
}
this._longestInteractionList.sort((a, b) => b._latency - a._latency);
if (this._longestInteractionList.length > MAX_INTERACTIONS_TO_CONSIDER) {
const removedInteractions = this._longestInteractionList.splice(MAX_INTERACTIONS_TO_CONSIDER);
for (const interaction2 of removedInteractions) {
this._longestInteractionMap.delete(interaction2.id);
}
}
this._onAfterProcessingINPCandidate?.(interaction);
}
}
}
const whenIdleOrHidden = (cb) => {
const rIC = WINDOW$1.requestIdleCallback || WINDOW$1.setTimeout;
if (WINDOW$1.document?.visibilityState === "hidden") {
cb();
} else {
cb = runOnce(cb);
addPageListener("visibilitychange", cb, { once: true, capture: true });
addPageListener("pagehide", cb, { once: true, capture: true });
rIC(() => {
cb();
removePageListener("visibilitychange", cb, { capture: true });
removePageListener("pagehide", cb, { capture: true });
});
}
};
const INPThresholds = [200, 500];
const DEFAULT_DURATION_THRESHOLD = 40;
const onINP = (onReport, opts = {}) => {
if (!(globalThis.PerformanceEventTiming && "interactionId" in PerformanceEventTiming.prototype)) {
return;
}
const visibilityWatcher = getVisibilityWatcher();
whenActivated(() => {
initInteractionCountPolyfill();
const metric = initMetric("INP");
let report;
const interactionManager = initUnique(opts, InteractionManager);
const handleEntries = (entries) => {
whenIdleOrHidden(() => {
for (const entry of entries) {
interactionManager._processEntry(entry);
}
const inp = interactionManager._estimateP98LongestInteraction();
if (inp && inp._latency !== metric.value) {
metric.value = inp._latency;
metric.entries = inp.entries;
report();
}
});
};
const po = observe("event", handleEntries, {
// Event Timing entries have their durations rounded to the nearest 8ms,
// so a duration of 40ms would be any event that spans 2.5 or more frames
// at 60Hz. This threshold is chosen to strike a balance between usefulness
// and performance. Running this callback for any interaction that spans
// just one or two frames is likely not worth the insight that could be
// gained.
durationThreshold: opts.durationThreshold ?? DEFAULT_DURATION_THRESHOLD
});
report = bindReporter(onReport, metric, INPThresholds, opts.reportAllChanges);
if (po) {
po.observe({ type: "first-input", buffered: true });
visibilityWatcher.onHidden(() => {
handleEntries(po.takeRecords());
report(true);
});
}
});
};
class LCPEntryManager {
// eslint-disable-next-line @typescript-eslint/explicit-member-accessibility, jsdoc/require-jsdoc
_processEntry(entry) {
this._onBeforeProcessingEntry?.(entry);
}
}
const LCPThresholds = [2500, 4e3];
const onLCP = (onReport, opts = {}) => {
whenActivated(() => {
const visibilityWatcher = getVisibilityWatcher();
const metric = initMetric("LCP");
let report;
const lcpEntryManager = initUnique(opts, LCPEntryManager);
const handleEntries = (entries) => {
if (!opts.reportAllChanges) {
entries = entries.slice(-1);
}
for (const entry of entries) {
lcpEntryManager._processEntry(entry);
if (entry.startTime < visibilityWatcher.firstHiddenTime) {
metric.value = Math.max(entry.startTime - getActivationStart(), 0);
metric.entries = [entry];
report();
}
}
};
const po = observe("largest-contentful-paint", handleEntries);
if (po) {
report = bindReporter(onReport, metric, LCPThresholds, opts.reportAllChanges);
const stopListening = runOnce(() => {
handleEntries(po.takeRecords());
po.disconnect();
report(true);
});
const stopListeningWrapper = (event) => {
if (event.isTrusted) {
whenIdleOrHidden(stopListening);
removePageListener(event.type, stopListeningWrapper, {
capture: true
});
}
};
for (const type of ["keydown", "click", "visibilitychange"]) {
addPageListener(type, stopListeningWrapper, {
capture: true
});
}
}
});
};
const TTFBThresholds = [800, 1800];
const whenReady = (callback) => {
if (WINDOW$1.document?.prerendering) {
whenActivated(() => whenReady(callback));
} else if (WINDOW$1.document?.readyState !== "complete") {
addEventListener("load", () => whenReady(callback), true);
} else {
setTimeout(callback);
}
};
const onTTFB = (onReport, opts = {}) => {
const metric = initMetric("TTFB");
const report = bindReporter(onReport, metric, TTFBThresholds, opts.reportAllChanges);
whenReady(() => {
const navigationEntry = getNavigationEntry();
if (navigationEntry) {
metric.value = Math.max(navigationEntry.responseStart - getActivationStart(), 0);
metric.entries = [navigationEntry];
report(true);
}
});
};
const handlers$1 = {};
const instrumented = {};
let _previousCls;
let _previousLcp;
let _previousTtfb;
let _previousInp;
function addClsInstrumentationHandler(callback, stopOnCallback = false) {
return addMetricObserver("cls", callback, instrumentCls, _previousCls, stopOnCallback);
}
function addLcpInstrumentationHandler(callback, stopOnCallback = false) {
return addMetricObserver("lcp", callback, instrumentLcp, _previousLcp, stopOnCallback);
}
function addTtfbInstrumentationHandler(callback) {
return addMetricObserver("ttfb", callback, instrumentTtfb, _previousTtfb);
}
function addInpInstrumentationHandler(callback) {
return addMetricObserver("inp", callback, instrumentInp, _previousInp);
}
function addPerformanceInstrumentationHandler(type, callback) {
addHandler(type, callback);
if (!instrumented[type]) {
instrumentPerformanceObserver(type);
instrumented[type] = true;
}
return getCleanupCallback(type, callback);
}
function triggerHandlers(type, data) {
const typeHandlers = handlers$1[type];
if (!typeHandlers?.length) {
return;
}
for (const handler of typeHandlers) {
try {
handler(data);
} catch (e) {
DEBUG_BUILD$2 && debug$1.error(
`Error while triggering instrumentation handler.
Type: ${type}
Name: ${getFunctionName(handler)}
Error:`,
e
);
}
}
}
function instrumentCls() {
return onCLS(
(metric) => {
triggerHandlers("cls", {
metric
});
_previousCls = metric;
},
// We want the callback to be called whenever the CLS value updates.
// By default, the callback is only called when the tab goes to the background.
{ reportAllChanges: true }
);
}
function instrumentLcp() {
return onLCP(
(metric) => {
triggerHandlers("lcp", {
metric
});
_previousLcp = metric;
},
// We want the callback to be called whenever the LCP value updates.
// By default, the callback is only called when the tab goes to the background.
{ reportAllChanges: true }
);
}
function instrumentTtfb() {
return onTTFB((metric) => {
triggerHandlers("ttfb", {
metric
});
_previousTtfb = metric;
});
}
function instrumentInp() {
return onINP((metric) => {
triggerHandlers("inp", {
metric
});
_previousInp = metric;
});
}
function addMetricObserver(type, callback, instrumentFn, previousValue, stopOnCallback = false) {
addHandler(type, callback);
let stopListening;
if (!instrumented[type]) {
stopListening = instrumentFn();
instrumented[type] = true;
}
if (previousValue) {
callback({ metric: previousValue });
}
return getCleanupCallback(type, callback, stopOnCallback ? stopListening : void 0);
}
function instrumentPerformanceObserver(type) {
const options = {};
if (type === "event") {
options.durationThreshold = 0;
}
observe(
type,
(entries) => {
triggerHandlers(type, { entries });
},
options
);
}
function addHandler(type, handler) {
handlers$1[type] = handlers$1[type] || [];
handlers$1[type].push(handler);
}
function getCleanupCallback(type, callback, stopListening) {
return () => {
if (stopListening) {
stopListening();
}
const typeHandlers = handlers$1[type];
if (!typeHandlers) {
return;
}
const index = typeHandlers.indexOf(callback);
if (index !== -1) {
typeHandlers.splice(index, 1);
}
};
}
function isPerformanceEventTiming(entry) {
return "duration" in entry;
}
const DEFAULT_MAX_STRING_LENGTH = 80;
const accessors = {};
try {
if (typeof Node !== "undefined") {
accessors.parentNode = Object.getOwnPropertyDescriptor(Node.prototype, "parentNode").get;
}
if (typeof Element !== "undefined") {
accessors.tagName = Object.getOwnPropertyDescriptor(Element.prototype, "tagName").get;
accessors.id = Object.getOwnPropertyDescriptor(Element.prototype, "id").get;
accessors.className = Object.getOwnPropertyDescriptor(Element.prototype, "className").get;
accessors.getAttribute = Element.prototype.getAttribute;
}
if (typeof HTMLElement !== "undefined") {
accessors.dataset = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "dataset").get;
}
} catch {
}
function _safeRead(el, prop, arg) {
const fn = accessors[prop];
if (fn) {
try {
return fn.call(el, arg);
} catch {
}
}
const val = el[prop];
return typeof val === "function" ? val.call(el, arg) : val;
}
function htmlTreeAsString(elem, options = {}) {
if (!elem) {
return "<unknown>";
}
try {
let currentElem = elem;
const MAX_TRAVERSE_HEIGHT = 5;
const out = [];
let height = 0;
let len = 0;
const separator = " > ";
const sepLength = separator.length;
let nextStr;
const keyAttrs = Array.isArray(options) ? options : options.keyAttrs;
const maxStringLength = !Array.isArray(options) && options.maxStringLength || DEFAULT_MAX_STRING_LENGTH;
while (currentElem && height++ < MAX_TRAVERSE_HEIGHT) {
nextStr = _htmlElementAsString(currentElem, keyAttrs);
if (nextStr === "html" || height > 1 && len + out.length * sepLength + nextStr.length >= maxStringLength) {
break;
}
out.push(nextStr);
len += nextStr.length;
currentElem = _safeRead(currentElem, "parentNode");
}
return out.reverse().join(separator);
} catch {
return "<unknown>";
}
}
function _htmlElementAsString(el, keyAttrs) {
const out = [];
const tagName = _safeRead(el, "tagName");
if (!tagName) {
return "";
}
if (typeof HTMLElement !== "undefined") {
if (el instanceof HTMLElement) {
const dataset = _safeRead(el, "dataset");
if (dataset) {
if (dataset["sentryComponent"]) {
return dataset["sentryComponent"];
}
if (dataset["sentryElement"]) {
return dataset["sentryElement"];
}
}
}
}
out.push(tagName.toLowerCase());
const keyAttrPairs = keyAttrs?.length ? keyAttrs.filter((keyAttr) => _safeRead(el, "getAttribute", keyAttr)).map((keyAttr) => [keyAttr, _safeRead(el, "getAttribute", keyAttr)]) : null;
if (keyAttrPairs?.length) {
keyAttrPairs.forEach((keyAttrPair) => {
out.push(`[${keyAttrPair[0]}="${keyAttrPair[1]}"]`);
});
} else {
const id = _safeRead(el, "id");
if (id) {
out.push(`#${id}`);
}
const className = _safeRead(el, "className");
if (className && isString(className)) {
const classes = className.split(/\s+/);
for (const c of classes) {
out.push(`.${c}`);
}
}
}
for (const k of ["aria-label", "type", "name", "title", "alt"]) {
const attr = _safeRead(el, "getAttribute", k);
if (attr) {
out.push(`[${k}="${attr}"]`);
}
}
return out.join("");
}
const onHidden = (cb) => {
const onHiddenOrPageHide = (event) => {
if (event.type === "pagehide" || WINDOW$1.document?.visibilityState === "hidden") {
cb(event);
}
};
addPageListener("visibilitychange", onHiddenOrPageHide, { capture: true, once: true });
addPageListener("pagehide", onHiddenOrPageHide, { capture: true, once: true });
};
function isMeasurementValue(value) {
return typeof value === "number" && isFinite(value);
}
function startAndEndSpan(parentSpan, startTimeInSeconds, endTime, { ...ctx }) {
const parentStartTime = spanToJSON(parentSpan).start_timestamp;
if (parentStartTime && parentStartTime > startTimeInSeconds) {
if (typeof parentSpan.updateStartTime === "function") {
parentSpan.updateStartTime(startTimeInSeconds);
}
}
return withActiveSpan(parentSpan, () => {
const span = startInactiveSpan({
startTime: startTimeInSeconds,
...ctx
});
if (span) {
span.end(endTime);
}
return span;
});
}
function startStandaloneWebVitalSpan(options) {
const client = getClient();
if (!client) {
return;
}
const { name, transaction, attributes: passedAttributes, startTime } = options;
const { release, environment } = client.getOptions();
const { userInfo } = client.getDataCollectionOptions();
const replay = client.getIntegrationByName("Replay");
const replayId = replay?.getReplayId();
const scope = getCurrentScope();
const user = scope.getUser();
const userDisplay = user !== void 0 ? user.email || user.id || user.ip_address : void 0;
let profileId;
try {
profileId = scope.getScopeData().contexts.profile.profile_id;
} catch {
}
const attributes = {
release,
environment,
user: userDisplay || void 0,
profile_id: profileId || void 0,
replay_id: replayId || void 0,
transaction,
// Web vital score calculation relies on the user agent to account for different
// browsers setting different thresholds for what is considered a good/meh/bad value.
// For example: Chrome vs. Chrome Mobile
"user_agent.original": WINDOW$1.navigator?.userAgent,
// This tells Sentry to infer the IP address from the request
"client.address": userInfo ? "{{auto}}" : void 0,
...passedAttributes
};
return startInactiveSpan({
name,
attributes,
startTime,
experimental: {
standalone: true
}
});
}
function getBrowserPerformanceAPI() {
return WINDOW$1.addEventListener && WINDOW$1.performance;
}
function msToSec(time) {
return time / 1e3;
}
function extractNetworkProtocol(nextHopProtocol) {
let name = "unknown";
let version = "unknown";
let _name = "";
for (const char of nextHopProtocol) {
if (char === "/") {
[name, version] = nextHopProtocol.split("/");
break;
}
if (!isNaN(Number(char))) {
name = _name === "h" ? "http" : _name;
version = nextHopProtocol.split(_name)[1];
break;
}
_name += char;
}
if (_name === nextHopProtocol) {
name = _name;
}
return { name, version };
}
function supportsWebVital(entryType) {
try {
return PerformanceObserver.supportedEntryTypes.includes(entryType);
} catch {
return false;
}
}
function listenForWebVitalReportEvents(client, collectorCallback) {
let pageloadSpan;
let collected = false;
function _runCollectorCallbackOnce(event) {
if (!collected && pageloadSpan) {
collectorCallback(event, pageloadSpan.spanContext().spanId, pageloadSpan);
}
collected = true;
}
onHidden(() => {
_runCollectorCallbackOnce("pagehide");
});
const unsubscribeStartNavigation = client.on("beforeStartNavigationSpan", (_, options) => {
if (!options?.isRedirect) {
_runCollectorCallbackOnce("navigation");
unsubscribeStartNavigation();
unsubscribeAfterStartPageLoadSpan();
}
});
const unsubscribeAfterStartPageLoadSpan = client.on("afterStartPageLoadSpan", (span) => {
pageloadSpan = span;
unsubscribeAfterStartPageLoadSpan();
});
}
function trackClsAsStandaloneSpan(client) {
let standaloneCLsValue = 0;
let standaloneClsEntry;
if (!supportsWebVital("layout-shift")) {
return;
}
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
standaloneCLsValue = metric.value;
standaloneClsEntry = entry;
}, true);
listenForWebVitalReportEvents(client, (reportEvent, pageloadSpanId) => {
_sendStandaloneClsSpan(standaloneCLsValue, standaloneClsEntry, pageloadSpanId, reportEvent);
cleanupClsHandler();
});
}
function _sendStandaloneClsSpan(clsValue, entry, pageloadSpanId, reportEvent) {
DEBUG_BUILD$2 && debug$1.log(`Sending CLS span (${clsValue})`);
const startTime = entry ? msToSec((browserPerformanceTimeOrigin() || 0) + entry.startTime) : timestampInSeconds();
const routeName = getCurrentScope().getScopeData().transactionName;
const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : "Layout shift";
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser.cls",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "ui.webvital.cls",
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0,
// attach the pageload span id to the CLS span so that we can link them in the UI
"sentry.pageload.span_id": pageloadSpanId,
// describes what triggered the web vital to be reported
"sentry.report_event": reportEvent
};
if (entry?.sources) {
entry.sources.forEach((source, index) => {
attributes[`cls.source.${index + 1}`] = htmlTreeAsString(source.node);
});
}
const span = startStandaloneWebVitalSpan({
name,
transaction: routeName,
attributes,
startTime
});
if (span) {
span.addEvent("cls", {
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: "",
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: clsValue
});
span.end(startTime);
}
}
const MAX_PLAUSIBLE_LCP_DURATION = 6e4;
function isValidLcpMetric(lcpValue) {
return lcpValue != null && lcpValue > 0 && lcpValue <= MAX_PLAUSIBLE_LCP_DURATION;
}
function trackLcpAsStandaloneSpan(client) {
let standaloneLcpValue = 0;
let standaloneLcpEntry;
if (!supportsWebVital("largest-contentful-paint")) {
return;
}
const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry || !isValidLcpMetric(metric.value)) {
return;
}
standaloneLcpValue = metric.value;
standaloneLcpEntry = entry;
}, true);
listenForWebVitalReportEvents(client, (reportEvent, pageloadSpanId) => {
_sendStandaloneLcpSpan(standaloneLcpValue, standaloneLcpEntry, pageloadSpanId, reportEvent);
cleanupLcpHandler();
});
}
function _sendStandaloneLcpSpan(lcpValue, entry, pageloadSpanId, reportEvent) {
if (!isValidLcpMetric(lcpValue)) {
return;
}
DEBUG_BUILD$2 && debug$1.log(`Sending LCP span (${lcpValue})`);
const startTime = msToSec((browserPerformanceTimeOrigin() || 0) + (entry?.startTime || 0));
const routeName = getCurrentScope().getScopeData().transactionName;
const name = entry ? htmlTreeAsString(entry.element) : "Largest contentful paint";
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser.lcp",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "ui.webvital.lcp",
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0,
// LCP is a point-in-time metric
// attach the pageload span id to the LCP span so that we can link them in the UI
"sentry.pageload.span_id": pageloadSpanId,
// describes what triggered the web vital to be reported
"sentry.report_event": reportEvent
};
if (entry) {
entry.element && (attributes["lcp.element"] = htmlTreeAsString(entry.element));
entry.id && (attributes["lcp.id"] = entry.id);
entry.url && (attributes["lcp.url"] = entry.url);
entry.loadTime != null && (attributes["lcp.loadTime"] = entry.loadTime);
entry.renderTime != null && (attributes["lcp.renderTime"] = entry.renderTime);
entry.size != null && (attributes["lcp.size"] = entry.size);
}
const span = startStandaloneWebVitalSpan({
name,
transaction: routeName,
attributes,
startTime
});
if (span) {
span.addEvent("lcp", {
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: "millisecond",
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: lcpValue
});
span.end(startTime);
}
}
function getAbsoluteTime$1(time) {
return time ? ((browserPerformanceTimeOrigin() || performance.timeOrigin) + time) / 1e3 : time;
}
function resourceTimingToSpanAttributes(resourceTiming) {
const timingSpanData = {};
if (resourceTiming.nextHopProtocol != void 0) {
const { name, version } = extractNetworkProtocol(resourceTiming.nextHopProtocol);
timingSpanData["network.protocol.version"] = version;
timingSpanData["network.protocol.name"] = name;
}
if (!(browserPerformanceTimeOrigin() || getBrowserPerformanceAPI()?.timeOrigin)) {
return timingSpanData;
}
return dropUndefinedKeysFromObject({
...timingSpanData,
"http.request.redirect_start": getAbsoluteTime$1(resourceTiming.redirectStart),
"http.request.redirect_end": getAbsoluteTime$1(resourceTiming.redirectEnd),
"http.request.worker_start": getAbsoluteTime$1(resourceTiming.workerStart),
"http.request.fetch_start": getAbsoluteTime$1(resourceTiming.fetchStart),
"http.request.domain_lookup_start": getAbsoluteTime$1(resourceTiming.domainLookupStart),
"http.request.domain_lookup_end": getAbsoluteTime$1(resourceTiming.domainLookupEnd),
"http.request.connect_start": getAbsoluteTime$1(resourceTiming.connectStart),
"http.request.secure_connection_start": getAbsoluteTime$1(resourceTiming.secureConnectionStart),
"http.request.connection_end": getAbsoluteTime$1(resourceTiming.connectEnd),
"http.request.request_start": getAbsoluteTime$1(resourceTiming.requestStart),
"http.request.response_start": getAbsoluteTime$1(resourceTiming.responseStart),
"http.request.response_end": getAbsoluteTime$1(resourceTiming.responseEnd),
// For TTFB we actually want the relative time from timeOrigin to responseStart
// This way, TTFB always measures the "first page load" experience.
// see: https://web.dev/articles/ttfb#measure-resource-requests
"http.request.time_to_first_byte": resourceTiming.responseStart != null ? resourceTiming.responseStart / 1e3 : void 0
});
}
function dropUndefinedKeysFromObject(attrs) {
return Object.fromEntries(Object.entries(attrs).filter(([, value]) => value != null));
}
const MAX_INT_AS_BYTES = 2147483647;
let _performanceCursor = 0;
let _measurements = {};
let _lcpEntry;
let _clsEntry;
function startTrackingWebVitals({
recordClsStandaloneSpans,
recordLcpStandaloneSpans,
client
}) {
const performance = getBrowserPerformanceAPI();
if (performance && browserPerformanceTimeOrigin()) {
if (performance.mark) {
WINDOW$1.performance.mark("sentry-tracing-init");
}
const lcpCleanupCallback = recordLcpStandaloneSpans ? trackLcpAsStandaloneSpan(client) : recordLcpStandaloneSpans === false ? _trackLCP() : void 0;
const clsCleanupCallback = recordClsStandaloneSpans ? trackClsAsStandaloneSpan(client) : recordClsStandaloneSpans === false ? _trackCLS() : void 0;
const ttfbCleanupCallback = _trackTtfb();
const fpFcpCleanupCallback = _trackFpFcp();
return () => {
ttfbCleanupCallback();
fpFcpCleanupCallback();
lcpCleanupCallback?.();
clsCleanupCallback?.();
};
}
return () => void 0;
}
function startTrackingLongTasks() {
addPerformanceInstrumentationHandler("longtask", ({ entries }) => {
const parent = getActiveSpan();
if (!parent) {
return;
}
const { op: parentOp, start_timestamp: parentStartTimestamp } = spanToJSON(parent);
for (const entry of entries) {
const startTime = msToSec(browserPerformanceTimeOrigin() + entry.startTime);
const duration = msToSec(entry.duration);
if (parentOp === "navigation" && parentStartTimestamp && startTime < parentStartTimestamp) {
continue;
}
startAndEndSpan(parent, startTime, startTime + duration, {
name: "Main UI thread blocked",
op: "ui.long-task",
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics"
}
});
}
});
}
function startTrackingLongAnimationFrames() {
const observer = new PerformanceObserver((list) => {
const parent = getActiveSpan();
if (!parent) {
return;
}
for (const entry of list.getEntries()) {
if (!entry.scripts[0]) {
continue;
}
const startTime = msToSec(browserPerformanceTimeOrigin() + entry.startTime);
const { start_timestamp: parentStartTimestamp, op: parentOp } = spanToJSON(parent);
if (parentOp === "navigation" && parentStartTimestamp && startTime < parentStartTimestamp) {
continue;
}
const duration = msToSec(entry.duration);
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics"
};
const initialScript = entry.scripts[0];
const { invoker, invokerType, sourceURL, sourceFunctionName, sourceCharPosition } = initialScript;
attributes["browser.script.invoker"] = invoker;
attributes["browser.script.invoker_type"] = invokerType;
if (sourceURL) {
attributes["code.filepath"] = sourceURL;
}
if (sourceFunctionName) {
attributes["code.function"] = sourceFunctionName;
}
if (sourceCharPosition !== -1) {
attributes["browser.script.source_char_position"] = sourceCharPosition;
}
startAndEndSpan(parent, startTime, startTime + duration, {
name: "Main UI thread blocked",
op: "ui.long-animation-frame",
attributes
});
}
});
observer.observe({ type: "long-animation-frame", buffered: true });
}
function startTrackingInteractions() {
addPerformanceInstrumentationHandler("event", ({ entries }) => {
const parent = getActiveSpan();
if (!parent) {
return;
}
for (const entry of entries) {
if (entry.name === "click") {
const startTime = msToSec(browserPerformanceTimeOrigin() + entry.startTime);
const duration = msToSec(entry.duration);
const spanOptions = {
name: htmlTreeAsString(entry.target),
op: `ui.interaction.${entry.name}`,
startTime,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics"
}
};
const componentName = getComponentName(entry.target);
if (componentName) {
spanOptions.attributes["ui.component_name"] = componentName;
}
startAndEndSpan(parent, startTime, startTime + duration, spanOptions);
}
}
});
}
function _trackCLS() {
return addClsInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
_measurements["cls"] = { value: metric.value, unit: "" };
_clsEntry = entry;
}, true);
}
function _trackLCP() {
return addLcpInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry || !isValidLcpMetric(metric.value)) {
return;
}
_measurements["lcp"] = { value: metric.value, unit: "millisecond" };
_lcpEntry = entry;
}, true);
}
function _trackTtfb() {
return addTtfbInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
_measurements["ttfb"] = { value: metric.value, unit: "millisecond" };
});
}
function _trackFpFcp() {
return addPerformanceInstrumentationHandler("paint", ({ entries }) => {
const firstHidden = getVisibilityWatcher();
for (const entry of entries) {
const shouldRecord = entry.startTime < firstHidden.firstHiddenTime;
if (entry.name === "first-paint" && shouldRecord) {
_measurements["fp"] = { value: entry.startTime, unit: "millisecond" };
}
if (entry.name === "first-contentful-paint" && shouldRecord) {
_measurements["fcp"] = { value: entry.startTime, unit: "millisecond" };
}
}
});
}
function addPerformanceEntries(span, options) {
const performance = getBrowserPerformanceAPI();
const origin = browserPerformanceTimeOrigin();
if (!performance?.getEntries || !origin) {
return;
}
const { spanStreamingEnabled, ignorePerformanceApiSpans, ignoreResourceSpans } = options;
const timeOrigin = msToSec(origin);
const performanceEntries = performance.getEntries();
const { op, start_timestamp: transactionStartTime } = spanToJSON(span);
performanceEntries.slice(_performanceCursor).forEach((entry) => {
const startTime = msToSec(entry.startTime);
const duration = msToSec(
// Inexplicably, Chrome sometimes emits a negative duration. We need to work around this.
// There is a SO post attempting to explain this, but it leaves one with open questions: https://stackoverflow.com/questions/23191918/peformance-getentries-and-negative-duration-display
// The way we clamp the value is probably not accurate, since we have observed this happen for things that may take a while to load, like for example the replay worker.
// TODO: Investigate why this happens and how to properly mitigate. For now, this is a workaround to prevent transactions being dropped due to negative duration spans.
Math.max(0, entry.duration)
);
if (op === "navigation" && transactionStartTime && timeOrigin + startTime < transactionStartTime) {
return;
}
switch (entry.entryType) {
case "navigation": {
_addNavigationSpans(span, entry, timeOrigin);
break;
}
case "mark":
case "paint":
case "measure": {
_addMeasureSpans(span, entry, startTime, duration, timeOrigin, ignorePerformanceApiSpans);
break;
}
case "resource": {
_addResourceSpans(
span,
entry,
entry.name,
startTime,
duration,
timeOrigin,
ignoreResourceSpans
);
break;
}
}
});
_performanceCursor = Math.max(performanceEntries.length - 1, 0);
_trackNavigator(span, spanStreamingEnabled);
}
function addWebVitalsToSpan(span, options) {
const origin = browserPerformanceTimeOrigin();
if (!getBrowserPerformanceAPI()?.getEntries || !origin) {
resetWebVitalState();
return;
}
const { spanStreamingEnabled, recordClsOnPageloadSpan, recordLcpOnPageloadSpan } = options;
const timeOrigin = msToSec(origin);
if (spanToJSON(span).op === "pageload") {
_addTtfbRequestTimeToMeasurements(_measurements);
if (spanStreamingEnabled) {
const setAttr = (shortWebVitalName, value, customAttrName) => {
const attrKey = customAttrName ?? `browser.web_vital.${shortWebVitalName}.value`;
span.setAttribute(attrKey, value);
DEBUG_BUILD$2 && debug$1.log("Setting web vital attribute", { [attrKey]: value }, "on pageload span");
};
["ttfb", "fp", "fcp"].forEach((measurementName) => {
if (_measurements[measurementName]) {
setAttr(measurementName, _measurements[measurementName].value);
}
});
if (_measurements["ttfb.requestTime"]) {
setAttr("ttfb.requestTime", _measurements["ttfb.requestTime"].value, "browser.web_vital.ttfb.request_time");
}
} else {
if (!recordClsOnPageloadSpan) {
delete _measurements.cls;
}
if (!recordLcpOnPageloadSpan) {
delete _measurements.lcp;
}
Object.entries(_measurements).forEach(([measurementName, measurement]) => {
setMeasurement(measurementName, measurement.value, measurement.unit, span);
});
_setWebVitalAttributes(span, options);
}
span.setAttribute(spanStreamingEnabled ? "browser.performance.time_origin" : "performance.timeOrigin", timeOrigin);
span.setAttribute(
spanStreamingEnabled ? "browser.performance.navigation.activation_start" : "performance.activationStart",
getActivationStart()
);
}
resetWebVitalState();
}
function resetWebVitalState() {
_lcpEntry = void 0;
_clsEntry = void 0;
_measurements = {};
}
function isReact19MeasureEntry(entry) {
if (entry?.entryType !== "measure") {
return;
}
try {
return entry.detail.devtools.track === "Components \u269B";
} catch {
return;
}
}
function _addMeasureSpans(span, entry, startTime, duration, timeOrigin, ignorePerformanceApiSpans) {
if (isReact19MeasureEntry(entry)) {
return;
}
if (["mark", "measure"].includes(entry.entryType) && stringMatchesSomePattern(entry.name, ignorePerformanceApiSpans)) {
return;
}
const navEntry = getNavigationEntry(false);
const requestTime = msToSec(navEntry ? navEntry.requestStart : 0);
const measureStartTimestamp = timeOrigin + Math.max(startTime, requestTime);
const startTimeStamp = timeOrigin + startTime;
const measureEndTimestamp = startTimeStamp + duration;
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.resource.browser.metrics"
};
if (measureStartTimestamp !== startTimeStamp) {
attributes["sentry.browser.measure_happened_before_request"] = true;
attributes["sentry.browser.measure_start_time"] = measureStartTimestamp;
}
_addDetailToSpanAttributes(attributes, entry);
if (measureStartTimestamp <= measureEndTimestamp) {
startAndEndSpan(span, measureStartTimestamp, measureEndTimestamp, {
name: entry.name,
op: entry.entryType,
attributes
});
}
}
function _addDetailToSpanAttributes(attributes, performanceMeasure) {
try {
const detail = performanceMeasure.detail;
if (!detail) {
return;
}
if (typeof detail === "object") {
for (const [key, value] of Object.entries(detail)) {
if (value && isPrimitive(value)) {
attributes[`sentry.browser.measure.detail.${key}`] = value;
} else if (value !== void 0) {
try {
attributes[`sentry.browser.measure.detail.${key}`] = JSON.stringify(value);
} catch {
}
}
}
return;
}
if (isPrimitive(detail)) {
attributes["sentry.browser.measure.detail"] = detail;
return;
}
try {
attributes["sentry.browser.measure.detail"] = JSON.stringify(detail);
} catch {
}
} catch {
}
}
function _addNavigationSpans(span, entry, timeOrigin) {
["unloadEvent", "redirect", "domContentLoadedEvent", "loadEvent", "connect"].forEach((event) => {
_addPerformanceNavigationTiming(span, entry, event, timeOrigin);
});
_addPerformanceNavigationTiming(span, entry, "secureConnection", timeOrigin, "TLS/SSL");
_addPerformanceNavigationTiming(span, entry, "fetch", timeOrigin, "cache");
_addPerformanceNavigationTiming(span, entry, "domainLookup", timeOrigin, "DNS");
_addRequest(span, entry, timeOrigin);
}
function _addPerformanceNavigationTiming(span, entry, event, timeOrigin, name = event) {
const eventEnd = _getEndPropertyNameForNavigationTiming(event);
const end = entry[eventEnd];
const start = entry[`${event}Start`];
if (!start || !end) {
return;
}
startAndEndSpan(span, timeOrigin + msToSec(start), timeOrigin + msToSec(end), {
op: `browser.${name}`,
name: entry.name,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics",
...event === "redirect" && entry.redirectCount != null ? { "http.redirect_count": entry.redirectCount } : {}
}
});
}
function _getEndPropertyNameForNavigationTiming(event) {
if (event === "secureConnection") {
return "connectEnd";
}
if (event === "fetch") {
return "domainLookupStart";
}
return `${event}End`;
}
function _addRequest(span, entry, timeOrigin) {
const requestStartTimestamp = timeOrigin + msToSec(entry.requestStart);
const responseEndTimestamp = timeOrigin + msToSec(entry.responseEnd);
const responseStartTimestamp = timeOrigin + msToSec(entry.responseStart);
if (entry.responseEnd) {
startAndEndSpan(span, requestStartTimestamp, responseEndTimestamp, {
op: "browser.request",
name: entry.name,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics"
}
});
startAndEndSpan(span, responseStartTimestamp, responseEndTimestamp, {
op: "browser.response",
name: entry.name,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.ui.browser.metrics"
}
});
}
}
function _addResourceSpans(span, entry, resourceUrl, startTime, duration, timeOrigin, ignoredResourceSpanOps) {
if (entry.initiatorType === "xmlhttprequest" || entry.initiatorType === "fetch") {
return;
}
const op = entry.initiatorType ? `resource.${entry.initiatorType}` : "resource.other";
if (ignoredResourceSpanOps?.includes(op)) {
return;
}
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.resource.browser.metrics"
};
const parsedUrl = parseUrl(resourceUrl);
if (parsedUrl.protocol) {
attributes["url.scheme"] = parsedUrl.protocol.split(":").pop();
}
if (parsedUrl.host) {
attributes["server.address"] = parsedUrl.host;
}
attributes["url.same_origin"] = resourceUrl.includes(WINDOW$1.location.origin);
attributes[Yu] = resourceUrl;
_setResourceRequestAttributes(entry, attributes, [
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/responseStatus
["responseStatus", "http.response.status_code"],
["transferSize", "http.response_transfer_size"],
["encodedBodySize", "http.response_content_length"],
["decodedBodySize", "http.decoded_response_content_length"],
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/renderBlockingStatus
["renderBlockingStatus", "resource.render_blocking_status"],
// https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming/deliveryType
["deliveryType", "http.response_delivery_type"]
]);
const attributesWithResourceTiming = { ...attributes, ...resourceTimingToSpanAttributes(entry) };
const startTimestamp = timeOrigin + startTime;
const endTimestamp = startTimestamp + duration;
startAndEndSpan(span, startTimestamp, endTimestamp, {
name: resourceUrl.replace(WINDOW$1.location.origin, ""),
op,
attributes: attributesWithResourceTiming
});
}
function _trackNavigator(span, spanStreamingEnabled) {
const navigator = WINDOW$1.navigator;
if (!navigator) {
return;
}
const connection = navigator.connection;
if (connection) {
if (connection.effectiveType) {
span.setAttribute(
spanStreamingEnabled ? "network.connection.effective_type" : "effectiveConnectionType",
connection.effectiveType
);
}
if (connection.type) {
span.setAttribute(spanStreamingEnabled ? "network.connection.type" : "connectionType", connection.type);
}
if (isMeasurementValue(connection.rtt)) {
if (spanStreamingEnabled) {
span.setAttribute("network.connection.rtt", connection.rtt);
} else if (spanToJSON(span).op === "pageload") {
setMeasurement("connection.rtt", connection.rtt, "millisecond");
}
}
}
if (isMeasurementValue(navigator.deviceMemory)) {
if (spanStreamingEnabled) {
span.setAttribute("device.memory.estimated_capacity", navigator.deviceMemory);
} else {
span.setAttribute("deviceMemory", `${navigator.deviceMemory} GB`);
}
}
if (isMeasurementValue(navigator.hardwareConcurrency)) {
if (spanStreamingEnabled) {
span.setAttribute("device.processor_count", navigator.hardwareConcurrency);
} else {
span.setAttribute("hardwareConcurrency", String(navigator.hardwareConcurrency));
}
}
}
function _setWebVitalAttributes(span, options) {
if (_lcpEntry && options.recordLcpOnPageloadSpan) {
if (_lcpEntry.element) {
span.setAttribute("lcp.element", htmlTreeAsString(_lcpEntry.element));
}
if (_lcpEntry.id) {
span.setAttribute("lcp.id", _lcpEntry.id);
}
if (_lcpEntry.url) {
span.setAttribute("lcp.url", _lcpEntry.url.trim().slice(0, 200));
}
if (_lcpEntry.loadTime != null) {
span.setAttribute("lcp.loadTime", _lcpEntry.loadTime);
}
if (_lcpEntry.renderTime != null) {
span.setAttribute("lcp.renderTime", _lcpEntry.renderTime);
}
span.setAttribute("lcp.size", _lcpEntry.size);
}
if (_clsEntry?.sources && options.recordClsOnPageloadSpan) {
_clsEntry.sources.forEach(
(source, index) => span.setAttribute(`cls.source.${index + 1}`, htmlTreeAsString(source.node))
);
}
}
function _setResourceRequestAttributes(entry, attributes, properties) {
properties.forEach(([entryKey, attributeKey]) => {
const entryVal = entry[entryKey];
if (entryVal != null && (typeof entryVal === "number" && entryVal < MAX_INT_AS_BYTES || typeof entryVal === "string")) {
attributes[attributeKey] = entryVal;
}
});
}
function _addTtfbRequestTimeToMeasurements(_measurements2) {
const navEntry = getNavigationEntry(false);
if (!navEntry) {
return;
}
const { responseStart, requestStart } = navEntry;
if (requestStart <= responseStart) {
_measurements2["ttfb.requestTime"] = {
value: responseStart - requestStart,
unit: "millisecond"
};
}
}
const LAST_INTERACTIONS = [];
const INTERACTIONS_SPAN_MAP = /* @__PURE__ */ new Map();
const ELEMENT_NAME_TIMESTAMP_MAP = /* @__PURE__ */ new Map();
const MAX_PLAUSIBLE_INP_DURATION = 60;
function startTrackingINP() {
const performance = getBrowserPerformanceAPI();
if (performance && browserPerformanceTimeOrigin()) {
const inpCallback = _trackINP();
return () => {
inpCallback();
};
}
return () => void 0;
}
const INP_ENTRY_MAP = {
click: "click",
pointerdown: "click",
pointerup: "click",
mousedown: "click",
mouseup: "click",
touchstart: "click",
touchend: "click",
mouseover: "hover",
mouseout: "hover",
mouseenter: "hover",
mouseleave: "hover",
pointerover: "hover",
pointerout: "hover",
pointerenter: "hover",
pointerleave: "hover",
dragstart: "drag",
dragend: "drag",
drag: "drag",
dragenter: "drag",
dragleave: "drag",
dragover: "drag",
drop: "drag",
keydown: "press",
keyup: "press",
keypress: "press",
input: "press"
};
function _trackINP() {
return addInpInstrumentationHandler(_onInp);
}
const _onInp = ({ metric }) => {
if (metric.value == void 0) {
return;
}
const duration = msToSec(metric.value);
if (duration > MAX_PLAUSIBLE_INP_DURATION) {
return;
}
const entry = metric.entries.find((entry2) => entry2.duration === metric.value && INP_ENTRY_MAP[entry2.name]);
if (!entry) {
return;
}
const { interactionId } = entry;
const interactionType = INP_ENTRY_MAP[entry.name];
const startTime = msToSec(browserPerformanceTimeOrigin() + entry.startTime);
const activeSpan = getActiveSpan();
const rootSpan = activeSpan ? getRootSpan(activeSpan) : void 0;
const cachedInteractionContext = interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : void 0;
const spanToUse = cachedInteractionContext?.span || rootSpan;
const routeName = spanToUse ? spanToJSON(spanToUse).description : getCurrentScope().getScopeData().transactionName;
const name = cachedInteractionContext?.elementName || htmlTreeAsString(entry.target);
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser.inp",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: `ui.interaction.${interactionType}`,
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration
};
const span = startStandaloneWebVitalSpan({
name,
transaction: routeName,
attributes,
startTime
});
if (span) {
span.addEvent("inp", {
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: "millisecond",
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: metric.value
});
span.end(startTime + duration);
}
};
function getCachedInteractionContext(interactionId) {
return interactionId != null ? INTERACTIONS_SPAN_MAP.get(interactionId) : void 0;
}
function registerInpInteractionListener() {
const interactionEvents = Object.keys(INP_ENTRY_MAP);
if (isBrowser()) {
interactionEvents.forEach((eventType) => {
WINDOW$1.addEventListener(eventType, captureElementFromEvent, { capture: true, passive: true });
});
}
function captureElementFromEvent(event) {
const target = event.target;
if (!target) {
return;
}
const elementName = htmlTreeAsString(target);
const timestamp = Math.round(event.timeStamp);
ELEMENT_NAME_TIMESTAMP_MAP.set(timestamp, elementName);
if (ELEMENT_NAME_TIMESTAMP_MAP.size > 50) {
const firstKey = ELEMENT_NAME_TIMESTAMP_MAP.keys().next().value;
if (firstKey !== void 0) {
ELEMENT_NAME_TIMESTAMP_MAP.delete(firstKey);
}
}
}
function resolveElementNameFromEntry(entry) {
const timestamp = Math.round(entry.startTime);
let elementName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp);
if (!elementName) {
for (let offset = -5; offset <= 5; offset++) {
const nearbyName = ELEMENT_NAME_TIMESTAMP_MAP.get(timestamp + offset);
if (nearbyName) {
elementName = nearbyName;
break;
}
}
}
return elementName || "<unknown>";
}
const handleEntries = ({ entries }) => {
const activeSpan = getActiveSpan();
const activeRootSpan = activeSpan && getRootSpan(activeSpan);
entries.forEach((entry) => {
if (!isPerformanceEventTiming(entry)) {
return;
}
const interactionId = entry.interactionId;
if (interactionId == null) {
return;
}
if (INTERACTIONS_SPAN_MAP.has(interactionId)) {
return;
}
const elementName = entry.target ? htmlTreeAsString(entry.target) : resolveElementNameFromEntry(entry);
if (LAST_INTERACTIONS.length > 10) {
const last = LAST_INTERACTIONS.shift();
INTERACTIONS_SPAN_MAP.delete(last);
}
LAST_INTERACTIONS.push(interactionId);
INTERACTIONS_SPAN_MAP.set(interactionId, {
span: activeRootSpan,
elementName
});
});
};
addPerformanceInstrumentationHandler("event", handleEntries);
addPerformanceInstrumentationHandler("first-input", handleEntries);
}
function _emitWebVitalSpan(options) {
const {
name,
op,
origin,
metricName,
value,
attributes: passedAttributes,
parentSpan,
reportEvent,
startTime,
endTime
} = options;
const routeName = getCurrentScope().getScopeData().transactionName;
const attributes = {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: origin,
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0,
[`browser.web_vital.${metricName}.value`]: value,
// oxlint-disable-next-line typescript-eslint/no-deprecated
[Xc]: routeName,
[Lc]: routeName,
// Web vital score calculation relies on the user agent
"user_agent.original": WINDOW$1.navigator?.userAgent,
...passedAttributes
};
if (parentSpan && spanToStreamedSpanJSON(parentSpan).attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] === "pageload") {
attributes["sentry.pageload.span_id"] = parentSpan.spanContext().spanId;
}
if (reportEvent) {
attributes[`browser.web_vital.${metricName}.report_event`] = reportEvent;
}
const span = startInactiveSpan({
name,
attributes,
startTime,
// if we have a pageload span, we let the web vital span start as its parent. This ensures that
// it is not started as a segment span, without having to manually set it to a "standalone" v2 span
// that has `segment: false` but no actual parent span.
parentSpan
});
if (span) {
span.end(endTime ?? startTime);
}
}
function trackLcpAsSpan(client) {
let lcpValue = 0;
let lcpEntry;
if (!supportsWebVital("largest-contentful-paint")) {
return;
}
const cleanupLcpHandler = addLcpInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry || !isValidLcpMetric(metric.value)) {
return;
}
lcpValue = metric.value;
lcpEntry = entry;
}, true);
listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {
_sendLcpSpan(lcpValue, lcpEntry, pageloadSpan, reportEvent);
cleanupLcpHandler();
});
}
function _sendLcpSpan(lcpValue, entry, pageloadSpan, reportEvent) {
if (!isValidLcpMetric(lcpValue)) {
return;
}
DEBUG_BUILD$2 && debug$1.log(`Sending LCP span (${lcpValue})`);
const performanceTimeOrigin = browserPerformanceTimeOrigin() || 0;
const timeOrigin = msToSec(performanceTimeOrigin);
const endTime = msToSec(performanceTimeOrigin + (entry?.startTime || 0));
const name = entry ? htmlTreeAsString(entry.element) : "Largest contentful paint";
const attributes = {};
entry?.element && (attributes["browser.web_vital.lcp.element"] = htmlTreeAsString(entry.element));
entry?.id && (attributes["browser.web_vital.lcp.id"] = entry.id);
entry?.url && (attributes["browser.web_vital.lcp.url"] = entry.url);
entry?.loadTime != null && (attributes["browser.web_vital.lcp.load_time"] = entry.loadTime);
entry?.renderTime != null && (attributes["browser.web_vital.lcp.render_time"] = entry.renderTime);
entry?.size != null && (attributes["browser.web_vital.lcp.size"] = entry.size);
_emitWebVitalSpan({
name,
op: "ui.webvital.lcp",
origin: "auto.http.browser.lcp",
metricName: "lcp",
value: lcpValue,
attributes,
parentSpan: pageloadSpan,
reportEvent,
startTime: timeOrigin,
endTime
});
}
function trackClsAsSpan(client) {
let clsValue = 0;
let clsEntry;
if (!supportsWebVital("layout-shift")) {
return;
}
const cleanupClsHandler = addClsInstrumentationHandler(({ metric }) => {
const entry = metric.entries[metric.entries.length - 1];
if (!entry) {
return;
}
clsValue = metric.value;
clsEntry = entry;
}, true);
listenForWebVitalReportEvents(client, (reportEvent, _, pageloadSpan) => {
_sendClsSpan(clsValue, clsEntry, pageloadSpan, reportEvent);
cleanupClsHandler();
});
}
function _sendClsSpan(clsValue, entry, pageloadSpan, reportEvent) {
DEBUG_BUILD$2 && debug$1.log(`Sending CLS span (${clsValue})`);
const startTime = entry ? msToSec((browserPerformanceTimeOrigin() || 0) + entry.startTime) : timestampInSeconds();
const name = entry ? htmlTreeAsString(entry.sources[0]?.node) : "Layout shift";
const attributes = {};
if (entry?.sources) {
entry.sources.forEach((source, index) => {
attributes[`browser.web_vital.cls.source.${index + 1}`] = htmlTreeAsString(source.node);
});
}
_emitWebVitalSpan({
name,
op: "ui.webvital.cls",
origin: "auto.http.browser.cls",
metricName: "cls",
value: clsValue,
attributes,
parentSpan: pageloadSpan,
reportEvent,
startTime
});
}
function trackInpAsSpan() {
const performance = getBrowserPerformanceAPI();
if (!performance || !browserPerformanceTimeOrigin()) {
return;
}
const onInp = ({ metric }) => {
if (metric.value == null) {
return;
}
const duration = msToSec(metric.value);
if (duration > MAX_PLAUSIBLE_INP_DURATION) {
return;
}
const entry = metric.entries.find((e) => e.duration === metric.value && INP_ENTRY_MAP[e.name]);
if (!entry) {
return;
}
_sendInpSpan(metric.value, entry);
};
addInpInstrumentationHandler(onInp);
}
function _sendInpSpan(inpValue, entry) {
DEBUG_BUILD$2 && debug$1.log(`Sending INP span (${inpValue})`);
const startTime = msToSec(browserPerformanceTimeOrigin() + entry.startTime);
const duration = msToSec(inpValue);
const interactionType = INP_ENTRY_MAP[entry.name];
const cachedContext = getCachedInteractionContext(entry.interactionId);
const activeSpan = getActiveSpan();
const rootSpan = activeSpan ? getRootSpan(activeSpan) : void 0;
const spanToUse = cachedContext?.span || rootSpan;
const routeName = spanToUse ? spanToStreamedSpanJSON(spanToUse).name : getCurrentScope().getScopeData().transactionName;
const name = cachedContext?.elementName || htmlTreeAsString(entry.target);
_emitWebVitalSpan({
name,
op: `ui.interaction.${interactionType}`,
origin: "auto.http.browser.inp",
metricName: "inp",
value: inpValue,
attributes: {
[SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration,
// oxlint-disable-next-line typescript-eslint/no-deprecated
[Xc]: routeName,
[Lc]: routeName
},
startTime,
endTime: startTime + duration,
parentSpan: spanToUse
});
}
const DEBOUNCE_DURATION = 1e3;
let debounceTimerID;
let lastCapturedEventType;
let lastCapturedEventTargetId;
function addClickKeypressInstrumentationHandler(handler) {
const type = "dom";
addHandler$1(type, handler);
maybeInstrument(type, instrumentDOM);
}
function instrumentDOM() {
if (!WINDOW$1.document) {
return;
}
const triggerDOMHandler = triggerHandlers$1.bind(null, "dom");
const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);
WINDOW$1.document.addEventListener("click", globalDOMEventHandler, false);
WINDOW$1.document.addEventListener("keypress", globalDOMEventHandler, false);
["EventTarget", "Node"].forEach((target) => {
const globalObject = WINDOW$1;
const proto = globalObject[target]?.prototype;
if (!proto?.hasOwnProperty?.("addEventListener")) {
return;
}
fill(proto, "addEventListener", function(originalAddEventListener) {
return function(type, listener, options) {
if (type === "click" || type == "keypress") {
try {
const handlers = this.__sentry_instrumentation_handlers__ = this.__sentry_instrumentation_handlers__ || {};
const handlerForType = handlers[type] = handlers[type] || { refCount: 0 };
if (!handlerForType.handler) {
const handler = makeDOMEventHandler(triggerDOMHandler);
handlerForType.handler = handler;
originalAddEventListener.call(this, type, handler, options);
}
handlerForType.refCount++;
} catch {
}
}
return originalAddEventListener.call(this, type, listener, options);
};
});
fill(
proto,
"removeEventListener",
function(originalRemoveEventListener) {
return function(type, listener, options) {
if (type === "click" || type == "keypress") {
try {
const handlers = this.__sentry_instrumentation_handlers__ || {};
const handlerForType = handlers[type];
if (handlerForType) {
handlerForType.refCount--;
if (handlerForType.refCount <= 0) {
originalRemoveEventListener.call(this, type, handlerForType.handler, options);
handlerForType.handler = void 0;
delete handlers[type];
}
if (Object.keys(handlers).length === 0) {
delete this.__sentry_instrumentation_handlers__;
}
}
} catch {
}
}
return originalRemoveEventListener.call(this, type, listener, options);
};
}
);
});
}
function isSimilarToLastCapturedEvent(event) {
if (event.type !== lastCapturedEventType) {
return false;
}
try {
if (!event.target || event.target._sentryId !== lastCapturedEventTargetId) {
return false;
}
} catch {
}
return true;
}
function shouldSkipDOMEvent(eventType, target) {
if (eventType !== "keypress") {
return false;
}
if (!target?.tagName) {
return true;
}
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable) {
return false;
}
return true;
}
function makeDOMEventHandler(handler, globalListener = false) {
return (event) => {
if (!event || event["_sentryCaptured"]) {
return;
}
const target = getEventTarget$1(event);
if (shouldSkipDOMEvent(event.type, target)) {
return;
}
addNonEnumerableProperty(event, "_sentryCaptured", true);
if (target && !target._sentryId) {
addNonEnumerableProperty(target, "_sentryId", uuid4());
}
const name = event.type === "keypress" ? "input" : event.type;
if (!isSimilarToLastCapturedEvent(event)) {
const handlerData = { event, name, global: globalListener };
handler(handlerData);
lastCapturedEventType = event.type;
lastCapturedEventTargetId = target ? target._sentryId : void 0;
}
clearTimeout(debounceTimerID);
debounceTimerID = WINDOW$1.setTimeout(() => {
lastCapturedEventTargetId = void 0;
lastCapturedEventType = void 0;
}, DEBOUNCE_DURATION);
};
}
function getEventTarget$1(event) {
try {
return event.target;
} catch {
return null;
}
}
let lastHref;
function addHistoryInstrumentationHandler(handler) {
const type = "history";
addHandler$1(type, handler);
maybeInstrument(type, instrumentHistory);
}
function instrumentHistory() {
WINDOW$1.addEventListener("popstate", () => {
const to = WINDOW$1.location.href;
const from = lastHref;
lastHref = to;
if (from === to) {
return;
}
const handlerData = { from, to };
triggerHandlers$1("history", handlerData);
});
if (!supportsHistory()) {
return;
}
function historyReplacementFunction(originalHistoryFunction) {
return function(...args) {
const url = args.length > 2 ? args[2] : void 0;
if (url) {
const from = lastHref;
const to = getAbsoluteUrl(String(url));
lastHref = to;
if (from === to) {
return originalHistoryFunction.apply(this, args);
}
const handlerData = { from, to };
triggerHandlers$1("history", handlerData);
}
return originalHistoryFunction.apply(this, args);
};
}
fill(WINDOW$1.history, "pushState", historyReplacementFunction);
fill(WINDOW$1.history, "replaceState", historyReplacementFunction);
}
function getAbsoluteUrl(urlOrPath) {
try {
const url = new URL(urlOrPath, WINDOW$1.location.origin);
return url.toString();
} catch {
return urlOrPath;
}
}
const cachedImplementations$2 = {};
function getNativeImplementation(name) {
const cached = cachedImplementations$2[name];
if (cached) {
return cached;
}
let impl = WINDOW$1[name];
if (isNativeFunction(impl)) {
return cachedImplementations$2[name] = impl.bind(WINDOW$1);
}
const document = WINDOW$1.document;
if (document && typeof document.createElement === "function") {
try {
const sandbox = document.createElement("iframe");
sandbox.hidden = true;
document.head.appendChild(sandbox);
const contentWindow = sandbox.contentWindow;
if (contentWindow?.[name]) {
impl = contentWindow[name];
}
document.head.removeChild(sandbox);
} catch (e) {
DEBUG_BUILD$2 && debug$1.warn(`Could not create sandbox iframe for ${name} check, bailing to window.${name}: `, e);
}
}
if (!impl) {
return impl;
}
return cachedImplementations$2[name] = impl.bind(WINDOW$1);
}
function clearCachedImplementation(name) {
cachedImplementations$2[name] = void 0;
}
function setTimeout$3(...rest) {
return getNativeImplementation("setTimeout")(...rest);
}
const SENTRY_XHR_DATA_KEY = "__sentry_xhr_v3__";
function addXhrInstrumentationHandler(handler) {
const type = "xhr";
addHandler$1(type, handler);
maybeInstrument(type, instrumentXHR);
}
function instrumentXHR() {
if (!WINDOW$1.XMLHttpRequest) {
return;
}
const xhrproto = XMLHttpRequest.prototype;
xhrproto.open = new Proxy(xhrproto.open, {
apply(originalOpen, xhrOpenThisArg, xhrOpenArgArray) {
const virtualError = new Error();
const startTimestamp = timestampInSeconds() * 1e3;
const method = isString(xhrOpenArgArray[0]) ? xhrOpenArgArray[0].toUpperCase() : void 0;
const url = parseXhrUrlArg(xhrOpenArgArray[1]);
if (!method || !url) {
return originalOpen.apply(xhrOpenThisArg, xhrOpenArgArray);
}
xhrOpenThisArg[SENTRY_XHR_DATA_KEY] = {
method,
url,
request_headers: {}
};
if (method === "POST" && url.match(/sentry_key/)) {
xhrOpenThisArg.__sentry_own_request__ = true;
}
const onreadystatechangeHandler = () => {
const xhrInfo = xhrOpenThisArg[SENTRY_XHR_DATA_KEY];
if (!xhrInfo) {
return;
}
if (xhrOpenThisArg.readyState === 4) {
try {
xhrInfo.status_code = xhrOpenThisArg.status;
} catch {
}
const handlerData = {
endTimestamp: timestampInSeconds() * 1e3,
startTimestamp,
xhr: xhrOpenThisArg,
virtualError
};
triggerHandlers$1("xhr", handlerData);
xhrOpenThisArg.removeEventListener("readystatechange", onreadystatechangeHandler);
}
};
if ("onreadystatechange" in xhrOpenThisArg && typeof xhrOpenThisArg.onreadystatechange === "function") {
xhrOpenThisArg.onreadystatechange = new Proxy(xhrOpenThisArg.onreadystatechange, {
apply(originalOnreadystatechange, onreadystatechangeThisArg, onreadystatechangeArgArray) {
onreadystatechangeHandler();
return originalOnreadystatechange.apply(onreadystatechangeThisArg, onreadystatechangeArgArray);
}
});
} else {
xhrOpenThisArg.addEventListener("readystatechange", onreadystatechangeHandler);
}
xhrOpenThisArg.setRequestHeader = new Proxy(xhrOpenThisArg.setRequestHeader, {
apply(originalSetRequestHeader, setRequestHeaderThisArg, setRequestHeaderArgArray) {
const [header, value] = setRequestHeaderArgArray;
const xhrInfo = setRequestHeaderThisArg[SENTRY_XHR_DATA_KEY];
if (xhrInfo && isString(header) && isString(value)) {
xhrInfo.request_headers[header.toLowerCase()] = value;
}
return originalSetRequestHeader.apply(setRequestHeaderThisArg, setRequestHeaderArgArray);
}
});
return originalOpen.apply(xhrOpenThisArg, xhrOpenArgArray);
}
});
xhrproto.send = new Proxy(xhrproto.send, {
apply(originalSend, sendThisArg, sendArgArray) {
const sentryXhrData = sendThisArg[SENTRY_XHR_DATA_KEY];
if (!sentryXhrData) {
return originalSend.apply(sendThisArg, sendArgArray);
}
if (sendArgArray[0] !== void 0) {
sentryXhrData.body = sendArgArray[0];
}
const handlerData = {
startTimestamp: timestampInSeconds() * 1e3,
xhr: sendThisArg
};
triggerHandlers$1("xhr", handlerData);
return originalSend.apply(sendThisArg, sendArgArray);
}
});
}
function parseXhrUrlArg(url) {
if (isString(url)) {
return url;
}
try {
return url.toString();
} catch {
}
return void 0;
}
const ORIGINAL_REQ_BODY = /* @__PURE__ */ Symbol.for("sentry__originalRequestBody");
function serializeFormData(formData) {
return new URLSearchParams(formData).toString();
}
function getBodyString(body, _debug = debug$1) {
try {
if (typeof body === "string") {
return [body];
}
if (body instanceof URLSearchParams) {
return [body.toString()];
}
if (body instanceof FormData) {
return [serializeFormData(body)];
}
if (!body) {
return [void 0];
}
} catch (error) {
DEBUG_BUILD$2 && _debug.error(error, "Failed to serialize body", body);
return [void 0, "BODY_PARSE_ERROR"];
}
DEBUG_BUILD$2 && _debug.log("Skipping network body because of body type", body);
return [void 0, "UNPARSEABLE_BODY_TYPE"];
}
function getFetchRequestArgBody(fetchArgs = []) {
if (fetchArgs.length >= 2 && fetchArgs[1] && typeof fetchArgs[1] === "object" && "body" in fetchArgs[1]) {
return fetchArgs[1].body;
}
if (fetchArgs.length >= 1 && fetchArgs[0] instanceof Request) {
const request = fetchArgs[0];
const originalBody = request[ORIGINAL_REQ_BODY];
if (originalBody !== void 0) {
return originalBody;
}
return void 0;
}
return void 0;
}
function parseXhrResponseHeaders(xhr) {
let headers;
try {
headers = xhr.getAllResponseHeaders();
} catch (error) {
DEBUG_BUILD$2 && debug$1.error(error, "Failed to get xhr response headers", xhr);
return {};
}
if (!headers) {
return {};
}
return headers.split("\r\n").reduce((acc, line) => {
const [key, value] = line.split(": ");
if (value) {
acc[key.toLowerCase()] = value;
}
return acc;
}, {});
}
function isElement$2(wat) {
if (typeof Element === "undefined") {
return false;
}
try {
return wat instanceof Element;
} catch {
return false;
}
}
const DEFAULT_BROWSER_TRANSPORT_BUFFER_SIZE = 40;
function makeFetchTransport(options, nativeFetch = getNativeImplementation("fetch")) {
let pendingBodySize = 0;
let pendingCount = 0;
async function makeRequest(request) {
const requestSize = request.body.length;
pendingBodySize += requestSize;
pendingCount++;
const requestOptions = {
body: request.body,
method: "POST",
referrerPolicy: "strict-origin",
headers: options.headers,
// Outgoing requests are usually cancelled when navigating to a different page, causing a "TypeError: Failed to
// fetch" error and sending a "network_error" client-outcome - in Chrome, the request status shows "(cancelled)".
// The `keepalive` flag keeps outgoing requests alive, even when switching pages. We want this since we're
// frequently sending events right before the user is switching pages (eg. when finishing navigation transactions).
// Gotchas:
// - `keepalive` isn't supported by Firefox
// - As per spec (https://fetch.spec.whatwg.org/#http-network-or-cache-fetch):
// If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error.
// We will therefore only activate the flag when we're below that limit.
// There is also a limit of requests that can be open at the same time, so we also limit this to 15
// See https://github.com/getsentry/sentry-javascript/pull/7553 for details
keepalive: pendingBodySize <= 6e4 && pendingCount < 15,
...options.fetchOptions
};
try {
const response = await nativeFetch(options.url, requestOptions);
return {
statusCode: response.status,
headers: {
"x-sentry-rate-limits": response.headers.get("X-Sentry-Rate-Limits"),
"retry-after": response.headers.get("Retry-After")
}
};
} catch (e) {
clearCachedImplementation("fetch");
throw e;
} finally {
pendingBodySize -= requestSize;
pendingCount--;
}
}
return createTransport(
options,
makeRequest,
makePromiseBuffer(options.bufferSize || DEFAULT_BROWSER_TRANSPORT_BUFFER_SIZE)
);
}
const DEBUG_BUILD$1 = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
const CHROME_PRIORITY = 30;
const GECKO_PRIORITY = 50;
function createFrame(filename, func, lineno, colno) {
const frame = {
filename,
function: func === "<anonymous>" ? UNKNOWN_FUNCTION : func,
in_app: true
// All browser frames are considered in_app
};
if (lineno !== void 0) {
frame.lineno = lineno;
}
if (colno !== void 0) {
frame.colno = colno;
}
return frame;
}
const chromeRegexNoFnName = /^\s*at (\S+?)(?::(\d+))(?::(\d+))\s*$/i;
const chromeRegex = /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
const chromeDataUriRegex = /at (.+?) ?\(data:(.+?),/;
const chromeStackParserFn = (line) => {
const dataUriMatch = line.match(chromeDataUriRegex);
if (dataUriMatch) {
return {
filename: `<data:${dataUriMatch[2]}>`,
function: dataUriMatch[1]
};
}
const noFnParts = chromeRegexNoFnName.exec(line);
if (noFnParts) {
const [, filename, line2, col] = noFnParts;
return createFrame(filename, UNKNOWN_FUNCTION, +line2, +col);
}
const parts = chromeRegex.exec(line);
if (parts) {
const isEval = parts[2]?.indexOf("eval") === 0;
if (isEval) {
const subMatch = chromeEvalRegex.exec(parts[2]);
if (subMatch) {
parts[2] = subMatch[1];
parts[3] = subMatch[2];
parts[4] = subMatch[3];
}
}
const [func, filename] = extractSafariExtensionDetails(parts[1] || UNKNOWN_FUNCTION, parts[2]);
return createFrame(filename, func, parts[3] ? +parts[3] : void 0, parts[4] ? +parts[4] : void 0);
}
return;
};
const chromeStackLineParser = [CHROME_PRIORITY, chromeStackParserFn];
const geckoREgex = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:[-a-z]+)?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js)|\/[\w\-. /=]+)(?::(\d+))?(?::(\d+))?\s*$/i;
const geckoEvalRegex = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
const gecko = (line) => {
const parts = geckoREgex.exec(line);
if (parts) {
const isEval = parts[3] && parts[3].indexOf(" > eval") > -1;
if (isEval) {
const subMatch = geckoEvalRegex.exec(parts[3]);
if (subMatch) {
parts[1] = parts[1] || "eval";
parts[3] = subMatch[1];
parts[4] = subMatch[2];
parts[5] = "";
}
}
let filename = parts[3];
let func = parts[1] || UNKNOWN_FUNCTION;
[func, filename] = extractSafariExtensionDetails(func, filename);
return createFrame(filename, func, parts[4] ? +parts[4] : void 0, parts[5] ? +parts[5] : void 0);
}
return;
};
const geckoStackLineParser = [GECKO_PRIORITY, gecko];
const defaultStackLineParsers = [chromeStackLineParser, geckoStackLineParser];
const defaultStackParser = createStackParser(...defaultStackLineParsers);
const extractSafariExtensionDetails = (func, filename) => {
const isSafariExtension = func.indexOf("safari-extension") !== -1;
const isSafariWebExtension = func.indexOf("safari-web-extension") !== -1;
return isSafariExtension || isSafariWebExtension ? [
func.indexOf("@") !== -1 ? func.split("@")[0] : UNKNOWN_FUNCTION,
isSafariExtension ? `safari-extension:${filename}` : `safari-web-extension:${filename}`
] : [func, filename];
};
const MAX_ALLOWED_STRING_LENGTH = 1024;
const INTEGRATION_NAME$4 = "Breadcrumbs";
const _breadcrumbsIntegration = ((options = {}) => {
const _options = {
console: true,
dom: true,
fetch: true,
history: true,
sentry: true,
xhr: true,
...options
};
return {
name: INTEGRATION_NAME$4,
setup(client) {
if (_options.console) {
addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client));
}
if (_options.dom) {
addClickKeypressInstrumentationHandler(_getDomBreadcrumbHandler(client, _options.dom));
}
if (_options.xhr) {
addXhrInstrumentationHandler(_getXhrBreadcrumbHandler(client));
}
if (_options.fetch) {
addFetchInstrumentationHandler(_getFetchBreadcrumbHandler(client));
}
if (_options.history) {
addHistoryInstrumentationHandler(_getHistoryBreadcrumbHandler(client));
}
if (_options.sentry) {
client.on("beforeSendEvent", _getSentryBreadcrumbHandler(client));
}
}
};
});
const breadcrumbsIntegration = defineIntegration(_breadcrumbsIntegration);
function _getSentryBreadcrumbHandler(client) {
return function addSentryBreadcrumb(event) {
if (getClient() !== client) {
return;
}
addBreadcrumb(
{
category: `sentry.${event.type === "transaction" ? "transaction" : "event"}`,
event_id: event.event_id,
level: event.level,
message: getEventDescription(event)
},
{
event
}
);
};
}
function _getDomBreadcrumbHandler(client, dom) {
return function _innerDomBreadcrumb(handlerData) {
if (getClient() !== client) {
return;
}
let target;
let componentName;
let keyAttrs = typeof dom === "object" ? dom.serializeAttribute : void 0;
let maxStringLength = typeof dom === "object" && typeof dom.maxStringLength === "number" ? dom.maxStringLength : void 0;
if (maxStringLength && maxStringLength > MAX_ALLOWED_STRING_LENGTH) {
DEBUG_BUILD$1 && debug$1.warn(
`\`dom.maxStringLength\` cannot exceed ${MAX_ALLOWED_STRING_LENGTH}, but a value of ${maxStringLength} was configured. Sentry will use ${MAX_ALLOWED_STRING_LENGTH} instead.`
);
maxStringLength = MAX_ALLOWED_STRING_LENGTH;
}
if (typeof keyAttrs === "string") {
keyAttrs = [keyAttrs];
}
try {
const event = handlerData.event;
const element = _isEvent(event) ? event.target : event;
target = htmlTreeAsString(element, { keyAttrs, maxStringLength });
componentName = getComponentName(element);
} catch {
target = "<unknown>";
}
if (target.length === 0) {
return;
}
const breadcrumb = {
category: `ui.${handlerData.name}`,
message: target
};
if (componentName) {
breadcrumb.data = { "ui.component_name": componentName };
}
addBreadcrumb(breadcrumb, {
event: handlerData.event,
name: handlerData.name,
global: handlerData.global
});
};
}
function _getConsoleBreadcrumbHandler(client) {
return function _consoleBreadcrumb(handlerData) {
if (getClient() !== client) {
return;
}
const breadcrumb = {
category: "console",
data: {
arguments: handlerData.args,
logger: "console"
},
level: severityLevelFromString(handlerData.level),
message: safeJoin(handlerData.args, " ")
};
if (handlerData.level === "assert") {
if (handlerData.args[0] === false) {
breadcrumb.message = `Assertion failed: ${safeJoin(handlerData.args.slice(1), " ") || "console.assert"}`;
breadcrumb.data.arguments = handlerData.args.slice(1);
} else {
return;
}
}
addBreadcrumb(breadcrumb, {
input: handlerData.args,
level: handlerData.level
});
};
}
function _getXhrBreadcrumbHandler(client) {
return function _xhrBreadcrumb(handlerData) {
if (getClient() !== client) {
return;
}
const { startTimestamp, endTimestamp } = handlerData;
const sentryXhrData = handlerData.xhr[SENTRY_XHR_DATA_KEY];
if (!startTimestamp || !endTimestamp || !sentryXhrData) {
return;
}
const { method, url, status_code, body } = sentryXhrData;
const data = {
method,
url,
status_code
};
const hint = {
xhr: handlerData.xhr,
input: body,
startTimestamp,
endTimestamp
};
const breadcrumb = {
category: "xhr",
data,
type: "http",
level: getBreadcrumbLogLevelFromHttpStatusCode(status_code)
};
client.emit("beforeOutgoingRequestBreadcrumb", breadcrumb, hint);
addBreadcrumb(breadcrumb, hint);
};
}
function _getFetchBreadcrumbHandler(client) {
return function _fetchBreadcrumb(handlerData) {
if (getClient() !== client) {
return;
}
const { startTimestamp, endTimestamp } = handlerData;
if (!endTimestamp) {
return;
}
if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === "POST") {
return;
}
if (handlerData.error) {
const hint = {
data: handlerData.error,
input: handlerData.args,
startTimestamp,
endTimestamp
};
const breadcrumb = {
category: "fetch",
data: handlerData.fetchData,
level: "error",
type: "http"
};
client.emit("beforeOutgoingRequestBreadcrumb", breadcrumb, hint);
addBreadcrumb(breadcrumb, hint);
} else {
const response = handlerData.response;
const data = {
...handlerData.fetchData,
status_code: response?.status
};
const hint = {
input: handlerData.args,
response,
startTimestamp,
endTimestamp
};
const breadcrumb = {
category: "fetch",
data,
type: "http",
level: getBreadcrumbLogLevelFromHttpStatusCode(data.status_code)
};
client.emit("beforeOutgoingRequestBreadcrumb", breadcrumb, hint);
addBreadcrumb(breadcrumb, hint);
}
};
}
function _getHistoryBreadcrumbHandler(client) {
return function _historyBreadcrumb(handlerData) {
if (getClient() !== client) {
return;
}
let from = handlerData.from;
let to = handlerData.to;
const parsedLoc = parseUrl(WINDOW$2.location.href);
let parsedFrom = from ? parseUrl(from) : void 0;
const parsedTo = parseUrl(to);
if (!parsedFrom?.path) {
parsedFrom = parsedLoc;
}
if (parsedLoc.protocol === parsedTo.protocol && parsedLoc.host === parsedTo.host) {
to = parsedTo.relative;
}
if (parsedLoc.protocol === parsedFrom.protocol && parsedLoc.host === parsedFrom.host) {
from = parsedFrom.relative;
}
addBreadcrumb({
category: "navigation",
data: {
from,
to
}
});
};
}
function _isEvent(event) {
return !!event && !!event.target;
}
const DEFAULT_EVENT_TARGET = "EventTarget,Window,Node,ApplicationCache,AudioTrackList,BroadcastChannel,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,SharedWorker,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload".split(
","
);
const INTEGRATION_NAME$3 = "BrowserApiErrors";
const _browserApiErrorsIntegration = ((options = {}) => {
const _options = {
XMLHttpRequest: true,
eventTarget: true,
requestAnimationFrame: true,
setInterval: true,
setTimeout: true,
unregisterOriginalCallbacks: false,
...options
};
return {
name: INTEGRATION_NAME$3,
// TODO: This currently only works for the first client this is setup
// We may want to adjust this to check for client etc.
setupOnce() {
if (_options.setTimeout) {
fill(WINDOW$2, "setTimeout", _wrapTimeFunction);
}
if (_options.setInterval) {
fill(WINDOW$2, "setInterval", _wrapTimeFunction);
}
if (_options.requestAnimationFrame) {
fill(WINDOW$2, "requestAnimationFrame", _wrapRAF);
}
if (_options.XMLHttpRequest && "XMLHttpRequest" in WINDOW$2) {
fill(XMLHttpRequest.prototype, "send", _wrapXHR);
}
const eventTargetOption = _options.eventTarget;
if (eventTargetOption) {
const eventTarget = Array.isArray(eventTargetOption) ? eventTargetOption : DEFAULT_EVENT_TARGET;
eventTarget.forEach((target) => _wrapEventTarget(target, _options));
}
}
};
});
const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration);
function _wrapTimeFunction(original) {
return function(...args) {
const originalCallback = args[0];
args[0] = wrap(originalCallback, {
mechanism: {
handled: false,
type: `auto.browser.browserapierrors.${getFunctionName(original)}`
}
});
return original.apply(this, args);
};
}
function _wrapRAF(original) {
return function(callback) {
return original.apply(this, [
wrap(callback, {
mechanism: {
data: {
handler: getFunctionName(original)
},
handled: false,
type: "auto.browser.browserapierrors.requestAnimationFrame"
}
})
]);
};
}
function _wrapXHR(originalSend) {
return function(...args) {
const xhr = this;
const xmlHttpRequestProps = ["onload", "onerror", "onprogress", "onreadystatechange"];
xmlHttpRequestProps.forEach((prop) => {
if (prop in xhr && typeof xhr[prop] === "function") {
fill(xhr, prop, function(original) {
const wrapOptions = {
mechanism: {
data: {
handler: getFunctionName(original)
},
handled: false,
type: `auto.browser.browserapierrors.xhr.${prop}`
}
};
const originalFunction = getOriginalFunction(original);
if (originalFunction) {
wrapOptions.mechanism.data.handler = getFunctionName(originalFunction);
}
return wrap(original, wrapOptions);
});
}
});
return originalSend.apply(this, args);
};
}
function _wrapEventTarget(target, integrationOptions) {
const globalObject = WINDOW$2;
const proto = globalObject[target]?.prototype;
if (!proto?.hasOwnProperty?.("addEventListener")) {
return;
}
fill(proto, "addEventListener", function(original) {
return function(eventName, fn, options) {
try {
if (isEventListenerObject(fn)) {
fn.handleEvent = wrap(fn.handleEvent, {
mechanism: {
data: {
handler: getFunctionName(fn),
target
},
handled: false,
type: "auto.browser.browserapierrors.handleEvent"
}
});
}
} catch {
}
if (integrationOptions.unregisterOriginalCallbacks) {
unregisterOriginalCallback(this, eventName, fn);
}
return original.apply(this, [
eventName,
wrap(fn, {
mechanism: {
data: {
handler: getFunctionName(fn),
target
},
handled: false,
type: "auto.browser.browserapierrors.addEventListener"
}
}),
options
]);
};
});
fill(proto, "removeEventListener", function(originalRemoveEventListener) {
return function(eventName, fn, options) {
try {
if (Object.prototype.hasOwnProperty.call(fn, "__sentry_wrapped__")) {
const originalEventHandler = fn.__sentry_wrapped__;
if (originalEventHandler) {
originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
}
}
} catch {
}
return originalRemoveEventListener.call(this, eventName, fn, options);
};
});
}
function isEventListenerObject(obj) {
return typeof obj.handleEvent === "function";
}
function unregisterOriginalCallback(target, eventName, fn) {
if (target && typeof target === "object" && "removeEventListener" in target && typeof target.removeEventListener === "function") {
target.removeEventListener(eventName, fn);
}
}
const browserSessionIntegration = defineIntegration((options = {}) => {
const lifecycle = options.lifecycle ?? "route";
return {
name: "BrowserSession",
setupOnce() {
if (typeof WINDOW$2.document === "undefined") {
DEBUG_BUILD$1 && debug$1.warn("Using the `browserSessionIntegration` in non-browser environments is not supported.");
return;
}
startSession({ ignoreDuration: true });
let initialSessionSent = false;
whenIdleOrHidden(() => {
if (!initialSessionSent) {
captureSession();
initialSessionSent = true;
}
});
const isolationScope = getIsolationScope();
let previousUser = isolationScope.getUser();
isolationScope.addScopeListener((scope) => {
const maybeNewUser = scope.getUser();
if (previousUser?.id !== maybeNewUser?.id || previousUser?.ip_address !== maybeNewUser?.ip_address) {
previousUser = maybeNewUser;
if (initialSessionSent) {
captureSession();
}
}
});
if (lifecycle === "route") {
addHistoryInstrumentationHandler(({ from, to }) => {
if (from !== to) {
startSession({ ignoreDuration: true });
captureSession();
initialSessionSent = true;
}
});
}
}
};
});
const INTEGRATION_NAME$2 = "CultureContext";
const _cultureContextIntegration = (() => {
return {
name: INTEGRATION_NAME$2,
preprocessEvent(event) {
const culture = getCultureContext();
if (culture) {
event.contexts = {
...event.contexts,
culture: { ...culture, ...event.contexts?.culture }
};
}
},
processSegmentSpan(span) {
const culture = getCultureContext();
if (culture) {
safeSetSpanJSONAttributes(span, {
"culture.locale": culture.locale,
"culture.timezone": culture.timezone,
"culture.calendar": culture.calendar
});
}
}
};
});
const cultureContextIntegration = defineIntegration(_cultureContextIntegration);
function getCultureContext() {
try {
const intl = WINDOW$2.Intl;
if (!intl) {
return void 0;
}
const options = intl.DateTimeFormat().resolvedOptions();
return {
locale: options.locale,
timezone: options.timeZone,
calendar: options.calendar
};
} catch {
return void 0;
}
}
const INTEGRATION_NAME$1 = "GlobalHandlers";
const _globalHandlersIntegration = ((options = {}) => {
const _options = {
onerror: true,
onunhandledrejection: true,
...options
};
return {
name: INTEGRATION_NAME$1,
setupOnce() {
Error.stackTraceLimit = 50;
},
setup(client) {
if (_options.onerror) {
_installGlobalOnErrorHandler(client);
globalHandlerLog("onerror");
}
if (_options.onunhandledrejection) {
_installGlobalOnUnhandledRejectionHandler(client);
globalHandlerLog("onunhandledrejection");
}
}
};
});
const globalHandlersIntegration = defineIntegration(_globalHandlersIntegration);
function _installGlobalOnErrorHandler(client) {
addGlobalErrorInstrumentationHandler((data) => {
const { stackParser, attachStacktrace } = getOptions();
if (getClient() !== client || shouldIgnoreOnError()) {
return;
}
const { msg, url, line, column, error } = data;
const event = _enhanceEventWithInitialFrame(
eventFromUnknownInput(stackParser, error || msg, void 0, attachStacktrace, false),
url,
line,
column
);
event.level = "error";
captureEvent(event, {
originalException: error,
mechanism: {
handled: false,
type: "auto.browser.global_handlers.onerror"
}
});
});
}
function _installGlobalOnUnhandledRejectionHandler(client) {
addGlobalUnhandledRejectionInstrumentationHandler((e) => {
const { stackParser, attachStacktrace } = getOptions();
if (getClient() !== client || shouldIgnoreOnError()) {
return;
}
const error = _getUnhandledRejectionError(e);
const event = isPrimitive(error) ? _eventFromRejectionWithPrimitive(error) : eventFromUnknownInput(stackParser, error, void 0, attachStacktrace, true);
event.level = "error";
captureEvent(event, {
originalException: error,
mechanism: {
handled: false,
type: "auto.browser.global_handlers.onunhandledrejection"
}
});
});
}
function _getUnhandledRejectionError(error) {
if (isPrimitive(error)) {
return error;
}
try {
if ("reason" in error) {
return error.reason;
}
if ("detail" in error && "reason" in error.detail) {
return error.detail.reason;
}
} catch {
}
return error;
}
function _eventFromRejectionWithPrimitive(reason) {
return {
exception: {
values: [
{
type: "UnhandledRejection",
// String() is needed because the Primitive type includes symbols (which can't be automatically stringified)
value: `Non-Error promise rejection captured with value: ${String(reason)}`
}
]
}
};
}
function _enhanceEventWithInitialFrame(event, url, lineno, colno) {
const e = event.exception = event.exception || {};
const ev = e.values = e.values || [];
const ev0 = ev[0] = ev[0] || {};
const ev0s = ev0.stacktrace = ev0.stacktrace || {};
const ev0sf = ev0s.frames = ev0s.frames || [];
if (ev0sf.length === 0) {
ev0sf.push({
colno,
lineno,
filename: getFilenameFromUrl(url) ?? getLocationHref(),
function: UNKNOWN_FUNCTION,
in_app: true
});
}
return event;
}
function globalHandlerLog(type) {
DEBUG_BUILD$1 && debug$1.log(`Global Handler attached: ${type}`);
}
function getOptions() {
const client = getClient();
const options = client?.getOptions() || {
stackParser: () => [],
attachStacktrace: false
};
return options;
}
function getFilenameFromUrl(url) {
if (!isString(url) || url.length === 0) {
return void 0;
}
if (url.startsWith("data:")) {
return `<${stripDataUrlContent(url, false)}>`;
}
return url;
}
const httpContextIntegration = defineIntegration(() => {
return {
name: "HttpContext",
preprocessEvent(event) {
if (!WINDOW$2.navigator && !WINDOW$2.location && !WINDOW$2.document) {
return;
}
const reqData = getHttpRequestData();
const headers = {
...reqData.headers,
...event.request?.headers
};
event.request = {
...reqData,
...event.request,
headers
};
},
processSegmentSpan(span) {
const spanOp = span.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP];
if (!WINDOW$2.navigator && !WINDOW$2.location && !WINDOW$2.document) {
return;
}
const reqData = getHttpRequestData();
safeSetSpanJSONAttributes(span, {
// Coerce empty string to undefined so the helper's nullish check drops it,
// rather than writing an empty `url.full` attribute onto the span.
[Yu]: spanOp !== "http.client" ? reqData.url : void 0,
"http.request.header.user_agent": reqData.headers["User-Agent"],
"http.request.header.referer": reqData.headers["Referer"]
});
}
};
});
const DEFAULT_KEY = "cause";
const DEFAULT_LIMIT = 5;
const INTEGRATION_NAME = "LinkedErrors";
const _linkedErrorsIntegration = ((options = {}) => {
const limit = options.limit || DEFAULT_LIMIT;
const key = options.key || DEFAULT_KEY;
return {
name: INTEGRATION_NAME,
preprocessEvent(event, hint, client) {
const options2 = client.getOptions();
applyAggregateErrorsToEvent(
// This differs from the LinkedErrors integration in core by using a different exceptionFromError function
exceptionFromError,
options2.stackParser,
key,
limit,
event,
hint
);
}
};
});
const linkedErrorsIntegration = defineIntegration(_linkedErrorsIntegration);
const HTML_ELEMENT_CONSTRUCTOR_NAME_REGEX = /^HTML(\w*)Element$/;
function normalizeStringifyValue(value) {
if (typeof window !== "undefined" && value === window) {
return "[Window]";
}
if (typeof document !== "undefined" && value === document) {
return "[Document]";
}
if (isElement$2(value)) {
const objName = getConstructorName(value);
if (HTML_ELEMENT_CONSTRUCTOR_NAME_REGEX.test(objName)) {
return `[HTMLElement: ${htmlTreeAsString(value)}]`;
}
}
return void 0;
}
function getConstructorName(value) {
const prototype = Object.getPrototypeOf(value);
return prototype?.constructor ? prototype.constructor.name : "null prototype";
}
function checkAndWarnIfIsEmbeddedBrowserExtension() {
if (_isEmbeddedBrowserExtension()) {
if (DEBUG_BUILD$1) {
consoleSandbox(() => {
console.error(
"[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/"
);
});
}
return true;
}
return false;
}
function _isEmbeddedBrowserExtension() {
if (typeof WINDOW$2.window === "undefined") {
return false;
}
const _window = WINDOW$2;
if (_window.nw) {
return false;
}
const extensionObject = _window["chrome"] || _window["browser"];
if (!extensionObject?.runtime?.id) {
return false;
}
const href = getLocationHref();
const isDedicatedExtensionPage = WINDOW$2 === WINDOW$2.top && /^(?:chrome-extension|moz-extension|ms-browser-extension|safari-web-extension):\/\//.test(href);
return !isDedicatedExtensionPage;
}
function getDefaultIntegrations(_options) {
return [
// TODO(v11): Replace with `eventFiltersIntegration` once we remove the deprecated `inboundFiltersIntegration`
// eslint-disable-next-line typescript/no-deprecated
inboundFiltersIntegration(),
functionToStringIntegration(),
conversationIdIntegration(),
browserApiErrorsIntegration(),
breadcrumbsIntegration(),
globalHandlersIntegration(),
linkedErrorsIntegration(),
dedupeIntegration(),
httpContextIntegration(),
cultureContextIntegration(),
browserSessionIntegration()
];
}
function init(options = {}) {
const shouldDisableBecauseIsBrowserExtenstion = !options.skipBrowserExtensionCheck && checkAndWarnIfIsEmbeddedBrowserExtension();
let defaultIntegrations = options.defaultIntegrations == null ? getDefaultIntegrations() : options.defaultIntegrations;
const clientOptions = {
...options,
enabled: shouldDisableBecauseIsBrowserExtenstion ? false : options.enabled,
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
integrations: getIntegrationsToSetup({
integrations: options.integrations,
defaultIntegrations
}),
transport: options.transport || makeFetchTransport
};
setNormalizeStringifier(normalizeStringifyValue);
return initAndBind(BrowserClient, clientOptions);
}
const WINDOW = GLOBAL_OBJ;
const REPLAY_SESSION_KEY = "sentryReplaySession";
const REPLAY_EVENT_NAME = "replay_event";
const UNABLE_TO_SEND_REPLAY = "Unable to send Replay";
const SESSION_IDLE_PAUSE_DURATION = 3e5;
const SESSION_IDLE_EXPIRE_DURATION = 9e5;
const DEFAULT_FLUSH_MIN_DELAY = 5e3;
const DEFAULT_FLUSH_MAX_DELAY = 5500;
const BUFFER_CHECKOUT_TIME = 6e4;
const RETRY_BASE_INTERVAL = 5e3;
const RETRY_MAX_COUNT = 3;
const NETWORK_BODY_MAX_SIZE = 15e4;
const CONSOLE_ARG_MAX_SIZE = 5e3;
const SLOW_CLICK_THRESHOLD = 3e3;
const SLOW_CLICK_SCROLL_TIMEOUT = 300;
const REPLAY_MAX_EVENT_BUFFER_SIZE = 2e7;
const MIN_REPLAY_DURATION = 4999;
const MIN_REPLAY_DURATION_LIMIT = 5e4;
const MAX_REPLAY_DURATION = 36e5;
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var NodeType$1 = /* @__PURE__ */ ((NodeType2) => {
NodeType2[NodeType2["Document"] = 0] = "Document";
NodeType2[NodeType2["DocumentType"] = 1] = "DocumentType";
NodeType2[NodeType2["Element"] = 2] = "Element";
NodeType2[NodeType2["Text"] = 3] = "Text";
NodeType2[NodeType2["CDATA"] = 4] = "CDATA";
NodeType2[NodeType2["Comment"] = 5] = "Comment";
return NodeType2;
})(NodeType$1 || {});
function isElement$1(n) {
return n.nodeType === n.ELEMENT_NODE;
}
function isShadowRoot(n) {
const host = n?.host;
return Boolean(host?.shadowRoot === n);
}
function isNativeShadowDom(shadowRoot) {
return Object.prototype.toString.call(shadowRoot) === "[object ShadowRoot]";
}
function fixBrowserCompatibilityIssuesInCSS(cssText) {
if (cssText.includes(" background-clip: text;") && !cssText.includes(" -webkit-background-clip: text;")) {
cssText = cssText.replace(
/\sbackground-clip:\s*text;/g,
" -webkit-background-clip: text; background-clip: text;"
);
}
return cssText;
}
function escapeImportStatement(rule) {
const { cssText } = rule;
if (cssText.split('"').length < 3) return cssText;
const statement = ["@import", `url(${JSON.stringify(rule.href)})`];
if (rule.layerName === "") {
statement.push(`layer`);
} else if (rule.layerName) {
statement.push(`layer(${rule.layerName})`);
}
if (rule.supportsText) {
statement.push(`supports(${rule.supportsText})`);
}
if (rule.media.length) {
statement.push(rule.media.mediaText);
}
return statement.join(" ") + ";";
}
function stringifyStylesheet(s) {
try {
const rules = s.rules || s.cssRules;
return rules ? fixBrowserCompatibilityIssuesInCSS(
Array.from(rules, stringifyRule).join("")
) : null;
} catch (error) {
return null;
}
}
function fixAllCssProperty(rule) {
let styles = "";
for (let i2 = 0; i2 < rule.style.length; i2++) {
const styleDeclaration = rule.style;
const attribute = styleDeclaration[i2];
const isImportant = styleDeclaration.getPropertyPriority(attribute);
styles += `${attribute}:${styleDeclaration.getPropertyValue(attribute)}${isImportant ? ` !important` : ""};`;
}
return `${rule.selectorText} { ${styles} }`;
}
function stringifyRule(rule) {
let importStringified;
if (isCSSImportRule(rule)) {
try {
importStringified = // for same-origin stylesheets,
// we can access the imported stylesheet rules directly
stringifyStylesheet(rule.styleSheet) || // work around browser issues with the raw string `@import url(...)` statement
escapeImportStatement(rule);
} catch (error) {
}
} else if (isCSSStyleRule(rule)) {
let cssText = rule.cssText;
const needsSafariColonFix = rule.selectorText.includes(":");
const needsAllFix = typeof rule.style["all"] === "string" && rule.style["all"];
if (needsAllFix) {
cssText = fixAllCssProperty(rule);
}
if (needsSafariColonFix) {
cssText = fixSafariColons(cssText);
}
if (needsSafariColonFix || needsAllFix) {
return cssText;
}
}
return importStringified || rule.cssText;
}
function fixSafariColons(cssStringified) {
const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
return cssStringified.replace(regex, "$1\\$2");
}
function isCSSImportRule(rule) {
return "styleSheet" in rule;
}
function isCSSStyleRule(rule) {
return "selectorText" in rule;
}
class Mirror {
constructor() {
__publicField(this, "idNodeMap", /* @__PURE__ */ new Map());
__publicField(this, "nodeMetaMap", /* @__PURE__ */ new WeakMap());
}
getId(n) {
if (!n) return -1;
const id = this.getMeta(n)?.id;
return id ?? -1;
}
getNode(id) {
return this.idNodeMap.get(id) || null;
}
getIds() {
return Array.from(this.idNodeMap.keys());
}
getMeta(n) {
return this.nodeMetaMap.get(n) || null;
}
// removes the node from idNodeMap
// doesn't remove the node from nodeMetaMap
removeNodeFromMap(n) {
const id = this.getId(n);
this.idNodeMap.delete(id);
if (n.childNodes) {
n.childNodes.forEach(
(childNode) => this.removeNodeFromMap(childNode)
);
}
}
has(id) {
return this.idNodeMap.has(id);
}
hasNode(node) {
return this.nodeMetaMap.has(node);
}
add(n, meta) {
const id = meta.id;
this.idNodeMap.set(id, n);
this.nodeMetaMap.set(n, meta);
}
replace(id, n) {
const oldNode = this.getNode(id);
if (oldNode) {
const meta = this.nodeMetaMap.get(oldNode);
if (meta) this.nodeMetaMap.set(n, meta);
}
this.idNodeMap.set(id, n);
}
reset() {
this.idNodeMap = /* @__PURE__ */ new Map();
this.nodeMetaMap = /* @__PURE__ */ new WeakMap();
}
}
function createMirror() {
return new Mirror();
}
function shouldMaskInput({
maskInputOptions,
tagName,
type
}) {
if (tagName === "OPTION") {
tagName = "SELECT";
}
return Boolean(
maskInputOptions[tagName.toLowerCase()] || type && maskInputOptions[type] || type === "password" || // Default to "text" option for inputs without a "type" attribute defined
tagName === "INPUT" && !type && maskInputOptions["text"]
);
}
function maskInputValue({
isMasked,
element,
value,
maskInputFn
}) {
let text = value || "";
if (!isMasked) {
return text;
}
if (maskInputFn) {
text = maskInputFn(text, element);
}
return "*".repeat(text.length);
}
function toLowerCase(str) {
return str.toLowerCase();
}
function toUpperCase(str) {
return str.toUpperCase();
}
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(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
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 ? (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
toLowerCase(type)
) : null;
}
function getInputValue(el, tagName, type) {
if (tagName === "INPUT" && (type === "radio" || type === "checkbox")) {
return el.getAttribute("value") || "";
}
return el.value;
}
function extractFileExtension(path, baseURL) {
let url;
try {
url = new URL(path, baseURL ?? window.location.href);
} catch (err) {
return null;
}
const regex = /\.([0-9a-z]+)(?:$)/i;
const match = url.pathname.match(regex);
return match?.[1] ?? null;
}
const cachedImplementations$1 = {};
function getImplementation$1(name) {
const cached = cachedImplementations$1[name];
if (cached) {
return cached;
}
const document2 = window.document;
let impl = window[name];
if (document2 && typeof document2.createElement === "function") {
try {
const sandbox = document2.createElement("iframe");
sandbox.hidden = true;
document2.head.appendChild(sandbox);
const contentWindow = sandbox.contentWindow;
if (contentWindow && contentWindow[name]) {
impl = // eslint-disable-next-line @typescript-eslint/unbound-method
contentWindow[name];
}
document2.head.removeChild(sandbox);
} catch (e) {
}
}
return cachedImplementations$1[name] = impl.bind(
window
);
}
function setTimeout$1(...rest) {
return getImplementation$1("setTimeout")(...rest);
}
function clearTimeout$1(...rest) {
return getImplementation$1("clearTimeout")(...rest);
}
function getIFrameContentDocument$1(iframe) {
try {
return iframe.contentDocument;
} catch {
}
}
function getIFrameContentWindow$1(iframe) {
try {
return iframe.contentWindow;
} catch {
}
}
let _id = 1;
const tagNameRegex = new RegExp("[^a-z0-9-_:]");
const IGNORED_NODE = -2;
function genId() {
return _id++;
}
function getValidTagName(element) {
if (element instanceof HTMLFormElement) {
return "form";
}
const processedTagName = toLowerCase(element.tagName);
if (tagNameRegex.test(processedTagName)) {
return "div";
}
return processedTagName;
}
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 URL_PROTOCOL_MATCH = /^(?:[a-z+]+:)?\/\//i;
const URL_WWW_MATCH = /^www\..*/i;
const DATA_URI = /^(data:)([^,]*),(.*)/i;
function filterCSSPropertiesFromInlineStyle(cssText, ignoredProperties) {
if (!cssText || ignoredProperties.size === 0) {
return cssText;
}
try {
const properties = cssText.split(";");
const filteredProperties = [];
for (let property of properties) {
property = property.trim();
if (!property) continue;
const colonIndex = property.indexOf(":");
if (colonIndex === -1) {
filteredProperties.push(property);
continue;
}
const propertyName = property.slice(0, colonIndex).trim();
if (!ignoredProperties.has(propertyName)) {
filteredProperties.push(property);
}
}
return filteredProperties.join("; ") + (filteredProperties.length > 0 && cssText.endsWith(";") ? ";" : "");
} catch (error) {
console.warn("Error filtering CSS properties:", error);
return cssText;
}
}
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 (URL_PROTOCOL_MATCH.test(filePath) || URL_WWW_MATCH.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 chars2;
const match = regEx.exec(attributeValue.substring(pos));
if (match) {
chars2 = match[0];
pos += chars2.length;
return chars2;
}
return "";
}
const 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) {
const 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(", ");
}
const cachedDocument = /* @__PURE__ */ new WeakMap();
function absoluteToDoc(doc, attributeValue) {
if (!attributeValue || attributeValue.trim() === "") {
return attributeValue;
}
return getHref(doc, attributeValue);
}
function isSVGElement(el) {
return Boolean(el.tagName === "svg" || el.ownerSVGElement);
}
function getHref(doc, customHref) {
let a = cachedDocument.get(doc);
if (!a) {
a = doc.createElement("a");
cachedDocument.set(doc, a);
}
if (!customHref) {
customHref = "";
} else if (customHref.startsWith("blob:") || customHref.startsWith("data:")) {
return customHref;
}
a.setAttribute("href", customHref);
return a.href;
}
function transformAttribute(doc, tagName, name, value, element, maskAttributeFn, ignoreCSSAttributes) {
if (!value) {
return value;
}
if (name === "src" || name === "href" && !(tagName === "use" && value[0] === "#")) {
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") {
let processedStyle = absoluteToStylesheet(value, getHref(doc));
if (ignoreCSSAttributes && ignoreCSSAttributes.size > 0) {
processedStyle = filterCSSPropertiesFromInlineStyle(
processedStyle,
ignoreCSSAttributes
);
}
return processedStyle;
} else if (tagName === "object" && name === "data") {
return absoluteToDoc(doc, value);
}
if (typeof maskAttributeFn === "function") {
return maskAttributeFn(name, value, element);
}
return value;
}
function ignoreAttribute(tagName, name, _value) {
return (tagName === "video" || tagName === "audio") && name === "autoplay";
}
function _isBlockedElement(element, blockClass, blockSelector, unblockSelector) {
try {
if (unblockSelector && element.matches(unblockSelector)) {
return false;
}
if (typeof blockClass === "string") {
if (element.classList.contains(blockClass)) {
return true;
}
} else {
for (let eIndex = element.classList.length; eIndex--; ) {
const className = element.classList[eIndex];
if (blockClass.test(className)) {
return true;
}
}
}
if (blockSelector) {
return element.matches(blockSelector);
}
} catch (e) {
}
return false;
}
function elementClassMatchesRegex(el, regex) {
for (let eIndex = el.classList.length; eIndex--; ) {
const className = el.classList[eIndex];
if (regex.test(className)) {
return true;
}
}
return false;
}
function distanceToMatch(node, matchPredicate, limit = Infinity, distance = 0) {
if (!node) return -1;
if (node.nodeType !== node.ELEMENT_NODE) return -1;
if (distance > limit) return -1;
if (matchPredicate(node)) return distance;
return distanceToMatch(node.parentNode, matchPredicate, limit, distance + 1);
}
function createMatchPredicate(className, selector) {
return (node) => {
const el = node;
if (el === null) return false;
try {
if (className) {
if (typeof className === "string") {
if (el.matches(`.${className}`)) return true;
} else if (elementClassMatchesRegex(el, className)) {
return true;
}
}
if (selector && el.matches(selector)) return true;
return false;
} catch {
return false;
}
};
}
function needMaskingText(node, maskTextClass, maskTextSelector, unmaskTextClass, unmaskTextSelector, maskAllText) {
try {
const el = node.nodeType === node.ELEMENT_NODE ? node : node.parentElement;
if (el === null) return false;
if (el.tagName === "INPUT") {
const autocomplete = el.getAttribute("autocomplete");
const disallowedAutocompleteValues = [
"current-password",
"new-password",
"cc-number",
"cc-exp",
"cc-exp-month",
"cc-exp-year",
"cc-csc"
];
if (disallowedAutocompleteValues.includes(autocomplete)) {
return true;
}
}
let maskDistance = -1;
let unmaskDistance = -1;
if (maskAllText) {
unmaskDistance = distanceToMatch(
el,
createMatchPredicate(unmaskTextClass, unmaskTextSelector)
);
if (unmaskDistance < 0) {
return true;
}
maskDistance = distanceToMatch(
el,
createMatchPredicate(maskTextClass, maskTextSelector),
unmaskDistance >= 0 ? unmaskDistance : Infinity
);
} else {
maskDistance = distanceToMatch(
el,
createMatchPredicate(maskTextClass, maskTextSelector)
);
if (maskDistance < 0) {
return false;
}
unmaskDistance = distanceToMatch(
el,
createMatchPredicate(unmaskTextClass, unmaskTextSelector),
maskDistance >= 0 ? maskDistance : Infinity
);
}
return maskDistance >= 0 ? unmaskDistance >= 0 ? maskDistance <= unmaskDistance : true : unmaskDistance >= 0 ? false : !!maskAllText;
} catch (e) {
}
return !!maskAllText;
}
function onceIframeLoaded(iframeEl, listener, iframeLoadTimeout) {
const win = getIFrameContentWindow$1(iframeEl);
if (!win) {
return;
}
let fired = false;
let readyState;
try {
readyState = win.document.readyState;
} catch (error) {
return;
}
if (readyState !== "complete") {
const timer = setTimeout$1(() => {
if (!fired) {
listener();
fired = true;
}
}, iframeLoadTimeout);
iframeEl.addEventListener("load", () => {
clearTimeout$1(timer);
fired = true;
listener();
});
return;
}
const blankUrl = "about:blank";
if (win.location.href !== blankUrl || iframeEl.src === blankUrl || iframeEl.src === "") {
setTimeout$1(listener, 0);
return iframeEl.addEventListener("load", listener);
}
iframeEl.addEventListener("load", listener);
}
function onceStylesheetLoaded(link, listener, styleSheetLoadTimeout) {
let fired = false;
let styleSheetLoaded;
try {
styleSheetLoaded = link.sheet;
} catch (error) {
styleSheetLoaded = null;
}
if (styleSheetLoaded) return;
const timer = setTimeout$1(() => {
if (!fired) {
listener();
fired = true;
}
}, styleSheetLoadTimeout);
link.addEventListener("load", () => {
clearTimeout$1(timer);
fired = true;
listener();
});
}
function serializeNode(n, options) {
const {
doc,
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskAttributeFn,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions = {},
maskTextFn,
maskInputFn,
dataURLOptions = {},
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
ignoreCSSAttributes
} = options;
const rootId = getRootId(doc, mirror);
switch (n.nodeType) {
case n.DOCUMENT_NODE:
if (n.compatMode !== "CSS1Compat") {
return {
type: NodeType$1.Document,
childNodes: [],
compatMode: n.compatMode
// probably "BackCompat"
};
} else {
return {
type: NodeType$1.Document,
childNodes: []
};
}
case n.DOCUMENT_TYPE_NODE:
return {
type: NodeType$1.DocumentType,
name: n.name,
publicId: n.publicId,
systemId: n.systemId,
rootId
};
case n.ELEMENT_NODE:
return serializeElementNode(n, {
doc,
blockClass,
blockSelector,
unblockSelector,
inlineStylesheet,
maskAttributeFn,
maskInputOptions,
maskInputFn,
dataURLOptions,
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
rootId,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
ignoreCSSAttributes
});
case n.TEXT_NODE:
return serializeTextNode(n, {
doc,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskTextFn,
maskInputOptions,
maskInputFn,
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 getRootId(doc, mirror) {
if (!mirror.hasNode(doc)) return void 0;
const docId = mirror.getId(doc);
return docId === 1 ? void 0 : docId;
}
function serializeTextNode(n, options) {
const {
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskTextFn,
maskInputOptions,
maskInputFn,
rootId
} = options;
const parentTagName = n.parentNode && n.parentNode.tagName;
let textContent = n.textContent;
const isStyle = parentTagName === "STYLE" ? true : void 0;
const isScript = parentTagName === "SCRIPT" ? true : void 0;
const isTextarea = parentTagName === "TEXTAREA" ? true : void 0;
if (isStyle && textContent) {
try {
if (n.nextSibling || n.previousSibling) {
} else if (n.parentNode.sheet?.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(options.doc));
}
if (isScript) {
textContent = "SCRIPT_PLACEHOLDER";
}
const forceMask = needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText
);
if (!isStyle && !isScript && !isTextarea && textContent && forceMask) {
textContent = maskTextFn ? maskTextFn(textContent, n.parentElement) : textContent.replace(/[\S]/g, "*");
}
if (isTextarea && textContent && (maskInputOptions.textarea || forceMask)) {
textContent = maskInputFn ? maskInputFn(textContent, n.parentNode) : textContent.replace(/[\S]/g, "*");
}
if (parentTagName === "OPTION" && textContent) {
const isInputMasked = shouldMaskInput({
type: null,
tagName: parentTagName,
maskInputOptions
});
textContent = maskInputValue({
isMasked: needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
isInputMasked
),
element: n,
value: textContent,
maskInputFn
});
}
return {
type: NodeType$1.Text,
textContent: textContent || "",
isStyle,
rootId
};
}
function serializeElementNode(n, options) {
const {
doc,
blockClass,
blockSelector,
unblockSelector,
inlineStylesheet,
maskInputOptions = {},
maskAttributeFn,
maskInputFn,
dataURLOptions = {},
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement = false,
rootId,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
ignoreCSSAttributes
} = options;
const needBlock = _isBlockedElement(
n,
blockClass,
blockSelector,
unblockSelector
);
const tagName = getValidTagName(n);
let attributes2 = {};
const len = n.attributes.length;
for (let i2 = 0; i2 < len; i2++) {
const attr = n.attributes[i2];
if (attr.name && !ignoreAttribute(tagName, attr.name, attr.value)) {
attributes2[attr.name] = transformAttribute(
doc,
tagName,
toLowerCase(attr.name),
attr.value,
n,
maskAttributeFn,
ignoreCSSAttributes
);
}
}
if (tagName === "link" && inlineStylesheet) {
const stylesheet = Array.from(doc.styleSheets).find((s) => {
return s.href === n.href;
});
let cssText = null;
if (stylesheet) {
cssText = stringifyStylesheet(stylesheet);
}
if (cssText) {
attributes2.rel = null;
attributes2.href = null;
attributes2.crossorigin = null;
attributes2._cssText = absoluteToStylesheet(cssText, stylesheet.href);
}
}
if (tagName === "style" && n.sheet && // TODO: Currently we only try to get dynamic stylesheet when it is an empty style element
!(n.innerText || n.textContent || "").trim().length) {
const cssText = stringifyStylesheet(
n.sheet
);
if (cssText) {
attributes2._cssText = absoluteToStylesheet(cssText, getHref(doc));
}
}
if (tagName === "input" || tagName === "textarea" || tagName === "select" || tagName === "option") {
const el = n;
const type = getInputType(el);
const value = getInputValue(el, toUpperCase(tagName), type);
const checked = el.checked;
if (type !== "submit" && type !== "button" && value) {
const forceMask = needMaskingText(
el,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
shouldMaskInput({
type,
tagName: toUpperCase(tagName),
maskInputOptions
})
);
attributes2.value = maskInputValue({
isMasked: forceMask,
element: el,
value,
maskInputFn
});
}
if (checked) {
attributes2.checked = checked;
}
}
if (tagName === "option") {
if (n.selected && !maskInputOptions["select"]) {
attributes2.selected = true;
} else {
delete attributes2.selected;
}
}
if (tagName === "canvas" && recordCanvas) {
if (n.__context === "2d") {
if (!is2DCanvasBlank(n)) {
attributes2.rr_dataURL = n.toDataURL(
dataURLOptions.type,
dataURLOptions.quality
);
}
} else if (!("__context" in n)) {
const canvasDataURL = n.toDataURL(
dataURLOptions.type,
dataURLOptions.quality
);
const blankCanvas = doc.createElement("canvas");
blankCanvas.width = n.width;
blankCanvas.height = n.height;
const blankCanvasDataURL = blankCanvas.toDataURL(
dataURLOptions.type,
dataURLOptions.quality
);
if (canvasDataURL !== blankCanvasDataURL) {
attributes2.rr_dataURL = canvasDataURL;
}
}
}
if (tagName === "img" && inlineImages) {
if (!canvasService) {
canvasService = doc.createElement("canvas");
canvasCtx = canvasService.getContext("2d");
}
const image = n;
const imageSrc = image.currentSrc || image.getAttribute("src") || "<unknown-src>";
const priorCrossOrigin = image.crossOrigin;
const recordInlineImage = () => {
image.removeEventListener("load", recordInlineImage);
try {
canvasService.width = image.naturalWidth;
canvasService.height = image.naturalHeight;
canvasCtx.drawImage(image, 0, 0);
attributes2.rr_dataURL = canvasService.toDataURL(
dataURLOptions.type,
dataURLOptions.quality
);
} catch (err) {
if (image.crossOrigin !== "anonymous") {
image.crossOrigin = "anonymous";
if (image.complete && image.naturalWidth !== 0)
recordInlineImage();
else image.addEventListener("load", recordInlineImage);
return;
} else {
console.warn(
`Cannot inline img src=${imageSrc}! Error: ${err}`
);
}
}
if (image.crossOrigin === "anonymous") {
priorCrossOrigin ? attributes2.crossOrigin = priorCrossOrigin : image.removeAttribute("crossorigin");
}
};
if (image.complete && image.naturalWidth !== 0) recordInlineImage();
else image.addEventListener("load", recordInlineImage);
}
if (tagName === "audio" || tagName === "video") {
attributes2.rr_mediaState = n.paused ? "paused" : "played";
attributes2.rr_mediaCurrentTime = n.currentTime;
}
if (!newlyAddedElement) {
if (n.scrollLeft) {
attributes2.rr_scrollLeft = n.scrollLeft;
}
if (n.scrollTop) {
attributes2.rr_scrollTop = n.scrollTop;
}
}
if (needBlock) {
const { width, height } = n.getBoundingClientRect();
attributes2 = {
class: attributes2.class,
rr_width: `${width}px`,
rr_height: `${height}px`
};
}
if (tagName === "iframe" && !keepIframeSrcFn(attributes2.src)) {
if (!needBlock && !getIFrameContentDocument$1(n)) {
attributes2.rr_src = attributes2.src;
}
delete attributes2.src;
}
let isCustomElement;
try {
if (customElements.get(tagName)) isCustomElement = true;
} catch (e) {
}
return {
type: NodeType$1.Element,
tagName,
attributes: attributes2,
childNodes: [],
isSVG: isSVGElement(n) || void 0,
needBlock,
rootId,
isCustom: isCustomElement
};
}
function lowerIfExists(maybeAttr) {
if (maybeAttr === void 0 || 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 && // script tag
(sn.tagName === "script" || // (module)preload link
sn.tagName === "link" && (sn.attributes.rel === "preload" || sn.attributes.rel === "modulepreload") || // prefetch link
sn.tagName === "link" && sn.attributes.rel === "prefetch" && typeof sn.attributes.href === "string" && extractFileExtension(sn.attributes.href) === "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):/) || // og = opengraph (facebook)
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"] !== void 0) {
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,
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild = false,
inlineStylesheet = true,
maskInputOptions = {},
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
dataURLOptions = {},
inlineImages = false,
recordCanvas = false,
onSerialize,
onIframeLoad,
iframeLoadTimeout = 5e3,
onBlockedImageLoad,
onStylesheetLoad,
stylesheetLoadTimeout = 5e3,
keepIframeSrcFn = () => false,
newlyAddedElement = false,
ignoreCSSAttributes
} = options;
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
doc,
mirror,
blockClass,
blockSelector,
maskAllText,
unblockSelector,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
dataURLOptions,
inlineImages,
recordCanvas,
keepIframeSrcFn,
newlyAddedElement,
ignoreCSSAttributes
});
if (!_serializedNode) {
console.warn(n, "not serialized");
return null;
}
let id;
if (mirror.hasNode(n)) {
id = mirror.getId(n);
} else if (slimDOMExcluded(_serializedNode, slimDOMOptions) || !preserveWhiteSpace && _serializedNode.type === NodeType$1.Text && !_serializedNode.isStyle && !_serializedNode.textContent.trim().length) {
id = IGNORED_NODE;
} else {
id = genId();
}
const serializedNode2 = Object.assign(_serializedNode, { id });
mirror.add(n, serializedNode2);
if (id === IGNORED_NODE) {
return null;
}
if (onSerialize) {
onSerialize(n);
}
let recordChild = !skipChild;
if (serializedNode2.type === NodeType$1.Element) {
recordChild = recordChild && !serializedNode2.needBlock;
const shadowRoot = n.shadowRoot;
if (shadowRoot && isNativeShadowDom(shadowRoot))
serializedNode2.isShadowHost = true;
}
if ((serializedNode2.type === NodeType$1.Document || serializedNode2.type === NodeType$1.Element) && recordChild) {
if (slimDOMOptions.headWhitespace && serializedNode2.type === NodeType$1.Element && serializedNode2.tagName === "head") {
preserveWhiteSpace = false;
}
const bypassOptions = {
doc,
mirror,
blockClass,
blockSelector,
maskAllText,
unblockSelector,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
dataURLOptions,
inlineImages,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
onBlockedImageLoad,
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes
};
const childNodes = n.childNodes ? Array.from(n.childNodes) : [];
for (const childN of childNodes) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
serializedNode2.childNodes.push(serializedChildNode);
}
}
if (isElement$1(n) && n.shadowRoot) {
for (const childN of Array.from(n.shadowRoot.childNodes)) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
isNativeShadowDom(n.shadowRoot) && (serializedChildNode.isShadow = true);
serializedNode2.childNodes.push(serializedChildNode);
}
}
}
}
if (n.parentNode && isShadowRoot(n.parentNode) && isNativeShadowDom(n.parentNode)) {
serializedNode2.isShadow = true;
}
if (serializedNode2.type === NodeType$1.Element && serializedNode2.tagName === "iframe" && !serializedNode2.needBlock) {
onceIframeLoaded(
n,
() => {
const iframeDoc = getIFrameContentDocument$1(n);
if (iframeDoc && onIframeLoad) {
const serializedIframeNode = serializeNodeWithId(iframeDoc, {
doc: iframeDoc,
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
dataURLOptions,
inlineImages,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes
});
if (serializedIframeNode) {
onIframeLoad(
n,
serializedIframeNode
);
}
}
},
iframeLoadTimeout
);
}
if (serializedNode2.type === NodeType$1.Element && serializedNode2.tagName === "img" && !n.complete && serializedNode2.needBlock) {
const image = n;
const updateImageDimensions = () => {
if (image.isConnected && !image.complete && onBlockedImageLoad) {
try {
const rect = image.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
onBlockedImageLoad(image, serializedNode2, rect);
}
} catch (error) {
}
}
image.removeEventListener("load", updateImageDimensions);
};
if (image.isConnected) {
image.addEventListener("load", updateImageDimensions);
}
}
if (serializedNode2.type === NodeType$1.Element && serializedNode2.tagName === "link" && typeof serializedNode2.attributes.rel === "string" && (serializedNode2.attributes.rel === "stylesheet" || serializedNode2.attributes.rel === "preload" && typeof serializedNode2.attributes.href === "string" && extractFileExtension(serializedNode2.attributes.href) === "css")) {
onceStylesheetLoaded(
n,
() => {
if (onStylesheetLoad) {
const serializedLinkNode = serializeNodeWithId(n, {
doc,
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
dataURLOptions,
inlineImages,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
ignoreCSSAttributes
});
if (serializedLinkNode) {
onStylesheetLoad(
n,
serializedLinkNode
);
}
}
},
stylesheetLoadTimeout
);
}
if (serializedNode2.type === NodeType$1.Element) {
delete serializedNode2.needBlock;
}
return serializedNode2;
}
function snapshot(n, options) {
const {
mirror = new Mirror(),
blockClass = "rr-block",
blockSelector = null,
unblockSelector = null,
maskAllText = false,
maskTextClass = "rr-mask",
unmaskTextClass = null,
maskTextSelector = null,
unmaskTextSelector = null,
inlineStylesheet = true,
inlineImages = false,
recordCanvas = false,
maskAllInputs = false,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOM = false,
dataURLOptions,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
onBlockedImageLoad,
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn = () => false,
ignoreCSSAttributes = /* @__PURE__ */ new Set([])
} = options || {};
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" ? (
// if true: set of sensible options that should not throw away any information
{
script: true,
comment: true,
headFavicon: true,
headWhitespace: true,
headMetaDescKeywords: slimDOM === "all",
// destructive
headMetaSocial: true,
headMetaRobots: true,
headMetaHttpEquiv: true,
headMetaAuthorship: true,
headMetaVerification: true
}
) : slimDOM === false ? {} : slimDOM;
return serializeNodeWithId(n, {
doc: n,
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
skipChild: false,
inlineStylesheet,
maskInputOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
slimDOMOptions,
dataURLOptions,
inlineImages,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
onBlockedImageLoad,
onStylesheetLoad,
stylesheetLoadTimeout,
keepIframeSrcFn,
newlyAddedElement: false,
ignoreCSSAttributes
});
}
function on(type, fn, target = document) {
const options = { capture: true, passive: true };
target.addEventListener(type, fn, options);
return () => target.removeEventListener(type, fn, options);
}
const DEPARTED_MIRROR_ACCESS_WARNING = "Please stop import mirror directly. Instead of that,\r\nnow you can use replayer.getMirror() to access the mirror instance of a replayer,\r\nor 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(...args) {
const now = Date.now();
if (!previous && options.leading === false) {
previous = now;
}
const remaining = wait - (now - previous);
const context = this;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout$2(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout$2(() => {
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$2(() => {
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 {
return () => {
};
}
}
let nowTimestamp = Date.now;
if (!/* @__PURE__ */ /[1-9][0-9]{12}/.test(Date.now().toString())) {
nowTimestamp = () => (/* @__PURE__ */ new Date()).getTime();
}
function getWindowScroll(win) {
const doc = win.document;
return {
left: doc.scrollingElement ? doc.scrollingElement.scrollLeft : win.pageXOffset !== void 0 ? win.pageXOffset : doc?.documentElement.scrollLeft || doc?.body?.parentElement?.scrollLeft || doc?.body?.scrollLeft || 0,
top: doc.scrollingElement ? doc.scrollingElement.scrollTop : win.pageYOffset !== void 0 ? win.pageYOffset : doc?.documentElement.scrollTop || doc?.body?.parentElement?.scrollTop || doc?.body?.scrollTop || 0
};
}
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 closestElementOfNode$1(node) {
if (!node) {
return null;
}
try {
const el = node.nodeType === node.ELEMENT_NODE ? node : node.parentElement;
return el;
} catch (error) {
return null;
}
}
function isBlocked(node, blockClass, blockSelector, unblockSelector, checkAncestors) {
if (!node) {
return false;
}
const el = closestElementOfNode$1(node);
if (!el) {
return false;
}
const blockedPredicate = createMatchPredicate(blockClass, blockSelector);
if (!checkAncestors) {
const isUnblocked = unblockSelector && el.matches(unblockSelector);
return blockedPredicate(el) && !isUnblocked;
}
const blockDistance = distanceToMatch(el, blockedPredicate);
let unblockDistance = -1;
if (blockDistance < 0) {
return false;
}
if (unblockSelector) {
unblockDistance = distanceToMatch(
el,
createMatchPredicate(null, unblockSelector)
);
}
if (blockDistance > -1 && unblockDistance < 0) {
return true;
}
return blockDistance < unblockDistance;
}
function isSerialized(n, mirror) {
return mirror.getId(n) !== -1;
}
function isIgnored(n, mirror) {
return mirror.getId(n) === IGNORED_NODE;
}
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 legacy_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 = (...args) => {
let node = args[0];
if (!(0 in args)) {
throw new TypeError("1 argument is required");
}
do {
if (this === node) {
return true;
}
} while (node = node && node.parentNode);
return false;
};
}
}
function isSerializedIframe(n, mirror) {
return Boolean(n.nodeName === "IFRAME" && mirror.getMeta(n));
}
function isSerializedStylesheet(n, mirror) {
return Boolean(
n.nodeName === "LINK" && n.nodeType === n.ELEMENT_NODE && n.getAttribute && n.getAttribute("rel") === "stylesheet" && mirror.getMeta(n)
);
}
function hasShadowRoot(n) {
return Boolean(n?.shadowRoot);
}
class StyleSheetMirror {
constructor() {
this.id = 1;
this.styleIDMap = /* @__PURE__ */ new WeakMap();
this.idStyleMap = /* @__PURE__ */ new Map();
}
getId(stylesheet) {
return this.styleIDMap.get(stylesheet) ?? -1;
}
has(stylesheet) {
return this.styleIDMap.has(stylesheet);
}
/**
* @returns If the stylesheet is in the mirror, returns the id of the stylesheet. If not, return the new assigned id.
*/
add(stylesheet, id) {
if (this.has(stylesheet)) return this.getId(stylesheet);
let newId;
if (id === void 0) {
newId = this.id++;
} else newId = id;
this.styleIDMap.set(stylesheet, newId);
this.idStyleMap.set(newId, stylesheet);
return newId;
}
getStyle(id) {
return this.idStyleMap.get(id) || null;
}
reset() {
this.styleIDMap = /* @__PURE__ */ new WeakMap();
this.idStyleMap = /* @__PURE__ */ new Map();
this.id = 1;
}
generateId() {
return this.id++;
}
}
function getShadowHost(n) {
let shadowHost = null;
if (n.getRootNode?.()?.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.getRootNode().host)
shadowHost = n.getRootNode().host;
return shadowHost;
}
function getRootShadowHost(n) {
let rootShadowHost = n;
let shadowHost;
while (shadowHost = getShadowHost(rootShadowHost))
rootShadowHost = shadowHost;
return rootShadowHost;
}
function shadowHostInDom(n) {
const doc = n.ownerDocument;
if (!doc) return false;
const shadowHost = getRootShadowHost(n);
return doc.contains(shadowHost);
}
function inDom(n) {
const doc = n.ownerDocument;
if (!doc) return false;
return doc.contains(n) || shadowHostInDom(n);
}
const cachedImplementations = {};
function getImplementation(name) {
const cached = cachedImplementations[name];
if (cached) {
return cached;
}
const document2 = window.document;
let impl = window[name];
if (document2 && typeof document2.createElement === "function") {
try {
const sandbox = document2.createElement("iframe");
sandbox.hidden = true;
document2.head.appendChild(sandbox);
const contentWindow = sandbox.contentWindow;
if (contentWindow && contentWindow[name]) {
impl = // eslint-disable-next-line @typescript-eslint/unbound-method
contentWindow[name];
}
document2.head.removeChild(sandbox);
} catch (e) {
}
}
return cachedImplementations[name] = impl.bind(
window
);
}
function onRequestAnimationFrame(...rest) {
return getImplementation("requestAnimationFrame")(...rest);
}
function setTimeout$2(...rest) {
return getImplementation("setTimeout")(...rest);
}
function clearTimeout$2(...rest) {
return getImplementation("clearTimeout")(...rest);
}
var EventType = /* @__PURE__ */ ((EventType2) => {
EventType2[EventType2["DomContentLoaded"] = 0] = "DomContentLoaded";
EventType2[EventType2["Load"] = 1] = "Load";
EventType2[EventType2["FullSnapshot"] = 2] = "FullSnapshot";
EventType2[EventType2["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
EventType2[EventType2["Meta"] = 4] = "Meta";
EventType2[EventType2["Custom"] = 5] = "Custom";
EventType2[EventType2["Plugin"] = 6] = "Plugin";
return EventType2;
})(EventType || {});
var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
IncrementalSource2[IncrementalSource2["Mutation"] = 0] = "Mutation";
IncrementalSource2[IncrementalSource2["MouseMove"] = 1] = "MouseMove";
IncrementalSource2[IncrementalSource2["MouseInteraction"] = 2] = "MouseInteraction";
IncrementalSource2[IncrementalSource2["Scroll"] = 3] = "Scroll";
IncrementalSource2[IncrementalSource2["ViewportResize"] = 4] = "ViewportResize";
IncrementalSource2[IncrementalSource2["Input"] = 5] = "Input";
IncrementalSource2[IncrementalSource2["TouchMove"] = 6] = "TouchMove";
IncrementalSource2[IncrementalSource2["MediaInteraction"] = 7] = "MediaInteraction";
IncrementalSource2[IncrementalSource2["StyleSheetRule"] = 8] = "StyleSheetRule";
IncrementalSource2[IncrementalSource2["CanvasMutation"] = 9] = "CanvasMutation";
IncrementalSource2[IncrementalSource2["Font"] = 10] = "Font";
IncrementalSource2[IncrementalSource2["Log"] = 11] = "Log";
IncrementalSource2[IncrementalSource2["Drag"] = 12] = "Drag";
IncrementalSource2[IncrementalSource2["StyleDeclaration"] = 13] = "StyleDeclaration";
IncrementalSource2[IncrementalSource2["Selection"] = 14] = "Selection";
IncrementalSource2[IncrementalSource2["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
IncrementalSource2[IncrementalSource2["CustomElement"] = 16] = "CustomElement";
return IncrementalSource2;
})(IncrementalSource || {});
var MouseInteractions = /* @__PURE__ */ ((MouseInteractions2) => {
MouseInteractions2[MouseInteractions2["MouseUp"] = 0] = "MouseUp";
MouseInteractions2[MouseInteractions2["MouseDown"] = 1] = "MouseDown";
MouseInteractions2[MouseInteractions2["Click"] = 2] = "Click";
MouseInteractions2[MouseInteractions2["ContextMenu"] = 3] = "ContextMenu";
MouseInteractions2[MouseInteractions2["DblClick"] = 4] = "DblClick";
MouseInteractions2[MouseInteractions2["Focus"] = 5] = "Focus";
MouseInteractions2[MouseInteractions2["Blur"] = 6] = "Blur";
MouseInteractions2[MouseInteractions2["TouchStart"] = 7] = "TouchStart";
MouseInteractions2[MouseInteractions2["TouchMove_Departed"] = 8] = "TouchMove_Departed";
MouseInteractions2[MouseInteractions2["TouchEnd"] = 9] = "TouchEnd";
MouseInteractions2[MouseInteractions2["TouchCancel"] = 10] = "TouchCancel";
return MouseInteractions2;
})(MouseInteractions || {});
var PointerTypes = /* @__PURE__ */ ((PointerTypes2) => {
PointerTypes2[PointerTypes2["Mouse"] = 0] = "Mouse";
PointerTypes2[PointerTypes2["Pen"] = 1] = "Pen";
PointerTypes2[PointerTypes2["Touch"] = 2] = "Touch";
return PointerTypes2;
})(PointerTypes || {});
var MediaInteractions = /* @__PURE__ */ ((MediaInteractions2) => {
MediaInteractions2[MediaInteractions2["Play"] = 0] = "Play";
MediaInteractions2[MediaInteractions2["Pause"] = 1] = "Pause";
MediaInteractions2[MediaInteractions2["Seeked"] = 2] = "Seeked";
MediaInteractions2[MediaInteractions2["VolumeChange"] = 3] = "VolumeChange";
MediaInteractions2[MediaInteractions2["RateChange"] = 4] = "RateChange";
return MediaInteractions2;
})(MediaInteractions || {});
let errorHandler;
function registerErrorHandler(handler) {
errorHandler = handler;
}
function unregisterErrorHandler() {
errorHandler = void 0;
}
const callbackWrapper = (cb) => {
if (!errorHandler) {
return cb;
}
const rrwebWrapped = ((...rest) => {
try {
return cb(...rest);
} catch (error) {
if (errorHandler && errorHandler(error) === true) {
return () => {
};
}
throw error;
}
});
return rrwebWrapped;
};
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var lookup = typeof Uint8Array === "undefined" ? [] : new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
class CanvasManagerNoop {
reset() {
}
freeze() {
}
unfreeze() {
}
lock() {
}
unlock() {
}
snapshot() {
}
addWindow() {
}
addShadowRoot() {
}
resetShadowRoots() {
}
}
function getIFrameContentDocument(iframe) {
try {
return iframe.contentDocument;
} catch (e2) {
}
}
function getIFrameContentWindow(iframe) {
try {
return iframe.contentWindow;
} catch (e2) {
}
}
class StyleDeclarationParser {
constructor(doc) {
this.doc = doc;
this.unattachedDoc = null;
}
parse(styleText) {
return this.parseWithConstructableStylesheet(styleText) || this.parseWithDetachedElement(styleText);
}
parseWithConstructableStylesheet(styleText) {
if (typeof CSSStyleSheet === "undefined" || typeof CSSStyleSheet.prototype.replaceSync !== "function") {
return null;
}
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(`x { ${styleText} }`);
const rule = sheet.cssRules[0];
if (!rule || rule.type !== CSSRule.STYLE_RULE) {
return null;
}
return rule.style;
} catch {
return null;
}
}
parseWithDetachedElement(styleText) {
const old = this.getUnattachedDoc().createElement("span");
old.setAttribute("style", styleText);
return old.style;
}
getUnattachedDoc() {
if (!this.unattachedDoc) {
try {
this.unattachedDoc = document.implementation.createHTMLDocument();
} catch {
this.unattachedDoc = this.doc;
}
}
return this.unattachedDoc;
}
}
function isNodeInLinkedList(n2) {
return "__ln" in n2;
}
class DoubleLinkedList {
constructor() {
this.length = 0;
this.head = null;
this.tail = 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?.next || null;
}
return current;
}
addNode(n2) {
const node = {
value: n2,
previous: null,
next: null
};
n2.__ln = node;
if (n2.previousSibling && isNodeInLinkedList(n2.previousSibling)) {
const current = n2.previousSibling.__ln.next;
node.next = current;
node.previous = n2.previousSibling.__ln;
n2.previousSibling.__ln.next = node;
if (current) {
current.previous = node;
}
} else if (n2.nextSibling && isNodeInLinkedList(n2.nextSibling) && n2.nextSibling.__ln.previous) {
const current = n2.nextSibling.__ln.previous;
node.previous = current;
node.next = n2.nextSibling.__ln;
n2.nextSibling.__ln.previous = node;
if (current) {
current.next = node;
}
} else {
if (this.head) {
this.head.previous = node;
}
node.next = this.head;
this.head = node;
}
if (node.next === null) {
this.tail = node;
}
this.length++;
}
removeNode(n2) {
const current = n2.__ln;
if (!this.head) {
return;
}
if (!current.previous) {
this.head = current.next;
if (this.head) {
this.head.previous = null;
} else {
this.tail = null;
}
} else {
current.previous.next = current.next;
if (current.next) {
current.next.previous = current.previous;
} else {
this.tail = current.previous;
}
}
if (n2.__ln) {
delete n2.__ln;
}
this.length--;
}
}
const moveKey = (id, parentId) => `${id}@${parentId}`;
class MutationBuffer {
constructor() {
this.frozen = false;
this.locked = false;
this.texts = [];
this.attributes = [];
this.attributeMap = /* @__PURE__ */ new WeakMap();
this.removes = [];
this.mapRemoves = [];
this.movedMap = {};
this.addedSet = /* @__PURE__ */ new Set();
this.movedSet = /* @__PURE__ */ new Set();
this.droppedSet = /* @__PURE__ */ new Set();
this.processMutations = (mutations) => {
mutations.forEach(this.processMutation);
this.emit();
};
this.emit = () => {
if (this.frozen || this.locked) {
return;
}
const adds = [];
const addedIds = /* @__PURE__ */ new Set();
const addList = new DoubleLinkedList();
const getNextId = (n2) => {
let ns = n2;
let nextId = IGNORED_NODE;
while (nextId === IGNORED_NODE) {
ns = ns && ns.nextSibling;
nextId = ns && this.mirror.getId(ns);
}
return nextId;
};
const pushAdd = (n2) => {
if (!n2.parentNode || !inDom(n2)) {
return;
}
const parentId = isShadowRoot(n2.parentNode) ? this.mirror.getId(getShadowHost(n2)) : this.mirror.getId(n2.parentNode);
const nextId = getNextId(n2);
if (parentId === -1 || nextId === -1) {
return addList.addNode(n2);
}
const sn = serializeNodeWithId(n2, {
doc: this.doc,
mirror: this.mirror,
blockClass: this.blockClass,
blockSelector: this.blockSelector,
maskAllText: this.maskAllText,
unblockSelector: this.unblockSelector,
maskTextClass: this.maskTextClass,
unmaskTextClass: this.unmaskTextClass,
maskTextSelector: this.maskTextSelector,
unmaskTextSelector: this.unmaskTextSelector,
skipChild: true,
newlyAddedElement: true,
inlineStylesheet: this.inlineStylesheet,
maskInputOptions: this.maskInputOptions,
maskAttributeFn: this.maskAttributeFn,
maskTextFn: this.maskTextFn,
maskInputFn: this.maskInputFn,
slimDOMOptions: this.slimDOMOptions,
dataURLOptions: this.dataURLOptions,
recordCanvas: this.recordCanvas,
inlineImages: this.inlineImages,
onSerialize: (currentN) => {
if (isSerializedIframe(currentN, this.mirror) && !isBlocked(
currentN,
this.blockClass,
this.blockSelector,
this.unblockSelector,
false
)) {
this.iframeManager.addIframe(currentN);
}
if (isSerializedStylesheet(currentN, this.mirror)) {
this.stylesheetManager.trackLinkElement(
currentN
);
}
if (hasShadowRoot(n2)) {
this.shadowDomManager.addShadowRoot(n2.shadowRoot, this.doc);
}
},
onIframeLoad: (iframe, childSn) => {
if (isBlocked(
iframe,
this.blockClass,
this.blockSelector,
this.unblockSelector,
false
)) {
return;
}
this.iframeManager.attachIframe(iframe, childSn);
const contentWindow = getIFrameContentWindow(iframe);
if (contentWindow) {
this.canvasManager.addWindow(contentWindow);
}
this.shadowDomManager.observeAttachShadow(iframe);
},
onStylesheetLoad: (link, childSn) => {
this.stylesheetManager.attachLinkElement(link, childSn);
},
onBlockedImageLoad: (_imageEl, serializedNode, { width, height }) => {
this.mutationCb({
adds: [],
removes: [],
texts: [],
attributes: [
{
id: serializedNode.id,
attributes: {
style: {
width: `${width}px`,
height: `${height}px`
}
}
}
]
});
},
ignoreCSSAttributes: this.ignoreCSSAttributes
});
if (sn) {
adds.push({
parentId,
nextId,
node: sn
});
addedIds.add(sn.id);
}
};
while (this.mapRemoves.length) {
this.mirror.removeNodeFromMap(this.mapRemoves.shift());
}
for (const n2 of this.movedSet) {
if (isParentRemoved(this.removes, n2, this.mirror) && !this.movedSet.has(n2.parentNode)) {
continue;
}
pushAdd(n2);
}
for (const n2 of this.addedSet) {
if (!isAncestorInSet(this.droppedSet, n2) && !isParentRemoved(this.removes, n2, this.mirror)) {
pushAdd(n2);
} else if (isAncestorInSet(this.movedSet, n2)) {
pushAdd(n2);
} else {
this.droppedSet.add(n2);
}
}
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) {
let tailNode = addList.tail;
while (tailNode) {
const _node = tailNode;
tailNode = tailNode.previous;
if (_node) {
const parentId = this.mirror.getId(_node.value.parentNode);
const nextId = getNextId(_node.value);
if (nextId === -1) continue;
else if (parentId !== -1) {
node = _node;
break;
} else {
const unhandledNode = _node.value;
if (unhandledNode.parentNode && unhandledNode.parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
const shadowHost = unhandledNode.parentNode.host;
const parentId2 = this.mirror.getId(shadowHost);
if (parentId2 !== -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) => !addedIds.has(text.id)).filter((text) => this.mirror.has(text.id)),
attributes: this.attributes.map((attribute) => {
const { attributes } = attribute;
if (typeof attributes.style === "string") {
const diffAsStr = JSON.stringify(attribute.styleDiff);
const unchangedAsStr = JSON.stringify(attribute._unchangedStyles);
if (diffAsStr.length < attributes.style.length) {
if ((diffAsStr + unchangedAsStr).split("var(").length === attributes.style.split("var(").length) {
attributes.style = attribute.styleDiff;
}
}
}
return {
id: this.mirror.getId(attribute.node),
attributes
};
}).filter((attribute) => !addedIds.has(attribute.id)).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.attributeMap = /* @__PURE__ */ new WeakMap();
this.removes = [];
this.addedSet = /* @__PURE__ */ new Set();
this.movedSet = /* @__PURE__ */ new Set();
this.droppedSet = /* @__PURE__ */ new Set();
this.movedMap = {};
this.mutationCb(payload);
};
this.processMutation = (m) => {
if (isIgnored(m.target, this.mirror)) {
return;
}
switch (m.type) {
case "characterData": {
const value = m.target.textContent;
if (!isBlocked(
m.target,
this.blockClass,
this.blockSelector,
this.unblockSelector,
false
) && value !== m.oldValue) {
this.texts.push({
value: needMaskingText(
m.target,
this.maskTextClass,
this.maskTextSelector,
this.unmaskTextClass,
this.unmaskTextSelector,
this.maskAllText
) && value ? this.maskTextFn ? this.maskTextFn(value, closestElementOfNode$1(m.target)) : value.replace(/[\S]/g, "*") : value,
node: m.target
});
}
break;
}
case "attributes": {
const target = m.target;
let attributeName = m.attributeName;
let value = m.target.getAttribute(attributeName);
if (attributeName === "value") {
const type = getInputType(target);
const tagName = target.tagName;
value = getInputValue(target, tagName, type);
const isInputMasked = shouldMaskInput({
maskInputOptions: this.maskInputOptions,
tagName,
type
});
const forceMask = needMaskingText(
m.target,
this.maskTextClass,
this.maskTextSelector,
this.unmaskTextClass,
this.unmaskTextSelector,
isInputMasked
);
value = maskInputValue({
isMasked: forceMask,
element: target,
value,
maskInputFn: this.maskInputFn
});
}
if (isBlocked(
m.target,
this.blockClass,
this.blockSelector,
this.unblockSelector,
false
) || value === m.oldValue) {
return;
}
let item = this.attributeMap.get(m.target);
if (target.tagName === "IFRAME" && attributeName === "src" && !this.keepIframeSrcFn(value)) {
const iframeDoc = getIFrameContentDocument(
target
);
if (!iframeDoc) {
attributeName = "rr_src";
} else {
return;
}
}
if (!item) {
item = {
node: m.target,
attributes: {},
styleDiff: {},
_unchangedStyles: {}
};
this.attributes.push(item);
this.attributeMap.set(m.target, item);
}
if (attributeName === "type" && target.tagName === "INPUT" && (m.oldValue || "").toLowerCase() === "password") {
target.setAttribute("data-rr-is-password", "true");
}
if (!ignoreAttribute(target.tagName, attributeName)) {
item.attributes[attributeName] = transformAttribute(
this.doc,
toLowerCase(target.tagName),
toLowerCase(attributeName),
value,
target,
this.maskAttributeFn
);
if (attributeName === "style") {
const oldStyle = m.oldValue ? this.styleDeclarationParser.parse(m.oldValue) : null;
for (const pname of Array.from(target.style)) {
const newValue = target.style.getPropertyValue(pname);
const newPriority = target.style.getPropertyPriority(pname);
if (newValue !== (oldStyle?.getPropertyValue(pname) || "") || newPriority !== (oldStyle?.getPropertyPriority(pname) || "")) {
if (newPriority === "") {
item.styleDiff[pname] = newValue;
} else {
item.styleDiff[pname] = [newValue, newPriority];
}
} else {
item._unchangedStyles[pname] = [newValue, newPriority];
}
}
if (oldStyle) {
for (const pname of Array.from(oldStyle)) {
if (target.style.getPropertyValue(pname) === "") {
item.styleDiff[pname] = false;
}
}
}
}
}
break;
}
case "childList": {
if (isBlocked(
m.target,
this.blockClass,
this.blockSelector,
this.unblockSelector,
true
)) {
return;
}
m.addedNodes.forEach((n2) => this.genAdds(n2, m.target));
m.removedNodes.forEach((n2) => {
const nodeId = this.mirror.getId(n2);
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,
false
) || isIgnored(n2, this.mirror) || !isSerialized(n2, this.mirror)) {
return;
}
if (this.addedSet.has(n2)) {
deepDelete(this.addedSet, n2);
this.droppedSet.add(n2);
} else if (this.addedSet.has(m.target) && nodeId === -1) ;
else if (isAncestorRemoved(m.target, this.mirror)) ;
else if (this.movedSet.has(n2) && this.movedMap[moveKey(nodeId, parentId)]) {
deepDelete(this.movedSet, n2);
} else {
this.removes.push({
parentId,
id: nodeId,
isShadow: isShadowRoot(m.target) && isNativeShadowDom(m.target) ? true : void 0
});
}
this.mapRemoves.push(n2);
});
break;
}
}
};
this.genAdds = (n2, target) => {
if (this.processedNodeManager.inOtherBuffer(n2, this)) return;
if (this.addedSet.has(n2) || this.movedSet.has(n2)) return;
if (this.mirror.hasNode(n2)) {
if (isIgnored(n2, this.mirror)) {
return;
}
this.movedSet.add(n2);
let targetId = null;
if (target && this.mirror.hasNode(target)) {
targetId = this.mirror.getId(target);
}
if (targetId && targetId !== -1) {
this.movedMap[moveKey(this.mirror.getId(n2), targetId)] = true;
}
} else {
this.addedSet.add(n2);
this.droppedSet.delete(n2);
}
if (!isBlocked(
n2,
this.blockClass,
this.blockSelector,
this.unblockSelector,
false
)) {
if (n2.childNodes) {
n2.childNodes.forEach((childN) => this.genAdds(childN));
}
if (hasShadowRoot(n2)) {
n2.shadowRoot.childNodes.forEach((childN) => {
this.processedNodeManager.add(childN, this);
this.genAdds(childN, n2);
});
}
}
};
}
init(options) {
[
"mutationCb",
"blockClass",
"blockSelector",
"unblockSelector",
"maskAllText",
"maskTextClass",
"unmaskTextClass",
"maskTextSelector",
"unmaskTextSelector",
"inlineStylesheet",
"maskInputOptions",
"maskAttributeFn",
"maskTextFn",
"maskInputFn",
"keepIframeSrcFn",
"recordCanvas",
"inlineImages",
"slimDOMOptions",
"dataURLOptions",
"doc",
"mirror",
"iframeManager",
"stylesheetManager",
"shadowDomManager",
"canvasManager",
"processedNodeManager",
"ignoreCSSAttributes"
].forEach((key) => {
this[key] = options[key];
});
this.styleDeclarationParser = new StyleDeclarationParser(this.doc);
}
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, n2) {
addsSet.delete(n2);
n2.childNodes?.forEach((childN) => deepDelete(addsSet, childN));
}
function isParentRemoved(removes, n2, mirror2) {
if (removes.length === 0) return false;
return _isParentRemoved(removes, n2, mirror2);
}
function _isParentRemoved(removes, n2, mirror2) {
let node = n2.parentNode;
while (node) {
const parentId = mirror2.getId(node);
if (removes.some((r2) => r2.id === parentId)) {
return true;
}
node = node.parentNode;
}
return false;
}
function isAncestorInSet(set, n2) {
if (set.size === 0) return false;
return _isAncestorInSet(set, n2);
}
function _isAncestorInSet(set, n2) {
const { parentNode } = n2;
if (!parentNode) {
return false;
}
if (set.has(parentNode)) {
return true;
}
return _isAncestorInSet(set, parentNode);
}
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 {
}
return event && event.target;
}
function initMutationObserver(options, rootEl) {
const mutationBuffer = new MutationBuffer();
mutationBuffers.push(mutationBuffer);
mutationBuffer.init(options);
let mutationObserverCtor = window.MutationObserver || /**
* Some websites may disable MutationObserver by removing it from the window object.
* If someone is using rrweb to build a browser extention or things like it, they
* could not change the website's code but can have an opportunity to inject some
* code before the website executing its JS logic.
* Then they can do this to store the native MutationObserver:
* window.__rrMutationObserver = MutationObserver
*/
window.__rrMutationObserver;
const angularZoneSymbol = window?.Zone?.__symbol__?.("MutationObserver");
if (angularZoneSymbol && window[angularZoneSymbol]) {
mutationObserverCtor = window[angularZoneSymbol];
}
const observer = new mutationObserverCtor(
callbackWrapper((mutations) => {
if (options.onMutation && options.onMutation(mutations) === false) {
return;
}
mutationBuffer.processMutations.bind(mutationBuffer)(mutations);
})
);
observer.observe(rootEl, {
attributes: true,
attributeOldValue: true,
characterData: true,
characterDataOldValue: true,
childList: true,
subtree: true
});
return observer;
}
function initMoveObserver({
mousemoveCb,
sampling,
doc,
mirror: mirror2
}) {
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(
callbackWrapper(
(source) => {
const totalOffset = Date.now() - timeBaseline;
mousemoveCb(
positions.map((p) => {
p.timeOffset -= totalOffset;
return p;
}),
source
);
positions = [];
timeBaseline = null;
}
),
callbackThreshold
);
const updatePosition = callbackWrapper(
throttle$1(
callbackWrapper((evt) => {
const target = getEventTarget(evt);
const { clientX, clientY } = legacy_isTouchEvent(evt) ? evt.changedTouches[0] : evt;
if (!timeBaseline) {
timeBaseline = nowTimestamp();
}
positions.push({
x: clientX,
y: clientY,
id: mirror2.getId(target),
timeOffset: nowTimestamp() - timeBaseline
});
wrappedCb(
typeof DragEvent !== "undefined" && evt instanceof DragEvent ? IncrementalSource.Drag : evt instanceof MouseEvent ? IncrementalSource.MouseMove : IncrementalSource.TouchMove
);
}),
threshold,
{
trailing: false
}
)
);
const handlers = [
on("mousemove", updatePosition, doc),
on("touchmove", updatePosition, doc),
on("drag", updatePosition, doc)
];
return callbackWrapper(() => {
handlers.forEach((h) => h());
});
}
function initMouseInteractionObserver({
mouseInteractionCb,
doc,
mirror: mirror2,
blockClass,
blockSelector,
unblockSelector,
sampling
}) {
if (sampling.mouseInteraction === false) {
return () => {
};
}
const disableMap = sampling.mouseInteraction === true || sampling.mouseInteraction === void 0 ? {} : sampling.mouseInteraction;
const handlers = [];
let currentPointerType = null;
const getHandler = (eventKey) => {
return (event) => {
const target = getEventTarget(event);
if (isBlocked(target, blockClass, blockSelector, unblockSelector, true)) {
return;
}
let pointerType = null;
let thisEventKey = eventKey;
if ("pointerType" in event) {
switch (event.pointerType) {
case "mouse":
pointerType = PointerTypes.Mouse;
break;
case "touch":
pointerType = PointerTypes.Touch;
break;
case "pen":
pointerType = PointerTypes.Pen;
break;
}
if (pointerType === PointerTypes.Touch) {
if (MouseInteractions[eventKey] === MouseInteractions.MouseDown) {
thisEventKey = "TouchStart";
} else if (MouseInteractions[eventKey] === MouseInteractions.MouseUp) {
thisEventKey = "TouchEnd";
}
} else if (pointerType === PointerTypes.Pen) ;
} else if (legacy_isTouchEvent(event)) {
pointerType = PointerTypes.Touch;
}
if (pointerType !== null) {
currentPointerType = pointerType;
if (thisEventKey.startsWith("Touch") && pointerType === PointerTypes.Touch || thisEventKey.startsWith("Mouse") && pointerType === PointerTypes.Mouse) {
pointerType = null;
}
} else if (MouseInteractions[eventKey] === MouseInteractions.Click) {
pointerType = currentPointerType;
currentPointerType = null;
}
const e2 = legacy_isTouchEvent(event) ? event.changedTouches[0] : event;
if (!e2) {
return;
}
const id = mirror2.getId(target);
const { clientX, clientY } = e2;
callbackWrapper(mouseInteractionCb)({
type: MouseInteractions[thisEventKey],
id,
x: clientX,
y: clientY,
...pointerType !== null && { pointerType }
});
};
};
Object.keys(MouseInteractions).filter(
(key) => Number.isNaN(Number(key)) && !key.endsWith("_Departed") && disableMap[key] !== false
).forEach((eventKey) => {
let eventName = toLowerCase(eventKey);
const handler = getHandler(eventKey);
if (window.PointerEvent) {
switch (MouseInteractions[eventKey]) {
case MouseInteractions.MouseDown:
case MouseInteractions.MouseUp:
eventName = eventName.replace(
"mouse",
"pointer"
);
break;
case MouseInteractions.TouchStart:
case MouseInteractions.TouchEnd:
return;
}
}
handlers.push(on(eventName, handler, doc));
});
return callbackWrapper(() => {
handlers.forEach((h) => h());
});
}
function initScrollObserver({
scrollCb,
doc,
mirror: mirror2,
blockClass,
blockSelector,
unblockSelector,
sampling
}) {
const updatePosition = callbackWrapper(
throttle$1(
callbackWrapper((evt) => {
const target = getEventTarget(evt);
if (!target || isBlocked(
target,
blockClass,
blockSelector,
unblockSelector,
true
)) {
return;
}
const id = mirror2.getId(target);
if (target === doc && doc.defaultView) {
const scrollLeftTop = getWindowScroll(doc.defaultView);
scrollCb({
id,
x: scrollLeftTop.left,
y: scrollLeftTop.top
});
} else {
scrollCb({
id,
x: target.scrollLeft,
y: target.scrollTop
});
}
}),
sampling.scroll || 100
)
);
return on("scroll", updatePosition, doc);
}
function initViewportResizeObserver({ viewportResizeCb }, { win }) {
let lastH = -1;
let lastW = -1;
const updateDimension = callbackWrapper(
throttle$1(
callbackWrapper(() => {
const height = getWindowHeight();
const width = getWindowWidth();
if (lastH !== height || lastW !== width) {
viewportResizeCb({
width: Number(width),
height: Number(height)
});
lastH = height;
lastW = width;
}
}),
200
)
);
return on("resize", updateDimension, win);
}
const INPUT_TAGS = ["INPUT", "TEXTAREA", "SELECT"];
const lastInputValueMap = /* @__PURE__ */ new WeakMap();
function initInputObserver({
inputCb,
doc,
mirror: mirror2,
blockClass,
blockSelector,
unblockSelector,
ignoreClass,
ignoreSelector,
maskInputOptions,
maskInputFn,
sampling,
userTriggeredOnInput,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector
}) {
function eventHandler(event) {
let target = getEventTarget(event);
const userTriggered = event.isTrusted;
const tagName = target && toUpperCase(target.tagName);
if (tagName === "OPTION") target = target.parentElement;
if (!target || !tagName || INPUT_TAGS.indexOf(tagName) < 0 || isBlocked(
target,
blockClass,
blockSelector,
unblockSelector,
true
)) {
return;
}
const el = target;
if (el.classList.contains(ignoreClass) || ignoreSelector && el.matches(ignoreSelector)) {
return;
}
const type = getInputType(target);
let text = getInputValue(el, tagName, type);
let isChecked = false;
const isInputMasked = shouldMaskInput({
maskInputOptions,
tagName,
type
});
const forceMask = needMaskingText(
target,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
isInputMasked
);
if (type === "radio" || type === "checkbox") {
isChecked = target.checked;
}
text = maskInputValue({
isMasked: forceMask,
element: target,
value: text,
maskInputFn
});
cbWithDedup(
target,
userTriggeredOnInput ? { text, isChecked, userTriggered } : { text, isChecked }
);
const name = target.name;
if (type === "radio" && name && isChecked) {
doc.querySelectorAll(`input[type="radio"][name="${name}"]`).forEach((el2) => {
if (el2 !== target) {
const text2 = maskInputValue({
// share mask behavior of `target`
isMasked: forceMask,
element: el2,
value: getInputValue(el2, tagName, type),
maskInputFn
});
cbWithDedup(
el2,
userTriggeredOnInput ? { text: text2, isChecked: !isChecked, userTriggered: false } : { text: text2, isChecked: !isChecked }
);
}
});
}
}
function cbWithDedup(target, v2) {
const lastInputValue = lastInputValueMap.get(target);
if (!lastInputValue || lastInputValue.text !== v2.text || lastInputValue.isChecked !== v2.isChecked) {
lastInputValueMap.set(target, v2);
const id = mirror2.getId(target);
callbackWrapper(inputCb)({
...v2,
id
});
}
}
const events = sampling.input === "last" ? ["change"] : ["input", "change"];
const handlers = events.map(
(eventName) => on(eventName, callbackWrapper(eventHandler), doc)
);
const currentWindow = doc.defaultView;
if (!currentWindow) {
return () => {
handlers.forEach((h) => h());
};
}
const propertyDescriptor = currentWindow.Object.getOwnPropertyDescriptor(
currentWindow.HTMLInputElement.prototype,
"value"
);
const hookProperties = [
[currentWindow.HTMLInputElement.prototype, "value"],
[currentWindow.HTMLInputElement.prototype, "checked"],
[currentWindow.HTMLSelectElement.prototype, "value"],
[currentWindow.HTMLTextAreaElement.prototype, "value"],
// Some UI library use selectedIndex to set select value
[currentWindow.HTMLSelectElement.prototype, "selectedIndex"],
[currentWindow.HTMLOptionElement.prototype, "selected"]
];
if (propertyDescriptor && propertyDescriptor.set) {
handlers.push(
...hookProperties.map(
(p) => hookSetter(
p[0],
p[1],
{
set() {
callbackWrapper(eventHandler)({
target: this,
isTrusted: false
// userTriggered to false as this could well be programmatic
});
}
},
false,
currentWindow
)
)
);
}
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 rules2 = Array.from(
childRule.parentRule.cssRules
);
const index = rules2.indexOf(childRule);
pos.unshift(index);
} else if (childRule.parentStyleSheet) {
const rules2 = Array.from(childRule.parentStyleSheet.cssRules);
const index = rules2.indexOf(childRule);
pos.unshift(index);
}
return pos;
}
return recurse(rule, positions);
}
function getIdAndStyleId(sheet, mirror2, styleMirror) {
let id, styleId;
if (!sheet) return {};
if (sheet.ownerNode) id = mirror2.getId(sheet.ownerNode);
else styleId = styleMirror.getId(sheet);
return {
styleId,
id
};
}
function initStyleSheetObserver({ styleSheetRuleCb, mirror: mirror2, stylesheetManager }, { 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, styleId } = getIdAndStyleId(
thisArg,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
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, styleId } = getIdAndStyleId(
thisArg,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
removes: [{ index }]
});
}
return target.apply(thisArg, argumentsList);
}
)
});
let replace;
if (win.CSSStyleSheet.prototype.replace) {
replace = win.CSSStyleSheet.prototype.replace;
win.CSSStyleSheet.prototype.replace = new Proxy(replace, {
apply: callbackWrapper(
(target, thisArg, argumentsList) => {
const [text] = argumentsList;
const { id, styleId } = getIdAndStyleId(
thisArg,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
replace: text
});
}
return target.apply(thisArg, argumentsList);
}
)
});
}
let replaceSync;
if (win.CSSStyleSheet.prototype.replaceSync) {
replaceSync = win.CSSStyleSheet.prototype.replaceSync;
win.CSSStyleSheet.prototype.replaceSync = new Proxy(replaceSync, {
apply: callbackWrapper(
(target, thisArg, argumentsList) => {
const [text] = argumentsList;
const { id, styleId } = getIdAndStyleId(
thisArg,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
replaceSync: text
});
}
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] = {
// eslint-disable-next-line @typescript-eslint/unbound-method
insertRule: type.prototype.insertRule,
// eslint-disable-next-line @typescript-eslint/unbound-method
deleteRule: type.prototype.deleteRule
};
type.prototype.insertRule = new Proxy(
unmodifiedFunctions[typeKey].insertRule,
{
apply: callbackWrapper(
(target, thisArg, argumentsList) => {
const [rule, index] = argumentsList;
const { id, styleId } = getIdAndStyleId(
thisArg.parentStyleSheet,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
adds: [
{
rule,
index: [
...getNestedCSSRulePositions(thisArg),
index || 0
// defaults to 0
]
}
]
});
}
return target.apply(thisArg, argumentsList);
}
)
}
);
type.prototype.deleteRule = new Proxy(
unmodifiedFunctions[typeKey].deleteRule,
{
apply: callbackWrapper(
(target, thisArg, argumentsList) => {
const [index] = argumentsList;
const { id, styleId } = getIdAndStyleId(
thisArg.parentStyleSheet,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleSheetRuleCb({
id,
styleId,
removes: [
{ index: [...getNestedCSSRulePositions(thisArg), index] }
]
});
}
return target.apply(thisArg, argumentsList);
}
)
}
);
});
return callbackWrapper(() => {
win.CSSStyleSheet.prototype.insertRule = insertRule;
win.CSSStyleSheet.prototype.deleteRule = deleteRule;
replace && (win.CSSStyleSheet.prototype.replace = replace);
replaceSync && (win.CSSStyleSheet.prototype.replaceSync = replaceSync);
Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule;
type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule;
});
});
}
function initAdoptedStyleSheetObserver({
mirror: mirror2,
stylesheetManager
}, host) {
let hostId = null;
if (host.nodeName === "#document") hostId = mirror2.getId(host);
else hostId = mirror2.getId(host.host);
const patchTarget = host.nodeName === "#document" ? host.defaultView?.Document : host.ownerDocument?.defaultView?.ShadowRoot;
const originalPropertyDescriptor = patchTarget?.prototype ? Object.getOwnPropertyDescriptor(
patchTarget?.prototype,
"adoptedStyleSheets"
) : void 0;
if (hostId === null || hostId === -1 || !patchTarget || !originalPropertyDescriptor)
return () => {
};
Object.defineProperty(host, "adoptedStyleSheets", {
configurable: originalPropertyDescriptor.configurable,
enumerable: originalPropertyDescriptor.enumerable,
get() {
return originalPropertyDescriptor.get?.call(this);
},
set(sheets) {
const result = originalPropertyDescriptor.set?.call(this, sheets);
if (hostId !== null && hostId !== -1) {
try {
stylesheetManager.adoptStyleSheets(sheets, hostId);
} catch (e2) {
}
}
return result;
}
});
return callbackWrapper(() => {
Object.defineProperty(host, "adoptedStyleSheets", {
configurable: originalPropertyDescriptor.configurable,
enumerable: originalPropertyDescriptor.enumerable,
// eslint-disable-next-line @typescript-eslint/unbound-method
get: originalPropertyDescriptor.get,
// eslint-disable-next-line @typescript-eslint/unbound-method
set: originalPropertyDescriptor.set
});
});
}
function initStyleDeclarationObserver({
styleDeclarationCb,
mirror: mirror2,
ignoreCSSAttributes,
stylesheetManager
}, { win }) {
const setProperty = win.CSSStyleDeclaration.prototype.setProperty;
win.CSSStyleDeclaration.prototype.setProperty = new Proxy(setProperty, {
apply: callbackWrapper(
(target, thisArg, argumentsList) => {
const [property, value, priority] = argumentsList;
if (ignoreCSSAttributes.has(property)) {
return setProperty.apply(thisArg, [property, value, priority]);
}
const { id, styleId } = getIdAndStyleId(
thisArg.parentRule?.parentStyleSheet,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleDeclarationCb({
id,
styleId,
set: {
property,
value,
priority
},
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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) => {
const [property] = argumentsList;
if (ignoreCSSAttributes.has(property)) {
return removeProperty.apply(thisArg, [property]);
}
const { id, styleId } = getIdAndStyleId(
thisArg.parentRule?.parentStyleSheet,
mirror2,
stylesheetManager.styleMirror
);
if (id && id !== -1 || styleId && styleId !== -1) {
styleDeclarationCb({
id,
styleId,
remove: {
property
},
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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: mirror2,
sampling,
doc
}) {
const handler = callbackWrapper(
(type) => throttle$1(
callbackWrapper((event) => {
const target = getEventTarget(event);
if (!target || isBlocked(
target,
blockClass,
blockSelector,
unblockSelector,
true
)) {
return;
}
const { currentTime, volume, muted, playbackRate } = target;
mediaInteractionCb({
type,
id: mirror2.getId(target),
currentTime,
volume,
muted,
playbackRate
});
}),
sampling.media || 500
)
);
const handlers = [
on("play", handler(MediaInteractions.Play), doc),
on("pause", handler(MediaInteractions.Pause), doc),
on("seeked", handler(MediaInteractions.Seeked), doc),
on("volumechange", handler(MediaInteractions.VolumeChange), doc),
on("ratechange", handler(MediaInteractions.RateChange), doc)
];
return callbackWrapper(() => {
handlers.forEach((h) => h());
});
}
function initFontObserver({ fontCb, doc }) {
const win = doc.defaultView;
if (!win) {
return () => {
};
}
const handlers = [];
const fontMap = /* @__PURE__ */ new WeakMap();
const originalFontFace = win.FontFace;
win.FontFace = function FontFace2(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$2(
callbackWrapper(() => {
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 initSelectionObserver(param) {
const {
doc,
mirror: mirror2,
blockClass,
blockSelector,
unblockSelector,
selectionCb
} = param;
let collapsed = true;
const updateSelection = callbackWrapper(() => {
const selection = doc.getSelection();
if (!selection || collapsed && selection?.isCollapsed) return;
collapsed = selection.isCollapsed || false;
const ranges = [];
const count = selection.rangeCount || 0;
for (let i2 = 0; i2 < count; i2++) {
const range = selection.getRangeAt(i2);
const { startContainer, startOffset, endContainer, endOffset } = range;
const blocked = isBlocked(
startContainer,
blockClass,
blockSelector,
unblockSelector,
true
) || isBlocked(
endContainer,
blockClass,
blockSelector,
unblockSelector,
true
);
if (blocked) continue;
ranges.push({
start: mirror2.getId(startContainer),
startOffset,
end: mirror2.getId(endContainer),
endOffset
});
}
selectionCb({ ranges });
});
updateSelection();
return on("selectionchange", updateSelection);
}
function initCustomElementObserver({
doc,
customElementCb
}) {
const win = doc.defaultView;
if (!win || !win.customElements) return () => {
};
const restoreHandler = patch(
win.customElements,
"define",
function(original) {
return function(name, constructor, options) {
try {
customElementCb({
define: {
name
}
});
} catch (e2) {
}
return original.apply(this, [name, constructor, options]);
};
}
);
return restoreHandler;
}
function initObservers(o2, _hooks = {}) {
const currentWindow = o2.doc.defaultView;
if (!currentWindow) {
return () => {
};
}
let mutationObserver;
if (o2.recordDOM) {
mutationObserver = initMutationObserver(o2, o2.doc);
}
const mousemoveHandler = initMoveObserver(o2);
const mouseInteractionHandler = initMouseInteractionObserver(o2);
const scrollHandler = initScrollObserver(o2);
const viewportResizeHandler = initViewportResizeObserver(o2, {
win: currentWindow
});
const inputHandler = initInputObserver(o2);
const mediaInteractionHandler = initMediaInteractionObserver(o2);
let styleSheetObserver = () => {
};
let adoptedStyleSheetObserver = () => {
};
let styleDeclarationObserver = () => {
};
let fontObserver = () => {
};
if (o2.recordDOM) {
styleSheetObserver = initStyleSheetObserver(o2, { win: currentWindow });
adoptedStyleSheetObserver = initAdoptedStyleSheetObserver(o2, o2.doc);
styleDeclarationObserver = initStyleDeclarationObserver(o2, {
win: currentWindow
});
if (o2.collectFonts) {
fontObserver = initFontObserver(o2);
}
}
const selectionObserver = initSelectionObserver(o2);
const customElementObserver = initCustomElementObserver(o2);
const pluginHandlers = [];
for (const plugin of o2.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();
styleSheetObserver();
adoptedStyleSheetObserver();
styleDeclarationObserver();
fontObserver();
selectionObserver();
customElementObserver();
pluginHandlers.forEach((h) => h());
});
}
function hasNestedCSSRule(prop) {
return typeof window[prop] !== "undefined";
}
function canMonkeyPatchNestedCSSRule(prop) {
return Boolean(
typeof window[prop] !== "undefined" && // Note: Generally, this check _shouldn't_ be necessary
// However, in some scenarios (e.g. jsdom) this can sometimes fail, so we check for it here
window[prop].prototype && "insertRule" in window[prop].prototype && "deleteRule" in window[prop].prototype
);
}
class CrossOriginIframeMirror {
constructor(generateIdFn) {
this.generateIdFn = generateIdFn;
this.iframeIdToRemoteIdMap = /* @__PURE__ */ new WeakMap();
this.iframeRemoteIdToIdMap = /* @__PURE__ */ new WeakMap();
}
getId(iframe, remoteId, idToRemoteMap, remoteToIdMap) {
const idToRemoteIdMap = idToRemoteMap || this.getIdToRemoteIdMap(iframe);
const remoteIdToIdMap = remoteToIdMap || this.getRemoteIdToIdMap(iframe);
let id = idToRemoteIdMap.get(remoteId);
if (!id) {
id = this.generateIdFn();
idToRemoteIdMap.set(remoteId, id);
remoteIdToIdMap.set(id, remoteId);
}
return id;
}
getIds(iframe, remoteId) {
const idToRemoteIdMap = this.getIdToRemoteIdMap(iframe);
const remoteIdToIdMap = this.getRemoteIdToIdMap(iframe);
return remoteId.map(
(id) => this.getId(iframe, id, idToRemoteIdMap, remoteIdToIdMap)
);
}
getRemoteId(iframe, id, map) {
const remoteIdToIdMap = map || this.getRemoteIdToIdMap(iframe);
if (typeof id !== "number") return id;
const remoteId = remoteIdToIdMap.get(id);
if (!remoteId) return -1;
return remoteId;
}
getRemoteIds(iframe, ids) {
const remoteIdToIdMap = this.getRemoteIdToIdMap(iframe);
return ids.map((id) => this.getRemoteId(iframe, id, remoteIdToIdMap));
}
reset(iframe) {
if (!iframe) {
this.iframeIdToRemoteIdMap = /* @__PURE__ */ new WeakMap();
this.iframeRemoteIdToIdMap = /* @__PURE__ */ new WeakMap();
return;
}
this.iframeIdToRemoteIdMap.delete(iframe);
this.iframeRemoteIdToIdMap.delete(iframe);
}
getIdToRemoteIdMap(iframe) {
let idToRemoteIdMap = this.iframeIdToRemoteIdMap.get(iframe);
if (!idToRemoteIdMap) {
idToRemoteIdMap = /* @__PURE__ */ new Map();
this.iframeIdToRemoteIdMap.set(iframe, idToRemoteIdMap);
}
return idToRemoteIdMap;
}
getRemoteIdToIdMap(iframe) {
let remoteIdToIdMap = this.iframeRemoteIdToIdMap.get(iframe);
if (!remoteIdToIdMap) {
remoteIdToIdMap = /* @__PURE__ */ new Map();
this.iframeRemoteIdToIdMap.set(iframe, remoteIdToIdMap);
}
return remoteIdToIdMap;
}
}
class IframeManagerNoop {
constructor() {
this.crossOriginIframeMirror = new CrossOriginIframeMirror(genId);
this.crossOriginIframeRootIdMap = /* @__PURE__ */ new WeakMap();
}
addIframe() {
}
addLoadListener() {
}
attachIframe() {
}
}
class IframeManager {
constructor(options) {
this.iframes = /* @__PURE__ */ new WeakMap();
this.crossOriginIframeMap = /* @__PURE__ */ new WeakMap();
this.crossOriginIframeMirror = new CrossOriginIframeMirror(genId);
this.crossOriginIframeRootIdMap = /* @__PURE__ */ new WeakMap();
this.mutationCb = options.mutationCb;
this.wrappedEmit = options.wrappedEmit;
this.stylesheetManager = options.stylesheetManager;
this.recordCrossOriginIframes = options.recordCrossOriginIframes;
this.crossOriginIframeStyleMirror = new CrossOriginIframeMirror(
this.stylesheetManager.styleMirror.generateId.bind(
this.stylesheetManager.styleMirror
)
);
this.mirror = options.mirror;
if (this.recordCrossOriginIframes) {
window.addEventListener("message", this.handleMessage.bind(this));
}
}
addIframe(iframeEl) {
this.iframes.set(iframeEl, true);
const contentWindow = getIFrameContentWindow(iframeEl);
if (contentWindow) this.crossOriginIframeMap.set(contentWindow, iframeEl);
}
addLoadListener(cb) {
this.loadListener = cb;
}
attachIframe(iframeEl, childSn) {
this.mutationCb({
adds: [
{
parentId: this.mirror.getId(iframeEl),
nextId: null,
node: childSn
}
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true
});
if (this.recordCrossOriginIframes) {
getIFrameContentWindow(iframeEl)?.addEventListener(
"message",
this.handleMessage.bind(this)
);
}
this.loadListener?.(iframeEl);
const iframeDoc = getIFrameContentDocument(iframeEl);
if (iframeDoc && iframeDoc.adoptedStyleSheets && iframeDoc.adoptedStyleSheets.length > 0)
this.stylesheetManager.adoptStyleSheets(
iframeDoc.adoptedStyleSheets,
this.mirror.getId(iframeDoc)
);
}
handleMessage(message) {
const crossOriginMessageEvent = message;
if (crossOriginMessageEvent.data.type !== "rrweb" || // To filter out the rrweb messages which are forwarded by some sites.
crossOriginMessageEvent.origin !== crossOriginMessageEvent.data.origin)
return;
const iframeSourceWindow = message.source;
if (!iframeSourceWindow) return;
const iframeEl = this.crossOriginIframeMap.get(message.source);
if (!iframeEl) return;
const transformedEvent = this.transformCrossOriginEvent(
iframeEl,
crossOriginMessageEvent.data.event
);
if (transformedEvent)
this.wrappedEmit(
transformedEvent,
crossOriginMessageEvent.data.isCheckout
);
}
transformCrossOriginEvent(iframeEl, e2) {
switch (e2.type) {
case EventType.FullSnapshot: {
this.crossOriginIframeMirror.reset(iframeEl);
this.crossOriginIframeStyleMirror.reset(iframeEl);
this.replaceIdOnNode(e2.data.node, iframeEl);
const rootId = e2.data.node.id;
this.crossOriginIframeRootIdMap.set(iframeEl, rootId);
this.patchRootIdOnNode(e2.data.node, rootId);
return {
timestamp: e2.timestamp,
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: this.mirror.getId(iframeEl),
nextId: null,
node: e2.data.node
}
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true
}
};
}
case EventType.Meta:
case EventType.Load:
case EventType.DomContentLoaded: {
return false;
}
case EventType.Plugin: {
return e2;
}
case EventType.Custom: {
this.replaceIds(
e2.data.payload,
iframeEl,
["id", "parentId", "previousId", "nextId"]
);
return e2;
}
case EventType.IncrementalSnapshot: {
switch (e2.data.source) {
case IncrementalSource.Mutation: {
e2.data.adds.forEach((n2) => {
this.replaceIds(n2, iframeEl, [
"parentId",
"nextId",
"previousId"
]);
this.replaceIdOnNode(n2.node, iframeEl);
const rootId = this.crossOriginIframeRootIdMap.get(iframeEl);
rootId && this.patchRootIdOnNode(n2.node, rootId);
});
e2.data.removes.forEach((n2) => {
this.replaceIds(n2, iframeEl, ["parentId", "id"]);
});
e2.data.attributes.forEach((n2) => {
this.replaceIds(n2, iframeEl, ["id"]);
});
e2.data.texts.forEach((n2) => {
this.replaceIds(n2, iframeEl, ["id"]);
});
return e2;
}
case IncrementalSource.Drag:
case IncrementalSource.TouchMove:
case IncrementalSource.MouseMove: {
e2.data.positions.forEach((p) => {
this.replaceIds(p, iframeEl, ["id"]);
});
return e2;
}
case IncrementalSource.ViewportResize: {
return false;
}
case IncrementalSource.MediaInteraction:
case IncrementalSource.MouseInteraction:
case IncrementalSource.Scroll:
case IncrementalSource.CanvasMutation:
case IncrementalSource.Input: {
this.replaceIds(e2.data, iframeEl, ["id"]);
return e2;
}
case IncrementalSource.StyleSheetRule:
case IncrementalSource.StyleDeclaration: {
this.replaceIds(e2.data, iframeEl, ["id"]);
this.replaceStyleIds(e2.data, iframeEl, ["styleId"]);
return e2;
}
case IncrementalSource.Font: {
return e2;
}
case IncrementalSource.Selection: {
e2.data.ranges.forEach((range) => {
this.replaceIds(range, iframeEl, ["start", "end"]);
});
return e2;
}
case IncrementalSource.AdoptedStyleSheet: {
this.replaceIds(e2.data, iframeEl, ["id"]);
this.replaceStyleIds(e2.data, iframeEl, ["styleIds"]);
e2.data.styles?.forEach((style) => {
this.replaceStyleIds(style, iframeEl, ["styleId"]);
});
return e2;
}
}
}
}
return false;
}
replace(iframeMirror, obj, iframeEl, keys) {
for (const key of keys) {
if (!Array.isArray(obj[key]) && typeof obj[key] !== "number") continue;
if (Array.isArray(obj[key])) {
obj[key] = iframeMirror.getIds(
iframeEl,
obj[key]
);
} else {
obj[key] = iframeMirror.getId(iframeEl, obj[key]);
}
}
return obj;
}
replaceIds(obj, iframeEl, keys) {
return this.replace(this.crossOriginIframeMirror, obj, iframeEl, keys);
}
replaceStyleIds(obj, iframeEl, keys) {
return this.replace(this.crossOriginIframeStyleMirror, obj, iframeEl, keys);
}
replaceIdOnNode(node, iframeEl) {
this.replaceIds(node, iframeEl, ["id", "rootId"]);
if ("childNodes" in node) {
node.childNodes.forEach((child) => {
this.replaceIdOnNode(child, iframeEl);
});
}
}
patchRootIdOnNode(node, rootId) {
if (node.type !== NodeType$1.Document && !node.rootId) node.rootId = rootId;
if ("childNodes" in node) {
node.childNodes.forEach((child) => {
this.patchRootIdOnNode(child, rootId);
});
}
}
}
class ShadowDomManagerNoop {
init() {
}
addShadowRoot() {
}
observeAttachShadow() {
}
reset() {
}
}
class ShadowDomManager {
constructor(options) {
this.shadowDoms = /* @__PURE__ */ new WeakSet();
this.restoreHandlers = [];
this.mutationCb = options.mutationCb;
this.scrollCb = options.scrollCb;
this.bypassOptions = options.bypassOptions;
this.mirror = options.mirror;
this.init();
}
init() {
this.reset();
this.patchAttachShadow(Element, document);
}
addShadowRoot(shadowRoot, doc) {
if (!isNativeShadowDom(shadowRoot)) return;
if (this.shadowDoms.has(shadowRoot)) return;
this.shadowDoms.add(shadowRoot);
this.bypassOptions.canvasManager.addShadowRoot(shadowRoot);
const observer = initMutationObserver(
{
...this.bypassOptions,
doc,
mutationCb: this.mutationCb,
mirror: this.mirror,
shadowDomManager: this
},
shadowRoot
);
this.restoreHandlers.push(() => observer.disconnect());
this.restoreHandlers.push(
initScrollObserver({
...this.bypassOptions,
scrollCb: this.scrollCb,
// https://gist.github.com/praveenpuglia/0832da687ed5a5d7a0907046c9ef1813
// scroll is not allowed to pass the boundary, so we need to listen the shadow document
doc: shadowRoot,
mirror: this.mirror
})
);
setTimeout$2(() => {
if (shadowRoot.adoptedStyleSheets && shadowRoot.adoptedStyleSheets.length > 0)
this.bypassOptions.stylesheetManager.adoptStyleSheets(
shadowRoot.adoptedStyleSheets,
this.mirror.getId(shadowRoot.host)
);
this.restoreHandlers.push(
initAdoptedStyleSheetObserver(
{
mirror: this.mirror,
stylesheetManager: this.bypassOptions.stylesheetManager
},
shadowRoot
)
);
}, 0);
}
/**
* Monkey patch 'attachShadow' of an IFrameElement to observe newly added shadow doms.
*/
observeAttachShadow(iframeElement) {
const iframeDoc = getIFrameContentDocument(iframeElement);
const iframeWindow = getIFrameContentWindow(iframeElement);
if (!iframeDoc || !iframeWindow) return;
this.patchAttachShadow(
iframeWindow.Element,
iframeDoc
);
}
/**
* Patch 'attachShadow' to observe newly added shadow doms.
*/
patchAttachShadow(element, doc) {
const manager = this;
this.restoreHandlers.push(
patch(
element.prototype,
"attachShadow",
function(original) {
return function(option) {
const shadowRoot = original.call(this, option);
if (this.shadowRoot && inDom(this))
manager.addShadowRoot(this.shadowRoot, doc);
return shadowRoot;
};
}
)
);
}
reset() {
this.restoreHandlers.forEach((handler) => {
try {
handler();
} catch (e2) {
}
});
this.restoreHandlers = [];
this.shadowDoms = /* @__PURE__ */ new WeakSet();
this.bypassOptions.canvasManager.resetShadowRoots();
}
}
class StylesheetManager {
constructor(options) {
this.trackedLinkElements = /* @__PURE__ */ new WeakSet();
this.styleMirror = new StyleSheetMirror();
this.mutationCb = options.mutationCb;
this.adoptedStyleSheetCb = options.adoptedStyleSheetCb;
}
attachLinkElement(linkEl, childSn) {
if ("_cssText" in childSn.attributes)
this.mutationCb({
adds: [],
removes: [],
texts: [],
attributes: [
{
id: childSn.id,
attributes: childSn.attributes
}
]
});
this.trackLinkElement(linkEl);
}
trackLinkElement(linkEl) {
if (this.trackedLinkElements.has(linkEl)) return;
this.trackedLinkElements.add(linkEl);
this.trackStylesheetInLinkElement(linkEl);
}
adoptStyleSheets(sheets, hostId) {
if (sheets.length === 0) return;
const adoptedStyleSheetData = {
id: hostId,
styleIds: []
};
const styles = [];
for (const sheet of sheets) {
let styleId;
if (!this.styleMirror.has(sheet)) {
styleId = this.styleMirror.add(sheet);
styles.push({
styleId,
rules: Array.from(sheet.rules || CSSRule, (r2, index) => ({
rule: stringifyRule(r2),
index
}))
});
} else styleId = this.styleMirror.getId(sheet);
adoptedStyleSheetData.styleIds.push(styleId);
}
if (styles.length > 0) adoptedStyleSheetData.styles = styles;
this.adoptedStyleSheetCb(adoptedStyleSheetData);
}
reset() {
this.styleMirror.reset();
this.trackedLinkElements = /* @__PURE__ */ new WeakSet();
}
// TODO: take snapshot on stylesheet reload by applying event listener
trackStylesheetInLinkElement(_linkEl) {
}
}
class ProcessedNodeManager {
constructor() {
this.nodeMap = /* @__PURE__ */ new WeakMap();
this.active = false;
}
inOtherBuffer(node, thisBuffer) {
const buffers = this.nodeMap.get(node);
return buffers && Array.from(buffers).some((buffer) => buffer !== thisBuffer);
}
add(node, buffer) {
if (!this.active) {
this.active = true;
onRequestAnimationFrame(() => {
this.nodeMap = /* @__PURE__ */ new WeakMap();
this.active = false;
});
}
this.nodeMap.set(node, (this.nodeMap.get(node) || /* @__PURE__ */ new Set()).add(buffer));
}
destroy() {
}
}
let wrappedEmit;
let _takeFullSnapshot;
try {
if (Array.from([1], (x) => x * 2)[0] !== 2) {
const cleanFrame = document.createElement("iframe");
document.body.appendChild(cleanFrame);
Array.from = cleanFrame.contentWindow?.Array.from || Array.from;
document.body.removeChild(cleanFrame);
}
} catch (err) {
console.debug("Unable to override Array.from", err);
}
const mirror = createMirror();
function record(options = {}) {
const {
emit,
checkoutEveryNms,
checkoutEveryNth,
blockClass = "rr-block",
blockSelector = null,
unblockSelector = null,
ignoreClass = "rr-ignore",
ignoreSelector = null,
maskAllText = false,
maskTextClass = "rr-mask",
unmaskTextClass = null,
maskTextSelector = null,
unmaskTextSelector = null,
inlineStylesheet = true,
maskAllInputs,
maskInputOptions: _maskInputOptions,
slimDOMOptions: _slimDOMOptions,
maskAttributeFn,
maskInputFn,
maskTextFn,
maxCanvasSize = null,
packFn,
sampling = {},
dataURLOptions = {},
mousemoveWait,
recordDOM = true,
recordCanvas = false,
recordCrossOriginIframes = false,
recordAfter = options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load",
userTriggeredOnInput = false,
collectFonts = false,
inlineImages = false,
plugins,
keepIframeSrcFn = () => false,
ignoreCSSAttributes = /* @__PURE__ */ new Set([]),
errorHandler,
onMutation,
getCanvasManager
} = options;
registerErrorHandler(errorHandler);
const inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
let passEmitsToParent = false;
if (!inEmittingFrame) {
try {
if (window.parent.document) {
passEmitsToParent = false;
}
} catch (e2) {
passEmitsToParent = true;
}
}
if (inEmittingFrame && !emit) {
throw new Error("emit function is required");
}
if (!inEmittingFrame && !passEmitsToParent) {
return () => {
};
}
if (mousemoveWait !== void 0 && sampling.mousemove === void 0) {
sampling.mousemove = mousemoveWait;
}
mirror.reset();
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 !== void 0 ? _maskInputOptions : {};
const slimDOMOptions = _slimDOMOptions === true || _slimDOMOptions === "all" ? {
script: true,
comment: true,
headFavicon: true,
headWhitespace: true,
headMetaSocial: true,
headMetaRobots: true,
headMetaHttpEquiv: true,
headMetaVerification: true,
// the following are off for slimDOMOptions === true,
// as they destroy some (hidden) info:
headMetaAuthorship: _slimDOMOptions === "all",
headMetaDescKeywords: _slimDOMOptions === "all"
} : _slimDOMOptions ? _slimDOMOptions : {};
polyfill();
let lastFullSnapshotEvent;
let incrementalSnapshotCount = 0;
const eventProcessor = (e2) => {
for (const plugin of plugins || []) {
if (plugin.eventProcessor) {
e2 = plugin.eventProcessor(e2);
}
}
if (packFn && // Disable packing events which will be emitted to parent frames.
!passEmitsToParent) {
e2 = packFn(e2);
}
return e2;
};
wrappedEmit = (r2, isCheckout) => {
const e2 = r2;
e2.timestamp = nowTimestamp();
if (mutationBuffers[0]?.isFrozen() && e2.type !== EventType.FullSnapshot && !(e2.type === EventType.IncrementalSnapshot && e2.data.source === IncrementalSource.Mutation)) {
mutationBuffers.forEach((buf) => buf.unfreeze());
}
if (inEmittingFrame) {
emit?.(eventProcessor(e2), isCheckout);
} else if (passEmitsToParent) {
const message = {
type: "rrweb",
event: eventProcessor(e2),
origin: window.location.origin,
isCheckout
};
window.parent.postMessage(message, "*");
}
if (e2.type === EventType.FullSnapshot) {
lastFullSnapshotEvent = e2;
incrementalSnapshotCount = 0;
} else if (e2.type === EventType.IncrementalSnapshot) {
if (e2.data.source === IncrementalSource.Mutation && e2.data.isAttachIframe) {
return;
}
incrementalSnapshotCount++;
const exceedCount = checkoutEveryNth && incrementalSnapshotCount >= checkoutEveryNth;
const exceedTime = checkoutEveryNms && lastFullSnapshotEvent && e2.timestamp - lastFullSnapshotEvent.timestamp > checkoutEveryNms;
if (exceedCount || exceedTime) {
takeFullSnapshot2(true);
}
}
};
const wrappedMutationEmit = (m) => {
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
...m
}
});
};
const wrappedScrollEmit = (p) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Scroll,
...p
}
});
const wrappedCanvasMutationEmit = (p) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p
}
});
const wrappedAdoptedStyleSheetEmit = (a2) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.AdoptedStyleSheet,
...a2
}
});
const stylesheetManager = new StylesheetManager({
mutationCb: wrappedMutationEmit,
adoptedStyleSheetCb: wrappedAdoptedStyleSheetEmit
});
const iframeManager = typeof __RRWEB_EXCLUDE_IFRAME__ === "boolean" && __RRWEB_EXCLUDE_IFRAME__ ? new IframeManagerNoop() : new IframeManager({
mirror,
mutationCb: wrappedMutationEmit,
stylesheetManager,
recordCrossOriginIframes,
wrappedEmit
});
for (const plugin of plugins || []) {
if (plugin.getMirror)
plugin.getMirror({
nodeMirror: mirror,
crossOriginIframeMirror: iframeManager.crossOriginIframeMirror,
crossOriginIframeStyleMirror: iframeManager.crossOriginIframeStyleMirror
});
}
const processedNodeManager = new ProcessedNodeManager();
const canvasManager = _getCanvasManager(
getCanvasManager,
{
mirror,
win: window,
mutationCb: (p) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CanvasMutation,
...p
}
}),
recordCanvas,
blockClass,
blockSelector,
unblockSelector,
maxCanvasSize,
sampling: sampling["canvas"],
dataURLOptions,
errorHandler
}
);
const shadowDomManager = typeof __RRWEB_EXCLUDE_SHADOW_DOM__ === "boolean" && __RRWEB_EXCLUDE_SHADOW_DOM__ ? new ShadowDomManagerNoop() : new ShadowDomManager({
mutationCb: wrappedMutationEmit,
scrollCb: wrappedScrollEmit,
bypassOptions: {
onMutation,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskInputOptions,
dataURLOptions,
maskAttributeFn,
maskTextFn,
maskInputFn,
recordCanvas,
inlineImages,
sampling,
slimDOMOptions,
iframeManager,
stylesheetManager,
canvasManager,
keepIframeSrcFn,
processedNodeManager,
ignoreCSSAttributes
},
mirror
});
const takeFullSnapshot2 = (isCheckout = false) => {
if (!recordDOM) {
return;
}
wrappedEmit(
{
type: EventType.Meta,
data: {
href: window.location.href,
width: getWindowWidth(),
height: getWindowHeight()
}
},
isCheckout
);
stylesheetManager.reset();
shadowDomManager.init();
mutationBuffers.forEach((buf) => buf.lock());
const node = snapshot(document, {
mirror,
blockClass,
blockSelector,
unblockSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
inlineStylesheet,
maskAllInputs: maskInputOptions,
maskAttributeFn,
maskInputFn,
maskTextFn,
slimDOM: slimDOMOptions,
dataURLOptions,
recordCanvas,
inlineImages,
onSerialize: (n2) => {
if (isSerializedIframe(n2, mirror)) {
iframeManager.addIframe(n2);
}
if (isSerializedStylesheet(n2, mirror)) {
stylesheetManager.trackLinkElement(n2);
}
if (hasShadowRoot(n2)) {
shadowDomManager.addShadowRoot(n2.shadowRoot, document);
}
},
onIframeLoad: (iframe, childSn) => {
iframeManager.attachIframe(iframe, childSn);
const contentWindow = getIFrameContentWindow(iframe);
if (contentWindow) {
canvasManager.addWindow(contentWindow);
}
shadowDomManager.observeAttachShadow(iframe);
},
onStylesheetLoad: (linkEl, childSn) => {
stylesheetManager.attachLinkElement(linkEl, childSn);
},
onBlockedImageLoad: (_imageEl, serializedNode, { width, height }) => {
wrappedMutationEmit({
adds: [],
removes: [],
texts: [],
attributes: [
{
id: serializedNode.id,
attributes: {
style: {
width: `${width}px`,
height: `${height}px`
}
}
}
]
});
},
keepIframeSrcFn,
ignoreCSSAttributes
});
if (!node) {
return console.warn("Failed to snapshot the document");
}
wrappedEmit({
type: EventType.FullSnapshot,
data: {
node,
initialOffset: getWindowScroll(window)
}
});
mutationBuffers.forEach((buf) => buf.unlock());
if (document.adoptedStyleSheets && document.adoptedStyleSheets.length > 0)
stylesheetManager.adoptStyleSheets(
document.adoptedStyleSheets,
mirror.getId(document)
);
};
_takeFullSnapshot = takeFullSnapshot2;
try {
const handlers = [];
const observe = (doc) => {
return callbackWrapper(initObservers)(
{
onMutation,
mutationCb: wrappedMutationEmit,
mousemoveCb: (positions, source) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source,
positions
}
}),
mouseInteractionCb: (d) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MouseInteraction,
...d
}
}),
scrollCb: wrappedScrollEmit,
viewportResizeCb: (d) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.ViewportResize,
...d
}
}),
inputCb: (v2) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Input,
...v2
}
}),
mediaInteractionCb: (p) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MediaInteraction,
...p
}
}),
styleSheetRuleCb: (r2) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleSheetRule,
...r2
}
}),
styleDeclarationCb: (r2) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleDeclaration,
...r2
}
}),
canvasMutationCb: wrappedCanvasMutationEmit,
fontCb: (p) => wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Font,
...p
}
}),
selectionCb: (p) => {
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Selection,
...p
}
});
},
customElementCb: (c2) => {
wrappedEmit({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.CustomElement,
...c2
}
});
},
blockClass,
ignoreClass,
ignoreSelector,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
unmaskTextSelector,
maskInputOptions,
inlineStylesheet,
sampling,
recordDOM,
recordCanvas,
inlineImages,
userTriggeredOnInput,
collectFonts,
doc,
maskAttributeFn,
maskInputFn,
maskTextFn,
keepIframeSrcFn,
blockSelector,
unblockSelector,
slimDOMOptions,
dataURLOptions,
mirror,
iframeManager,
stylesheetManager,
shadowDomManager,
processedNodeManager,
canvasManager,
ignoreCSSAttributes,
plugins: plugins?.filter((p) => p.observer)?.map((p) => ({
observer: p.observer,
options: p.options,
callback: (payload) => wrappedEmit({
type: EventType.Plugin,
data: {
plugin: p.name,
payload
}
})
})) || []
},
{}
);
};
iframeManager.addLoadListener((iframeEl) => {
try {
handlers.push(observe(iframeEl.contentDocument));
} catch (error) {
console.warn(error);
}
});
const init = () => {
takeFullSnapshot2();
handlers.push(observe(document));
};
if (document.readyState === "interactive" || document.readyState === "complete") {
init();
} else {
handlers.push(
on("DOMContentLoaded", () => {
wrappedEmit({
type: EventType.DomContentLoaded,
data: {}
});
if (recordAfter === "DOMContentLoaded") init();
})
);
handlers.push(
on(
"load",
() => {
wrappedEmit({
type: EventType.Load,
data: {}
});
if (recordAfter === "load") init();
},
window
)
);
}
return () => {
handlers.forEach((h) => h());
processedNodeManager.destroy();
_takeFullSnapshot = void 0;
unregisterErrorHandler();
};
} catch (error) {
console.warn(error);
}
}
function takeFullSnapshot(isCheckout) {
if (!_takeFullSnapshot) {
throw new Error("please take full snapshot after start recording");
}
_takeFullSnapshot(isCheckout);
}
record.mirror = mirror;
record.takeFullSnapshot = takeFullSnapshot;
function _getCanvasManager(getCanvasManagerFn, options) {
try {
return getCanvasManagerFn ? getCanvasManagerFn(options) : new CanvasManagerNoop();
} catch {
console.warn("Unable to initialize CanvasManager");
return new CanvasManagerNoop();
}
}
var n;
!(function(t2) {
t2[t2.NotStarted = 0] = "NotStarted", t2[t2.Running = 1] = "Running", t2[t2.Stopped = 2] = "Stopped";
})(n || (n = {}));
const ReplayEventTypeIncrementalSnapshot = 3;
const ReplayEventTypeCustom = 5;
function timestampToMs(timestamp) {
const isMs = timestamp > 9999999999;
return isMs ? timestamp : timestamp * 1e3;
}
function timestampToS(timestamp) {
const isMs = timestamp > 9999999999;
return isMs ? timestamp / 1e3 : timestamp;
}
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(() => {
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) * 1e3,
data: {
tag: "breadcrumb",
// normalize to max. 10 depth and 1_000 properties per object
payload: normalize(breadcrumb, 10, 1e3)
}
});
return breadcrumb.category === "console";
});
}
const INTERACTIVE_SELECTOR = "button,a";
function getClosestInteractive(element) {
const closestInteractive = element.closest(INTERACTIVE_SELECTOR);
return closestInteractive || element;
}
function getClickTargetNode(event) {
const target = getTargetNode(event);
if (!target || !(target instanceof Element)) {
return target;
}
return getClosestInteractive(target);
}
function getTargetNode(event) {
if (isEventWithTarget(event)) {
return event.target;
}
return event;
}
function isEventWithTarget(event) {
return isObjectLike(event) && "target" in event;
}
let handlers;
function onWindowOpen(cb) {
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 {
}
}
return originalWindowOpen.apply(WINDOW, args);
};
});
}
const IncrementalMutationSources = /* @__PURE__ */ new Set([
IncrementalSource.Mutation,
IncrementalSource.StyleSheetRule,
IncrementalSource.StyleDeclaration,
IncrementalSource.AdoptedStyleSheet,
IncrementalSource.CanvasMutation,
IncrementalSource.Selection,
IncrementalSource.MediaInteraction
]);
function handleClick(clickDetector, clickBreadcrumb, node) {
clickDetector.handleClick(clickBreadcrumb, node);
}
class ClickDetector {
constructor(replay, slowClickConfig, _addBreadcrumbEvent = addBreadcrumbEvent) {
this._lastMutation = 0;
this._lastScroll = 0;
this._clicks = [];
this._timeout = slowClickConfig.timeout / 1e3;
this._threshold = slowClickConfig.threshold / 1e3;
this._scrollTimeout = slowClickConfig.scrollTimeout / 1e3;
this._replay = replay;
this._ignoreSelector = slowClickConfig.ignoreSelector;
this._addBreadcrumbEvent = _addBreadcrumbEvent;
}
/** Register click detection handlers on mutation or scroll. */
addListeners() {
const cleanupWindowOpen = onWindowOpen(() => {
this._lastMutation = nowInSeconds();
});
this._teardown = () => {
cleanupWindowOpen();
this._clicks = [];
this._lastMutation = 0;
this._lastScroll = 0;
};
}
/** Clean up listeners. */
removeListeners() {
if (this._teardown) {
this._teardown();
}
if (this._checkClickTimeout) {
clearTimeout(this._checkClickTimeout);
}
}
/** @inheritDoc */
handleClick(breadcrumb, node) {
if (ignoreElement(node, this._ignoreSelector) || !isClickBreadcrumb(breadcrumb)) {
return;
}
const newClick = {
timestamp: timestampToS(breadcrumb.timestamp),
clickBreadcrumb: breadcrumb,
// Set this to 0 so we know it originates from the click breadcrumb
clickCount: 0,
node
};
if (this._clicks.some((click) => click.node === newClick.node && Math.abs(click.timestamp - newClick.timestamp) < 1)) {
return;
}
this._clicks.push(newClick);
if (this._clicks.length === 1) {
this._scheduleCheckClicks();
}
}
/** @inheritDoc */
registerMutation(timestamp = Date.now()) {
this._lastMutation = timestampToS(timestamp);
}
/** @inheritDoc */
registerScroll(timestamp = Date.now()) {
this._lastScroll = timestampToS(timestamp);
}
/** @inheritDoc */
registerClick(element) {
const node = getClosestInteractive(element);
this._handleMultiClick(node);
}
/** Count multiple clicks on elements. */
_handleMultiClick(node) {
this._getClicks(node).forEach((click) => {
click.clickCount++;
});
}
/** Get all pending clicks for a given node. */
_getClicks(node) {
return this._clicks.filter((click) => click.node === node);
}
/** 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 : void 0;
}
if (!click.scrollAfter && this._lastScroll) {
click.scrollAfter = click.timestamp <= this._lastScroll ? this._lastScroll - click.timestamp : void 0;
}
if (click.timestamp + this._timeout <= now) {
timedOutClicks.push(click);
}
});
for (const click of timedOutClicks) {
const pos = this._clicks.indexOf(click);
if (pos > -1) {
this._generateBreadcrumbs(click);
this._clicks.splice(pos, 1);
}
}
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._scrollTimeout;
const hadMutation = click.mutationAfter && click.mutationAfter <= this._threshold;
const isSlowClick = !hadScroll && !hadMutation;
const { clickCount, clickBreadcrumb } = click;
if (isSlowClick) {
const timeAfterClickMs = Math.min(click.mutationAfter || this._timeout, this._timeout) * 1e3;
const endReason = timeAfterClickMs < this._timeout * 1e3 ? "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;
}
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() {
if (this._checkClickTimeout) {
clearTimeout(this._checkClickTimeout);
}
this._checkClickTimeout = setTimeout$3(() => this._checkClicks(), 1e3);
}
}
const SLOW_CLICK_TAGS = ["A", "BUTTON", "INPUT"];
function ignoreElement(node, ignoreSelector) {
if (!SLOW_CLICK_TAGS.includes(node.tagName)) {
return true;
}
if (node.tagName === "INPUT" && !["submit", "button"].includes(node.getAttribute("type") || "")) {
return true;
}
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);
}
function nowInSeconds() {
return Date.now() / 1e3;
}
function updateClickDetectorForRecordingEvent(clickDetector, event) {
try {
if (!isIncrementalEvent(event)) {
return;
}
const { source } = event.data;
if (IncrementalMutationSources.has(source)) {
clickDetector.registerMutation(event.timestamp);
}
if (source === IncrementalSource.Scroll) {
clickDetector.registerScroll(event.timestamp);
}
if (isIncrementalMouseInteraction(event)) {
const { type, id } = event.data;
const node = record.mirror.getNode(id);
if (node instanceof HTMLElement && type === MouseInteractions.Click) {
clickDetector.registerClick(node);
}
}
} catch {
}
}
function isIncrementalEvent(event) {
return event.type === ReplayEventTypeIncrementalSnapshot;
}
function isIncrementalMouseInteraction(event) {
return event.data.source === IncrementalSource.MouseInteraction;
}
function createBreadcrumb(breadcrumb) {
return {
timestamp: Date.now() / 1e3,
type: "default",
...breadcrumb
};
}
var NodeType = /* @__PURE__ */ ((NodeType2) => {
NodeType2[NodeType2["Document"] = 0] = "Document";
NodeType2[NodeType2["DocumentType"] = 1] = "DocumentType";
NodeType2[NodeType2["Element"] = 2] = "Element";
NodeType2[NodeType2["Text"] = 3] = "Text";
NodeType2[NodeType2["CDATA"] = 4] = "CDATA";
NodeType2[NodeType2["Comment"] = 5] = "Comment";
return NodeType2;
})(NodeType || {});
const ATTRIBUTES_TO_RECORD = /* @__PURE__ */ new Set([
"id",
"class",
"aria-label",
"role",
"name",
"alt",
"title",
"data-test-id",
"data-testid",
"disabled",
"aria-disabled",
"data-sentry-component"
]);
function getAttributesToRecord(attributes) {
const obj = {};
if (!attributes["data-sentry-component"] && attributes["data-sentry-element"]) {
attributes["data-sentry-component"] = attributes["data-sentry-element"];
}
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 : void 0;
if (isClick && replay.clickDetector && event?.target && !event.altKey && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
handleClick(
replay.clickDetector,
result,
getClickTargetNode(handlerData.event)
);
}
addBreadcrumbEvent(replay, result);
};
};
function getBaseDomBreadcrumb(target, message) {
const nodeId = record.mirror.getId(target);
const node = nodeId && record.mirror.getNode(nodeId);
const meta = node && record.mirror.getMeta(node);
const element = meta && isElement(meta) ? meta : null;
return {
message,
data: element ? {
nodeId,
node: {
id: nodeId,
tagName: element.tagName,
textContent: Array.from(element.childNodes).map((node2) => node2.type === NodeType.Text && node2.textContent).filter(Boolean).map((text) => text.trim()).join(""),
attributes: getAttributesToRecord(element.attributes)
}
} : {}
};
}
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;
try {
target = isClick ? getClickTargetNode(handlerData.event) : getTargetNode(handlerData.event);
message = htmlTreeAsString(target, { maxStringLength: 200 }) || "<unknown>";
} catch {
message = "<unknown>";
}
return { target, message };
}
function isElement(node) {
return node.type === NodeType.Element;
}
function handleKeyboardEvent(replay, event) {
if (!replay.isEnabled()) {
return;
}
replay.updateUserActivity();
const breadcrumb = getKeyboardBreadcrumb(event);
if (!breadcrumb) {
return;
}
addBreadcrumbEvent(replay, breadcrumb);
}
function getKeyboardBreadcrumb(event) {
const { metaKey, shiftKey, ctrlKey, altKey, key, target } = event;
if (!target || isInputElement(target) || !key) {
return null;
}
const hasModifierKey = metaKey || ctrlKey || altKey;
const isCharacterKey = key.length === 1;
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 ENTRY_TYPES = {
// @ts-expect-error TODO: entry type does not fit the create* functions entry type
resource: createResourceEntry,
paint: createPaintEntry,
// @ts-expect-error TODO: entry type does not fit the create* functions entry type
navigation: createNavigationEntry
};
function webVitalHandler(getter, replay) {
return ({ metric }) => void replay.replayPerformanceEntries.push(getter(metric));
}
function createPerformanceEntries(entries) {
return entries.map(createPerformanceEntry).filter(Boolean);
}
function createPerformanceEntry(entry) {
const entryType = ENTRY_TYPES[entry.entryType];
if (!entryType) {
return null;
}
return entryType(entry);
}
function getAbsoluteTime(time) {
return ((browserPerformanceTimeOrigin() || WINDOW.performance.timeOrigin) + time) / 1e3;
}
function createPaintEntry(entry) {
const { duration, entryType, name, startTime } = entry;
const start = getAbsoluteTime(startTime);
return {
type: entryType,
name,
start,
end: start + duration,
data: void 0
};
}
function createNavigationEntry(entry) {
const {
entryType,
name,
decodedBodySize,
duration,
domComplete,
encodedBodySize,
domContentLoadedEventStart,
domContentLoadedEventEnd,
domInteractive,
loadEventStart,
loadEventEnd,
redirectCount,
startTime,
transferSize,
type
} = entry;
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;
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 getLargestContentfulPaint(metric) {
const lastEntry = metric.entries[metric.entries.length - 1];
const node = lastEntry?.element ? [lastEntry.element] : void 0;
return getWebVital(metric, "largest-contentful-paint", node);
}
function isLayoutShift(entry) {
return entry.sources !== void 0;
}
function getCumulativeLayoutShift(metric) {
const layoutShifts = [];
const nodes = [];
for (const entry of metric.entries) {
if (isLayoutShift(entry)) {
const nodeIds = [];
for (const source of entry.sources) {
if (source.node) {
nodes.push(source.node);
const nodeId = record.mirror.getId(source.node);
if (nodeId) {
nodeIds.push(nodeId);
}
}
}
layoutShifts.push({ value: entry.value, nodeIds: nodeIds.length ? nodeIds : void 0 });
}
}
return getWebVital(metric, "cumulative-layout-shift", nodes, layoutShifts);
}
function getInteractionToNextPaint(metric) {
const lastEntry = metric.entries[metric.entries.length - 1];
const node = lastEntry?.target ? [lastEntry.target] : void 0;
return getWebVital(metric, "interaction-to-next-paint", node);
}
function getWebVital(metric, name, nodes, attributions) {
const value = metric.value;
const rating = metric.rating;
const end = getAbsoluteTime(value);
return {
type: "web-vital",
name,
start: end,
end,
data: {
value,
size: value,
rating,
nodeIds: nodes ? nodes.map((node) => record.mirror.getId(node)) : void 0,
attributions
}
};
}
function setupPerformanceObserver(replay) {
function addPerformanceEntry(entry) {
if (!replay.performanceEntries.includes(entry)) {
replay.performanceEntries.push(entry);
}
}
function onEntries({ entries }) {
entries.forEach(addPerformanceEntry);
}
const clearCallbacks = [];
["navigation", "paint", "resource"].forEach((type) => {
clearCallbacks.push(addPerformanceInstrumentationHandler(type, onEntries));
});
clearCallbacks.push(
addLcpInstrumentationHandler(webVitalHandler(getLargestContentfulPaint, replay)),
addClsInstrumentationHandler(webVitalHandler(getCumulativeLayoutShift, replay)),
addInpInstrumentationHandler(webVitalHandler(getInteractionToNextPaint, replay))
);
return () => {
clearCallbacks.forEach((clearCallback) => clearCallback());
};
}
const DEBUG_BUILD = (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__);
const t = `var t=Uint8Array,n=Uint16Array,r=Int32Array,e=new t([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,0,0,0]),i=new t([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,0,0]),s=new t([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),a=function(t,e){for(var i=new n(31),s=0;s<31;++s)i[s]=e+=1<<t[s-1];var a=new r(i[30]);for(s=1;s<30;++s)for(var o=i[s];o<i[s+1];++o)a[o]=o-i[s]<<5|s;return{b:i,r:a}},o=a(e,2),h=o.b,f=o.r;h[28]=258,f[258]=28;for(var l=a(i,0).r,u=new n(32768),c=0;c<32768;++c){var v=(43690&c)>>1|(21845&c)<<1;v=(61680&(v=(52428&v)>>2|(13107&v)<<2))>>4|(3855&v)<<4,u[c]=((65280&v)>>8|(255&v)<<8)>>1}var d=function(t,r,e){for(var i=t.length,s=0,a=new n(r);s<i;++s)t[s]&&++a[t[s]-1];var o,h=new n(r);for(s=1;s<r;++s)h[s]=h[s-1]+a[s-1]<<1;if(e){o=new n(1<<r);var f=15-r;for(s=0;s<i;++s)if(t[s])for(var l=s<<4|t[s],c=r-t[s],v=h[t[s]-1]++<<c,d=v|(1<<c)-1;v<=d;++v)o[u[v]>>f]=l}else for(o=new n(i),s=0;s<i;++s)t[s]&&(o[s]=u[h[t[s]-1]++]>>15-t[s]);return o},p=new t(288);for(c=0;c<144;++c)p[c]=8;for(c=144;c<256;++c)p[c]=9;for(c=256;c<280;++c)p[c]=7;for(c=280;c<288;++c)p[c]=8;var g=new t(32);for(c=0;c<32;++c)g[c]=5;var w=d(p,9,0),y=d(g,5,0),m=function(t){return(t+7)/8|0},b=function(n,r,e){return(null==e||e>n.length)&&(e=n.length),new t(n.subarray(r,e))},M=["unexpected EOF","invalid block type","invalid length/literal","invalid distance","stream finished","no stream handler",,"no callback","invalid UTF-8 data","extra field too long","date not in range 1980-2099","filename too long","stream finishing","invalid zip data"],E=function(t,n,r){var e=new Error(n||M[t]);if(e.code=t,Error.captureStackTrace&&Error.captureStackTrace(e,E),!r)throw e;return e},z=function(t,n,r){r<<=7&n;var e=n/8|0;t[e]|=r,t[e+1]|=r>>8},_=function(t,n,r){r<<=7&n;var e=n/8|0;t[e]|=r,t[e+1]|=r>>8,t[e+2]|=r>>16},x=function(r,e){for(var i=[],s=0;s<r.length;++s)r[s]&&i.push({s:s,f:r[s]});var a=i.length,o=i.slice();if(!a)return{t:F,l:0};if(1==a){var h=new t(i[0].s+1);return h[i[0].s]=1,{t:h,l:1}}i.sort(function(t,n){return t.f-n.f}),i.push({s:-1,f:25001});var f=i[0],l=i[1],u=0,c=1,v=2;for(i[0]={s:-1,f:f.f+l.f,l:f,r:l};c!=a-1;)f=i[i[u].f<i[v].f?u++:v++],l=i[u!=c&&i[u].f<i[v].f?u++:v++],i[c++]={s:-1,f:f.f+l.f,l:f,r:l};var d=o[0].s;for(s=1;s<a;++s)o[s].s>d&&(d=o[s].s);var p=new n(d+1),g=A(i[c-1],p,0);if(g>e){s=0;var w=0,y=g-e,m=1<<y;for(o.sort(function(t,n){return p[n.s]-p[t.s]||t.f-n.f});s<a;++s){var b=o[s].s;if(!(p[b]>e))break;w+=m-(1<<g-p[b]),p[b]=e}for(w>>=y;w>0;){var M=o[s].s;p[M]<e?w-=1<<e-p[M]++-1:++s}for(;s>=0&&w;--s){var E=o[s].s;p[E]==e&&(--p[E],++w)}g=e}return{t:new t(p),l:g}},A=function(t,n,r){return-1==t.s?Math.max(A(t.l,n,r+1),A(t.r,n,r+1)):n[t.s]=r},D=function(t){for(var r=t.length;r&&!t[--r];);for(var e=new n(++r),i=0,s=t[0],a=1,o=function(t){e[i++]=t},h=1;h<=r;++h)if(t[h]==s&&h!=r)++a;else{if(!s&&a>2){for(;a>138;a-=138)o(32754);a>2&&(o(a>10?a-11<<5|28690:a-3<<5|12305),a=0)}else if(a>3){for(o(s),--a;a>6;a-=6)o(8304);a>2&&(o(a-3<<5|8208),a=0)}for(;a--;)o(s);a=1,s=t[h]}return{c:e.subarray(0,i),n:r}},T=function(t,n){for(var r=0,e=0;e<n.length;++e)r+=t[e]*n[e];return r},k=function(t,n,r){var e=r.length,i=m(n+2);t[i]=255&e,t[i+1]=e>>8,t[i+2]=255^t[i],t[i+3]=255^t[i+1];for(var s=0;s<e;++s)t[i+s+4]=r[s];return 8*(i+4+e)},U=function(t,r,a,o,h,f,l,u,c,v,m){z(r,m++,a),++h[256];for(var b=x(h,15),M=b.t,E=b.l,A=x(f,15),U=A.t,C=A.l,F=D(M),I=F.c,S=F.n,L=D(U),O=L.c,j=L.n,q=new n(19),B=0;B<I.length;++B)++q[31&I[B]];for(B=0;B<O.length;++B)++q[31&O[B]];for(var G=x(q,7),H=G.t,J=G.l,K=19;K>4&&!H[s[K-1]];--K);var N,P,Q,R,V=v+5<<3,W=T(h,p)+T(f,g)+l,X=T(h,M)+T(f,U)+l+14+3*K+T(q,H)+2*q[16]+3*q[17]+7*q[18];if(c>=0&&V<=W&&V<=X)return k(r,m,t.subarray(c,c+v));if(z(r,m,1+(X<W)),m+=2,X<W){N=d(M,E,0),P=M,Q=d(U,C,0),R=U;var Y=d(H,J,0);z(r,m,S-257),z(r,m+5,j-1),z(r,m+10,K-4),m+=14;for(B=0;B<K;++B)z(r,m+3*B,H[s[B]]);m+=3*K;for(var Z=[I,O],$=0;$<2;++$){var tt=Z[$];for(B=0;B<tt.length;++B){var nt=31&tt[B];z(r,m,Y[nt]),m+=H[nt],nt>15&&(z(r,m,tt[B]>>5&127),m+=tt[B]>>12)}}}else N=w,P=p,Q=y,R=g;for(B=0;B<u;++B){var rt=o[B];if(rt>255){_(r,m,N[(nt=rt>>18&31)+257]),m+=P[nt+257],nt>7&&(z(r,m,rt>>23&31),m+=e[nt]);var et=31&rt;_(r,m,Q[et]),m+=R[et],et>3&&(_(r,m,rt>>5&8191),m+=i[et])}else _(r,m,N[rt]),m+=P[rt]}return _(r,m,N[256]),m+P[256]},C=new r([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),F=new t(0),I=function(){for(var t=new Int32Array(256),n=0;n<256;++n){for(var r=n,e=9;--e;)r=(1&r&&-306674912)^r>>>1;t[n]=r}return t}(),S=function(){var t=1,n=0;return{p:function(r){for(var e=t,i=n,s=0|r.length,a=0;a!=s;){for(var o=Math.min(a+2655,s);a<o;++a)i+=e+=r[a];e=(65535&e)+15*(e>>16),i=(65535&i)+15*(i>>16)}t=e,n=i},d:function(){return(255&(t%=65521))<<24|(65280&t)<<8|(255&(n%=65521))<<8|n>>8}}},L=function(s,a,o,h,u){if(!u&&(u={l:1},a.dictionary)){var c=a.dictionary.subarray(-32768),v=new t(c.length+s.length);v.set(c),v.set(s,c.length),s=v,u.w=c.length}return function(s,a,o,h,u,c){var v=c.z||s.length,d=new t(h+v+5*(1+Math.ceil(v/7e3))+u),p=d.subarray(h,d.length-u),g=c.l,w=7&(c.r||0);if(a){w&&(p[0]=c.r>>3);for(var y=C[a-1],M=y>>13,E=8191&y,z=(1<<o)-1,_=c.p||new n(32768),x=c.h||new n(z+1),A=Math.ceil(o/3),D=2*A,T=function(t){return(s[t]^s[t+1]<<A^s[t+2]<<D)&z},F=new r(25e3),I=new n(288),S=new n(32),L=0,O=0,j=c.i||0,q=0,B=c.w||0,G=0;j+2<v;++j){var H=T(j),J=32767&j,K=x[H];if(_[J]=K,x[H]=J,B<=j){var N=v-j;if((L>7e3||q>24576)&&(N>423||!g)){w=U(s,p,0,F,I,S,O,q,G,j-G,w),q=L=O=0,G=j;for(var P=0;P<286;++P)I[P]=0;for(P=0;P<30;++P)S[P]=0}var Q=2,R=0,V=E,W=J-K&32767;if(N>2&&H==T(j-W))for(var X=Math.min(M,N)-1,Y=Math.min(32767,j),Z=Math.min(258,N);W<=Y&&--V&&J!=K;){if(s[j+Q]==s[j+Q-W]){for(var $=0;$<Z&&s[j+$]==s[j+$-W];++$);if($>Q){if(Q=$,R=W,$>X)break;var tt=Math.min(W,$-2),nt=0;for(P=0;P<tt;++P){var rt=j-W+P&32767,et=rt-_[rt]&32767;et>nt&&(nt=et,K=rt)}}}W+=(J=K)-(K=_[J])&32767}if(R){F[q++]=268435456|f[Q]<<18|l[R];var it=31&f[Q],st=31&l[R];O+=e[it]+i[st],++I[257+it],++S[st],B=j+Q,++L}else F[q++]=s[j],++I[s[j]]}}for(j=Math.max(j,B);j<v;++j)F[q++]=s[j],++I[s[j]];w=U(s,p,g,F,I,S,O,q,G,j-G,w),g||(c.r=7&w|p[w/8|0]<<3,w-=7,c.h=x,c.p=_,c.i=j,c.w=B)}else{for(j=c.w||0;j<v+g;j+=65535){var at=j+65535;at>=v&&(p[w/8|0]=g,at=v),w=k(p,w+1,s.subarray(j,at))}c.i=v}return b(d,0,h+m(w)+u)}(s,null==a.level?6:a.level,null==a.mem?u.l?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(s.length)))):20:12+a.mem,o,h,u)},O=function(t,n,r){for(;r;++n)t[n]=r,r>>>=8},j=function(){function n(n,r){if("function"==typeof n&&(r=n,n={}),this.ondata=r,this.o=n||{},this.s={l:0,i:32768,w:32768,z:32768},this.b=new t(98304),this.o.dictionary){var e=this.o.dictionary.subarray(-32768);this.b.set(e,32768-e.length),this.s.i=32768-e.length}}return n.prototype.p=function(t,n){this.ondata(L(t,this.o,0,0,this.s),n)},n.prototype.push=function(n,r){this.ondata||E(5),this.s.l&&E(4);var e=n.length+this.s.z;if(e>this.b.length){if(e>2*this.b.length-32768){var i=new t(-32768&e);i.set(this.b.subarray(0,this.s.z)),this.b=i}var s=this.b.length-this.s.z;this.b.set(n.subarray(0,s),this.s.z),this.s.z=this.b.length,this.p(this.b,!1),this.b.set(this.b.subarray(-32768)),this.b.set(n.subarray(s),32768),this.s.z=n.length-s+32768,this.s.i=32766,this.s.w=32768}else this.b.set(n,this.s.z),this.s.z+=n.length;this.s.l=1&r,(this.s.z>this.s.w+8191||r)&&(this.p(this.b,r||!1),this.s.w=this.s.i,this.s.i-=2)},n.prototype.flush=function(){this.ondata||E(5),this.s.l&&E(4),this.p(this.b,!1),this.s.w=this.s.i,this.s.i-=2},n}();function q(t,n){n||(n={});var r=function(){var t=-1;return{p:function(n){for(var r=t,e=0;e<n.length;++e)r=I[255&r^n[e]]^r>>>8;t=r},d:function(){return~t}}}(),e=t.length;r.p(t);var i,s=L(t,n,10+((i=n).filename?i.filename.length+1:0),8),a=s.length;return function(t,n){var r=n.filename;if(t[0]=31,t[1]=139,t[2]=8,t[8]=n.level<2?4:9==n.level?2:0,t[9]=3,0!=n.mtime&&O(t,4,Math.floor(new Date(n.mtime||Date.now())/1e3)),r){t[3]=8;for(var e=0;e<=r.length;++e)t[e+10]=r.charCodeAt(e)}}(s,n),O(s,a-8,r.d()),O(s,a-4,e),s}var B=function(){function t(t,n){this.c=S(),this.v=1,j.call(this,t,n)}return t.prototype.push=function(t,n){this.c.p(t),j.prototype.push.call(this,t,n)},t.prototype.p=function(t,n){var r=L(t,this.o,this.v&&(this.o.dictionary?6:2),n&&4,this.s);this.v&&(function(t,n){var r=n.level,e=0==r?0:r<6?1:9==r?3:2;if(t[0]=120,t[1]=e<<6|(n.dictionary&&32),t[1]|=31-(t[0]<<8|t[1])%31,n.dictionary){var i=S();i.p(n.dictionary),O(t,2,i.d())}}(r,this.o),this.v=0),n&&O(r,r.length-4,this.c.d()),this.ondata(r,n)},t.prototype.flush=function(){j.prototype.flush.call(this)},t}(),G="undefined"!=typeof TextEncoder&&new TextEncoder,H="undefined"!=typeof TextDecoder&&new TextDecoder;try{H.decode(F,{stream:!0})}catch(t){}var J=function(){function t(t){this.ondata=t}return t.prototype.push=function(t,n){this.ondata||E(5),this.d&&E(4),this.ondata(K(t),this.d=n||!1)},t}();function K(n,r){if(G)return G.encode(n);for(var e=n.length,i=new t(n.length+(n.length>>1)),s=0,a=function(t){i[s++]=t},o=0;o<e;++o){if(s+5>i.length){var h=new t(s+8+(e-o<<1));h.set(i),i=h}var f=n.charCodeAt(o);f<128||r?a(f):f<2048?(a(192|f>>6),a(128|63&f)):f>55295&&f<57344?(a(240|(f=65536+(1047552&f)|1023&n.charCodeAt(++o))>>18),a(128|f>>12&63),a(128|f>>6&63),a(128|63&f)):(a(224|f>>12),a(128|f>>6&63),a(128|63&f))}return b(i,0,s)}const N=new class{constructor(){this._init()}clear(){this._init()}addEvent(t){if(!t)throw new Error("Adding invalid event");const n=this._hasEvents?",":"";this.stream.push(n+t),this._hasEvents=!0}finish(){this.stream.push("]",!0);const t=function(t){let n=0;for(const r of t)n+=r.length;const r=new Uint8Array(n);for(let n=0,e=0,i=t.length;n<i;n++){const i=t[n];r.set(i,e),e+=i.length}return r}(this._deflatedData);return this._init(),t}_init(){this._hasEvents=!1,this._deflatedData=[],this.deflate=new B,this.deflate.ondata=(t,n)=>{this._deflatedData.push(t)},this.stream=new J((t,n)=>{this.deflate.push(t,n)}),this.stream.push("[")}},P={clear:()=>{N.clear()},addEvent:t=>N.addEvent(t),finish:()=>N.finish(),compress:t=>function(t){return q(K(t))}(t)};addEventListener("message",function(t){const n=t.data.method,r=t.data.id,e=t.data.arg;if(n in P&&"function"==typeof P[n])try{const t=P[n](e);postMessage({id:r,method:n,success:!0,response:t})}catch(t){postMessage({id:r,method:n,success:!1,response:t.message}),console.error(t)}}),postMessage({id:void 0,method:"init",success:!0,response:void 0});`;
function e() {
const e2 = new Blob([t], { type: "text/javascript" });
return URL.createObjectURL(e2);
}
const CONSOLE_LEVELS = ["log", "warn", "error"];
const PREFIX = "[Replay] ";
function _addBreadcrumb(message, level = "info") {
addBreadcrumb(
{
category: "console",
data: {
logger: "replay"
},
level,
message: `${PREFIX}${message}`
},
{ level }
);
}
function makeReplayDebugLogger() {
let _capture = false;
let _trace = false;
const _debug = {
exception: () => void 0,
infoTick: () => void 0,
setConfig: (opts) => {
_capture = !!opts.captureExceptions;
_trace = !!opts.traceInternals;
}
};
if (DEBUG_BUILD) {
CONSOLE_LEVELS.forEach((name) => {
_debug[name] = (...args) => {
debug$1[name](PREFIX, ...args);
if (_trace) {
_addBreadcrumb(args.join(""), severityLevelFromString(name));
}
};
});
_debug.exception = (error, ...message) => {
if (message.length && _debug.error) {
_debug.error(...message);
}
debug$1.error(PREFIX, error);
if (_capture) {
captureException(error, {
mechanism: {
handled: true,
type: "auto.function.replay.debug"
}
});
} else if (_trace) {
_addBreadcrumb(error, "error");
}
};
_debug.infoTick = (...args) => {
debug$1.log(PREFIX, ...args);
if (_trace) {
setTimeout(() => _addBreadcrumb(args[0]), 0);
}
};
} else {
CONSOLE_LEVELS.forEach((name) => {
_debug[name] = () => void 0;
});
}
return _debug;
}
const debug = makeReplayDebugLogger();
class EventBufferSizeExceededError extends Error {
constructor() {
super(`Event buffer exceeded maximum size of ${REPLAY_MAX_EVENT_BUFFER_SIZE}.`);
}
}
class WorkerDestroyedError extends Error {
constructor() {
super("Worker destroyed");
}
}
class EventBufferArray {
constructor() {
this.events = [];
this._totalSize = 0;
this.hasCheckout = false;
this.waitForCheckout = false;
}
/** @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) => {
const eventsRet = this.events;
this.clear();
resolve(JSON.stringify(eventsRet));
});
}
/** @inheritdoc */
clear() {
this.events = [];
this._totalSize = 0;
this.hasCheckout = false;
}
/** @inheritdoc */
getEarliestTimestamp() {
let ts = null;
for (const { timestamp } of this.events) {
if (ts === null || timestamp < ts) ts = timestamp;
}
return ts === null ? ts : timestampToMs(ts);
}
}
class WorkerHandler {
constructor(worker) {
this._onMessage = ({ data }) => {
const response = data;
if (typeof response.id !== "number") {
return;
}
const pending = this._pending.get(response.id);
if (!pending || pending.method !== response.method) {
return;
}
this._pending.delete(response.id);
if (!response.success) {
DEBUG_BUILD && debug.error("Error in compression worker: ", response.response);
pending.reject(new Error("Error in compression worker"));
return;
}
pending.resolve(response.response);
};
this._worker = worker;
this._id = 0;
this._pending = /* @__PURE__ */ new Map();
this._worker.addEventListener("message", this._onMessage);
}
/**
* Ensure the worker is ready (or not).
* This will either resolve when the worker is ready, or reject if an error occurred.
*/
ensureReady() {
if (this._ensureReadyPromise) {
return this._ensureReadyPromise;
}
this._ensureReadyPromise = new Promise((resolve, reject) => {
this._worker.addEventListener(
"message",
({ data }) => {
if (data.success) {
resolve();
} else {
DEBUG_BUILD && debug.warn("Received worker message with unsuccessful status", data);
reject(new Error("Received worker message with unsuccessful status"));
}
},
{ once: true }
);
this._worker.addEventListener(
"error",
(error) => {
DEBUG_BUILD && debug.warn("Failed to load Replay compression worker", error);
reject(
new Error(
`Failed to load Replay compression worker: ${error instanceof ErrorEvent && error.message ? error.message : "Unknown error. This can happen due to CSP policy restrictions, network issues, or the worker script failing to load."}`
)
);
},
{ once: true }
);
});
return this._ensureReadyPromise;
}
/**
* Destroy the worker.
*/
destroy() {
DEBUG_BUILD && debug.log("Destroying compression worker");
this._worker.removeEventListener("message", this._onMessage);
this._pending.forEach((pending) => pending.reject(new WorkerDestroyedError()));
this._pending.clear();
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) => {
this._pending.set(id, {
method,
resolve,
reject
});
try {
this._worker.postMessage({ id, method, arg });
} catch (error) {
this._pending.delete(id);
reject(error);
}
});
}
/** Get the current ID and increment it for the next call. */
_getAndIncrementId() {
return this._id++;
}
}
class EventBufferCompressionWorker {
constructor(worker) {
this._worker = new WorkerHandler(worker);
this._earliestTimestamp = null;
this._totalSize = 0;
this.hasCheckout = false;
this.waitForCheckout = false;
}
/** @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 occurred.
*/
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 successfully 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;
this.hasCheckout = false;
this._worker.postMessage("clear").then(null, (e) => {
DEBUG_BUILD && debug.exception(e, 'Sending "clear" message to worker failed', e);
});
}
/** @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;
}
}
class EventBufferProxy {
constructor(worker) {
this._fallback = new EventBufferArray();
this._compression = new EventBufferCompressionWorker(worker);
this._used = this._fallback;
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
}
/** @inheritdoc */
get waitForCheckout() {
return this._used.waitForCheckout;
}
/** @inheritdoc */
get type() {
return this._used.type;
}
/** @inheritDoc */
get hasEvents() {
return this._used.hasEvents;
}
/** @inheritdoc */
get hasCheckout() {
return this._used.hasCheckout;
}
/** @inheritdoc */
set hasCheckout(value) {
this._used.hasCheckout = value;
}
/** @inheritdoc */
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures
set waitForCheckout(value) {
this._used.waitForCheckout = value;
}
/** @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() {
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) {
DEBUG_BUILD && debug.exception(error, "Failed to load the compression worker, falling back to simple buffer");
return;
}
await this._switchToCompressionWorker();
}
/** Switch the used buffer to the compression worker. */
async _switchToCompressionWorker() {
const { events, hasCheckout, waitForCheckout } = this._fallback;
const addEventPromises = [];
for (const event of events) {
addEventPromises.push(this._compression.addEvent(event));
}
this._compression.hasCheckout = hasCheckout;
this._compression.waitForCheckout = waitForCheckout;
this._used = this._compression;
try {
await Promise.all(addEventPromises);
this._fallback.clear();
} catch (error) {
if (error instanceof WorkerDestroyedError) {
return;
}
DEBUG_BUILD && debug.exception(error, "Failed to add events when switching buffers.");
}
}
}
function createEventBuffer({
useCompression,
workerUrl: customWorkerUrl
}) {
if (useCompression && // eslint-disable-next-line no-restricted-globals
window.Worker) {
const worker = _loadWorker(customWorkerUrl);
if (worker) {
return worker;
}
}
DEBUG_BUILD && debug.log("Using simple buffer");
return new EventBufferArray();
}
function _loadWorker(customWorkerUrl) {
try {
const workerUrl = customWorkerUrl || _getWorkerUrl();
if (!workerUrl) {
return;
}
DEBUG_BUILD && debug.log(`Using compression worker${customWorkerUrl ? ` from ${customWorkerUrl}` : ""}`);
const worker = new Worker(workerUrl);
return new EventBufferProxy(worker);
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to create compression worker");
}
}
function _getWorkerUrl() {
if (typeof __SENTRY_EXCLUDE_REPLAY_WORKER__ === "undefined" || !__SENTRY_EXCLUDE_REPLAY_WORKER__) {
return e();
}
return "";
}
function hasSessionStorage() {
try {
return "sessionStorage" in WINDOW && !!WINDOW.sessionStorage;
} catch {
return false;
}
}
function clearSession(replay) {
deleteSession();
replay.session = void 0;
}
function deleteSession() {
if (!hasSessionStorage()) {
return;
}
try {
WINDOW.sessionStorage.removeItem(REPLAY_SESSION_KEY);
} catch {
}
}
function isSampled(sampleRate) {
if (sampleRate === void 0) {
return false;
}
return Math.random() < sampleRate;
}
function saveSession(session) {
if (!hasSessionStorage()) {
return;
}
try {
WINDOW.sessionStorage.setItem(REPLAY_SESSION_KEY, JSON.stringify(session));
} catch {
}
}
function makeSession(session) {
const now = Date.now();
const id = session.id || uuid4();
const started = session.started || now;
const lastActivity = session.lastActivity || now;
const segmentId = session.segmentId || 0;
const sampled = session.sampled;
const previousSessionId = session.previousSessionId;
const dirty = session.dirty || false;
return {
id,
started,
lastActivity,
segmentId,
sampled,
previousSessionId,
dirty
};
}
function getSessionSampleType(sessionSampleRate, allowBuffering) {
return isSampled(sessionSampleRate) ? "session" : allowBuffering ? "buffer" : false;
}
function createSession({ sessionSampleRate, allowBuffering, stickySession = false }, { previousSessionId } = {}) {
const sampled = getSessionSampleType(sessionSampleRate, allowBuffering);
const session = makeSession({
sampled,
previousSessionId
});
if (stickySession) {
saveSession(session);
}
return session;
}
function fetchSession() {
if (!hasSessionStorage()) {
return null;
}
try {
const sessionStringFromStorage = WINDOW.sessionStorage.getItem(REPLAY_SESSION_KEY);
if (!sessionStringFromStorage) {
return null;
}
const sessionObj = JSON.parse(sessionStringFromStorage);
DEBUG_BUILD && debug.infoTick("Loading existing session");
return makeSession(sessionObj);
} catch {
return null;
}
}
function isExpired(initialTime, expiry, targetTime = +/* @__PURE__ */ new Date()) {
if (initialTime === null || expiry === void 0 || expiry < 0) {
return true;
}
if (expiry === 0) {
return false;
}
return initialTime + expiry <= targetTime;
}
function isSessionExpired(session, {
maxReplayDuration,
sessionIdleExpire,
targetTime = Date.now()
}) {
return (
// First, check that maximum session length has not been exceeded
isExpired(session.started, maxReplayDuration, 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, sessionIdleExpire, targetTime)
);
}
function shouldRefreshSession(session, { sessionIdleExpire, maxReplayDuration }) {
if (!isSessionExpired(session, { sessionIdleExpire, maxReplayDuration })) {
return false;
}
if (session.sampled === "buffer" && session.segmentId === 0) {
return false;
}
return true;
}
function loadOrCreateSession({
sessionIdleExpire,
maxReplayDuration,
previousSessionId
}, sessionOptions) {
const existingSession = sessionOptions.stickySession && fetchSession();
if (!existingSession) {
DEBUG_BUILD && debug.infoTick("Creating new session");
return createSession(sessionOptions, { previousSessionId });
}
if (!shouldRefreshSession(existingSession, { sessionIdleExpire, maxReplayDuration })) {
return existingSession;
}
DEBUG_BUILD && debug.infoTick("Session in sessionStorage is expired, creating new one...");
return createSession(sessionOptions, { previousSessionId: existingSession.id });
}
function isCustomEvent(event) {
return event.type === EventType.Custom;
}
function addEventSync(replay, event, isCheckout) {
if (!shouldAddEvent(replay, event)) {
return false;
}
_addEvent(replay, event, isCheckout);
return true;
}
function addEvent(replay, event, isCheckout) {
if (!shouldAddEvent(replay, event)) {
return Promise.resolve(null);
}
return _addEvent(replay, event, isCheckout);
}
async function _addEvent(replay, event, isCheckout) {
const { eventBuffer } = replay;
if (!eventBuffer || eventBuffer.waitForCheckout && !isCheckout) {
return null;
}
const isBufferMode = replay.recordingMode === "buffer";
try {
if (isCheckout && isBufferMode) {
eventBuffer.clear();
}
if (isCheckout) {
eventBuffer.hasCheckout = true;
eventBuffer.waitForCheckout = false;
}
const replayOptions = replay.getOptions();
const eventAfterPossibleCallback = maybeApplyCallback(event, replayOptions.beforeAddRecordingEvent);
if (!eventAfterPossibleCallback) {
return;
}
return await eventBuffer.addEvent(eventAfterPossibleCallback);
} catch (error) {
if (!replay.isEnabled()) {
replay.handleException(error);
return null;
}
const isExceeded = error && error instanceof EventBufferSizeExceededError;
const reason = isExceeded ? "eventBufferOverflow" : "eventBufferError";
const client = getClient();
if (client) {
const dropReason = isExceeded ? "buffer_overflow" : "internal_sdk_error";
client.recordDroppedEvent(dropReason, "replay");
}
if (isExceeded && isBufferMode) {
eventBuffer.clear();
eventBuffer.waitForCheckout = true;
return null;
}
replay.handleException(error);
await replay.stop({ reason });
}
}
function shouldAddEvent(replay, event) {
if (!replay.eventBuffer || replay.isPaused() || !replay.isEnabled()) {
return false;
}
const timestampInMs = timestampToMs(event.timestamp);
if (timestampInMs + replay.timeouts.sessionIdlePause < Date.now()) {
return false;
}
if (timestampInMs > replay.getContext().initialTimestamp + replay.getOptions().maxReplayDuration) {
DEBUG_BUILD && debug.infoTick(`Skipping event with timestamp ${timestampInMs} because it is after maxReplayDuration`);
return false;
}
return true;
}
function maybeApplyCallback(event, callback) {
try {
if (typeof callback === "function" && isCustomEvent(event)) {
return callback(event);
}
} catch (error) {
DEBUG_BUILD && debug.exception(error, "An error occurred in the `beforeAddRecordingEvent` callback, skipping the event...");
return null;
}
return event;
}
const MAX_CONTEXT_VALUES = 100;
function addSegmentDetailsToContext(replay, traceId, segmentName) {
const replayContext = replay.getContext();
if (traceId && replayContext.traceIds.size < MAX_CONTEXT_VALUES) {
replayContext.traceIds.add(traceId);
}
if (segmentName && replayContext.segmentNames.size < MAX_CONTEXT_VALUES) {
replayContext.segmentNames.add(segmentName);
}
}
function handleProcessSegmentSpan(replay) {
return (spanJSON) => {
if (!replay.isEnabled()) {
return;
}
addSegmentDetailsToContext(replay, spanJSON.trace_id, spanJSON.name || void 0);
};
}
function isErrorEvent(event) {
return !event.type;
}
function isTransactionEvent(event) {
return event.type === "transaction";
}
function isReplayEvent(event) {
return event.type === "replay_event";
}
function isFeedbackEvent(event) {
return event.type === "feedback";
}
function handleAfterSendEvent(replay) {
return (event, sendResponse) => {
if (!replay.isEnabled() || !isErrorEvent(event) && !isTransactionEvent(event)) {
return;
}
const statusCode = sendResponse.statusCode;
if (!statusCode || statusCode < 200 || statusCode >= 300) {
return;
}
if (isTransactionEvent(event)) {
handleTransactionEvent(replay, event);
return;
}
handleErrorEvent(replay, event);
};
}
function handleTransactionEvent(replay, event) {
addSegmentDetailsToContext(replay, event.contexts?.trace?.trace_id, event.transaction);
}
function handleErrorEvent(replay, event) {
const replayContext = replay.getContext();
if (event.event_id && replayContext.errorIds.size < 100) {
replayContext.errorIds.add(event.event_id);
}
if (replay.recordingMode !== "buffer" || !event.tags?.replayId) {
return;
}
const { beforeErrorSampling } = replay.getOptions();
if (typeof beforeErrorSampling === "function" && !beforeErrorSampling(event)) {
return;
}
setTimeout$3(async () => {
try {
await replay.sendBufferedReplayOrFlush();
} catch (err) {
replay.handleException(err);
}
});
}
function handleBeforeSendEvent(replay) {
return (event) => {
if (!replay.isEnabled() || !isErrorEvent(event)) {
return;
}
handleHydrationError(replay, event);
};
}
function handleHydrationError(replay, event) {
const exceptionValue = event.exception?.values?.[0]?.value;
if (typeof exceptionValue !== "string") {
return;
}
if (
// Only matches errors in production builds of react-dom
// Example https://reactjs.org/docs/error-decoder.html?invariant=423
// With newer React versions, the messages changed to a different website https://react.dev/errors/418
exceptionValue.match(
/(reactjs\.org\/docs\/error-decoder\.html\?invariant=|react\.dev\/errors\/)(418|419|422|423|425)/
) || // Development builds of react-dom
// Error 1: Hydration failed because the initial UI does not match what was rendered on the server.
// Error 2: Text content does not match server-rendered HTML. Warning: Text content did not match.
exceptionValue.match(/(does not match server-rendered HTML|Hydration failed because)/i)
) {
const breadcrumb = createBreadcrumb({
category: "replay.hydrate-error",
data: {
url: getLocationHref()
}
});
addBreadcrumbEvent(replay, breadcrumb);
}
}
function handleBreadcrumbs(replay) {
const client = getClient();
if (!client) {
return;
}
client.on("beforeAddBreadcrumb", (breadcrumb) => beforeAddBreadcrumb(replay, breadcrumb));
}
function beforeAddBreadcrumb(replay, breadcrumb) {
if (!replay.isEnabled() || !isBreadcrumbWithCategory(breadcrumb)) {
return;
}
const result = normalizeBreadcrumb(breadcrumb);
if (result) {
addBreadcrumbEvent(replay, result);
}
}
function normalizeBreadcrumb(breadcrumb) {
if (!isBreadcrumbWithCategory(breadcrumb) || [
// fetch & xhr are handled separately,in handleNetworkBreadcrumbs
"fetch",
"xhr",
// These two are breadcrumbs for emitted sentry events, we don't care about them
"sentry.event",
"sentry.transaction"
].includes(breadcrumb.category) || // We capture UI breadcrumbs separately
breadcrumb.category.startsWith("ui.")) {
return null;
}
if (breadcrumb.category === "console") {
return normalizeConsoleBreadcrumb(breadcrumb);
}
return createBreadcrumb(breadcrumb);
}
function normalizeConsoleBreadcrumb(breadcrumb) {
const args = breadcrumb.data?.arguments;
if (!Array.isArray(args) || args.length === 0) {
return createBreadcrumb(breadcrumb);
}
let isTruncated = false;
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)}\u2026`;
}
return arg;
}
if (typeof arg === "object") {
try {
const normalizedArg = normalize(arg, 7);
const stringified = JSON.stringify(normalizedArg);
if (stringified.length > CONSOLE_ARG_MAX_SIZE) {
isTruncated = true;
return `${JSON.stringify(normalizedArg, null, 2).slice(0, CONSOLE_ARG_MAX_SIZE)}\u2026`;
}
return normalizedArg;
} catch {
}
}
return arg;
});
return createBreadcrumb({
...breadcrumb,
data: {
...breadcrumb.data,
arguments: normalizedArgs,
...isTruncated ? { _meta: { warnings: ["CONSOLE_ARG_TRUNCATED"] } } : {}
}
});
}
function isBreadcrumbWithCategory(breadcrumb) {
return !!breadcrumb.category;
}
function isRrwebError(event, hint) {
if (event.type || !event.exception?.values?.length) {
return false;
}
if (hint.originalException?.__rrweb__) {
return true;
}
return false;
}
function resetReplayIdOnDynamicSamplingContext() {
const dsc = getCurrentScope().getPropagationContext().dsc;
if (dsc) {
delete dsc.replay_id;
}
const activeSpan = getActiveSpan();
if (activeSpan) {
const dsc2 = getDynamicSamplingContextFromSpan(activeSpan);
delete dsc2.replay_id;
}
}
function setReplayIdOnDynamicSamplingContext(replayId) {
const dsc = getCurrentScope().getPropagationContext().dsc;
if (dsc) {
dsc.replay_id = replayId;
}
const activeSpan = getActiveSpan();
if (activeSpan) {
const dsc2 = getDynamicSamplingContextFromSpan(activeSpan);
dsc2.replay_id = replayId;
}
}
function addFeedbackBreadcrumb(replay, event) {
replay.triggerUserActivity();
replay.addUpdate(() => {
if (!event.timestamp) {
return true;
}
replay.throttledAddEvent({
type: EventType.Custom,
timestamp: event.timestamp * 1e3,
data: {
tag: "breadcrumb",
payload: {
timestamp: event.timestamp,
type: "default",
category: "sentry.feedback",
data: {
feedbackId: event.event_id
}
}
}
});
return false;
});
}
function shouldSampleForBufferEvent(replay, event) {
if (replay.recordingMode !== "buffer") {
return false;
}
if (event.message === UNABLE_TO_SEND_REPLAY) {
return false;
}
if (!event.exception || event.type) {
return false;
}
return isSampled(replay.getOptions().errorSampleRate);
}
function handleGlobalEventListener(replay) {
return Object.assign(
(event, hint) => {
if (replay.session && shouldRefreshSession(replay.session, {
maxReplayDuration: replay.getOptions().maxReplayDuration,
sessionIdleExpire: replay.timeouts.sessionIdleExpire
})) {
resetReplayIdOnDynamicSamplingContext();
}
if (!replay.isEnabled() || replay.isPaused()) {
return event;
}
if (isReplayEvent(event)) {
delete event.breadcrumbs;
return event;
}
if (!isErrorEvent(event) && !isTransactionEvent(event) && !isFeedbackEvent(event)) {
return event;
}
const isSessionActive = replay.checkAndHandleExpiredSession();
if (!isSessionActive) {
resetReplayIdOnDynamicSamplingContext();
return event;
}
if (isFeedbackEvent(event)) {
replay.flush();
event.contexts.feedback.replay_id = replay.getSessionId();
addFeedbackBreadcrumb(replay, event);
return event;
}
if (isRrwebError(event, hint) && !replay.getOptions()._experiments.captureExceptions) {
DEBUG_BUILD && debug.log("Ignoring error from rrweb internals", event);
return null;
}
const isErrorEventSampled = shouldSampleForBufferEvent(replay, event);
const shouldTagReplayId = isErrorEventSampled || replay.recordingMode === "session";
if (shouldTagReplayId) {
event.tags = { ...event.tags, replayId: replay.getSessionId() };
}
if (isErrorEventSampled && replay.recordingMode === "buffer" && replay.session?.sampled === "buffer") {
const session = replay.session;
session.dirty = true;
if (replay.getOptions().stickySession) {
saveSession(session);
}
}
return event;
},
{ id: "Replay" }
);
}
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
}
}
});
return typeof response === "string" ? Promise.resolve(null) : response;
});
}
function handleHistory(handlerData) {
const { from, to } = handlerData;
const now = Date.now() / 1e3;
return {
type: "navigation.push",
start: now,
end: now,
name: to,
data: {
previous: from
}
};
}
function handleHistorySpanListener(replay) {
return (handlerData) => {
if (!replay.isEnabled()) {
return;
}
const result = handleHistory(handlerData);
if (result === null) {
return;
}
replay.getContext().urls.push(result.name);
replay.triggerUserActivity();
replay.addUpdate(() => {
createPerformanceSpans(replay, [result]);
return false;
});
};
}
function shouldFilterRequest(replay, url) {
if (DEBUG_BUILD && replay.getOptions()._experiments.traceInternals) {
return false;
}
return isSentryRequestUrl(url, getClient());
}
function addNetworkBreadcrumb(replay, result) {
if (!replay.isEnabled()) {
return;
}
if (result === null) {
return;
}
if (shouldFilterRequest(replay, result.name)) {
return;
}
replay.addUpdate(() => {
createPerformanceSpans(replay, [result]);
return true;
});
}
function getBodySize(body) {
if (!body) {
return void 0;
}
const textEncoder = new TextEncoder();
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;
}
} catch {
}
return void 0;
}
function parseContentLengthHeader(header) {
if (!header) {
return void 0;
}
const size = parseInt(header, 10);
return isNaN(size) ? void 0 : size;
}
function mergeWarning(info, warning) {
if (!info) {
return {
headers: {},
size: void 0,
_meta: {
warnings: [warning]
}
};
}
const newMeta = { ...info._meta };
const existingWarnings = newMeta.warnings || [];
newMeta.warnings = [...existingWarnings, warning];
info._meta = newMeta;
return info;
}
function makeNetworkReplayBreadcrumb(type, data) {
if (!data) {
return null;
}
const { startTimestamp, endTimestamp, url, method, statusCode, request, response } = data;
const result = {
type,
start: startTimestamp / 1e3,
end: endTimestamp / 1e3,
name: url,
data: {
method,
statusCode,
request,
response
}
};
return result;
}
function buildSkippedNetworkRequestOrResponse(bodySize) {
return {
headers: {},
size: bodySize,
_meta: {
warnings: ["URL_SKIPPED"]
}
};
}
function buildNetworkRequestOrResponse(headers, bodySize, body) {
if (!bodySize && Object.keys(headers).length === 0) {
return void 0;
}
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) {
info._meta = {
warnings
};
}
return info;
}
function getAllowedHeaders(headers, allowedHeaders) {
return Object.entries(headers).reduce((filteredHeaders, [key, value]) => {
const normalizedKey = key.toLowerCase();
if (allowedHeaders.includes(normalizedKey) && headers[key]) {
filteredHeaders[normalizedKey] = value;
}
return filteredHeaders;
}, {});
}
function normalizeNetworkBody(body) {
if (!body || typeof body !== "string") {
return {
body
};
}
const exceedsSizeLimit = body.length > NETWORK_BODY_MAX_SIZE;
const isProbablyJson = _strIsProbablyJson(body);
if (exceedsSizeLimit) {
const truncatedBody = body.slice(0, NETWORK_BODY_MAX_SIZE);
if (isProbablyJson) {
return {
body: truncatedBody,
warnings: ["MAYBE_JSON_TRUNCATED"]
};
}
return {
body: `${truncatedBody}\u2026`,
warnings: ["TEXT_TRUNCATED"]
};
}
if (isProbablyJson) {
try {
const jsonBody = JSON.parse(body);
return {
body: jsonBody
};
} catch {
}
}
return {
body
};
}
function _strIsProbablyJson(str) {
const first = str[0];
const last = str[str.length - 1];
return first === "[" && last === "]" || first === "{" && last === "}";
}
function urlMatches(url, urls) {
const fullUrl = getFullUrl(url);
return stringMatchesSomePattern(fullUrl, urls);
}
function getFullUrl(url, baseURI = WINDOW.document.baseURI) {
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith(WINDOW.location.origin)) {
return url;
}
const fixedUrl = new URL(url, baseURI);
if (fixedUrl.origin !== new URL(baseURI).origin) {
return url;
}
const fullUrl = fixedUrl.href;
if (!url.endsWith("/") && fullUrl.endsWith("/")) {
return fullUrl.slice(0, -1);
}
return fullUrl;
}
async function captureFetchBreadcrumbToReplay(breadcrumb, hint, options) {
try {
const data = await _prepareFetchData(breadcrumb, hint, options);
const result = makeNetworkReplayBreadcrumb("resource.fetch", data);
addNetworkBreadcrumb(options.replay, result);
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to capture fetch breadcrumb");
}
}
function enrichFetchBreadcrumb(breadcrumb, hint) {
const { input, response } = hint;
const body = input ? getFetchRequestArgBody(input) : void 0;
const reqSize = getBodySize(body);
const resSize = response ? parseContentLengthHeader(response.headers.get("content-length")) : void 0;
if (reqSize !== void 0) {
breadcrumb.data.request_body_size = reqSize;
}
if (resSize !== void 0) {
breadcrumb.data.response_body_size = resSize;
}
}
async function _prepareFetchData(breadcrumb, hint, options) {
const now = Date.now();
const { startTimestamp = now, endTimestamp = now } = hint;
const {
url,
method,
status_code: statusCode = 0,
request_body_size: requestBodySize,
response_body_size: responseBodySize
} = breadcrumb.data;
const captureDetails = urlMatches(url, options.networkDetailAllowUrls) && !urlMatches(url, options.networkDetailDenyUrls);
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 = input ? getRequestHeaders(input, networkRequestHeaders) : {};
if (!networkCaptureBodies) {
return buildNetworkRequestOrResponse(headers, requestBodySize, void 0);
}
const requestBody = getFetchRequestArgBody(input);
const [bodyStr, warning] = getBodyString(requestBody, debug);
const data = buildNetworkRequestOrResponse(headers, requestBodySize, bodyStr);
if (warning) {
return mergeWarning(data, warning);
}
return data;
}
async function _getResponseInfo(captureDetails, {
networkCaptureBodies,
networkResponseHeaders
}, response, responseBodySize) {
if (!captureDetails && responseBodySize !== void 0) {
return buildSkippedNetworkRequestOrResponse(responseBodySize);
}
const headers = response ? getAllHeaders(response.headers, networkResponseHeaders) : {};
if (!response || !networkCaptureBodies && responseBodySize !== void 0) {
return buildNetworkRequestOrResponse(headers, responseBodySize, void 0);
}
const [bodyText, warning] = await _parseFetchResponseBody(response);
const result = getResponseData(bodyText, {
networkCaptureBodies,
responseBodySize,
captureDetails,
headers
});
if (warning) {
return mergeWarning(result, warning);
}
return result;
}
function getResponseData(bodyText, {
networkCaptureBodies,
responseBodySize,
captureDetails,
headers
}) {
try {
const size = bodyText?.length && responseBodySize === void 0 ? getBodySize(bodyText) : responseBodySize;
if (!captureDetails) {
return buildSkippedNetworkRequestOrResponse(size);
}
if (networkCaptureBodies) {
return buildNetworkRequestOrResponse(headers, size, bodyText);
}
return buildNetworkRequestOrResponse(headers, size, void 0);
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to serialize response body");
return buildNetworkRequestOrResponse(headers, responseBodySize, void 0);
}
}
async function _parseFetchResponseBody(response) {
const res = _tryCloneResponse(response);
if (!res) {
return [void 0, "BODY_PARSE_ERROR"];
}
try {
const text = await _tryGetResponseText(res);
return [text];
} catch (error) {
if (error instanceof Error && error.message.indexOf("Timeout") > -1) {
DEBUG_BUILD && debug.warn("Parsing text body from response timed out");
return [void 0, "BODY_PARSE_TIMEOUT"];
}
DEBUG_BUILD && debug.exception(error, "Failed to get text body from response");
return [void 0, "BODY_PARSE_ERROR"];
}
}
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);
}
if (Array.isArray(headers)) {
return {};
}
return getAllowedHeaders(headers, allowedHeaders);
}
function _tryCloneResponse(response) {
try {
return response.clone();
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to clone response body");
}
}
function _tryGetResponseText(response) {
return new Promise((resolve, reject) => {
const timeout = setTimeout$3(() => reject(new Error("Timeout while trying to read response body")), 500);
_getResponseText(response).then(
(txt) => resolve(txt),
(reason) => reject(reason)
).finally(() => clearTimeout(timeout));
});
}
async function _getResponseText(response) {
return await response.text();
}
async function captureXhrBreadcrumbToReplay(breadcrumb, hint, options) {
try {
const data = _prepareXhrData(breadcrumb, hint, options);
const result = makeNetworkReplayBreadcrumb("resource.xhr", data);
addNetworkBreadcrumb(options.replay, result);
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to capture xhr breadcrumb");
}
}
function enrichXhrBreadcrumb(breadcrumb, hint) {
const { xhr, input } = hint;
if (!xhr) {
return;
}
const reqSize = getBodySize(input);
const resSize = xhr.getResponseHeader("content-length") ? parseContentLengthHeader(xhr.getResponseHeader("content-length")) : _getBodySize(xhr.response, xhr.responseType);
if (reqSize !== void 0) {
breadcrumb.data.request_body_size = reqSize;
}
if (resSize !== void 0) {
breadcrumb.data.response_body_size = resSize;
}
}
function _prepareXhrData(breadcrumb, hint, options) {
const now = Date.now();
const { startTimestamp = now, endTimestamp = now, 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 (!xhr || !urlMatches(url, options.networkDetailAllowUrls) || urlMatches(url, options.networkDetailDenyUrls)) {
const request2 = buildSkippedNetworkRequestOrResponse(requestBodySize);
const response2 = buildSkippedNetworkRequestOrResponse(responseBodySize);
return {
startTimestamp,
endTimestamp,
url,
method,
statusCode,
request: request2,
response: response2
};
}
const xhrInfo = xhr[SENTRY_XHR_DATA_KEY];
const networkRequestHeaders = xhrInfo ? getAllowedHeaders(xhrInfo.request_headers, options.networkRequestHeaders) : {};
const networkResponseHeaders = getAllowedHeaders(parseXhrResponseHeaders(xhr), options.networkResponseHeaders);
const [requestBody, requestWarning] = options.networkCaptureBodies ? getBodyString(input, debug) : [void 0];
const [responseBody, responseWarning] = options.networkCaptureBodies ? _getXhrResponseBody(xhr) : [void 0];
const request = buildNetworkRequestOrResponse(networkRequestHeaders, requestBodySize, requestBody);
const response = buildNetworkRequestOrResponse(networkResponseHeaders, responseBodySize, responseBody);
return {
startTimestamp,
endTimestamp,
url,
method,
statusCode,
request: requestWarning ? mergeWarning(request, requestWarning) : request,
response: responseWarning ? mergeWarning(response, responseWarning) : response
};
}
function _getXhrResponseBody(xhr) {
const errors = [];
try {
return [xhr.responseText];
} catch (e) {
errors.push(e);
}
try {
return _parseXhrResponse(xhr.response, xhr.responseType);
} catch (e) {
errors.push(e);
}
DEBUG_BUILD && debug.warn("Failed to get xhr response body", ...errors);
return [void 0];
}
function _parseXhrResponse(body, responseType) {
try {
if (typeof body === "string") {
return [body];
}
if (body instanceof Document) {
return [body.body.outerHTML];
}
if (responseType === "json" && isObjectLike(body)) {
return [JSON.stringify(body)];
}
if (!body) {
return [void 0];
}
} catch (error) {
DEBUG_BUILD && debug.exception(error, "Failed to serialize body", body);
return [void 0, "BODY_PARSE_ERROR"];
}
DEBUG_BUILD && debug.log("Skipping network body because of body type", body);
return [void 0, "UNPARSEABLE_BODY_TYPE"];
}
function _getBodySize(body, responseType) {
try {
const bodyStr = responseType === "json" && isObjectLike(body) ? JSON.stringify(body) : body;
return getBodySize(bodyStr);
} catch {
return void 0;
}
}
function handleNetworkBreadcrumbs(replay) {
const client = getClient();
try {
const {
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders,
networkResponseHeaders
} = replay.getOptions();
const options = {
replay,
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders,
networkResponseHeaders
};
if (client) {
client.on("beforeAddBreadcrumb", (breadcrumb, hint) => beforeAddNetworkBreadcrumb(options, breadcrumb, hint));
}
} catch {
}
}
function beforeAddNetworkBreadcrumb(options, breadcrumb, hint) {
if (!breadcrumb.data) {
return;
}
try {
if (_isXhrBreadcrumb(breadcrumb) && _isXhrHint(hint)) {
enrichXhrBreadcrumb(breadcrumb, hint);
captureXhrBreadcrumbToReplay(breadcrumb, hint, options);
}
if (_isFetchBreadcrumb(breadcrumb) && _isFetchHint(hint)) {
enrichFetchBreadcrumb(breadcrumb, hint);
captureFetchBreadcrumbToReplay(breadcrumb, hint, options);
}
} catch (e) {
DEBUG_BUILD && debug.exception(e, "Error when enriching network breadcrumb");
}
}
function _isXhrBreadcrumb(breadcrumb) {
return breadcrumb.category === "xhr";
}
function _isFetchBreadcrumb(breadcrumb) {
return breadcrumb.category === "fetch";
}
function _isXhrHint(hint) {
return hint?.xhr;
}
function _isFetchHint(hint) {
return hint?.input !== void 0;
}
function addGlobalListeners(replay) {
const client = getClient();
addClickKeypressInstrumentationHandler(handleDomListener(replay));
addHistoryInstrumentationHandler(handleHistorySpanListener(replay));
handleBreadcrumbs(replay);
handleNetworkBreadcrumbs(replay);
const eventProcessor = handleGlobalEventListener(replay);
addEventProcessor(eventProcessor);
if (client) {
client.on("beforeSendEvent", handleBeforeSendEvent(replay));
client.on("afterSendEvent", handleAfterSendEvent(replay));
client.on("createDsc", (dsc) => {
const replayId = replay.getSessionId();
if (replayId && replay.isEnabled() && replay.recordingMode === "session") {
const isSessionActive = replay.checkAndHandleExpiredSession();
if (isSessionActive) {
dsc.replay_id = replayId;
}
}
});
client.on("processSegmentSpan", handleProcessSegmentSpan(replay));
client.on("spanStart", (span) => {
replay.lastActiveSpan = span;
});
client.on("spanEnd", (span) => {
replay.lastActiveSpan = span;
});
client.on("beforeSendFeedback", async (feedbackEvent, options) => {
const replayId = replay.getSessionId();
if (options?.includeReplay && replay.isEnabled() && replayId && feedbackEvent.contexts?.feedback) {
if (feedbackEvent.contexts.feedback.source === "api") {
await replay.sendBufferedReplayOrFlush();
}
feedbackEvent.contexts.feedback.replay_id = replayId;
}
});
client.on("openFeedbackWidget", async () => {
await replay.sendBufferedReplayOrFlush();
});
}
}
async function addMemoryEntry(replay) {
try {
return Promise.all(
createPerformanceSpans(replay, [
// @ts-expect-error memory doesn't exist on type Performance as the API is non-standard (we check that it exists above)
createMemoryEntry(WINDOW.performance.memory)
])
);
} catch {
return [];
}
}
function createMemoryEntry(memoryEntry) {
const { jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize } = memoryEntry;
const time = Date.now() / 1e3;
return {
type: "memory",
name: "memory",
start: time,
end: time,
data: {
memory: {
jsHeapSizeLimit,
totalJSHeapSize,
usedJSHeapSize
}
}
};
}
function debounce(func, wait, options) {
return debounce$1(func, wait, {
...options,
// @ts-expect-error - Not quite sure why these types do not match, but this is fine
setTimeoutImpl: setTimeout$3
});
}
const NAVIGATOR = GLOBAL_OBJ.navigator;
function getRecordingSamplingOptions() {
if (/iPhone|iPad|iPod/i.test(NAVIGATOR?.userAgent ?? "") || /Macintosh/i.test(NAVIGATOR?.userAgent ?? "") && NAVIGATOR?.maxTouchPoints && NAVIGATOR?.maxTouchPoints > 1) {
return {
sampling: {
mousemove: false
}
};
}
return {};
}
function getHandleRecordingEmit(replay) {
let hadFirstEvent = false;
return (event, _isCheckout) => {
try {
if (!replay.checkAndHandleExpiredSession()) {
DEBUG_BUILD && debug.warn("Received replay event after session expired.");
return;
}
const isCheckout = _isCheckout || !hadFirstEvent;
hadFirstEvent = true;
syncMirrorAttributesFromMutationEvent(event);
if (replay.clickDetector) {
updateClickDetectorForRecordingEvent(replay.clickDetector, event);
}
replay.addUpdate(() => {
if (replay.recordingMode === "buffer" && isCheckout) {
replay.setInitialState();
}
if (!addEventSync(replay, event, isCheckout)) {
return true;
}
if (!isCheckout) {
return false;
}
const session = replay.session;
addSettingsEvent(replay, isCheckout);
if (replay.recordingMode === "buffer" && session && replay.eventBuffer && !session.dirty) {
const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
if (earliestEvent) {
DEBUG_BUILD && debug.log(`Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`);
session.started = earliestEvent;
if (replay.getOptions().stickySession) {
saveSession(session);
}
}
}
if (session?.previousSessionId) {
return true;
}
if (replay.recordingMode === "session") {
void replay.flush();
}
return true;
});
} catch (error) {
replay.handleException(error);
}
};
}
function syncMirrorAttributesFromMutationEvent(event) {
const data = event.data;
if (event.type !== EventType.IncrementalSnapshot || !data || typeof data !== "object" || !("source" in data) || data.source !== IncrementalSource.Mutation || !("attributes" in data) || !Array.isArray(data.attributes)) {
return;
}
for (const mutation of data.attributes) {
const node = record.mirror.getNode(mutation.id);
const meta = node && record.mirror.getMeta(node);
if (meta?.type !== NodeType.Element) {
continue;
}
for (const [attributeName, value] of Object.entries(mutation.attributes)) {
if (value === null) {
delete meta.attributes[attributeName];
} else {
meta.attributes[attributeName] = value;
}
}
}
}
function createOptionsEvent(replay) {
const options = replay.getOptions();
return {
type: EventType.Custom,
timestamp: Date.now(),
data: {
tag: "options",
payload: {
shouldRecordCanvas: replay.isRecordingCanvas(),
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
}
}
};
}
function addSettingsEvent(replay, isCheckout) {
if (!isCheckout || replay.session?.segmentId !== 0) {
return;
}
addEventSync(replay, createOptionsEvent(replay), false);
}
function closestElementOfNode(node) {
if (!node) {
return null;
}
try {
const el = node.nodeType === node.ELEMENT_NODE ? node : node.parentElement;
return el;
} catch {
return null;
}
}
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
]
]
);
}
function prepareRecordingData({
recordingData,
headers
}) {
let payloadWithSequence;
const replayHeaders = `${JSON.stringify(headers)}
`;
if (typeof recordingData === "string") {
payloadWithSequence = `${replayHeaders}${recordingData}`;
} else {
const enc = new TextEncoder();
const sequence = enc.encode(replayHeaders);
payloadWithSequence = new Uint8Array(sequence.length + recordingData.length);
payloadWithSequence.set(sequence);
payloadWithSequence.set(recordingData, sequence.length);
}
return payloadWithSequence;
}
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"]) : void 0;
const eventHint = { event_id, integrations };
client.emit("preprocessEvent", event, eventHint);
const preparedEvent = await prepareEvent(
client.getOptions(),
event,
eventHint,
scope,
client,
getIsolationScope()
);
if (!preparedEvent) {
return null;
}
client.emit("postprocessEvent", preparedEvent, eventHint);
preparedEvent.platform = preparedEvent.platform || "javascript";
const metadata = client.getSdkMetadata();
const { name, version, settings } = metadata?.sdk || {};
preparedEvent.sdk = {
...preparedEvent.sdk,
name: name || "sentry.javascript.unknown",
version: version || "0.0.0",
settings
};
return preparedEvent;
}
async function sendReplayRequest({
recordingData,
replayId,
segmentId: segment_id,
eventContext,
timestamp,
session
}) {
const preparedRecordingData = prepareRecordingData({
recordingData,
headers: {
segment_id
}
});
const { urls, errorIds, traceIds, segmentNames, initialTimestamp } = eventContext;
const client = getClient();
const scope = getCurrentScope();
const transport = client?.getTransport();
const dsn = client?.getDsn();
if (!client || !transport || !dsn || !session.sampled) {
return Promise.resolve({});
}
const baseEvent = {
type: REPLAY_EVENT_NAME,
replay_start_timestamp: initialTimestamp / 1e3,
timestamp: timestamp / 1e3,
error_ids: errorIds,
trace_ids: traceIds,
segment_names: segmentNames,
urls,
replay_id: replayId,
segment_id,
replay_type: session.sampled
};
const replayEvent = await prepareReplayEvent({ scope, client, replayId, event: baseEvent });
if (!replayEvent) {
client.recordDroppedEvent("event_processor", "replay");
DEBUG_BUILD && debug.log("An event processor returned `null`, will not send event.");
return Promise.resolve({});
}
delete replayEvent.sdkProcessingMetadata;
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 {
error.cause = err;
} catch {
}
throw error;
}
const rateLimits = updateRateLimits({}, response);
if (isRateLimited(rateLimits, "replay")) {
throw new RateLimitError(rateLimits);
}
if (typeof response.statusCode === "number" && (response.statusCode < 200 || response.statusCode >= 300)) {
throw new TransportStatusCodeError(response.statusCode);
}
return response;
}
class TransportStatusCodeError extends Error {
constructor(statusCode) {
super(`Transport returned status code ${statusCode}`);
}
}
class RateLimitError extends Error {
constructor(rateLimits) {
super("Rate limit hit");
this.rateLimits = rateLimits;
}
}
class ReplayDurationLimitError extends Error {
constructor() {
super("Session is too long, not sending replay");
}
}
async function sendReplay(replayData, retryConfig = {
count: 0,
interval: RETRY_BASE_INTERVAL
}) {
const { recordingData, onError } = replayData;
if (!recordingData.length) {
return;
}
try {
await sendReplayRequest(replayData);
return true;
} catch (err) {
if (err instanceof TransportStatusCodeError || err instanceof RateLimitError) {
throw err;
}
setContext("Replays", {
_retryCount: retryConfig.count
});
if (onError) {
onError(err);
}
if (retryConfig.count >= RETRY_MAX_COUNT) {
const error = new Error(`${UNABLE_TO_SEND_REPLAY} - max retries exceeded`);
try {
error.cause = err;
} catch {
}
throw error;
}
retryConfig.interval *= ++retryConfig.count;
return new Promise((resolve, reject) => {
setTimeout$3(async () => {
try {
await sendReplay(replayData, retryConfig);
resolve(true);
} catch (err2) {
reject(err2);
}
}, retryConfig.interval);
});
}
}
const THROTTLED = "__THROTTLED";
const SKIPPED = "__SKIPPED";
function throttle(fn, maxCount, durationSeconds) {
const counter = /* @__PURE__ */ 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) => {
const now = Math.floor(Date.now() / 1e3);
_cleanup(now);
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);
};
}
class ReplayContainer {
constructor({
options,
recordingOptions
}) {
this.eventBuffer = null;
this.performanceEntries = [];
this.replayPerformanceEntries = [];
this.recordingMode = "session";
this.timeouts = {
sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION
};
this._lastActivity = Date.now();
this._isEnabled = false;
this._isPaused = false;
this._requiresManualStart = false;
this._hasInitializedCoreListeners = false;
this._context = {
errorIds: /* @__PURE__ */ new Set(),
traceIds: /* @__PURE__ */ new Set(),
segmentNames: /* @__PURE__ */ new Set(),
urls: [],
initialTimestamp: Date.now(),
initialUrl: ""
};
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(",") : ""
} : void 0;
if (slowClickConfig) {
this.clickDetector = new ClickDetector(this, slowClickConfig);
}
if (DEBUG_BUILD) {
const experiments = options._experiments;
debug.setConfig({
captureExceptions: !!experiments.captureExceptions,
traceInternals: !!experiments.traceInternals
});
}
this._handleVisibilityChange = () => {
if (WINDOW.document.visibilityState === "visible") {
this._doChangeToForegroundTasks();
} else {
this._doChangeToBackgroundTasks();
}
};
this._handleWindowBlur = () => {
const breadcrumb = createBreadcrumb({
category: "ui.blur"
});
this._doChangeToBackgroundTasks(breadcrumb);
};
this._handleWindowFocus = () => {
const breadcrumb = createBreadcrumb({
category: "ui.focus"
});
this._doChangeToForegroundTasks(breadcrumb);
};
this._handleKeyboardEvent = (event) => {
handleKeyboardEvent(this, event);
};
}
/** 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;
}
/**
* Determine if canvas recording is enabled
*/
isRecordingCanvas() {
return Boolean(this._canvas);
}
/** Get the replay integration options. */
getOptions() {
return this._options;
}
/** A wrapper to conditionally capture exceptions. */
handleException(error) {
DEBUG_BUILD && debug.exception(error);
if (this._options.onError) {
this._options.onError(error);
}
}
/**
* Initializes the plugin based on sampling configuration. Should not be
* called outside of constructor.
*/
initializeSampling(previousSessionId) {
const { errorSampleRate, sessionSampleRate } = this._options;
const requiresManualStart = errorSampleRate <= 0 && sessionSampleRate <= 0;
this._requiresManualStart = requiresManualStart;
if (requiresManualStart) {
return;
}
this._initializeSessionForSampling(previousSessionId);
if (!this.session) {
DEBUG_BUILD && debug.exception(new Error("Unable to initialize and create session"));
return;
}
if (this.session.sampled === false) {
return;
}
this.recordingMode = this.session.sampled === "buffer" && this.session.segmentId === 0 ? "buffer" : "session";
DEBUG_BUILD && debug.infoTick(`Starting replay in ${this.recordingMode} mode`);
this._initializeRecording();
}
/**
* Start a replay regardless of sampling rate. Calling this will always
* create a new session. Will log a message 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") {
DEBUG_BUILD && debug.log("Recording is already in progress");
return;
}
if (this._isEnabled && this.recordingMode === "buffer") {
DEBUG_BUILD && debug.log("Buffering is in progress, call `flush()` to save the replay");
return;
}
DEBUG_BUILD && debug.infoTick("Starting replay in session mode");
this._updateUserActivity();
const session = loadOrCreateSession(
{
maxReplayDuration: this._options.maxReplayDuration,
sessionIdleExpire: this.timeouts.sessionIdleExpire
},
{
stickySession: this._options.stickySession,
// This is intentional: create a new session-based replay when calling `start()`
sessionSampleRate: 1,
allowBuffering: false
}
);
this.session = session;
this.recordingMode = "session";
this._initializeRecording();
}
/**
* Start replay buffering. Buffers until `flush()` is called or, if
* `replaysOnErrorSampleRate` > 0, an error occurs.
*/
startBuffering() {
if (this._isEnabled) {
DEBUG_BUILD && debug.log("Buffering is in progress, call `flush()` to save the replay");
return;
}
DEBUG_BUILD && debug.infoTick("Starting replay in buffer mode");
const session = loadOrCreateSession(
{
sessionIdleExpire: this.timeouts.sessionIdleExpire,
maxReplayDuration: this._options.maxReplayDuration
},
{
stickySession: this._options.stickySession,
sessionSampleRate: 0,
allowBuffering: true
}
);
this.session = session;
this.recordingMode = "buffer";
this._initializeRecording();
}
/**
* Start recording.
*
* Note that this will cause a new DOM checkout
*/
startRecording() {
try {
const canvasOptions = this._canvas;
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 } : (
// Otherwise, use experimental option w/ min checkout time of 6 minutes
// This is to improve playback seeking as there could potentially be
// less mutations to process in the worse cases.
//
// checkout by "N" events is probably ideal, but means we have less
// control about the number of checkouts we make (which generally
// increases replay size)
this._options._experiments.continuousCheckout && {
// Minimum checkout time is 6 minutes
checkoutEveryNms: Math.max(36e4, this._options._experiments.continuousCheckout)
}
),
emit: getHandleRecordingEmit(this),
...getRecordingSamplingOptions(),
onMutation: this._onMutationHandler.bind(this),
...canvasOptions ? {
recordCanvas: canvasOptions.recordCanvas,
getCanvasManager: canvasOptions.getCanvasManager,
sampling: canvasOptions.sampling,
dataURLOptions: canvasOptions.dataURLOptions
} : {}
});
} 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 = void 0;
}
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({
forceFlush = false,
reason
} = {}) {
if (!this._isEnabled) {
return;
}
this._isEnabled = false;
this.recordingMode = "buffer";
const stopReason = reason ?? "manual";
getClient()?.emit("replayEnd", { sessionId: this.session?.id, reason: stopReason });
try {
DEBUG_BUILD && debug.log(`Stopping Replay triggered by ${stopReason}`);
resetReplayIdOnDynamicSamplingContext();
this._removeListeners();
this.stopRecording();
this._debouncedFlush.cancel();
if (forceFlush) {
await this._flush({ force: true });
}
this.eventBuffer?.destroy();
this.eventBuffer = null;
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() {
if (this._isPaused) {
return;
}
this._isPaused = true;
this.stopRecording();
DEBUG_BUILD && debug.log("Pausing replay");
}
/**
* Resumes recording, see notes for `pause().
*
* Note that calling `startRecording()` here will cause a
* new DOM checkout.`
*/
resume() {
if (!this._isPaused || !this._checkSession()) {
return;
}
this._isPaused = false;
this.startRecording();
DEBUG_BUILD && debug.log("Resuming replay");
}
/**
* 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();
DEBUG_BUILD && debug.log("Converting buffer to session");
await this.flushImmediate();
const hasStoppedRecording = this.stopRecording();
if (!continueRecording || !hasStoppedRecording) {
return;
}
if (this.recordingMode === "session") {
return;
}
this.recordingMode = "session";
if (this.session) {
this.session.dirty = false;
this._updateUserActivity(activityTime);
this._updateSessionActivity(activityTime);
this._maybeSaveSession();
setReplayIdOnDynamicSamplingContext(this.session.id);
}
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) {
const cbResult = cb();
if (this.recordingMode === "buffer" || !this._isEnabled) {
return;
}
if (cbResult === true) {
return;
}
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();
if (!this._stopRecording) {
if (!this._checkSession()) {
return;
}
this.resume();
return;
}
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 multiple flushes happening closely together.
*/
flushImmediate() {
this._debouncedFlush();
return this._debouncedFlush.flush();
}
/**
* Cancels queued up flushes.
*/
cancelFlush() {
this._debouncedFlush.cancel();
}
/** Get the current session (=replay) ID
*
* @param onlyIfSampled - If true, will only return the session ID if the session is sampled.
*/
getSessionId(onlyIfSampled) {
if (onlyIfSampled && this.session?.sampled === false) {
return void 0;
}
return 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() {
if (this._lastActivity && isExpired(this._lastActivity, this.timeouts.sessionIdlePause) && this.session?.sampled === "session") {
this.pause();
return;
}
if (!this._checkSession()) {
return false;
}
return true;
}
/**
* 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.performanceEntries = [];
this.replayPerformanceEntries = [];
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 (res === THROTTLED) {
const breadcrumb = createBreadcrumb({
category: "replay.throttled"
});
this.addUpdate(() => {
return !addEventSync(this, {
type: ReplayEventTypeCustom,
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 lastActiveSpan = this.lastActiveSpan || getActiveSpan();
const lastRootSpan = lastActiveSpan && getRootSpan(lastActiveSpan);
const attributes = lastRootSpan && spanToJSON(lastRootSpan).data || {};
const source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
if (!lastRootSpan || !source || !["route", "custom"].includes(source)) {
return void 0;
}
return spanToJSON(lastRootSpan).description;
}
/**
* Initialize and start all listeners to varying events (DOM,
* Performance Observer, Recording, Sentry SDK, etc)
*/
_initializeRecording() {
this.setInitialState();
this._updateSessionActivity();
this.eventBuffer = createEventBuffer({
useCompression: this._options.useCompression,
workerUrl: this._options.workerUrl
});
this._removeListeners();
this._addListeners();
this._isEnabled = true;
this._isPaused = false;
if (this.session) {
getClient()?.emit("replayStart", {
sessionId: this.session.id,
recordingMode: this.recordingMode
});
}
this.startRecording();
if (this.recordingMode === "session" && this.session) {
setReplayIdOnDynamicSamplingContext(this.session.id);
}
}
/**
* Loads (or refreshes) the current session.
*/
_initializeSessionForSampling(previousSessionId) {
const allowBuffering = this._options.errorSampleRate > 0;
const session = loadOrCreateSession(
{
sessionIdleExpire: this.timeouts.sessionIdleExpire,
maxReplayDuration: this._options.maxReplayDuration,
previousSessionId
},
{
stickySession: this._options.stickySession,
sessionSampleRate: this._options.sessionSampleRate,
allowBuffering
}
);
this.session = session;
}
/**
* Checks and potentially refreshes the current session.
* Returns false if session is not recorded.
*/
_checkSession() {
if (!this.session) {
return false;
}
const currentSession = this.session;
if (shouldRefreshSession(currentSession, {
sessionIdleExpire: this.timeouts.sessionIdleExpire,
maxReplayDuration: this._options.maxReplayDuration
})) {
this._refreshSession(currentSession);
return false;
}
return true;
}
/**
* Refresh a session with a new one.
* This stops the current session (without forcing a flush, as that would never work since we are expired),
* and then does a new sampling based on the refreshed session.
*/
async _refreshSession(session) {
if (!this._isEnabled) {
return;
}
await this.stop({ reason: "sessionExpired" });
this.initializeSampling(session.id);
}
/**
* 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();
}
if (!this._hasInitializedCoreListeners) {
addGlobalListeners(this);
this._hasInitializedCoreListeners = true;
}
} catch (err) {
this.handleException(err);
}
this._performanceCleanupCallback = 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._performanceCleanupCallback) {
this._performanceCleanupCallback();
}
} catch (err) {
this.handleException(err);
}
}
/**
* Tasks to run when we consider a page to be hidden (via blurring and/or visibility)
*/
_doChangeToBackgroundTasks(breadcrumb) {
if (!this.session) {
return;
}
const expired = shouldRefreshSession(this.session, {
maxReplayDuration: this._options.maxReplayDuration,
sessionIdleExpire: this.timeouts.sessionIdleExpire
});
if (expired) {
resetReplayIdOnDynamicSamplingContext();
return;
}
if (breadcrumb) {
this._createCustomBreadcrumb(breadcrumb);
}
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) {
DEBUG_BUILD && debug.log("Document has become active, but session has expired");
return;
}
if (breadcrumb) {
this._createCustomBreadcrumb(breadcrumb);
}
}
/**
* 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(() => {
this.throttledAddEvent({
type: EventType.Custom,
timestamp: breadcrumb.timestamp || 0,
data: {
tag: "breadcrumb",
payload: breadcrumb
}
});
});
}
/**
* Observed performance events are added to `this.performanceEntries`. These
* are included in the replay event before it is finished and sent to Sentry.
*/
_addPerformanceEntries() {
let performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries);
this.performanceEntries = [];
this.replayPerformanceEntries = [];
if (this._requiresManualStart) {
const initialTimestampInSeconds = this._context.initialTimestamp / 1e3;
performanceEntries = performanceEntries.filter((entry) => entry.start >= initialTimestampInSeconds);
}
return Promise.all(createPerformanceSpans(this, performanceEntries));
}
/**
* Clear _context
*/
_clearContext() {
this._context.errorIds.clear();
this._context.traceIds.clear();
this._context.segmentNames.clear();
this._context.urls = [];
}
/** Update the initial timestamp based on the buffer content. */
_updateInitialTimestampFromEventBuffer() {
const { session, eventBuffer } = this;
if (!session || !eventBuffer || this._requiresManualStart) {
return;
}
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),
segmentNames: Array.from(this._context.segmentNames),
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() {
const replayId = this.getSessionId();
if (!this.session || !this.eventBuffer || !replayId) {
DEBUG_BUILD && debug.error("No session or eventBuffer found to flush.");
return;
}
await this._addPerformanceEntries();
if (!this.eventBuffer?.hasEvents) {
return;
}
await addMemoryEntry(this);
if (!this.eventBuffer) {
return;
}
if (replayId !== this.getSessionId()) {
return;
}
try {
this._updateInitialTimestampFromEventBuffer();
const timestamp = Date.now();
if (timestamp - this._context.initialTimestamp > this._options.maxReplayDuration + 3e4) {
throw new ReplayDurationLimitError();
}
const eventContext = this._popEventContext();
const segmentId = this.session.segmentId++;
this._maybeSaveSession();
const recordingData = await this.eventBuffer.finish();
await sendReplay({
replayId,
recordingData,
segmentId,
eventContext,
session: this.session,
timestamp,
onError: (err) => this.handleException(err)
});
} catch (err) {
this.handleException(err);
this.stop({ reason: "sendError" });
const client = getClient();
if (client) {
let dropReason;
if (err instanceof RateLimitError) {
dropReason = "ratelimit_backoff";
} else if (err instanceof ReplayDurationLimitError) {
dropReason = "invalid";
} else {
dropReason = "send_error";
}
client.recordDroppedEvent(dropReason, "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.
*/
async _flush({
force = false
} = {}) {
if (!this._isEnabled && !force) {
return;
}
if (!this.checkAndHandleExpiredSession()) {
DEBUG_BUILD && debug.error("Attempting to finish replay event after session expired.");
return;
}
if (!this.session) {
return;
}
const start = this.session.started;
const now = Date.now();
const duration = now - start;
this._debouncedFlush.cancel();
const tooShort = duration < this._options.minReplayDuration;
const tooLong = duration > this._options.maxReplayDuration + 5e3;
if (tooShort || tooLong) {
DEBUG_BUILD && debug.log(
`Session duration (${Math.floor(duration / 1e3)}s) is too ${tooShort ? "short" : "long"}, not sending replay.`
);
if (tooShort) {
this._debouncedFlush();
}
return;
}
const eventBuffer = this.eventBuffer;
if (eventBuffer && this.session.segmentId === 0 && !eventBuffer.hasCheckout) {
DEBUG_BUILD && debug.log("Flushing initial segment without checkout.");
}
const _flushInProgress = !!this._flushLock;
if (!this._flushLock) {
this._flushLock = this._runFlush();
}
try {
await this._flushLock;
} catch (err) {
this.handleException(err);
} finally {
this._flushLock = void 0;
if (_flushInProgress) {
this._debouncedFlush();
}
}
}
/** Save the session, if it is sticky */
_maybeSaveSession() {
if (this.session && this._options.stickySession) {
saveSession(this.session);
}
}
/** Handler for rrweb.record.onMutation */
_onMutationHandler(mutations) {
const { ignoreMutations } = this._options._experiments;
if (ignoreMutations?.length) {
if (mutations.some((mutation) => {
const el = closestElementOfNode(mutation.target);
const selector = ignoreMutations.join(",");
return el?.matches(selector);
})) {
return false;
}
}
const count = mutations.length;
const mutationLimit = this._options.mutationLimit;
const mutationBreadcrumbLimit = this._options.mutationBreadcrumbLimit;
const overMutationLimit = mutationLimit && count > mutationLimit;
if (count > mutationBreadcrumbLimit || overMutationLimit) {
const breadcrumb = createBreadcrumb({
category: "replay.mutations",
data: {
count,
limit: overMutationLimit
}
});
this._createCustomBreadcrumb(breadcrumb);
}
if (overMutationLimit) {
this.stop({ reason: "mutationLimit", forceFlush: this.recordingMode === "session" });
return false;
}
return true;
}
}
function getOption(selectors, defaultSelectors) {
return [
...selectors,
// sentry defaults
...defaultSelectors
].join(",");
}
function getPrivacyOptions({ mask, unmask, block, unblock, ignore }) {
const defaultBlockedElements = ["base", "iframe[srcdoc]:not([src])"];
const maskSelector = getOption(mask, [".sentry-mask", "[data-sentry-mask]"]);
const unmaskSelector = getOption(unmask, []);
const options = {
// We are making the decision to make text and input selectors the same
maskTextSelector: maskSelector,
unmaskTextSelector: unmaskSelector,
blockSelector: getOption(block, [".sentry-block", "[data-sentry-block]", ...defaultBlockedElements]),
unblockSelector: getOption(unblock, []),
ignoreSelector: getOption(ignore, [".sentry-ignore", "[data-sentry-ignore]", 'input[type="file"]'])
};
return options;
}
function maskAttribute({
el,
key,
maskAttributes,
maskAllText,
privacyOptions,
value
}) {
if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) {
return value;
}
const masksNamedAttribute = maskAttributes.includes(key);
const masksSubmitButtonValue = maskAllText && key === "value" && el.tagName === "INPUT" && ["submit", "button"].includes(el.getAttribute("type") || "");
if (masksNamedAttribute || masksSubmitButtonValue) {
return value.replace(/[\S]/g, "*");
}
return value;
}
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"];
const ORIGINAL_BODY = /* @__PURE__ */ Symbol.for("sentry__originalRequestBody");
let _initialized = false;
let _isRequestInstrumented = false;
function _INTERNAL_instrumentRequestInterface() {
if (typeof Request === "undefined" || _isRequestInstrumented) {
return;
}
const OriginalRequest = Request;
try {
const SentryRequest = function(input, init) {
const request = new OriginalRequest(input, init);
if (init?.body != null) {
request[ORIGINAL_BODY] = init.body;
}
return request;
};
SentryRequest.prototype = OriginalRequest.prototype;
GLOBAL_OBJ.Request = SentryRequest;
_isRequestInstrumented = true;
} catch {
}
}
const replayIntegration = ((options) => {
return new Replay(options);
});
class Replay {
constructor({
flushMinDelay = DEFAULT_FLUSH_MIN_DELAY,
flushMaxDelay = DEFAULT_FLUSH_MAX_DELAY,
minReplayDuration = MIN_REPLAY_DURATION,
maxReplayDuration = MAX_REPLAY_DURATION,
stickySession = true,
useCompression = true,
workerUrl,
_experiments = {},
maskAllText = true,
maskAllInputs = true,
blockAllMedia = true,
mutationBreadcrumbLimit = 750,
mutationLimit = 1e4,
slowClickTimeout = 7e3,
slowClickIgnoreSelectors = [],
networkDetailAllowUrls = [],
networkDetailDenyUrls = [],
networkCaptureBodies = true,
networkRequestHeaders = [],
networkResponseHeaders = [],
mask = [],
maskAttributes = ["title", "placeholder", "aria-label"],
unmask = [],
block = [],
unblock = [],
ignore = [],
maskFn,
beforeAddRecordingEvent,
beforeErrorSampling,
onError,
attachRawBodyFromRequest = false
} = {}) {
this.name = "Replay";
const privacyOptions = getPrivacyOptions({
mask,
unmask,
block,
unblock,
ignore
});
this._recordingOptions = {
maskAllInputs,
maskAllText,
maskInputOptions: { password: true },
maskTextFn: maskFn,
maskInputFn: maskFn,
maskAttributeFn: (key, value, el) => maskAttribute({
maskAttributes,
maskAllText,
privacyOptions,
key,
value,
el
}),
...privacyOptions,
// 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,
errorHandler: (err) => {
try {
err.__rrweb__ = true;
} catch {
}
},
// experimental support for recording iframes from different origins
recordCrossOriginIframes: Boolean(_experiments.recordCrossOriginIframes)
};
this._initialOptions = {
flushMinDelay,
flushMaxDelay,
minReplayDuration: Math.min(minReplayDuration, MIN_REPLAY_DURATION_LIMIT),
maxReplayDuration: Math.min(maxReplayDuration, MAX_REPLAY_DURATION),
stickySession,
useCompression,
workerUrl,
blockAllMedia,
maskAllInputs,
maskAllText,
mutationBreadcrumbLimit,
mutationLimit,
slowClickTimeout,
slowClickIgnoreSelectors,
networkDetailAllowUrls,
networkDetailDenyUrls,
networkCaptureBodies,
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
networkResponseHeaders: _getMergedNetworkHeaders(networkResponseHeaders),
beforeAddRecordingEvent,
beforeErrorSampling,
onError,
attachRawBodyFromRequest,
_experiments
};
if (this._initialOptions.blockAllMedia) {
this._recordingOptions.blockSelector = !this._recordingOptions.blockSelector ? MEDIA_SELECTORS : `${this._recordingOptions.blockSelector},${MEDIA_SELECTORS}`;
this._recordingOptions.ignoreCSSAttributes = /* @__PURE__ */ new Set(["background-image"]);
}
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
*/
afterAllSetup(client) {
if (!isBrowser() || this._replay) {
return;
}
if (this._initialOptions.attachRawBodyFromRequest) {
_INTERNAL_instrumentRequestInterface();
}
this._setup(client);
this._initialize(client);
}
/**
* Start a replay regardless of sampling rate. Calling this will always
* create a new session. Will log a message 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
*
* @param options.flush - Whether to flush the pending replay segment when stopping.
* When recording in `session` mode, `stop()` flushes the buffered-but-unsent segment by
* default (`flush: true`), matching the previous behavior. Pass `flush: false` to stop
* recording without sending that pending segment — useful when a user withdraws consent
* and no further data should leave the browser.
*
* Note: `flush: false` only prevents the *pending* (final) segment from being sent. It does
* **not** retract segments that were already sent earlier during `session` recording.
*/
stop(options) {
if (!this._replay) {
return Promise.resolve();
}
return this._replay.stop({
forceFlush: options?.flush ?? this._replay.recordingMode === "session",
reason: "manual"
});
}
/**
* If not in "session" recording mode, flush event buffer which will create a new replay.
* If replay is not enabled, a new session replay is started.
* 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) {
return Promise.resolve();
}
if (!this._replay.isEnabled()) {
this._replay.start();
return Promise.resolve();
}
return this._replay.sendBufferedReplayOrFlush(options);
}
/**
* Get the current session ID.
*
* @param onlyIfSampled - If true, will only return the session ID if the session is sampled.
*
*/
getReplayId(onlyIfSampled) {
if (!this._replay?.isEnabled()) {
return;
}
return this._replay.getSessionId(onlyIfSampled);
}
/**
* Get the current recording mode. This can be either `session` or `buffer`.
*
* `session`: Recording the whole session, sending it continuously
* `buffer`: Always keeping the last 60s of recording, requires:
* - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
* - or calling `flush()` to send the replay
*/
getRecordingMode() {
if (!this._replay?.isEnabled()) {
return;
}
return this._replay.recordingMode;
}
processSpan(span) {
const replayId = this.getReplayId(true);
if (replayId) {
safeSetSpanJSONAttributes(span, { "sentry.replay_id": replayId });
if (this.getRecordingMode() === "buffer") {
safeSetSpanJSONAttributes(span, { "sentry._internal.replay_is_buffering": true });
}
}
}
/**
* Initializes replay.
*/
_initialize(client) {
if (!this._replay) {
return;
}
this._maybeLoadFromReplayCanvasIntegration(client);
this._replay.initializeSampling();
}
/** Setup the integration. */
_setup(client) {
const finalOptions = loadReplayOptionsFromClient(this._initialOptions, client);
this._replay = new ReplayContainer({
options: finalOptions,
recordingOptions: this._recordingOptions
});
}
/** Get canvas options from ReplayCanvas integration, if it is also added. */
_maybeLoadFromReplayCanvasIntegration(client) {
try {
const canvasIntegration = client.getIntegrationByName("ReplayCanvas");
if (!canvasIntegration) {
return;
}
this._replay["_canvas"] = canvasIntegration.getOptions();
} catch {
}
}
}
function loadReplayOptionsFromClient(initialOptions, client) {
const opt = client.getOptions();
const finalOptions = {
sessionSampleRate: 0,
errorSampleRate: 0,
...initialOptions
};
const replaysSessionSampleRate = parseSampleRate(opt.replaysSessionSampleRate);
const replaysOnErrorSampleRate = parseSampleRate(opt.replaysOnErrorSampleRate);
if (replaysSessionSampleRate == null && replaysOnErrorSampleRate == null) {
consoleSandbox(() => {
console.warn(
"Replay is disabled because neither `replaysSessionSampleRate` nor `replaysOnErrorSampleRate` are set."
);
});
}
if (replaysSessionSampleRate != null) {
finalOptions.sessionSampleRate = replaysSessionSampleRate;
}
if (replaysOnErrorSampleRate != null) {
finalOptions.errorSampleRate = replaysOnErrorSampleRate;
}
return finalOptions;
}
function _getMergedNetworkHeaders(headers) {
return [...DEFAULT_NETWORK_HEADERS, ...headers.map((header) => header.toLowerCase())];
}
function baggageHeaderHasSentryValues(baggageHeader) {
return baggageHeader.split(",").some((value) => value.trim().startsWith("sentry-"));
}
function getFullURL(url) {
try {
const parsed = new URL(url, WINDOW$2.location.origin);
return parsed.href;
} catch {
return void 0;
}
}
function isPerformanceResourceTiming(entry) {
return entry.entryType === "resource" && "initiatorType" in entry && typeof entry.nextHopProtocol === "string" && (entry.initiatorType === "fetch" || entry.initiatorType === "xmlhttprequest");
}
function createHeadersSafely(headers) {
try {
return new Headers(headers);
} catch {
return void 0;
}
}
const defaultRequestInstrumentationOptions = {
traceFetch: true,
traceXHR: true,
enableHTTPTimings: true,
trackFetchStreamPerformance: false
};
function instrumentOutgoingRequests(client, _options) {
const {
traceFetch,
traceXHR,
shouldCreateSpanForRequest,
enableHTTPTimings,
tracePropagationTargets,
onRequestSpanStart,
onRequestSpanEnd
} = {
...defaultRequestInstrumentationOptions,
..._options
};
const shouldCreateSpan = typeof shouldCreateSpanForRequest === "function" ? shouldCreateSpanForRequest : (_) => true;
const shouldAttachHeadersWithTargets = (url) => shouldAttachHeaders(url, tracePropagationTargets);
const spans = {};
const propagateTraceparent = client.getOptions().propagateTraceparent;
if (traceFetch) {
addFetchInstrumentationHandler((handlerData) => {
const createdSpan = instrumentFetchRequest(handlerData, shouldCreateSpan, shouldAttachHeadersWithTargets, spans, {
propagateTraceparent,
onRequestSpanEnd
});
if (createdSpan) {
const fullUrl = getFullURL(handlerData.fetchData.url);
const host = fullUrl ? parseUrl(fullUrl).host : void 0;
const sanitizedFullUrl = fullUrl ? stripDataUrlContent(fullUrl) : void 0;
createdSpan.setAttributes({
// oxlint-disable-next-line typescript/no-deprecated
[ws]: sanitizedFullUrl,
// `url.full` must match `http.url`. Setting it here ensures parentless `http.client`
// segment spans don't get `url.full` backfilled with the host page URL (see httpContextIntegration).
[Yu]: sanitizedFullUrl,
"server.address": host
});
if (enableHTTPTimings) {
addHTTPTimings(createdSpan, client);
}
onRequestSpanStart?.(createdSpan, { headers: handlerData.headers });
}
});
}
if (traceXHR) {
addXhrInstrumentationHandler((handlerData) => {
const createdSpan = xhrCallback(
handlerData,
shouldCreateSpan,
shouldAttachHeadersWithTargets,
spans,
propagateTraceparent,
onRequestSpanEnd
);
if (createdSpan) {
if (enableHTTPTimings) {
addHTTPTimings(createdSpan, client);
}
onRequestSpanStart?.(createdSpan, {
headers: createHeadersSafely(handlerData.xhr.__sentry_xhr_v3__?.request_headers)
});
}
});
}
}
const HTTP_TIMING_WAIT_MS = 300;
function addHTTPTimings(span, client) {
const { url } = spanToJSON(span).data;
if (!url || typeof url !== "string") {
return;
}
let onEntryFound = () => void setTimeout(unsubscribePerformanceObsever);
if (hasSpanStreamingEnabled(client)) {
const originalEnd = span.end.bind(span);
span.end = (endTimestamp) => {
const capturedEndTimestamp = endTimestamp ?? timestampInSeconds();
let isEnded = false;
const endSpanAndCleanup = () => {
if (isEnded) {
return;
}
isEnded = true;
setTimeout(unsubscribePerformanceObsever);
originalEnd(capturedEndTimestamp);
clearTimeout(fallbackTimeout);
};
onEntryFound = endSpanAndCleanup;
const fallbackTimeout = setTimeout(endSpanAndCleanup, HTTP_TIMING_WAIT_MS);
};
}
const unsubscribePerformanceObsever = addPerformanceInstrumentationHandler("resource", ({ entries }) => {
entries.forEach((entry) => {
if (isPerformanceResourceTiming(entry) && entry.name.endsWith(url)) {
span.setAttributes(resourceTimingToSpanAttributes(entry));
onEntryFound();
}
});
});
}
function shouldAttachHeaders(targetUrl, tracePropagationTargets) {
const href = getLocationHref();
if (!href) {
const isRelativeSameOriginRequest = !!targetUrl.match(/^\/(?!\/)/);
if (!tracePropagationTargets) {
return isRelativeSameOriginRequest;
} else {
return stringMatchesSomePattern(targetUrl, tracePropagationTargets);
}
} else {
let resolvedUrl;
let currentOrigin;
try {
resolvedUrl = new URL(targetUrl, href);
currentOrigin = new URL(href).origin;
} catch {
return false;
}
const isSameOriginRequest = resolvedUrl.origin === currentOrigin;
if (!tracePropagationTargets) {
return isSameOriginRequest;
} else {
return stringMatchesSomePattern(resolvedUrl.toString(), tracePropagationTargets) || isSameOriginRequest && stringMatchesSomePattern(resolvedUrl.pathname, tracePropagationTargets);
}
}
}
function xhrCallback(handlerData, shouldCreateSpan, shouldAttachHeaders2, spans, propagateTraceparent, onRequestSpanEnd) {
const xhr = handlerData.xhr;
const sentryXhrData = xhr?.[SENTRY_XHR_DATA_KEY];
if (!xhr || xhr.__sentry_own_request__ || !sentryXhrData) {
return void 0;
}
const { url, method } = sentryXhrData;
const shouldCreateSpanResult = hasSpansEnabled() && shouldCreateSpan(url);
if (handlerData.endTimestamp) {
const spanId = xhr.__sentry_xhr_span_id__;
if (!spanId) return;
const span2 = spans[spanId];
if (span2) {
if (shouldCreateSpanResult && sentryXhrData.status_code !== void 0) {
setHttpStatus(span2, sentryXhrData.status_code);
span2.end();
onRequestSpanEnd?.(span2, {
headers: createHeadersSafely(parseXhrResponseHeaders(xhr)),
error: handlerData.error
});
}
delete spans[spanId];
}
return void 0;
}
const fullUrl = getFullURL(url);
const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url);
const sanitizedFullUrl = fullUrl ? stripDataUrlContent(fullUrl) : void 0;
const urlForSpanName = stripDataUrlContent(stripUrlQueryAndFragment(url));
const client = getClient();
const hasParent = !!getActiveSpan();
const shouldEmitSpan = hasParent || !!client && hasSpanStreamingEnabled(client);
const span = shouldCreateSpanResult && shouldEmitSpan ? startInactiveSpan({
name: `${method} ${urlForSpanName}`,
attributes: {
url: stripDataUrlContent(url),
type: "xhr",
"http.method": method,
"http.url": sanitizedFullUrl,
// `url.full` must match `http.url`. Setting it here ensures parentless `http.client`
// segment spans don't get `url.full` backfilled with the host page URL (see httpContextIntegration).
[Yu]: sanitizedFullUrl,
"server.address": parsedUrl?.host,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "http.client",
...parsedUrl?.search && { "http.query": parsedUrl?.search },
...parsedUrl?.hash && { "http.fragment": parsedUrl?.hash }
}
}) : new SentryNonRecordingSpan();
const spanForTraceHeaders = spanIsIgnored(span) && hasParent ? void 0 : span;
if (shouldCreateSpanResult && !shouldEmitSpan) {
client?.recordDroppedEvent("no_parent_span", "span");
}
xhr.__sentry_xhr_span_id__ = span.spanContext().spanId;
spans[xhr.__sentry_xhr_span_id__] = span;
if (shouldAttachHeaders2(url)) {
addTracingHeadersToXhrRequest(
xhr,
// If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction),
// we do not want to use the span as base for the trace headers,
// which means that the headers will be generated from the scope and the sampling decision is deferred
hasSpansEnabled() && shouldEmitSpan ? spanForTraceHeaders : void 0,
propagateTraceparent
);
}
if (client) {
client.emit("beforeOutgoingRequestSpan", span, handlerData);
}
return span;
}
function addTracingHeadersToXhrRequest(xhr, span, propagateTraceparent) {
const { "sentry-trace": sentryTrace, baggage, traceparent } = getTraceData({ span, propagateTraceparent });
if (sentryTrace) {
setHeaderOnXhr(xhr, sentryTrace, baggage, traceparent);
}
}
function setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader, traceparentHeader) {
const originalHeaders = xhr.__sentry_xhr_v3__?.request_headers;
if (originalHeaders?.["sentry-trace"] || !xhr.setRequestHeader) {
return;
}
try {
xhr.setRequestHeader("sentry-trace", sentryTraceHeader);
if (traceparentHeader && !originalHeaders?.["traceparent"]) {
xhr.setRequestHeader("traceparent", traceparentHeader);
}
if (sentryBaggageHeader) {
const originalBaggageHeader = originalHeaders?.["baggage"];
if (!originalBaggageHeader || !baggageHeaderHasSentryValues(originalBaggageHeader)) {
xhr.setRequestHeader("baggage", sentryBaggageHeader);
}
}
} catch {
}
}
const responseToStreamSpan = /* @__PURE__ */ new WeakMap();
const responseToFallbackTimeout = /* @__PURE__ */ new WeakMap();
const STREAM_RESOLVE_FALLBACK_MS = 9e4;
const STREAMING_CONTENT_TYPES = ["text/event-stream", "application/x-ndjson", "application/stream+json"];
const fetchStreamPerformanceIntegration = defineIntegration(() => {
return {
name: "FetchStreamPerformance",
setup() {
addFetchEndInstrumentationHandler((handlerData) => {
if (handlerData.response) {
const streamSpan = responseToStreamSpan.get(handlerData.response);
if (streamSpan && handlerData.endTimestamp) {
streamSpan.end(handlerData.endTimestamp);
const fallbackTimeout = responseToFallbackTimeout.get(handlerData.response);
if (fallbackTimeout) {
clearTimeout(fallbackTimeout);
}
}
}
});
addFetchInstrumentationHandler((handlerData) => {
if (handlerData.endTimestamp && handlerData.response) {
const contentType = handlerData.response.headers?.get("content-type") || "";
if (handlerData.response.headers?.get("content-length") || !STREAMING_CONTENT_TYPES.some((t) => contentType.startsWith(t))) {
return;
}
const url = handlerData.fetchData?.url || "";
const method = handlerData.fetchData?.method || "GET";
const parsedUrl = parseStringToURLObject(url);
const sanitizedUrl = url.startsWith("data:") ? stripDataUrlContent(url) : parsedUrl ? getSanitizedUrlStringFromUrlObject(parsedUrl) : url;
const streamSpan = startInactiveSpan({
name: `${method} ${sanitizedUrl}`,
startTime: handlerData.endTimestamp,
attributes: {
url: stripDataUrlContent(url),
"http.method": method,
type: "fetch",
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "http.client.stream",
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.http.browser.stream"
}
});
responseToStreamSpan.set(handlerData.response, streamSpan);
const fallbackTimeout = setTimeout(() => {
if (streamSpan.isRecording()) {
streamSpan.end();
}
}, STREAM_RESOLVE_FALLBACK_MS);
responseToFallbackTimeout.set(handlerData.response, fallbackTimeout);
}
});
}
};
});
const WEB_VITALS_INTEGRATION_NAME = "WebVitals";
const webVitalsIntegration = defineIntegration((options = {}) => {
const ignored = new Set(options.ignore ?? []);
return {
name: WEB_VITALS_INTEGRATION_NAME,
setup(client) {
const spanStreamingEnabled = hasSpanStreamingEnabled(client);
const { enableStandaloneClsSpans, enableStandaloneLcpSpans } = options._experiments ?? {};
const recordClsStandaloneSpans = spanStreamingEnabled || ignored.has("cls") ? void 0 : enableStandaloneClsSpans || false;
const recordLcpStandaloneSpans = spanStreamingEnabled || ignored.has("lcp") ? void 0 : enableStandaloneLcpSpans || false;
const finalizeWebVitals = startTrackingWebVitals({
recordClsStandaloneSpans,
recordLcpStandaloneSpans,
client
});
const pageloadSpans = /* @__PURE__ */ new WeakSet();
client.on("afterStartPageLoadSpan", (span) => {
pageloadSpans.add(span);
});
client.on("spanEnd", (span) => {
if (!pageloadSpans.delete(span)) {
return;
}
finalizeWebVitals();
addWebVitalsToSpan(span, {
// CLS/LCP are recorded as pageload span measurements only when they're neither
// tracked as standalone spans nor handled by span streaming (and not ignored).
recordClsOnPageloadSpan: recordClsStandaloneSpans === false,
recordLcpOnPageloadSpan: recordLcpStandaloneSpans === false,
spanStreamingEnabled
});
});
if (spanStreamingEnabled) {
if (!ignored.has("lcp")) {
trackLcpAsSpan(client);
}
if (!ignored.has("cls")) {
trackClsAsSpan(client);
}
if (!ignored.has("inp")) {
trackInpAsSpan();
}
} else if (!ignored.has("inp")) {
startTrackingINP();
}
},
afterAllSetup() {
if (!ignored.has("inp")) {
registerInpInteractionListener();
}
}
};
});
function registerBackgroundTabDetection() {
if (WINDOW$2.document) {
WINDOW$2.document.addEventListener("visibilitychange", () => {
const activeSpan = getActiveSpan();
if (!activeSpan) {
return;
}
const rootSpan = getRootSpan(activeSpan);
if (WINDOW$2.document.hidden && rootSpan) {
const cancelledStatus = "cancelled";
const { op, status } = spanToJSON(rootSpan);
if (DEBUG_BUILD$1) {
debug$1.log(`[Tracing] Transaction: ${cancelledStatus} -> since tab moved to the background, op: ${op}`);
}
if (!status) {
rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: cancelledStatus });
}
rootSpan.setAttribute("sentry.cancellation_reason", "document.hidden");
rootSpan.end();
}
});
} else {
DEBUG_BUILD$1 && debug$1.warn("[Tracing] Could not set up background tab detection due to lack of global document");
}
}
const PREVIOUS_TRACE_MAX_DURATION = 3600;
const PREVIOUS_TRACE_KEY = "sentry_previous_trace";
const PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE = "sentry.previous_trace";
function linkTraces(client, {
linkPreviousTrace,
consistentTraceSampling
}) {
const useSessionStorage = linkPreviousTrace === "session-storage";
let inMemoryPreviousTraceInfo = useSessionStorage ? getPreviousTraceFromSessionStorage() : void 0;
client.on("spanStart", (span) => {
if (getRootSpan(span) !== span) {
return;
}
const oldPropagationContext = getCurrentScope().getPropagationContext();
inMemoryPreviousTraceInfo = addPreviousTraceSpanLink(inMemoryPreviousTraceInfo, span, oldPropagationContext);
if (useSessionStorage) {
storePreviousTraceInSessionStorage(inMemoryPreviousTraceInfo);
}
});
let isFirstTraceOnPageload = true;
if (consistentTraceSampling) {
client.on("beforeSampling", (mutableSamplingContextData) => {
if (!inMemoryPreviousTraceInfo) {
return;
}
const scope = getCurrentScope();
const currentPropagationContext = scope.getPropagationContext();
if (isFirstTraceOnPageload && currentPropagationContext.parentSpanId) {
isFirstTraceOnPageload = false;
return;
}
scope.setPropagationContext({
...currentPropagationContext,
dsc: {
...currentPropagationContext.dsc,
sample_rate: String(inMemoryPreviousTraceInfo.sampleRate),
sampled: String(spanContextSampled(inMemoryPreviousTraceInfo.spanContext))
},
sampleRand: inMemoryPreviousTraceInfo.sampleRand
});
mutableSamplingContextData.parentSampled = spanContextSampled(inMemoryPreviousTraceInfo.spanContext);
mutableSamplingContextData.parentSampleRate = inMemoryPreviousTraceInfo.sampleRate;
mutableSamplingContextData.spanAttributes = {
...mutableSamplingContextData.spanAttributes,
[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]: inMemoryPreviousTraceInfo.sampleRate
};
});
}
}
function addPreviousTraceSpanLink(previousTraceInfo, span, oldPropagationContext) {
const spanJson = spanToJSON(span);
function getSampleRate() {
try {
const oldSampleRate = Number(
spanJson.data?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ?? oldPropagationContext.dsc?.sample_rate
);
return Number.isNaN(oldSampleRate) ? 0 : oldSampleRate;
} catch {
return 0;
}
}
const updatedPreviousTraceInfo = {
spanContext: span.spanContext(),
startTimestamp: spanJson.start_timestamp,
sampleRate: getSampleRate(),
sampleRand: oldPropagationContext.sampleRand
};
if (!previousTraceInfo) {
return updatedPreviousTraceInfo;
}
const previousTraceSpanCtx = previousTraceInfo.spanContext;
if (previousTraceSpanCtx.traceId === spanJson.trace_id) {
return previousTraceInfo;
}
if (Date.now() / 1e3 - previousTraceInfo.startTimestamp <= PREVIOUS_TRACE_MAX_DURATION) {
if (DEBUG_BUILD$1) {
debug$1.log(
`Adding previous_trace \`${JSON.stringify(previousTraceSpanCtx)}\` link to span \`${JSON.stringify({
op: spanJson.op,
...span.spanContext()
})}\``
);
}
span.addLink({
context: previousTraceSpanCtx,
attributes: {
[SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE]: "previous_trace"
}
});
span.setAttribute(
PREVIOUS_TRACE_TMP_SPAN_ATTRIBUTE,
`${previousTraceSpanCtx.traceId}-${previousTraceSpanCtx.spanId}-${spanContextSampled(previousTraceSpanCtx) ? 1 : 0}`
);
}
return updatedPreviousTraceInfo;
}
function storePreviousTraceInSessionStorage(previousTraceInfo) {
try {
WINDOW$2.sessionStorage.setItem(PREVIOUS_TRACE_KEY, JSON.stringify(previousTraceInfo));
} catch (e) {
DEBUG_BUILD$1 && debug$1.warn("Could not store previous trace in sessionStorage", e);
}
}
function getPreviousTraceFromSessionStorage() {
try {
const previousTraceInfo = WINDOW$2.sessionStorage?.getItem(PREVIOUS_TRACE_KEY);
return JSON.parse(previousTraceInfo);
} catch {
return void 0;
}
}
function spanContextSampled(ctx) {
return ctx.traceFlags === 1;
}
const BROWSER_TRACING_INTEGRATION_ID = "BrowserTracing";
const BOT_USER_AGENT_RE = /Googlebot|Google-InspectionTool|Storebot-Google|Bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Facebot|facebookexternalhit|LinkedInBot|Twitterbot|Applebot/i;
function isBotUserAgent() {
const nav = WINDOW$2.navigator;
if (!nav?.userAgent) {
return false;
}
return BOT_USER_AGENT_RE.test(nav.userAgent);
}
const DEFAULT_BROWSER_TRACING_OPTIONS = {
...TRACING_DEFAULTS,
instrumentNavigation: true,
instrumentPageLoad: true,
markBackgroundSpan: true,
enableLongTask: true,
enableLongAnimationFrame: true,
enableInp: true,
ignoreResourceSpans: [],
ignorePerformanceApiSpans: [],
detectRedirects: true,
linkPreviousTrace: "in-memory",
consistentTraceSampling: false,
enableReportPageLoaded: false,
_experiments: {},
...defaultRequestInstrumentationOptions
};
const browserTracingIntegration = ((options = {}) => {
if ("enableElementTiming" in options) {
consoleSandbox(() => {
console.warn(
"[Sentry] `enableElementTiming` is deprecated and no longer has any effect. Use the standalone `elementTimingIntegration` instead."
);
});
}
const latestRoute = {
name: void 0,
source: void 0
};
const optionalWindowDocument = WINDOW$2.document;
const {
enableInp,
enableLongTask,
enableLongAnimationFrame,
_experiments: { enableInteractions, enableStandaloneClsSpans, enableStandaloneLcpSpans },
beforeStartSpan,
idleTimeout,
finalTimeout,
childSpanTimeout,
markBackgroundSpan,
traceFetch,
traceXHR,
// eslint-disable-next-line typescript/no-deprecated
trackFetchStreamPerformance,
shouldCreateSpanForRequest,
enableHTTPTimings,
ignoreResourceSpans,
ignorePerformanceApiSpans,
instrumentPageLoad,
instrumentNavigation,
detectRedirects,
linkPreviousTrace,
consistentTraceSampling,
enableReportPageLoaded,
onRequestSpanStart,
onRequestSpanEnd
} = {
...DEFAULT_BROWSER_TRACING_OPTIONS,
...options
};
const _isBot = isBotUserAgent();
let lastInteractionTimestamp;
let _pageloadSpan;
function _createRouteSpan(client, startSpanOptions, makeActive = true, url) {
const isPageloadSpan = startSpanOptions.op === "pageload";
const initialSpanName = startSpanOptions.name;
const finalStartSpanOptions = beforeStartSpan ? beforeStartSpan(startSpanOptions) : startSpanOptions;
const urlObject = parseStringToURLObject(url || getLocationHref());
const attributes = {
...urlObject?.pathname && { [Vu]: urlObject.pathname },
...urlObject && !isURLObjectRelative(urlObject) && { [Yu]: urlObject.href },
...finalStartSpanOptions.attributes
};
if (initialSpanName !== finalStartSpanOptions.name) {
attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = "custom";
}
finalStartSpanOptions.attributes = attributes;
if (!makeActive) {
const now = dateTimestampInSeconds();
startInactiveSpan({
...finalStartSpanOptions,
startTime: now
}).end(now);
return;
}
latestRoute.name = finalStartSpanOptions.name;
latestRoute.source = attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
const idleSpan = startIdleSpan(finalStartSpanOptions, {
idleTimeout,
finalTimeout,
childSpanTimeout,
// should wait for finish signal if it's a pageload transaction
disableAutoFinish: isPageloadSpan,
beforeSpanEnd: (span) => {
addPerformanceEntries(span, {
ignoreResourceSpans,
ignorePerformanceApiSpans,
spanStreamingEnabled: hasSpanStreamingEnabled(client)
});
setActiveIdleSpan(client, void 0);
const scope = getCurrentScope();
const oldPropagationContext = scope.getPropagationContext();
scope.setPropagationContext({
...oldPropagationContext,
traceId: idleSpan.spanContext().traceId,
sampled: spanIsSampled(idleSpan),
dsc: getDynamicSamplingContextFromSpan(span)
});
if (isPageloadSpan) {
_pageloadSpan = void 0;
}
},
trimIdleSpanEndTimestamp: !enableReportPageLoaded
});
if (isPageloadSpan && enableReportPageLoaded) {
_pageloadSpan = idleSpan;
}
setActiveIdleSpan(client, idleSpan);
function emitFinish() {
if (optionalWindowDocument && ["interactive", "complete"].includes(optionalWindowDocument.readyState)) {
client.emit("idleSpanEnableAutoFinish", idleSpan);
optionalWindowDocument.removeEventListener("readystatechange", emitFinish);
}
}
if (isPageloadSpan && !enableReportPageLoaded && optionalWindowDocument) {
optionalWindowDocument.addEventListener("readystatechange", emitFinish);
emitFinish();
}
}
return {
name: BROWSER_TRACING_INTEGRATION_ID,
setup(client) {
if (_isBot) {
DEBUG_BUILD$1 && debug$1.log("[Tracing] Skipping browserTracingIntegration setup for bot user agent.");
return;
}
registerSpanErrorInstrumentation();
if (enableLongAnimationFrame && GLOBAL_OBJ.PerformanceObserver && PerformanceObserver.supportedEntryTypes?.includes("long-animation-frame")) {
startTrackingLongAnimationFrames();
} else if (enableLongTask) {
startTrackingLongTasks();
}
if (enableInteractions) {
startTrackingInteractions();
}
if (detectRedirects && optionalWindowDocument) {
const interactionHandler = () => {
lastInteractionTimestamp = timestampInSeconds();
};
addEventListener("click", interactionHandler, { capture: true });
addEventListener("keydown", interactionHandler, { capture: true, passive: true });
}
function maybeEndActiveSpan() {
const activeSpan = getActiveIdleSpan(client);
if (activeSpan && !spanToJSON(activeSpan).timestamp) {
DEBUG_BUILD$1 && debug$1.log(`[Tracing] Finishing current active span with op: ${spanToJSON(activeSpan).op}`);
activeSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, "cancelled");
activeSpan.end();
}
}
client.on("startNavigationSpan", (startSpanOptions, navigationOptions) => {
if (getClient() !== client) {
return;
}
if (navigationOptions?.isRedirect) {
DEBUG_BUILD$1 && debug$1.warn("[Tracing] Detected redirect, navigation span will not be the root span, but a child span.");
_createRouteSpan(
client,
{
op: "navigation.redirect",
...startSpanOptions
},
false,
navigationOptions.url
);
return;
}
lastInteractionTimestamp = void 0;
maybeEndActiveSpan();
getIsolationScope().setPropagationContext({
traceId: generateTraceId(),
sampleRand: Math.random(),
propagationSpanId: hasSpansEnabled() ? void 0 : generateSpanId()
});
const scope = getCurrentScope();
scope.setPropagationContext({
traceId: generateTraceId(),
sampleRand: Math.random(),
propagationSpanId: hasSpansEnabled() ? void 0 : generateSpanId()
});
scope.setSDKProcessingMetadata({
normalizedRequest: void 0
});
_createRouteSpan(
client,
{
op: "navigation",
...startSpanOptions,
// Navigation starts a new trace and is NOT parented under any active interaction (e.g. ui.action.click)
parentSpan: null,
forceTransaction: true
},
true,
navigationOptions?.url
);
});
client.on("startPageLoadSpan", (startSpanOptions, traceOptions = {}) => {
if (getClient() !== client) {
return;
}
maybeEndActiveSpan();
const sentryTrace = traceOptions.sentryTrace || getMetaContent("sentry-trace") || getServerTiming("sentry-trace");
const baggage = traceOptions.baggage || getMetaContent("baggage") || getServerTiming("baggage");
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
const scope = getCurrentScope();
scope.setPropagationContext(propagationContext);
if (!hasSpansEnabled()) {
scope.getPropagationContext().propagationSpanId = generateSpanId();
}
scope.setSDKProcessingMetadata({
normalizedRequest: getHttpRequestData()
});
_createRouteSpan(client, {
op: "pageload",
...startSpanOptions
});
});
client.on("endPageloadSpan", () => {
if (enableReportPageLoaded && _pageloadSpan) {
_pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, "reportPageLoaded");
_pageloadSpan.end();
}
});
},
afterAllSetup(client) {
if (_isBot) {
return;
}
if (client.addIntegration && !client.getIntegrationByName?.(WEB_VITALS_INTEGRATION_NAME)) {
client.addIntegration(
webVitalsIntegration({
ignore: enableInp ? [] : ["inp"],
_experiments: {
enableStandaloneClsSpans,
enableStandaloneLcpSpans
}
})
);
}
let startingUrl = getLocationHref();
if (linkPreviousTrace !== "off") {
linkTraces(client, { linkPreviousTrace, consistentTraceSampling });
}
if (WINDOW$2.location) {
if (instrumentPageLoad) {
const origin = browserPerformanceTimeOrigin();
startBrowserTracingPageLoadSpan(client, {
name: WINDOW$2.location.pathname,
// pageload should always start at timeOrigin (and needs to be in s, not ms)
startTime: origin ? origin / 1e3 : void 0,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url",
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.pageload.browser"
}
});
}
if (instrumentNavigation) {
addHistoryInstrumentationHandler(({ to, from }) => {
if (from === void 0 && startingUrl?.indexOf(to) !== -1) {
startingUrl = void 0;
return;
}
startingUrl = void 0;
const parsed = parseStringToURLObject(to);
const activeSpan = getActiveIdleSpan(client);
const navigationIsRedirect = activeSpan && detectRedirects && isRedirect(activeSpan, lastInteractionTimestamp);
startBrowserTracingNavigationSpan(
client,
{
name: parsed?.pathname || WINDOW$2.location.pathname,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url",
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.browser"
}
},
{ url: to, isRedirect: navigationIsRedirect }
);
});
}
}
if (markBackgroundSpan) {
registerBackgroundTabDetection();
}
if (enableInteractions) {
registerInteractionListener(client, idleTimeout, finalTimeout, childSpanTimeout, latestRoute);
}
instrumentOutgoingRequests(client, {
traceFetch,
traceXHR,
tracePropagationTargets: client.getOptions().tracePropagationTargets,
shouldCreateSpanForRequest,
enableHTTPTimings,
onRequestSpanStart,
onRequestSpanEnd
});
if (trackFetchStreamPerformance) {
client.addIntegration(fetchStreamPerformanceIntegration());
}
}
};
});
function startBrowserTracingPageLoadSpan(client, spanOptions, traceOptions) {
client.emit("startPageLoadSpan", spanOptions, traceOptions);
getCurrentScope().setTransactionName(spanOptions.name);
const pageloadSpan = getActiveIdleSpan(client);
if (pageloadSpan) {
client.emit("afterStartPageLoadSpan", pageloadSpan);
}
return pageloadSpan;
}
function startBrowserTracingNavigationSpan(client, spanOptions, options) {
const { url, isRedirect: isRedirect2 } = options || {};
client.emit("beforeStartNavigationSpan", spanOptions, { isRedirect: isRedirect2, url });
client.emit("startNavigationSpan", spanOptions, { isRedirect: isRedirect2, url });
const scope = getCurrentScope();
scope.setTransactionName(spanOptions.name);
if (url && !isRedirect2) {
scope.setSDKProcessingMetadata({
normalizedRequest: {
...getHttpRequestData(),
url
}
});
}
return getActiveIdleSpan(client);
}
function getMetaContent(metaName) {
const optionalWindowDocument = WINDOW$2.document;
const metaTag = optionalWindowDocument?.querySelector(`meta[name=${metaName}]`);
return metaTag?.getAttribute("content") || void 0;
}
function getServerTiming(name) {
const navigation = WINDOW$2.performance?.getEntriesByType?.("navigation")[0];
const entry = navigation?.serverTiming?.find((entry2) => entry2.name === name);
return entry?.description;
}
function registerInteractionListener(client, idleTimeout, finalTimeout, childSpanTimeout, latestRoute) {
const optionalWindowDocument = WINDOW$2.document;
let inflightInteractionSpan;
const registerInteractionTransaction = () => {
const op = "ui.action.click";
const activeIdleSpan = getActiveIdleSpan(client);
if (activeIdleSpan) {
const currentRootSpanOp = spanToJSON(activeIdleSpan).op;
if (["navigation", "pageload"].includes(currentRootSpanOp)) {
DEBUG_BUILD$1 && debug$1.warn(`[Tracing] Did not create ${op} span because a pageload or navigation span is in progress.`);
return void 0;
}
}
if (inflightInteractionSpan) {
inflightInteractionSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_IDLE_SPAN_FINISH_REASON, "interactionInterrupted");
inflightInteractionSpan.end();
inflightInteractionSpan = void 0;
}
if (!latestRoute.name) {
DEBUG_BUILD$1 && debug$1.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`);
return void 0;
}
inflightInteractionSpan = startIdleSpan(
{
name: latestRoute.name,
op,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: latestRoute.source || "url"
}
},
{
idleTimeout,
finalTimeout,
childSpanTimeout
}
);
};
if (optionalWindowDocument) {
addEventListener("click", registerInteractionTransaction, { capture: true });
}
}
const ACTIVE_IDLE_SPAN_PROPERTY = "_sentry_idleSpan";
function getActiveIdleSpan(client) {
return client[ACTIVE_IDLE_SPAN_PROPERTY];
}
function setActiveIdleSpan(client, span) {
addNonEnumerableProperty(client, ACTIVE_IDLE_SPAN_PROPERTY, span);
}
const REDIRECT_THRESHOLD = 1.5;
function isRedirect(activeSpan, lastInteractionTimestamp) {
const spanData = spanToJSON(activeSpan);
const now = dateTimestampInSeconds();
const startTimestamp = spanData.start_timestamp;
if (now - startTimestamp > REDIRECT_THRESHOLD) {
return false;
}
if (lastInteractionTimestamp && now - lastInteractionTimestamp <= REDIRECT_THRESHOLD) {
return false;
}
return true;
}
const TRACES_SAMPLE_RATE = 0.1;
const REPLAYS_SESSION_SAMPLE_RATE = 0;
const REPLAYS_ON_ERROR_SAMPLE_RATE = 1;
let sentryInitialized = false;
const isPreviewSession = () => Boolean(window.chs?.response?.attributes?.is_preview);
const initSentry = () => {
const dsn = "https://a03d66542af633f94d31af3e4039a631@o135652.ingest.us.sentry.io/4512052450754561";
if (sentryInitialized || !dsn || isPreviewSession()) {
return;
}
try {
init({
dsn,
environment: "production" || "production",
release: "958b1183f1354cfb76945e95198d67f3b85c2fd7" || void 0,
integrations: [
browserTracingIntegration(),
replayIntegration({
maskAllText: true,
maskAllInputs: true,
blockAllMedia: true
})
],
tracesSampleRate: TRACES_SAMPLE_RATE,
replaysSessionSampleRate: REPLAYS_SESSION_SAMPLE_RATE,
replaysOnErrorSampleRate: REPLAYS_ON_ERROR_SAMPLE_RATE
});
sentryInitialized = true;
} catch {
}
};
const setSentryContext = (responseUuid) => {
if (!sentryInitialized) {
return;
}
try {
setTag("response_uuid", responseUuid);
const chs = window.chs;
if (chs?.study) {
setTag("study_uuid", chs.study.id);
setTag("study_name", chs.study.attributes?.name);
}
if (chs?.response) {
setTag("is_preview", chs.response.attributes?.is_preview);
setTag("hash_child_id", chs.response.attributes?.hash_child_id);
}
} catch {
}
};
const captureCHSError = (error, context) => {
try {
captureException(
error,
context ? { tags: { chs_context: context } } : void 0
);
} catch {
}
};
const get_session_recording_data = () => {
const sessionRecorder = window.chs.sessionRecorder;
return sessionRecorder ? sessionRecorder.getSessionTrialRecordingData() : null;
};
const add_session_recording_data = (data, recordingData) => {
if (recordingData && !data.chs_recording) {
data.chs_recording = recordingData;
}
};
const retryWithBackoff = async (fn, {
retries = 3,
baseDelayMs = 1e3
} = {}) => {
for (let attempt = 0; ; attempt++) {
try {
return await fn();
} catch (err) {
if (attempt >= retries) {
throw err;
}
await new Promise(
(resolve) => setTimeout(resolve, baseDelayMs * 2 ** attempt)
);
}
}
};
const on_data_update = (jsPsychInstance, responseUuid, userFunc) => {
return async function(data) {
if (!jsPsychInstance || !jsPsychInstance.data) {
throw new NoJsPsychInstanceError();
}
await Api.updateResponse(responseUuid, {
exp_data: jsPsychInstance.data.get().values()
});
await Api.finish();
if (typeof userFunc === "function") {
userFunc(data);
}
};
};
const on_finish = (jsPsychInstance, responseUuid, userFunc) => {
return async function(data) {
if (!jsPsychInstance || !jsPsychInstance.getDisplayElement) {
throw new NoJsPsychInstanceError();
}
jsPsychInstance.getDisplayElement().innerHTML = chsTemplates.loadingAnimation();
const exp_data = data.values();
const { exit_url } = window.chs.study.attributes;
if (typeof userFunc === "function") {
userFunc(data);
}
try {
await retryWithBackoff(
() => Api.updateResponse(responseUuid, {
exp_data,
completed: true
})
);
await Api.finish();
} catch (err) {
console.error(
"Error while saving final response data after retries: ",
err
);
captureCHSError(err, "on_finish_save_response_data");
}
try {
if (window.chs.pendingUploads) {
const uploads = window.chs.pendingUploads;
const results = await Promise.allSettled(uploads.map((u) => u.promise));
results.forEach((result, i) => {
if (result.status === "rejected") {
console.error(
`Pending upload failed for "${uploads[i].file}": `,
result.reason
);
captureCHSError(result.reason, "recording_upload_failed");
}
});
let annotatedAny = false;
uploads.forEach((upload) => {
const trials = exp_data.filter(
(trial) => trial.chs_recording !== void 0 && trial.chs_recording.filename === upload.file
);
if (trials.length === 0) {
console.warn(
`No trial data found for uploaded recording "${upload.file}"; its upload status was not recorded in the data.`
);
}
trials.forEach((trial) => {
trial.chs_recording.upload_status = upload.status;
if (upload.error_message) {
trial.chs_recording.upload_error = upload.error_message;
}
annotatedAny = true;
});
});
if (annotatedAny) {
try {
await Api.updateResponse(responseUuid, {
exp_data,
completed: true
});
await Api.finish();
} catch (err) {
console.error(
"Error while saving recording upload statuses: ",
err
);
captureCHSError(err, "on_finish_save_upload_statuses");
}
}
}
} catch (err) {
console.error("Error while waiting for pending uploads: ", err);
captureCHSError(err, "on_finish_pending_uploads");
}
if (exit_url) {
let url;
try {
url = new URL(exit_url);
} catch {
try {
url = new URL(`https://${exit_url}`);
} catch {
url = new URL(window.location.origin);
}
}
const hash_child_id = window.chs.response.attributes.hash_child_id;
if (hash_child_id)
url.searchParams.set("child", hash_child_id);
url.searchParams.set("response", window.chs.response.id);
window.location.replace(url.toString());
}
};
};
const isTimelineNodeArray = (description) => {
return (Boolean(description.timeline) || Array.isArray(description)) && !description.type;
};
const isTrialWithType = (description) => {
return typeof description === "object" && !isTimelineNodeArray(
description
);
};
const lookitInitJsPsych = (responseUuid) => {
return function(opts) {
initSentry();
setSentryContext(responseUuid);
const {
on_data_update: userOnDataUpdate,
on_finish: userOnFinish,
on_trial_start: userOnTrialStart,
on_trial_finish: userOnTrialFinish,
...otherOpts
} = opts || {};
let jsPsychInstance = null;
let sessionTrialRecordingData = null;
const onTrialStart = (trial) => {
sessionTrialRecordingData = get_session_recording_data();
userOnTrialStart?.(trial);
};
const onTrialFinish = (data) => {
add_session_recording_data(data, sessionTrialRecordingData);
sessionTrialRecordingData = null;
userOnTrialFinish?.(data);
};
const onDataUpdate = (...args) => {
return on_data_update(
jsPsychInstance,
responseUuid,
userOnDataUpdate
)(...args);
};
const onFinish = (...args) => {
return on_finish(jsPsychInstance, responseUuid, userOnFinish)(...args);
};
const jsPsych = jspsychModule__namespace.initJsPsych({
...otherOpts,
on_data_update: onDataUpdate,
on_finish: onFinish,
on_trial_start: onTrialStart,
on_trial_finish: onTrialFinish
});
jsPsychInstance = jsPsych;
const origJsPsychRun = jsPsych.run;
const lookitJsPsych = jsPsych;
lookitJsPsych.run = async function(timeline) {
const handleTrialTypes = (timeline2, callback) => {
return timeline2.map(
(el) => {
if (isTimelineNodeArray(
el
)) {
if (Array.isArray(el)) {
return handleTrialTypes(el, callback);
} else if ("timeline" in el && Array.isArray(el.timeline)) {
const chsTimelineDescription = {
...el,
timeline: handleTrialTypes(
el.timeline,
callback
)
};
return chsTimelineDescription;
} else {
throw new UndefinedTimelineError(el);
}
} else if (isTrialWithType(
el
)) {
if (el !== null && "type" in el && el.type !== null && el.type !== void 0) {
const chsTrialDescription = el;
callback(chsTrialDescription);
return chsTrialDescription;
} else {
throw new UndefinedTypeError(el);
}
} else {
throw new UndefinedTimelineError(el);
}
}
);
};
const modifiedTimeline = handleTrialTypes(
timeline,
(trial) => {
if ("type" in trial) {
if (trial.type?.chsData) {
trial.data = { ...trial.data, ...trial.type.chsData() };
}
if (trial.type?.info?.name === "assent-video") {
const originalOnFinish = trial.on_finish;
trial.on_finish = (data) => {
originalOnFinish?.(data);
if (data["response"] === false) {
jsPsych.abortExperiment();
}
};
}
}
}
);
return await origJsPsychRun(modifiedTimeline);
};
return lookitJsPsych;
};
};
return lookitInitJsPsych;
})(jsPsychModule, chsData, chsTemplates);
//# sourceMappingURL=https://unpkg.com/@lookit/lookit-initjspsych@4.1.0/dist/index.browser.js.map