diff --git a/packages/telemetry/browser-telemetry/package.json b/packages/telemetry/browser-telemetry/package.json
index 4b26088a1..d7180ec0a 100644
--- a/packages/telemetry/browser-telemetry/package.json
+++ b/packages/telemetry/browser-telemetry/package.json
@@ -43,9 +43,6 @@
"bugs": {
"url": "https://github.com/launchdarkly/js-core/issues"
},
- "dependencies": {
- "tracekit": "^0.4.6"
- },
"devDependencies": {
"@jest/globals": "^29.7.0",
"@launchdarkly/js-client-sdk": "0.3.2",
diff --git a/packages/telemetry/browser-telemetry/src/BrowserTelemetryImpl.ts b/packages/telemetry/browser-telemetry/src/BrowserTelemetryImpl.ts
index 9e030dbe9..6de65b1ed 100644
--- a/packages/telemetry/browser-telemetry/src/BrowserTelemetryImpl.ts
+++ b/packages/telemetry/browser-telemetry/src/BrowserTelemetryImpl.ts
@@ -1,5 +1,3 @@
-import * as TraceKit from 'tracekit';
-
/**
* A limited selection of type information is provided by the browser client SDK.
* This is only a type dependency and these types should be compatible between
@@ -23,6 +21,7 @@ import makeInspectors from './inspectors';
import { ParsedOptions, ParsedStackOptions } from './options';
import randomUuidV4 from './randomUuidV4';
import parse from './stack/StackParser';
+import { getTraceKit } from './vendor/TraceKit';
// TODO: Use a ring buffer for the breadcrumbs/pending events instead of shifting. (SDK-914)
@@ -54,13 +53,18 @@ function safeValue(u: unknown): string | boolean | number | undefined {
}
function configureTraceKit(options: ParsedStackOptions) {
+ const TraceKit = getTraceKit();
// Include before + after + source line.
// TraceKit only takes a total context size, so we have to over capture and then reduce the lines.
// So, for instance if before is 3 and after is 4 we need to capture 4 and 4 and then drop a line
// from the before context.
// The typing for this is a bool, but it accepts a number.
const beforeAfterMax = Math.max(options.source.afterLines, options.source.beforeLines);
- (TraceKit as any).linesOfContext = beforeAfterMax * 2 + 1;
+ // The assignment here has bene split to prevent esbuild from complaining about an assigment to
+ // an import. TraceKit exports a single object and the interface requires modifying an exported
+ // var.
+ const anyObj = TraceKit as any;
+ anyObj.linesOfContext = beforeAfterMax * 2 + 1;
}
export default class BrowserTelemetryImpl implements BrowserTelemetry {
diff --git a/packages/telemetry/browser-telemetry/src/index.ts b/packages/telemetry/browser-telemetry/src/index.ts
index b1c13e734..c2c50a935 100644
--- a/packages/telemetry/browser-telemetry/src/index.ts
+++ b/packages/telemetry/browser-telemetry/src/index.ts
@@ -1 +1,11 @@
+import { BrowserTelemetry } from './api/BrowserTelemetry';
+import { Options } from './api/Options';
+import BrowserTelemetryImpl from './BrowserTelemetryImpl';
+import parse from './options';
+
export * from './api';
+
+export function initializeTelemetry(options?: Options): BrowserTelemetry {
+ const parsedOptions = parse(options || {});
+ return new BrowserTelemetryImpl(parsedOptions);
+}
diff --git a/packages/telemetry/browser-telemetry/src/stack/StackParser.ts b/packages/telemetry/browser-telemetry/src/stack/StackParser.ts
index 89b88ab86..73f755294 100644
--- a/packages/telemetry/browser-telemetry/src/stack/StackParser.ts
+++ b/packages/telemetry/browser-telemetry/src/stack/StackParser.ts
@@ -1,8 +1,7 @@
-import { computeStackTrace } from 'tracekit';
-
import { StackFrame } from '../api/stack/StackFrame';
import { StackTrace } from '../api/stack/StackTrace';
import { ParsedStackOptions } from '../options';
+import { getTraceKit } from '../vendor/TraceKit';
/**
* In the browser we will not always be able to determine the source file that code originates
@@ -195,7 +194,7 @@ export function getSrcLines(
* @returns The stack trace for the given error.
*/
export default function parse(error: Error, options: ParsedStackOptions): StackTrace {
- const parsed = computeStackTrace(error);
+ const parsed = getTraceKit().computeStackTrace(error);
const frames: StackFrame[] = parsed.stack.reverse().map((inFrame) => ({
fileName: processUrlToFileName(inFrame.url, window.location.origin),
function: inFrame.func,
diff --git a/packages/telemetry/browser-telemetry/src/vendor/TraceKit.ts b/packages/telemetry/browser-telemetry/src/vendor/TraceKit.ts
new file mode 100644
index 000000000..a832ddf22
--- /dev/null
+++ b/packages/telemetry/browser-telemetry/src/vendor/TraceKit.ts
@@ -0,0 +1,1110 @@
+/**
+ * https://github.com/csnover/TraceKit
+ * @license MIT
+ * @namespace TraceKit
+ */
+
+/**
+ * This file has been vendored to make it compatible with ESM and to any potential window
+ * level TraceKit instance.
+ *
+ * Functionality unused by this SDK has been removed to minimize size.
+ *
+ * It has additionally been converted to typescript.
+ */
+
+/**
+ * Currently the conversion to typescript is minimal, so the following eslint
+ * rules are disabled.
+ */
+
+/* eslint-disable func-names */
+/* eslint-disable no-shadow-restricted-names */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-cond-assign */
+/* eslint-disable consistent-return */
+/* eslint-disable no-empty */
+/* eslint-disable no-plusplus */
+/* eslint-disable prefer-rest-params */
+/* eslint-disable no-useless-escape */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/* eslint-disable @typescript-eslint/no-use-before-define */
+/* eslint-disable no-continue */
+/* eslint-disable no-underscore-dangle */
+
+export interface TraceKitStatic {
+ computeStackTrace: {
+ (ex: Error, depth?: number): StackTrace;
+ augmentStackTraceWithInitialElement: (
+ stackInfo: StackTrace,
+ url: string,
+ lineNo: number | string,
+ message: string,
+ ) => boolean;
+ computeStackTraceFromStackProp: (ex: Error) => StackTrace | null;
+ guessFunctionName: (url: string, lineNo: number | string) => string;
+ gatherContext: (url: string, line: number | string) => string[] | null;
+ ofCaller: (depth?: number) => StackTrace;
+ getSource: (url: string) => string[];
+ };
+ remoteFetching: boolean;
+ collectWindowErrors: boolean;
+ linesOfContext: number;
+ debug: boolean;
+}
+
+const TraceKit: any = {};
+
+export interface StackFrame {
+ url: string;
+ func: string;
+ args?: string[];
+ line?: number;
+ column?: number;
+ context?: string[];
+}
+
+export type Mode = 'stack' | 'stacktrace' | 'multiline' | 'callers' | 'onerror' | 'failed';
+
+export interface StackTrace {
+ name: string;
+ message: string;
+ stack: StackFrame[];
+ mode: Mode;
+ incomplete?: boolean;
+ partial?: boolean;
+}
+
+(function (window, undefined) {
+ if (!window) {
+ return;
+ }
+
+ // global reference to slice
+ const _slice = [].slice;
+ const UNKNOWN_FUNCTION = '?';
+
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#Error_types
+ const ERROR_TYPES_RE =
+ /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/;
+
+ /**
+ * A better form of hasOwnProperty
+ * Example: `_has(MainHostObject, property) === true/false`
+ *
+ * @param {Object} object to check property
+ * @param {string} key to check
+ * @return {Boolean} true if the object has the key and it is not inherited
+ */
+ function _has(object: any, key: string): boolean {
+ return Object.prototype.hasOwnProperty.call(object, key);
+ }
+
+ /**
+ * Returns true if the parameter is undefined
+ * Example: `_isUndefined(val) === true/false`
+ *
+ * @param {*} what Value to check
+ * @return {Boolean} true if undefined and false otherwise
+ */
+ function _isUndefined(what: any): boolean {
+ return typeof what === 'undefined';
+ }
+
+ /**
+ * Wrap any function in a TraceKit reporter
+ * Example: `func = TraceKit.wrap(func);`
+ *
+ * @param {Function} func Function to be wrapped
+ * @return {Function} The wrapped func
+ * @memberof TraceKit
+ */
+ TraceKit.wrap = function traceKitWrapper(func: Function): Function {
+ function wrapped(this: any) {
+ try {
+ return func.apply(this, arguments);
+ } catch (e) {
+ TraceKit.report(e);
+ throw e;
+ }
+ }
+ return wrapped;
+ };
+
+ /**
+ * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
+ *
+ * Syntax:
+ * ```js
+ * s = TraceKit.computeStackTrace.ofCaller([depth])
+ * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
+ * ```
+ *
+ * Supports:
+ * - Firefox: full stack trace with line numbers and unreliable column
+ * number on top frame
+ * - Opera 10: full stack trace with line and column numbers
+ * - Opera 9-: full stack trace with line numbers
+ * - Chrome: full stack trace with line and column numbers
+ * - Safari: line and column number for the topmost stacktrace element
+ * only
+ * - IE: no line numbers whatsoever
+ *
+ * Tries to guess names of anonymous functions by looking for assignments
+ * in the source code. In IE and Safari, we have to guess source file names
+ * by searching for function bodies inside all page scripts. This will not
+ * work for scripts that are loaded cross-domain.
+ * Here be dragons: some function names may be guessed incorrectly, and
+ * duplicate functions may be mismatched.
+ *
+ * TraceKit.computeStackTrace should only be used for tracing purposes.
+ * Logging of unhandled exceptions should be done with TraceKit.report,
+ * which builds on top of TraceKit.computeStackTrace and provides better
+ * IE support by utilizing the window.onerror event to retrieve information
+ * about the top of the stack.
+ *
+ * Note: In IE and Safari, no stack trace is recorded on the Error object,
+ * so computeStackTrace instead walks its *own* chain of callers.
+ * This means that:
+ * * in Safari, some methods may be missing from the stack trace;
+ * * in IE, the topmost function in the stack trace will always be the
+ * caller of computeStackTrace.
+ *
+ * This is okay for tracing (because you are likely to be calling
+ * computeStackTrace from the function you want to be the topmost element
+ * of the stack trace anyway), but not okay for logging unhandled
+ * exceptions (because your catch block will likely be far away from the
+ * inner function that actually caused the exception).
+ *
+ * Tracing example:
+ * ```js
+ * function trace(message) {
+ * var stackInfo = TraceKit.computeStackTrace.ofCaller();
+ * var data = message + "\n";
+ * for(var i in stackInfo.stack) {
+ * var item = stackInfo.stack[i];
+ * data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
+ * }
+ * if (window.console)
+ * console.info(data);
+ * else
+ * alert(data);
+ * }
+ * ```
+ * @memberof TraceKit
+ * @namespace
+ */
+ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
+ const debug = false;
+ const sourceCache: Record = {};
+
+ /**
+ * Attempts to retrieve source code via XMLHttpRequest, which is used
+ * to look up anonymous function names.
+ * @param {string} url URL of source code.
+ * @return {string} Source contents.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function loadSource(url: string): string {
+ if (!TraceKit.remoteFetching) {
+ // Only attempt request if remoteFetching is on.
+ return '';
+ }
+ try {
+ const getXHR = function () {
+ try {
+ return new window.XMLHttpRequest();
+ } catch (e) {
+ // explicitly bubble up the exception if not found
+ // @ts-ignore
+ return new window.ActiveXObject('Microsoft.XMLHTTP');
+ }
+ };
+
+ const request = getXHR();
+ request.open('GET', url, false);
+ request.send('');
+ return request.responseText;
+ } catch (e) {
+ return '';
+ }
+ }
+
+ /**
+ * Retrieves source code from the source code cache.
+ * @param {string} url URL of source code.
+ * @return {Array.} Source contents.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function getSource(url: string): string[] {
+ if (typeof url !== 'string') {
+ return [];
+ }
+
+ if (!_has(sourceCache, url)) {
+ // URL needs to be able to fetched within the acceptable domain. Otherwise,
+ // cross-domain errors will be triggered.
+ /*
+ Regex matches:
+ 0 - Full Url
+ 1 - Protocol
+ 2 - Domain
+ 3 - Port (Useful for internal applications)
+ 4 - Path
+ */
+ let source = '';
+ let domain = '';
+ try {
+ domain = window.document.domain;
+ } catch (e) {}
+ const match = /(.*)\:\/\/([^:\/]+)([:\d]*)\/{0,1}([\s\S]*)/.exec(url);
+ if (match && match[2] === domain) {
+ source = loadSource(url);
+ }
+ sourceCache[url] = source ? source.split('\n') : [];
+ }
+
+ return sourceCache[url];
+ }
+
+ /**
+ * Tries to use an externally loaded copy of source code to determine
+ * the name of a function by looking at the name of the variable it was
+ * assigned to, if any.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} lineNo Line number in source code.
+ * @return {string} The function name, if discoverable.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function guessFunctionName(url: string, lineNo: string | number) {
+ if (typeof lineNo !== 'number') {
+ lineNo = Number(lineNo);
+ }
+ const reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/;
+ const reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/;
+ let line = '';
+ const maxLines = 10;
+ const source = getSource(url);
+ let m;
+
+ if (!source.length) {
+ return UNKNOWN_FUNCTION;
+ }
+
+ // Walk backwards from the first line in the function until we find the line which
+ // matches the pattern above, which is the function definition
+ for (let i = 0; i < maxLines; ++i) {
+ line = source[lineNo - i] + line;
+
+ if (!_isUndefined(line)) {
+ if ((m = reGuessFunction.exec(line))) {
+ return m[1];
+ }
+ if ((m = reFunctionArgNames.exec(line))) {
+ return m[1];
+ }
+ }
+ }
+
+ return UNKNOWN_FUNCTION;
+ }
+
+ /**
+ * Retrieves the surrounding lines from where an exception occurred.
+ * @param {string} url URL of source code.
+ * @param {(string|number)} line Line number in source code to center around for context.
+ * @return {?Array.} Lines of source code.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function gatherContext(url: string, line: string | number): string[] | null {
+ if (typeof line !== 'number') {
+ line = Number(line);
+ }
+ const source = getSource(url);
+
+ if (!source.length) {
+ return null;
+ }
+
+ const context = [];
+ // linesBefore & linesAfter are inclusive with the offending line.
+ // if linesOfContext is even, there will be one extra line
+ // *before* the offending line.
+ const linesBefore = Math.floor(TraceKit.linesOfContext / 2);
+ // Add one extra line if linesOfContext is odd
+ const linesAfter = linesBefore + (TraceKit.linesOfContext % 2);
+ const start = Math.max(0, line - linesBefore - 1);
+ const end = Math.min(source.length, line + linesAfter - 1);
+
+ line -= 1; // convert to 0-based index
+
+ for (let i = start; i < end; ++i) {
+ if (!_isUndefined(source[i])) {
+ context.push(source[i]);
+ }
+ }
+
+ return context.length > 0 ? context : null;
+ }
+
+ /**
+ * Escapes special characters, except for whitespace, in a string to be
+ * used inside a regular expression as a string literal.
+ * @param {string} text The string.
+ * @return {string} The escaped string literal.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function escapeRegExp(text: string): string {
+ return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
+ }
+
+ /**
+ * Escapes special characters in a string to be used inside a regular
+ * expression as a string literal. Also ensures that HTML entities will
+ * be matched the same as their literal friends.
+ * @param {string} body The string.
+ * @return {string} The escaped string.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function escapeCodeAsRegExpForMatchingInsideHTML(body: string): string {
+ return escapeRegExp(body)
+ .replace('<', '(?:<|<)')
+ .replace('>', '(?:>|>)')
+ .replace('&', '(?:&|&)')
+ .replace('"', '(?:"|")')
+ .replace(/\s+/g, '\\s+');
+ }
+
+ /**
+ * Determines where a code fragment occurs in the source code.
+ * @param {RegExp} re The function definition.
+ * @param {Array.} urls A list of URLs to search.
+ * @return {?Object.} An object containing
+ * the url, line, and column number of the defined function.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function findSourceInUrls(
+ re: RegExp,
+ urls: string[],
+ ): {
+ url: string;
+ line: number;
+ column: number;
+ } | null {
+ let source: any;
+ let m: any;
+ for (let i = 0, j = urls.length; i < j; ++i) {
+ if ((source = getSource(urls[i])).length) {
+ source = source.join('\n');
+ if ((m = re.exec(source))) {
+ return {
+ url: urls[i],
+ line: source.substring(0, m.index).split('\n').length,
+ column: m.index - source.lastIndexOf('\n', m.index) - 1,
+ };
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines at which column a code fragment occurs on a line of the
+ * source code.
+ * @param {string} fragment The code fragment.
+ * @param {string} url The URL to search.
+ * @param {(string|number)} line The line number to examine.
+ * @return {?number} The column number.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function findSourceInLine(fragment: string, url: string, line: string | number): number | null {
+ if (typeof line !== 'number') {
+ line = Number(line);
+ }
+ const source = getSource(url);
+ const re = new RegExp(`\\b${escapeRegExp(fragment)}\\b`);
+ let m: any;
+
+ line -= 1;
+
+ if (source && source.length > line && (m = re.exec(source[line]))) {
+ return m.index;
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines where a function was defined within the source code.
+ * @param {(Function|string)} func A function reference or serialized
+ * function definition.
+ * @return {?Object.} An object containing
+ * the url, line, and column number of the defined function.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function findSourceByFunctionBody(func: Function | string) {
+ if (_isUndefined(window && window.document)) {
+ return;
+ }
+
+ const urls = [window.location.href];
+ const scripts = window.document.getElementsByTagName('script');
+ let body;
+ const code = `${func}`;
+ const codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/;
+ const eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/;
+ let re;
+ let parts;
+ let result;
+
+ for (let i = 0; i < scripts.length; ++i) {
+ const script = scripts[i];
+ if (script.src) {
+ urls.push(script.src);
+ }
+ }
+
+ if (!(parts = codeRE.exec(code))) {
+ re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
+ }
+
+ // not sure if this is really necessary, but I don’t have a test
+ // corpus large enough to confirm that and it was in the original.
+ else {
+ const name = parts[1] ? `\\s+${parts[1]}` : '';
+ const args = parts[2].split(',').join('\\s*,\\s*');
+
+ body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
+ re = new RegExp(`function${name}\\s*\\(\\s*${args}\\s*\\)\\s*{\\s*${body}\\s*}`);
+ }
+
+ // look for a normal function definition
+ if ((result = findSourceInUrls(re, urls))) {
+ return result;
+ }
+
+ // look for an old-school event handler function
+ if ((parts = eventRE.exec(code))) {
+ const event = parts[1];
+ body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
+
+ // look for a function defined in HTML as an onXXX handler
+ re = new RegExp(`on${event}=[\\'"]\\s*${body}\\s*[\\'"]`, 'i');
+
+ // The below line is as it appears in the original code.
+ // @ts-expect-error TODO (SDK-1037): Determine if this is a bug or handling for some unexpected case.
+ if ((result = findSourceInUrls(re, urls[0]))) {
+ return result;
+ }
+
+ // look for ???
+ re = new RegExp(body);
+
+ if ((result = findSourceInUrls(re, urls))) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ // Contents of Exception in various browsers.
+ //
+ // SAFARI:
+ // ex.message = Can't find variable: qq
+ // ex.line = 59
+ // ex.sourceId = 580238192
+ // ex.sourceURL = http://...
+ // ex.expressionBeginOffset = 96
+ // ex.expressionCaretOffset = 98
+ // ex.expressionEndOffset = 98
+ // ex.name = ReferenceError
+ //
+ // FIREFOX:
+ // ex.message = qq is not defined
+ // ex.fileName = http://...
+ // ex.lineNumber = 59
+ // ex.columnNumber = 69
+ // ex.stack = ...stack trace... (see the example below)
+ // ex.name = ReferenceError
+ //
+ // CHROME:
+ // ex.message = qq is not defined
+ // ex.name = ReferenceError
+ // ex.type = not_defined
+ // ex.arguments = ['aa']
+ // ex.stack = ...stack trace...
+ //
+ // INTERNET EXPLORER:
+ // ex.message = ...
+ // ex.name = ReferenceError
+ //
+ // OPERA:
+ // ex.message = ...message... (see the example below)
+ // ex.name = ReferenceError
+ // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
+ // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
+
+ /**
+ * Computes stack trace information from the stack property.
+ * Chrome and Gecko use this property.
+ * @param {Error} ex
+ * @return {?TraceKit.StackTrace} Stack trace information.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function computeStackTraceFromStackProp(ex: any): StackTrace | null {
+ if (!ex.stack) {
+ return null;
+ }
+
+ const chrome =
+ /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
+ const gecko =
+ /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i;
+ const winjs =
+ /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i;
+
+ // Used to additionally parse URL/line/column from eval frames
+ let isEval;
+ const geckoEval = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i;
+ const chromeEval = /\((\S*)(?::(\d+))(?::(\d+))\)/;
+
+ const lines = ex.stack.split('\n');
+ const stack: any = [];
+ let submatch: any;
+ let parts: any;
+ let element: any;
+ const reference: any = /^(.*) is undefined$/.exec(ex.message);
+
+ for (let i = 0, j = lines.length; i < j; ++i) {
+ if ((parts = chrome.exec(lines[i]))) {
+ const isNative = parts[2] && parts[2].indexOf('native') === 0; // start of line
+ isEval = parts[2] && parts[2].indexOf('eval') === 0; // start of line
+ if (isEval && (submatch = chromeEval.exec(parts[2]))) {
+ // throw out eval line/column and use top-most line/column number
+ parts[2] = submatch[1]; // url
+ parts[3] = submatch[2]; // line
+ parts[4] = submatch[3]; // column
+ }
+ element = {
+ url: !isNative ? parts[2] : null,
+ func: parts[1] || UNKNOWN_FUNCTION,
+ args: isNative ? [parts[2]] : [],
+ line: parts[3] ? +parts[3] : null,
+ column: parts[4] ? +parts[4] : null,
+ };
+ } else if ((parts = winjs.exec(lines[i]))) {
+ element = {
+ url: parts[2],
+ func: parts[1] || UNKNOWN_FUNCTION,
+ args: [],
+ line: +parts[3],
+ column: parts[4] ? +parts[4] : null,
+ };
+ } else if ((parts = gecko.exec(lines[i]))) {
+ isEval = parts[3] && parts[3].indexOf(' > eval') > -1;
+ if (isEval && (submatch = geckoEval.exec(parts[3]))) {
+ // throw out eval line/column and use top-most line number
+ parts[3] = submatch[1];
+ parts[4] = submatch[2];
+ parts[5] = null; // no column when eval
+ } else if (i === 0 && !parts[5] && !_isUndefined(ex.columnNumber)) {
+ // FireFox uses this awesome columnNumber property for its top frame
+ // Also note, Firefox's column number is 0-based and everything else expects 1-based,
+ // so adding 1
+ // NOTE: this hack doesn't work if top-most frame is eval
+ stack[0].column = ex.columnNumber + 1;
+ }
+ element = {
+ url: parts[3],
+ func: parts[1] || UNKNOWN_FUNCTION,
+ args: parts[2] ? parts[2].split(',') : [],
+ line: parts[4] ? +parts[4] : null,
+ column: parts[5] ? +parts[5] : null,
+ };
+ } else {
+ continue;
+ }
+
+ if (!element.func && element.line) {
+ element.func = guessFunctionName(element.url, element.line);
+ }
+
+ element.context = element.line ? gatherContext(element.url, element.line) : null;
+ stack.push(element);
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ if (stack[0] && stack[0].line && !stack[0].column && reference) {
+ stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
+ }
+
+ return {
+ mode: 'stack',
+ name: ex.name,
+ message: ex.message,
+ stack,
+ };
+ }
+
+ /**
+ * Computes stack trace information from the stacktrace property.
+ * Opera 10+ uses this property.
+ * @param {Error} ex
+ * @return {?TraceKit.StackTrace} Stack trace information.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function computeStackTraceFromStacktraceProp(ex: any): StackTrace | null {
+ // Access and store the stacktrace property before doing ANYTHING
+ // else to it because Opera is not very good at providing it
+ // reliably in other circumstances.
+ const { stacktrace } = ex;
+ if (!stacktrace) {
+ return null;
+ }
+
+ const opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i;
+ const opera11Regex =
+ / line (\d+), column (\d+)\s*(?:in (?:]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i;
+ const lines = stacktrace.split('\n');
+ const stack = [];
+ let parts;
+
+ for (let line = 0; line < lines.length; line += 2) {
+ let element: any = null;
+ if ((parts = opera10Regex.exec(lines[line]))) {
+ element = {
+ url: parts[2],
+ line: +parts[1],
+ column: null,
+ func: parts[3],
+ args: [],
+ };
+ } else if ((parts = opera11Regex.exec(lines[line]))) {
+ element = {
+ url: parts[6],
+ line: +parts[1],
+ column: +parts[2],
+ func: parts[3] || parts[4],
+ args: parts[5] ? parts[5].split(',') : [],
+ };
+ }
+
+ if (element) {
+ if (!element.func && element.line) {
+ element.func = guessFunctionName(element.url, element.line);
+ }
+ if (element.line) {
+ try {
+ element.context = gatherContext(element.url, element.line);
+ } catch (exc) {}
+ }
+
+ if (!element.context) {
+ element.context = [lines[line + 1]];
+ }
+
+ stack.push(element);
+ }
+ }
+
+ if (!stack.length) {
+ return null;
+ }
+
+ return {
+ mode: 'stacktrace',
+ name: ex.name,
+ message: ex.message,
+ stack,
+ };
+ }
+
+ /**
+ * NOT TESTED.
+ * Computes stack trace information from an error message that includes
+ * the stack trace.
+ * Opera 9 and earlier use this method if the option to show stack
+ * traces is turned on in opera:config.
+ * @param {Error} ex
+ * @return {?TraceKit.StackTrace} Stack information.
+ * @memberof TraceKit.computeStackTrace
+ */
+ function computeStackTraceFromOperaMultiLineMessage(ex: Error): StackTrace | null {
+ // TODO: Clean this function up
+ // Opera includes a stack trace into the exception message. An example is:
+ //
+ // Statement on line 3: Undefined variable: undefinedFunc
+ // Backtrace:
+ // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
+ // undefinedFunc(a);
+ // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
+ // zzz(x, y, z);
+ // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
+ // yyy(a, a, a);
+ // Line 1 of function script
+ // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
+ // ...
+
+ const lines = ex.message.split('\n');
+ if (lines.length < 4) {
+ return null;
+ }
+
+ const lineRE1 =
+ /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i;
+ const lineRE2 =
+ /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i;
+ const lineRE3 = /^\s*Line (\d+) of function script\s*$/i;
+ const stack = [];
+ const scripts = window && window.document && window.document.getElementsByTagName('script');
+ const inlineScriptBlocks = [];
+ let parts: any;
+
+ for (const s in scripts) {
+ if (_has(scripts, s) && !scripts[s].src) {
+ inlineScriptBlocks.push(scripts[s]);
+ }
+ }
+
+ for (let line = 2; line < lines.length; line += 2) {
+ let item: any = null;
+ if ((parts = lineRE1.exec(lines[line]))) {
+ item = {
+ url: parts[2],
+ func: parts[3],
+ args: [],
+ line: +parts[1],
+ column: null,
+ };
+ } else if ((parts = lineRE2.exec(lines[line]))) {
+ item = {
+ url: parts[3],
+ func: parts[4],
+ args: [],
+ line: +parts[1],
+ column: null, // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
+ };
+ const relativeLine = +parts[1]; // relative to the start of the