@backtrace/browser
Version:
Backtrace-JavaScript web browser integration
1 lines • 356 kB
Source Map (JSON)
{"version":3,"file":"bundle.cjs","sources":["../../sdk-core/lib/bundle.mjs","../src/agentDefinition.ts","../../../node_modules/tslib/tslib.es6.js","../src/BacktraceBrowserRequestHandler.ts","../src/BacktraceApi.ts","../src/BacktraceBrowserSessionProvider.ts","../src/attributes/ApplicationInformationAttributeProvider.ts","../../../node_modules/ua-parser-js/src/ua-parser.js","../src/attributes/UserAgentAttributeProvider.ts","../src/attributes/UserIdentifierAttributeProvider.ts","../src/attributes/WebsiteAttributeProvider.ts","../src/attributes/WindowAttributeProvider.ts","../src/breadcrumbs/DocumentEventSubscriber.ts","../src/breadcrumbs/HistoryEventSubscriber.ts","../src/breadcrumbs/WebRequestEventSubscriber.ts","../src/builder/BacktraceClientBuilder.ts","../src/engineDetector.ts","../src/converters/JavaScriptCoreStackTraceConverter.ts","../src/converters/SpiderMonkeyStackTraceConverter.ts","../src/converters/getStackTraceConverter.ts","../src/BacktraceClient.ts","../src/redux/BacktraceReduxMiddleware.ts"],"sourcesContent":["class BacktraceReportSubmissionResult {\n message;\n get result() {\n return this._result;\n }\n status = 'Ok';\n _result;\n constructor(statusOrResponse, message) {\n this.message = message;\n if (this.isSubmissionResponse(statusOrResponse)) {\n this.status = statusOrResponse;\n return;\n }\n this._result = statusOrResponse;\n }\n static OnLimitReached(target = 'Server') {\n return new BacktraceReportSubmissionResult('Limit reached', `${target} report limit reached`);\n }\n static SdkDisabled() {\n return new BacktraceReportSubmissionResult('Disabled SDK');\n }\n static Unsupported(message) {\n return new BacktraceReportSubmissionResult('Unsupported', message);\n }\n static ReportSkipped() {\n return new BacktraceReportSubmissionResult('Report skipped');\n }\n static OnInternalServerError(message) {\n return new BacktraceReportSubmissionResult('Server Error', message);\n }\n static OnInvalidToken() {\n return new BacktraceReportSubmissionResult('Invalid token');\n }\n static OnUnknownError(message) {\n return new BacktraceReportSubmissionResult('Unknown', message);\n }\n static OnNetworkingError(message) {\n return new BacktraceReportSubmissionResult('Network Error', message);\n }\n static Ok(response) {\n return new BacktraceReportSubmissionResult(response);\n }\n isSubmissionResponse(statusOrResponse) {\n return typeof statusOrResponse === 'string';\n }\n}\n\nfunction jsonEscaper() {\n const ancestors = [];\n const keys = [];\n // in TypeScript add \"this: any\" param to avoid compliation errors - as follows\n // return function (this: any, field: any, value: any) {\n return function (key, value) {\n if (value === null) {\n return value;\n }\n const valueType = typeof value;\n if (valueType === 'bigint') {\n return value.toString();\n }\n if (valueType !== 'object') {\n return value;\n }\n // `this` is the object that value is contained in,\n // i.e., its direct parent.\n while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) {\n ancestors.pop();\n keys.pop();\n }\n if (ancestors.includes(value)) {\n return `[Circular].${keys.filter((k) => !!k).join('.')}.${key}`;\n }\n keys.push(key);\n ancestors.push(value);\n return value;\n };\n}\n\nclass SubmissionUrlInformation {\n static SUBMIT_PREFIX = 'submit.backtrace.io/';\n /**\n * Convert url/token from credentials to JSON submission URL\n * @param url credentials URL\n * @param token credentials token\n * @returns JSON submissionURL\n */\n static toJsonReportSubmissionUrl(url, token) {\n // if the token doesn't exist - use URL\n if (!token) {\n return url;\n }\n // if the url points to submit, we should always use it without any modifications\n if (url.includes(this.SUBMIT_PREFIX)) {\n return url;\n }\n // if the URL has token in the URL, the user probably added a token once again\n // in this case, don't do anything\n if (url.indexOf(token) !== -1) {\n return url;\n }\n const result = new URL(`/post`, url);\n result.searchParams.append('format', 'json');\n result.searchParams.append('token', token);\n return result.href;\n }\n /**\n * Converts full submission JSON URL to PlCrashReporter submission URL\n * @param submissionUrl Backtrace Submission URL\n */\n static toPlCrashReporterSubmissionUrl(submissionUrl) {\n return this.changeSubmissionFormat(submissionUrl, 'plcrash');\n }\n /**\n * Converts full submission JSON URL to minidump submission URL\n * @param submissionUrl Backtrace Submission URL\n */\n static toMinidumpSubmissionUrl(submissionUrl) {\n return this.changeSubmissionFormat(submissionUrl, 'minidump');\n }\n static toAttachmentSubmissionUrl(submissionUrl, rxid, attachmentName) {\n const query = `object=${rxid}&attachment_name=${attachmentName}`;\n if (submissionUrl.includes('?')) {\n return (submissionUrl += `&` + query);\n }\n return (submissionUrl += '?' + query);\n }\n /**\n * Find the universe based on the submission URL\n * @param submissionUrl submission URL - full submission URL to Backtrace.\n * @returns universe name\n */\n static findUniverse(submissionUrl) {\n const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX);\n if (submitIndex !== -1) {\n // submit format URL\n // submit.backtrace.io/universe/token/format\n // we can expect the universe name just after the hostname\n const universeStartIndex = submitIndex + this.SUBMIT_PREFIX.length;\n const endOfUniverseName = submissionUrl.indexOf('/', universeStartIndex);\n return submissionUrl.substring(universeStartIndex, endOfUniverseName);\n }\n // the universe name should be available in the hostname\n // for example abc.sp.backtrace.io or zyx.in.backtrace.io or foo.backtrace.io\n const domainIndex = submissionUrl.indexOf('.backtrace.io');\n if (domainIndex === -1) {\n return undefined;\n }\n const protocolSeparator = '://';\n let protocolEndIndex = submissionUrl.indexOf(protocolSeparator);\n if (protocolEndIndex === -1) {\n protocolEndIndex = 0;\n }\n else {\n protocolEndIndex += protocolSeparator.length;\n }\n const hostname = submissionUrl.substring(protocolEndIndex, domainIndex);\n const endOfUniverseName = hostname.indexOf('.');\n return endOfUniverseName === -1 ? hostname : hostname.substring(0, endOfUniverseName);\n }\n static findToken(submissionUrl) {\n const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX);\n if (submitIndex !== -1) {\n const submissionUrlParts = submissionUrl.split('/');\n // submit format URL\n // submit.backtrace.io/universe/token/format\n // by spliting the submission URL by `/` and dropping the last\n // part of the URL, the last element on the list is the token.\n return submissionUrlParts[submissionUrlParts.length - 2] ?? null;\n }\n const url = new URL(submissionUrl);\n return url.searchParams.get('token');\n }\n static changeSubmissionFormat(submissionUrl, desiredFormat) {\n const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX);\n if (submitIndex !== -1) {\n const queryParametersIndex = submissionUrl.indexOf('?');\n const queryParameters = queryParametersIndex === -1 ? '' : submissionUrl.substring(queryParametersIndex);\n const pathname = submissionUrl.substring(submitIndex + this.SUBMIT_PREFIX.length, queryParametersIndex === -1 ? undefined : queryParametersIndex);\n const pathParts = pathname.split('/');\n // path parts are prefixed with '/' character. Expected and valid submit format is:\n // /universe/token/format\n // splitting pathname should generate at least 4 elements ('', universe, token, format)\n // if pathParts length is not equal to 4 then the invalid were passed.\n const expectedMinimalPathParts = 3;\n if (pathParts.length < expectedMinimalPathParts) {\n return submissionUrl;\n }\n pathParts[2] = desiredFormat;\n return (submissionUrl.substring(0, submitIndex + this.SUBMIT_PREFIX.length) +\n pathParts.join('/') +\n queryParameters);\n }\n else {\n const url = new URL(submissionUrl);\n url.searchParams.set('format', desiredFormat);\n return url.href;\n }\n }\n}\n\nclass RequestBacktraceReportSubmission {\n _requestHandler;\n _submissionUrl;\n constructor(options, _requestHandler) {\n this._requestHandler = _requestHandler;\n this._submissionUrl = SubmissionUrlInformation.toJsonReportSubmissionUrl(options.url, options.token);\n }\n send(data, attachments, abortSignal) {\n const json = JSON.stringify(data, jsonEscaper());\n return this._requestHandler.postError(this._submissionUrl, json, attachments, abortSignal);\n }\n async sendAttachment(rxid, attachment, abortSignal) {\n if (!this._requestHandler.postAttachment) {\n return BacktraceReportSubmissionResult.Unsupported('postAttachment is not implemented');\n }\n return await this._requestHandler.postAttachment(SubmissionUrlInformation.toAttachmentSubmissionUrl(this._submissionUrl, rxid, attachment.name), attachment, abortSignal);\n }\n}\n\nconst DEFAULT_TIMEOUT = 15_000;\n\nclass ConnectionError {\n /**\n * Verifies if an Error is a connection error\n * @param err error\n * @returns true if the error was caused by ETIMEDOUT or ECONNRESET or ECONNABORTED\n */\n static isConnectionError(err) {\n const error = err;\n return error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET' || error.code === 'ECONNABORTED';\n }\n}\n\nclass MetricsUrlInformation {\n static generateSummedEventsUrl(hostname, submissionUrl, credentialsToken) {\n const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken);\n if (!submissionInformation) {\n return undefined;\n }\n return this.generateEventsServiceUrl(hostname, 'summed-events', submissionInformation.universe, submissionInformation.token);\n }\n static generateUniqueEventsUrl(hostname, submissionUrl, credentialsToken) {\n const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken);\n if (!submissionInformation) {\n return undefined;\n }\n return this.generateEventsServiceUrl(hostname, 'unique-events', submissionInformation.universe, submissionInformation.token);\n }\n static generateEventsServiceUrl(hostname, eventServiceName, universe, token) {\n return new URL(`/api/${eventServiceName}/submit?universe=${universe}&token=${token}`, hostname).toString();\n }\n static findSubmissionInformation(submissionUrl, token) {\n const universe = SubmissionUrlInformation.findUniverse(submissionUrl);\n if (!universe) {\n return undefined;\n }\n token = token ?? SubmissionUrlInformation.findToken(submissionUrl);\n if (!token) {\n return undefined;\n }\n return { universe, token };\n }\n}\n\nclass BacktraceCoreApi {\n _requestHandler;\n _summedMetricsSubmissionUrl;\n _uniqueMetricsSubmissionUrl;\n _requestBacktraceReportSubmission;\n constructor(options, _requestHandler) {\n this._requestHandler = _requestHandler;\n this._summedMetricsSubmissionUrl = MetricsUrlInformation.generateSummedEventsUrl(options.metrics?.url ?? 'https://events.backtrace.io', options.url, options.token);\n this._uniqueMetricsSubmissionUrl = MetricsUrlInformation.generateUniqueEventsUrl(options.metrics?.url ?? 'https://events.backtrace.io', options.url, options.token);\n this._requestBacktraceReportSubmission =\n options.requestBacktraceReportSubmission ??\n new RequestBacktraceReportSubmission({\n url: options.url,\n }, this._requestHandler);\n }\n sendReport(data, attachments, abortSignal) {\n return this._requestBacktraceReportSubmission.send(data, attachments, abortSignal);\n }\n sendAttachment(rxid, attachment, abortSignal) {\n return this._requestBacktraceReportSubmission.sendAttachment(rxid, attachment, abortSignal);\n }\n sendUniqueMetrics(metrics, abortSignal) {\n if (!this._uniqueMetricsSubmissionUrl) {\n throw new Error('Unique metrics URL is not available.');\n }\n return this._requestHandler.post(this._uniqueMetricsSubmissionUrl, JSON.stringify(metrics), abortSignal);\n }\n sendSummedMetrics(metrics, abortSignal) {\n if (!this._summedMetricsSubmissionUrl) {\n throw new Error('Summed metrics URL is not available.');\n }\n return this._requestHandler.post(this._summedMetricsSubmissionUrl, JSON.stringify(metrics), abortSignal);\n }\n}\n\nclass Events {\n _callbacks = {};\n on(event, callback) {\n this.addCallback(event, { callback });\n return this;\n }\n once(event, callback) {\n this.addCallback(event, { callback, once: true });\n return this;\n }\n off(event, callback) {\n this.removeCallback(event, callback);\n return this;\n }\n emit(event, ...args) {\n const callbacks = this._callbacks[event];\n if (!callbacks || !callbacks.length) {\n return false;\n }\n for (const { callback, once } of [...callbacks]) {\n try {\n callback(...args);\n }\n catch {\n // Do nothing\n }\n if (once) {\n this.removeCallback(event, callback);\n }\n }\n return true;\n }\n addCallback(event, callback) {\n const list = this._callbacks[event];\n if (list) {\n list.push(callback);\n }\n else {\n this._callbacks[event] = [callback];\n }\n }\n removeCallback(event, callback) {\n const list = this._callbacks[event];\n if (!list) {\n return;\n }\n const index = list.findIndex((el) => el.callback === callback);\n if (index === -1) {\n return;\n }\n list.splice(index, 1);\n if (!list.length) {\n delete this._callbacks[event];\n }\n }\n}\n\nclass TimeHelper {\n static now() {\n return Date.now();\n }\n static toTimestampInSec(timestampMs) {\n return Math.floor(timestampMs / 1000);\n }\n static convertSecondsToMilliseconds(timeInSec) {\n return timeInSec * 1000;\n }\n}\n\nclass BacktraceReport {\n data;\n attributes;\n attachments;\n /**\n * Report classifiers\n */\n classifiers = [];\n /**\n * Report annotations\n */\n annotations = {};\n /**\n * Report stack trace\n */\n stackTrace = {};\n /**\n * Report message\n */\n message;\n /**\n * Report inner errors\n */\n innerReport = [];\n /**\n * Report timestamp in ms\n */\n timestamp = TimeHelper.now();\n /**\n * Sets how many top frames should be skipped.\n */\n skipFrames = 0;\n addStackTrace(name, stack, message = '') {\n if (typeof stack === 'string') {\n this.stackTrace[name] = {\n stack,\n message,\n };\n }\n else {\n this.stackTrace[name] = stack;\n }\n return this;\n }\n constructor(data, attributes = {}, attachments = [], options = {}) {\n this.data = data;\n this.attributes = attributes;\n this.attachments = attachments;\n this.skipFrames = options?.skipFrames ?? 0;\n let errorType = 'Exception';\n if (data instanceof Error) {\n this.message = this.generateErrorMessage(data.message);\n this.annotations['error'] = {\n ...data,\n message: this.message,\n name: data.name,\n stack: data.stack,\n };\n this.classifiers = [data.name];\n this.stackTrace['main'] = {\n stack: data.stack ?? '',\n message: this.message,\n };\n // Supported in ES2022\n if (data.cause) {\n this.innerReport.push(data.cause);\n }\n }\n else {\n this.message = this.generateErrorMessage(data);\n this.stackTrace['main'] = {\n stack: new Error().stack ?? '',\n message: this.message,\n };\n this.classifiers = ['Message'];\n errorType = 'Message';\n this.skipFrames += 1;\n }\n if (!this.attributes['error.type']) {\n this.attributes['error.type'] = errorType;\n }\n this.attributes['error.message'] = this.message;\n if (options?.timestamp) {\n this.timestamp = options.timestamp;\n }\n if (options?.classifiers) {\n this.classifiers.unshift(...options.classifiers);\n }\n }\n generateErrorMessage(data) {\n return typeof data === 'object' ? JSON.stringify(data, jsonEscaper()) : (data?.toString() ?? '');\n }\n}\n\nclass AttachmentManager {\n attachmentEvents;\n _attachmentProviders = [];\n constructor() {\n this.attachmentEvents = new Events();\n }\n /**\n * Adds attachment to manager cache.\n * @param attachments attachments or attachment returning functions\n */\n add(...attachments) {\n this.addProviders(...attachments.map((a) => typeof a === 'function'\n ? {\n type: 'dynamic',\n get: a,\n }\n : {\n type: 'scoped',\n get: () => a,\n }));\n }\n /**\n * Adds `BacktraceAttachmentProvider` to manager cache.\n * @param attachmentProviders attachment providers\n */\n addProviders(...attachmentProviders) {\n let anyScoped = false;\n for (const provider of attachmentProviders) {\n if (provider.type === 'dynamic') {\n this._attachmentProviders.push(provider);\n }\n else {\n const attachment = provider.get();\n this._attachmentProviders.push({\n type: 'scoped',\n get: () => attachment,\n });\n anyScoped = true;\n }\n }\n if (anyScoped) {\n this.attachmentEvents.emit('scoped-attachments-updated', this.get('scoped'));\n }\n }\n /**\n * Returns scoped, dynamic, or all attachments.\n * @param type optional type to filter attachments\n * @returns array of `BacktraceAttachment`\n */\n get(type) {\n const result = [];\n for (const provider of this._attachmentProviders) {\n if (type && provider.type !== type) {\n continue;\n }\n const attachment = provider.get();\n if (!attachment) {\n continue;\n }\n if (Array.isArray(attachment)) {\n result.push(...attachment);\n }\n else {\n result.push(attachment);\n }\n }\n return result;\n }\n}\n\nclass ReportDataBuilder {\n static build(attributes) {\n const result = { annotations: {}, attributes: {} };\n if (!attributes) {\n return result;\n }\n for (const attributeKey in attributes) {\n const attribute = attributes[attributeKey];\n if (attribute == null) {\n result.attributes[attributeKey] = attribute;\n continue;\n }\n switch (typeof attribute) {\n case 'object': {\n result.annotations[attributeKey] = attribute;\n break;\n }\n case 'bigint': {\n result.attributes[attributeKey] = attribute.toString();\n break;\n }\n default: {\n result.attributes[attributeKey] = attribute;\n break;\n }\n }\n }\n return result;\n }\n}\n\nclass AttributeManager {\n attributeEvents;\n _attributeProviders = [];\n constructor(providers) {\n this.attributeEvents = new Events();\n for (const provider of providers) {\n this.addProvider(provider);\n }\n }\n /**\n * Adds attributes to manager cache\n * @param attributes attributes object\n */\n add(attributes) {\n if (typeof attributes === 'function') {\n this.addProvider({ type: 'dynamic', get: attributes });\n }\n else {\n this.addProvider({ type: 'scoped', get: () => attributes });\n }\n }\n /**\n * Adds attribute provider to the manager\n * @param attributeProvider\n * @returns\n */\n addProvider(attributeProvider) {\n if (attributeProvider.type === 'dynamic') {\n this._attributeProviders.push(attributeProvider);\n return;\n }\n else {\n const attributes = attributeProvider.get();\n this._attributeProviders.push({\n type: 'scoped',\n get: () => attributes,\n });\n this.attributeEvents.emit('scoped-attributes-updated', this.get('scoped'));\n }\n }\n /**\n * Gets client attributes\n * @returns Report attribute - client attributes and annotations\n */\n get(attributeType) {\n const result = {\n annotations: {},\n attributes: {},\n };\n for (const attributeProvider of this._attributeProviders) {\n if (attributeType && attributeProvider.type != attributeType) {\n continue;\n }\n const providerResult = ReportDataBuilder.build(attributeProvider.get());\n result.attributes = {\n ...result.attributes,\n ...providerResult.attributes,\n };\n result.annotations = {\n ...result.annotations,\n ...providerResult.annotations,\n };\n }\n return result;\n }\n}\n\nclass ClientAttributeProvider {\n _sdkName;\n _sdkVersion;\n _sessionId;\n constructor(_sdkName, _sdkVersion, _sessionId) {\n this._sdkName = _sdkName;\n this._sdkVersion = _sdkVersion;\n this._sessionId = _sessionId;\n }\n get type() {\n return 'scoped';\n }\n get() {\n return {\n 'application.session': this._sessionId,\n 'backtrace.agent': this._sdkName,\n 'backtrace.version': this._sdkVersion,\n };\n }\n}\n\nclass UserAttributeProvider {\n type;\n _source;\n constructor(source) {\n this._source = typeof source === 'function' ? source : () => source;\n this.type = typeof source === 'function' ? 'dynamic' : 'scoped';\n }\n get() {\n return this._source();\n }\n}\n\nfunction stringifiedSize(value) {\n return JSON.stringify(value).length;\n}\nfunction toStringSize(value) {\n return value.toString().length;\n}\nconst stringSize = (value) => stringifiedSize(value);\nconst numberSize = (toStringSize);\nconst bigintSize = (toStringSize);\nconst symbolSize = 0;\nconst functionSize = 0;\nconst booleanSize = (value) => (value ? 4 : 5);\nconst undefinedSize = 0;\nconst nullSize = 'null'.length;\nfunction arraySize(array, replacer) {\n const bracketLength = 2;\n const commaLength = array.length - 1;\n let elementsLength = 0;\n for (let i = 0; i < array.length; i++) {\n const element = array[i];\n switch (typeof element) {\n case 'function':\n case 'symbol':\n case 'undefined':\n elementsLength += nullSize;\n break;\n default:\n elementsLength += _jsonSize(array, i.toString(), element, replacer);\n }\n }\n return bracketLength + commaLength + elementsLength;\n}\nconst objectSize = (obj, replacer) => {\n const entries = Object.entries(obj);\n const bracketLength = 2;\n let entryCount = 0;\n let entriesLength = 0;\n for (const [k, v] of entries) {\n const valueSize = _jsonSize(obj, k, v, replacer);\n if (valueSize === 0) {\n continue;\n }\n entryCount++;\n // +1 adds the comma size\n entriesLength += keySize(k) + valueSize + 1;\n }\n // -1 removes previously added last comma size (there is no trailing comma)\n const commaLength = Math.max(0, entryCount - 1);\n return bracketLength + commaLength + entriesLength;\n};\nfunction keySize(key) {\n const QUOTE_SIZE = 2;\n if (key === null) {\n return nullSize + QUOTE_SIZE;\n }\n else if (key === undefined) {\n return '\"undefined\"'.length;\n }\n switch (typeof key) {\n case 'string':\n return stringSize(key);\n case 'number':\n return numberSize(key) + QUOTE_SIZE;\n case 'boolean':\n return booleanSize(key) + QUOTE_SIZE;\n case 'symbol':\n return symbolSize; // key not used in JSON\n default:\n return stringSize(key.toString());\n }\n}\nfunction _jsonSize(parent, key, value, replacer) {\n if (value && typeof value === 'object' && 'toJSON' in value && typeof value.toJSON === 'function') {\n value = value.toJSON();\n }\n value = replacer ? replacer.call(parent, key, value) : value;\n if (value === null) {\n return nullSize;\n }\n else if (value === undefined) {\n return undefinedSize;\n }\n if (Array.isArray(value)) {\n return arraySize(value, replacer);\n }\n switch (typeof value) {\n case 'bigint':\n return bigintSize(value);\n case 'boolean':\n return booleanSize(value);\n case 'function':\n return functionSize;\n case 'number':\n return numberSize(value);\n case 'object':\n return objectSize(value, replacer);\n case 'string':\n return stringSize(value);\n case 'symbol':\n return symbolSize;\n case 'undefined':\n return undefinedSize;\n }\n return 0;\n}\n/**\n * Calculates size of the object as it would be serialized into JSON.\n *\n * _Should_ return the same value as `JSON.stringify(value, replacer).length`.\n * This may not be 100% accurate, but should work for our requirements.\n * @param value Value to compute length for.\n * @param replacer A function that transforms the results as in `JSON.stringify`.\n * @returns Final string length.\n */\nfunction jsonSize(value, replacer) {\n return _jsonSize(undefined, '', value, replacer);\n}\n\nconst REMOVED_PLACEHOLDER = '<removed>';\nfunction limitObjectDepth(obj, depth) {\n if (!(depth < Infinity)) {\n return obj;\n }\n if (depth < 0) {\n return REMOVED_PLACEHOLDER;\n }\n const limitIfObject = (value) => typeof value === 'object' && value ? limitObjectDepth(value, depth - 1) : value;\n const result = {};\n for (const key in obj) {\n const value = obj[key];\n if (Array.isArray(value)) {\n result[key] = value.map(limitIfObject);\n }\n else {\n result[key] = limitIfObject(value);\n }\n }\n return result;\n}\n\nfunction textFormatter() {\n const defaultFormatter = fallbackFormatter(jsonEscaper());\n try {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const util = require('util');\n return util.format ?? defaultFormatter;\n }\n catch {\n return defaultFormatter;\n }\n}\nfunction fallbackFormatter(jsonEscapeFunction) {\n return function fallbackFormatter(...params) {\n let result = '';\n for (const param of params) {\n result += typeof param === 'object' ? JSON.stringify(param, jsonEscapeFunction) : param?.toString();\n }\n return result;\n };\n}\n\nvar BreadcrumbLogLevel;\n(function (BreadcrumbLogLevel) {\n BreadcrumbLogLevel[BreadcrumbLogLevel[\"Verbose\"] = 1] = \"Verbose\";\n BreadcrumbLogLevel[BreadcrumbLogLevel[\"Debug\"] = 2] = \"Debug\";\n BreadcrumbLogLevel[BreadcrumbLogLevel[\"Info\"] = 4] = \"Info\";\n BreadcrumbLogLevel[BreadcrumbLogLevel[\"Warning\"] = 8] = \"Warning\";\n BreadcrumbLogLevel[BreadcrumbLogLevel[\"Error\"] = 16] = \"Error\";\n})(BreadcrumbLogLevel || (BreadcrumbLogLevel = {}));\nconst defaultBreadcrumbsLogLevel = (1 << 5) - 1;\n\nvar BreadcrumbType;\n(function (BreadcrumbType) {\n BreadcrumbType[BreadcrumbType[\"Manual\"] = 1] = \"Manual\";\n BreadcrumbType[BreadcrumbType[\"Log\"] = 2] = \"Log\";\n BreadcrumbType[BreadcrumbType[\"Navigation\"] = 4] = \"Navigation\";\n BreadcrumbType[BreadcrumbType[\"Http\"] = 8] = \"Http\";\n BreadcrumbType[BreadcrumbType[\"System\"] = 16] = \"System\";\n BreadcrumbType[BreadcrumbType[\"User\"] = 32] = \"User\";\n BreadcrumbType[BreadcrumbType[\"Configuration\"] = 64] = \"Configuration\";\n})(BreadcrumbType || (BreadcrumbType = {}));\nconst defaultBreadcurmbType = (1 << 7) - 1;\n\nclass ConsoleEventSubscriber {\n /**\n * All overriden console events\n */\n _events = {};\n _formatter;\n start(backtraceBreadcrumbs) {\n if ((backtraceBreadcrumbs.breadcrumbsType & BreadcrumbType.Log) !== BreadcrumbType.Log) {\n return;\n }\n this._formatter = textFormatter();\n this.bindToConsoleMethod('log', BreadcrumbLogLevel.Info, backtraceBreadcrumbs);\n this.bindToConsoleMethod('warn', BreadcrumbLogLevel.Warning, backtraceBreadcrumbs);\n this.bindToConsoleMethod('error', BreadcrumbLogLevel.Error, backtraceBreadcrumbs);\n this.bindToConsoleMethod('debug', BreadcrumbLogLevel.Debug, backtraceBreadcrumbs);\n this.bindToConsoleMethod('trace', BreadcrumbLogLevel.Verbose, backtraceBreadcrumbs);\n }\n dispose() {\n for (const key in this._events) {\n const consoleMethod = this._events[key];\n console[key] = consoleMethod;\n }\n }\n bindToConsoleMethod(name, level, backtraceBreadcrumbs) {\n const originalMethod = console[name];\n console[name] = (...args) => {\n originalMethod(...args);\n const message = this._formatter(...args);\n backtraceBreadcrumbs.addBreadcrumb(message, level, BreadcrumbType.Log);\n };\n this._events[name] = originalMethod;\n }\n}\n\n/**\n * Constrains `value` to `min` and `max` values, wrapping not matching values around.\n * @param min minimum value to allow\n * @param max maximum value to allow\n * @returns function accepting `value`\n *\n * @example\n * const wrap = wrapped(10, 20);\n * console.log(wrap(15)); // 15\n * console.log(wrap(21)); // 10, wrapped around\n * console.log(wrap(8)); // 18, wrapped around\n */\nfunction wrapped(min, max) {\n function wrapped(value) {\n const range = max - min;\n let newValue;\n if (value < min) {\n newValue = max - ((min - value) % range);\n if (newValue === max) {\n newValue = min;\n }\n }\n else if (value >= max) {\n newValue = min + ((value - max) % range);\n if (newValue === max) {\n newValue = min;\n }\n }\n else {\n newValue = value;\n }\n return newValue;\n }\n return wrapped;\n}\n/**\n * Constrains `value` to `min` and `max` values.\n * @param min minimum value to allow\n * @param max maximum value to allow\n * @returns function accepting `value`\n *\n * @example\n * const clamp = clamped(10, 20);\n * console.log(wrap(15)); // 15\n * console.log(wrap(21)); // 20\n * console.log(wrap(8)); // 10\n */\nfunction clamped(min, max) {\n function clamped(value) {\n return Math.max(min, Math.min(value, max));\n }\n return clamped;\n}\n\nclass OverwritingArray {\n capacity;\n _array;\n _headConstraint;\n _lengthConstraint;\n _head = 0;\n _length = 0;\n get head() {\n return this._head;\n }\n set head(value) {\n this._head = this._headConstraint(value);\n }\n get length() {\n return this._length;\n }\n set length(value) {\n this._length = this._lengthConstraint(value);\n }\n get start() {\n return this._headConstraint(this.head - this.length);\n }\n constructor(capacity, items) {\n this.capacity = capacity;\n this._array = new Array(capacity);\n // Head must be always between 0 and capacity.\n // If lower than 0, it needs to go from the end\n // If larger than capacity, it needs to go from the start\n // Wrapping solves this\n this._headConstraint = wrapped(0, capacity);\n // Length must be always no less than 0 and no larger than capacity\n this._lengthConstraint = clamped(0, capacity);\n if (items) {\n this.push(...items);\n }\n }\n add(item) {\n return this.pushOne(item);\n }\n push(...items) {\n for (const item of items) {\n this.pushOne(item);\n }\n return this.length;\n }\n pop() {\n this.head--;\n const element = this._array[this.head];\n this._array[this.head] = undefined;\n this.length--;\n return element;\n }\n shift() {\n const element = this._array[this.start];\n this._array[this.start] = undefined;\n this.length--;\n return element;\n }\n at(index) {\n return this._array[this.index(index)];\n }\n *values() {\n for (let i = 0; i < this.length; i++) {\n const index = this.index(i);\n yield this._array[index];\n }\n }\n *keys() {\n for (let i = 0; i < this.length; i++) {\n yield i;\n }\n }\n *entries() {\n for (let i = 0; i < this.length; i++) {\n const index = this.index(i);\n yield [i, this._array[index]];\n }\n }\n [Symbol.iterator]() {\n return this.values();\n }\n pushOne(item) {\n this._array[this.head] = item;\n this.head++;\n this.length++;\n }\n index(value) {\n if (!this.length) {\n return this._headConstraint(value);\n }\n const index = (value % this.length) + this.start;\n return this._headConstraint(index);\n }\n}\n\nclass InMemoryBreadcrumbsStorage {\n _limits;\n get lastBreadcrumbId() {\n return this._lastBreadcrumbId;\n }\n /**\n * Breadcrumb name\n */\n name = 'bt-breadcrumbs-0';\n _lastBreadcrumbId = TimeHelper.toTimestampInSec(TimeHelper.now());\n _breadcrumbs;\n _breadcrumbSizes;\n constructor(_limits) {\n this._limits = _limits;\n this._breadcrumbs = new OverwritingArray(_limits.maximumBreadcrumbs ?? 100);\n this._breadcrumbSizes = new OverwritingArray(this._breadcrumbs.capacity);\n }\n getAttachments() {\n return [this];\n }\n getAttachmentProviders() {\n return [\n {\n get: () => this,\n type: 'scoped',\n },\n ];\n }\n static factory({ limits }) {\n return new InMemoryBreadcrumbsStorage(limits);\n }\n /**\n * Returns breadcrumbs in the JSON format\n * @returns Breadcrumbs JSON\n */\n get() {\n return JSON.stringify([...this._breadcrumbs], jsonEscaper());\n }\n add(rawBreadcrumb) {\n this._lastBreadcrumbId++;\n const id = this._lastBreadcrumbId;\n const breadcrumb = {\n id,\n message: rawBreadcrumb.message,\n timestamp: TimeHelper.now(),\n type: BreadcrumbType[rawBreadcrumb.type].toLowerCase(),\n level: BreadcrumbLogLevel[rawBreadcrumb.level].toLowerCase(),\n };\n if (rawBreadcrumb.attributes) {\n breadcrumb.attributes = rawBreadcrumb.attributes;\n }\n this._breadcrumbs.add(breadcrumb);\n if (this._limits.maximumTotalBreadcrumbsSize) {\n const size = jsonSize(breadcrumb, jsonEscaper());\n this._breadcrumbSizes.add(size);\n let totalSize = this.totalSize();\n while (totalSize > this._limits.maximumTotalBreadcrumbsSize) {\n this._breadcrumbs.shift();\n const removedSize = this._breadcrumbSizes.shift() ?? 0;\n // We subtract removedSize plus comma in JSON\n totalSize -= removedSize + 1;\n }\n }\n return id;\n }\n totalSize() {\n let sum = 0;\n for (const size of this._breadcrumbSizes) {\n sum += size;\n }\n // Sum of:\n // - all breadcrumbs\n // - comma count\n // - brackets\n return sum + Math.max(0, this._breadcrumbSizes.length - 1) + 2;\n }\n}\n\nconst BREADCRUMB_ATTRIBUTE_NAME = 'breadcrumbs.lastId';\n/**\n * @returns `undefined` if value is `false`, else `value` if defined, else `defaultValue`\n */\nconst defaultIfNotFalse = (value, defaultValue) => {\n return value === false ? undefined : value !== undefined ? value : defaultValue;\n};\nclass BreadcrumbsManager {\n /**\n * Breadcrumbs type\n */\n breadcrumbsType;\n /**\n * Breadcrumbs Log level\n */\n logLevel;\n /**\n * Determines if the breadcrumb manager is enabled.\n */\n _enabled = false;\n _limits;\n _eventSubscribers = [new ConsoleEventSubscriber()];\n _interceptor;\n _storage;\n constructor(configuration, options) {\n this._limits = {\n maximumBreadcrumbs: defaultIfNotFalse(configuration?.maximumBreadcrumbs, 100),\n maximumAttributesDepth: defaultIfNotFalse(configuration?.maximumAttributesDepth, 2),\n maximumBreadcrumbMessageLength: defaultIfNotFalse(configuration?.maximumBreadcrumbMessageLength, 255),\n maximumBreadcrumbSize: defaultIfNotFalse(configuration?.maximumBreadcrumbSize, 64 * 1024),\n maximumTotalBreadcrumbsSize: defaultIfNotFalse(configuration?.maximumTotalBreadcrumbsSize, 1024 * 1024),\n };\n this.breadcrumbsType = configuration?.eventType ?? defaultBreadcurmbType;\n this.logLevel = configuration?.logLevel ?? defaultBreadcrumbsLogLevel;\n this._storage = (options?.storage ?? InMemoryBreadcrumbsStorage.factory)({ limits: this._limits });\n this._interceptor = configuration?.intercept;\n if (options?.subscribers) {\n this._eventSubscribers.push(...options.subscribers);\n }\n }\n addEventSubscriber(subscriber) {\n if (this._enabled) {\n subscriber.start(this);\n }\n this._eventSubscribers.push(subscriber);\n }\n setStorage(storage) {\n if (typeof storage === 'function') {\n this._storage = storage({ limits: this._limits });\n }\n else {\n this._storage = storage;\n }\n }\n dispose() {\n this._enabled = false;\n for (const subscriber of this._eventSubscribers) {\n subscriber.dispose();\n }\n }\n bind({ client, attachmentManager }) {\n if (this._storage.getAttachmentProviders) {\n attachmentManager.addProviders(...this._storage.getAttachmentProviders());\n }\n else {\n attachmentManager.add(...this._storage.getAttachments());\n }\n client.addAttribute(() => ({\n [BREADCRUMB_ATTRIBUTE_NAME]: this._storage.lastBreadcrumbId,\n }));\n client.on('before-skip', (report) => this.logReport(report));\n }\n initialize() {\n if (this._enabled) {\n return;\n }\n for (const subscriber of this._eventSubscribers) {\n subscriber.start(this);\n }\n this._enabled = true;\n }\n verbose(message, attributes) {\n return this.log(message, BreadcrumbLogLevel.Verbose, attributes);\n }\n debug(message, attributes) {\n return this.log(message, BreadcrumbLogLevel.Debug, attributes);\n }\n info(message, attributes) {\n return this.log(message, BreadcrumbLogLevel.Info, attributes);\n }\n warn(message, attributes) {\n return this.log(message, BreadcrumbLogLevel.Warning, attributes);\n }\n error(message, attributes) {\n return this.log(message, BreadcrumbLogLevel.Error, attributes);\n }\n log(message, level, attributes) {\n return this.addBreadcrumb(message, level, BreadcrumbType.Manual, attributes);\n }\n logReport(report) {\n const level = report.data instanceof Error ? BreadcrumbLogLevel.Error : BreadcrumbLogLevel.Warning;\n return this.addBreadcrumb(report.message, level, BreadcrumbType.System);\n }\n addBreadcrumb(message, level, type, attributes) {\n if (!this._enabled) {\n return false;\n }\n let rawBreadcrumb = {\n message: this.prepareBreadcrumbMessage(message),\n level,\n type,\n attributes,\n };\n if (this._interceptor) {\n const interceptorBreadcrumb = this._interceptor(rawBreadcrumb);\n if (!interceptorBreadcrumb) {\n return false;\n }\n rawBreadcrumb = interceptorBreadcrumb;\n }\n if ((this.logLevel & rawBreadcrumb.level) !== level) {\n return false;\n }\n if ((this.breadcrumbsType & rawBreadcrumb.type) !== type) {\n return false;\n }\n if (this._limits.maximumBreadcrumbMessageLength !== undefined) {\n rawBreadcrumb.message = rawBreadcrumb.message.substring(0, this._limits.maximumBreadcrumbMessageLength);\n }\n let limitedBreadcrumb;\n if (this._limits.maximumAttributesDepth !== undefined && rawBreadcrumb.attributes) {\n limitedBreadcrumb = {\n ...rawBreadcrumb,\n attributes: limitObjectDepth(rawBreadcrumb.attributes, this._limits.maximumAttributesDepth),\n };\n }\n else {\n limitedBreadcrumb = rawBreadcrumb;\n }\n if (this._limits.maximumBreadcrumbSize !== undefined) {\n const breadcrumbSize = jsonSize(limitedBreadcrumb, jsonEscaper());\n if (breadcrumbSize > this._limits.maximumBreadcrumbSize) {\n // TODO: Trim the breadcrumb\n return false;\n }\n }\n const id = this._storage.add(limitedBreadcrumb);\n return id !== undefined;\n }\n /**\n * The expectation is, message should always be defined and passed as string.\n * However, logger can pass as a message an object or any other unknown type.\n * To be sure the code won't break, this method ensures the message is always a string\n * no matter what the logger gives us.\n * @param message breadcrumb message\n */\n prepareBreadcrumbMessage(message) {\n if (message == null) {\n return '';\n }\n const messageType = typeof message;\n switch (messageType) {\n case 'string': {\n return message;\n }\n case 'object': {\n return JSON.stringify(message, jsonEscaper());\n }\n default: {\n return message.toString();\n }\n }\n }\n}\n\nconst UNKNOWN_FRAME = 'unknown';\nconst ANONYMOUS_FUNCTION = 'anonymous';\n\nclass V8StackTraceConverter {\n addressSeparator;\n get engine() {\n return 'v8';\n }\n constructor(addressSeparator = '') {\n this.addressSeparator = addressSeparator;\n }\n convert(stackTrace, message) {\n const result = [];\n let stackFrames = stackTrace.split('\\n');\n const errorHeader = message.split('\\n');\n // remove error header from stack trace - if the error header exists\n if (stackFrames[0].indexOf(errorHeader[0]) !== -1) {\n stackFrames = stackFrames.slice(errorHeader.length);\n }\n else {\n stackFrames = stackFrames.slice(1);\n }\n for (const stackFrame of stackFrames) {\n const normalizedStackFrame = stackFrame.trim();\n if (!normalizedStackFrame) {\n continue;\n }\n const frame = this.parseFrame(normalizedStackFrame);\n result.push(frame);\n }\n return result;\n }\n parseFrame(stackFrame) {\n const frameSeparator = 'at ';\n if (!stackFrame.startsWith(frameSeparator)) {\n return {\n funcName: stackFrame,\n library: UNKNOWN_FRAME,\n };\n }\n stackFrame = stackFrame.substring(stackFrame.indexOf(frameSeparator) + frameSeparator.length);\n const asyncKeyword = 'async ';\n const sourceCodeSeparator = ' (';\n let sourceCodeStartIndex = stackFrame.indexOf(sourceCodeSeparator);\n const anonymousFunction = sourceCodeStartIndex === -1;\n if (anonymousFunction) {\n if (stackFrame.startsWith(asyncKeyword)) {\n stackFrame = stackFrame.substring(asyncKeyword.length);\n }\n return {\n funcName: ANONYMOUS_FUNCTION,\n ...this.parseSourceCodeInformation(stackFrame),\n };\n }\n let sourceCodeInformation = stackFrame.substring(sourceCodeStartIndex + sourceCodeSeparator.length - 1, stackFrame.length);\n const anonymousGenericSymbol = '(<anonymous>)';\n if (sourceCodeInformation.startsWith(anonymousGenericSymbol)) {\n sourceCodeStartIndex += anonymousGenericSymbol.length + 1;\n sourceCodeInformation = sourceCodeInformation.substring(anonymousGenericSymbol.length);\n }\n if (sourceCodeInformation.startsWith(` ${frameSeparator}`)) {\n sourceCodeInformation = sourceCodeInformation.substring(frameSeparator.length + 1);\n }\n else {\n sourceCodeInformation = sourceCodeInformation.substring(1, sourceCodeInformation.length - 1);\n }\n let functionName = stackFrame.substring(0, sourceCodeStartIndex);\n if (functionName.startsWith(asyncKeyword)) {\n functionName = functionName.substring(asyncKeyword.length);\n }\n return {\n funcName: functionName,\n ...this.parseSourceCodeInformation(sourceCodeInformation),\n };\n }\n parseSourceCodeInformation(sourceCodeInformation) {\n if (sourceCodeInformation.startsWith('eval')) {\n return this.extractEvalInformation(sourceCodeInformation);\n }\n if (this.addressSeparator && sourceCodeInformation.startsWith(this.addressSeparator)) {\n sourceCodeInformation = sourceCodeInformation.substring(this.addressSeparator.length).trimStart();\n }\n const sourceCodeParts = sourceCodeInformation.split(':');\n const column = parseInt(sourceCodeParts[sourceCodeParts.length - 1]);\n const lineNumber = parseInt(sourceCodeParts[sourceCodeParts.length - 2]);\n const library = sourceCodeParts.slice(0, sourceCodeParts.length - 2).join(':');\n return {\n library,\n column: isNaN(column) ? undefined : column,\n line: isNaN(lineNumber) ? undefined : lineNumber,\n };\n }\n extractEvalInformation(evalSourceCodeInformation) {\n const sourceCodeStartSeparatorChar = '(';\n const sourceCodeEndSeparatorChar = ')';\n const sourceCodeStart = evalSourceCodeInformation.indexOf(sourceCodeStartSeparatorChar);\n const sourceCodeEnd = evalSourceCodeInformation.indexOf(sourceCodeEndSeparatorChar);\n if (sourceCodeStart === -1 || sourceCodeEnd === -1 || sourceCodeStart > sourceCodeEnd) {\n return {\n library: UNKNOWN_FRAME,\n };\n }\n const sourceCodeInformation = evalSourceCodeInformation.substring(sourceCodeStart + sourceCodeStartSeparatorChar.length, sourceCodeEnd);\n return this.parseSourceCodeInformation(sourceCodeInformation);\n }\n}\n\nclass IdGenerator {\n static uuid() {\n const bytes = [...new Array(16)].map(() => Math.floor(Math.random() * 256));\n bytes[6] = (bytes[6] & 0x0f) | 0x40;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n return (bytes\n