@flareapp/js 2.0.0-rc.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,466 +1,590 @@
1
- // src/env/index.ts
2
- var CLIENT_VERSION = false ? "?" : '"2.0.0-rc.1"';
3
- var KEY = typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY;
4
- var SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION;
1
+ import ErrorStackParser from "error-stack-parser";
2
+
3
+ //#region src/browser/catchWindowErrors.ts
4
+ function catchWindowErrors() {
5
+ if (typeof window === "undefined") return;
6
+ window.addEventListener("error", (event) => {
7
+ const flare = window.flare;
8
+ if (!flare) return;
9
+ if (event.error instanceof Error) flare.reportSilently(event.error);
10
+ });
11
+ window.addEventListener("unhandledrejection", (event) => {
12
+ const flare = window.flare;
13
+ if (!flare) return;
14
+ const reason = event.reason;
15
+ if (reason instanceof Error) {
16
+ flare.reportSilently(reason);
17
+ return;
18
+ }
19
+ if (hasStack$1(reason)) {
20
+ const error = new Error(describeRejectionReason(reason));
21
+ error.stack = reason.stack;
22
+ flare.reportSilently(error);
23
+ return;
24
+ }
25
+ Promise.resolve(flare.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
26
+ });
27
+ }
28
+ function describeRejectionReason(reason) {
29
+ if (typeof reason === "string") return reason;
30
+ if (reason && typeof reason === "object") {
31
+ const message = reason.message;
32
+ if (typeof message === "string") return message;
33
+ try {
34
+ return JSON.stringify(reason);
35
+ } catch {
36
+ return "Unhandled promise rejection (non-serializable reason)";
37
+ }
38
+ }
39
+ return String(reason);
40
+ }
41
+ function hasStack$1(value) {
42
+ return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
43
+ }
44
+
45
+ //#endregion
46
+ //#region src/env/index.ts
47
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.0.1" : "?";
48
+ const KEY = typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY;
49
+ const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION;
5
50
 
6
- // src/util/assert.ts
51
+ //#endregion
52
+ //#region src/util/assert.ts
7
53
  function assert(value, message, debug) {
8
- if (debug && !value) {
9
- console.error(`Flare JavaScript client v${CLIENT_VERSION}: ${message}`);
10
- }
11
- return !!value;
54
+ if (debug && !value) console.error(`Flare JavaScript client v${CLIENT_VERSION}: ${message}`);
55
+ return !!value;
12
56
  }
13
57
 
14
- // src/util/assertKey.ts
58
+ //#endregion
59
+ //#region src/util/convertToError.ts
60
+ function convertToError(error) {
61
+ if (error instanceof Error) return error;
62
+ if (typeof error === "string") return new Error(error);
63
+ if (typeof error === "object" && error !== null) {
64
+ const obj = error;
65
+ const message = typeof obj.message === "string" ? obj.message : String(error);
66
+ const converted = new Error(message);
67
+ if (typeof obj.stack === "string") converted.stack = obj.stack;
68
+ if (typeof obj.name === "string") converted.name = obj.name;
69
+ return converted;
70
+ }
71
+ return new Error(String(error));
72
+ }
73
+
74
+ //#endregion
75
+ //#region src/util/assertKey.ts
15
76
  function assertKey(key, debug) {
16
- return assert(
17
- key,
18
- "The client was not yet initialised with an API key. Run client.light('<flare-project-key>') when you initialise your app. If you are running in dev mode and didn't run the light command on purpose, you can ignore this error.",
19
- debug
20
- );
77
+ return assert(key, "The client was not yet initialised with an API key. Run client.light('<flare-project-key>') when you initialise your app. If you are running in dev mode and didn't run the light command on purpose, you can ignore this error.", debug);
21
78
  }
22
79
 
23
- // src/util/assertSolutionProvider.ts
24
- function assertSolutionProvider(solutionProvider, debug) {
25
- return assert("canSolve" in solutionProvider, "A solution provider without a [canSolve] property was added.", debug) && assert(
26
- "getSolutions" in solutionProvider,
27
- "A solution provider without a [getSolutions] property was added.",
28
- debug
29
- );
80
+ //#endregion
81
+ //#region src/util/extractCode.ts
82
+ const MAX_CODE_LENGTH = 64;
83
+ function extractCode(error) {
84
+ const code = error.code;
85
+ if (typeof code !== "string" || code.length === 0) return;
86
+ return code.slice(0, MAX_CODE_LENGTH);
30
87
  }
31
88
 
32
- // src/util/flatJsonStringify.ts
89
+ //#endregion
90
+ //#region src/util/flatJsonStringify.ts
33
91
  function flatJsonStringify(json) {
34
- let cache = [];
35
- const flattenedStringifiedJson = JSON.stringify(json, function(_, value) {
36
- if (typeof value === "object" && value !== null) {
37
- if (cache.indexOf(value) !== -1) {
38
- try {
39
- return JSON.parse(JSON.stringify(value));
40
- } catch (error) {
41
- return;
42
- }
43
- }
44
- cache.push(value);
45
- }
46
- return value;
47
- });
48
- cache = null;
49
- return flattenedStringifiedJson;
92
+ return JSON.stringify(decycle(json));
93
+ }
94
+ function isPlainObject(value) {
95
+ if (typeof value !== "object" || value === null) return false;
96
+ const proto = Object.getPrototypeOf(value);
97
+ return proto === Object.prototype || proto === null;
98
+ }
99
+ function decycle(root) {
100
+ const inPath = /* @__PURE__ */ new WeakSet();
101
+ function clone(node) {
102
+ if (Array.isArray(node)) {
103
+ if (inPath.has(node)) return "[Circular]";
104
+ inPath.add(node);
105
+ const result = node.map(clone);
106
+ inPath.delete(node);
107
+ return result;
108
+ }
109
+ if (isPlainObject(node)) {
110
+ if (inPath.has(node)) return "[Circular]";
111
+ inPath.add(node);
112
+ const result = {};
113
+ for (const [k, v] of Object.entries(node)) result[k] = clone(v);
114
+ inPath.delete(node);
115
+ return result;
116
+ }
117
+ return node;
118
+ }
119
+ return clone(root);
50
120
  }
51
121
 
52
- // src/util/flattenOnce.ts
53
- function flattenOnce(array) {
54
- return array.reduce((flat, toFlatten) => {
55
- return flat.concat(toFlatten);
56
- }, []);
122
+ //#endregion
123
+ //#region src/util/glowsToEvents.ts
124
+ function glowsToEvents(glows) {
125
+ return glows.map((glow) => ({
126
+ type: "php_glow",
127
+ startTimeUnixNano: Math.round(glow.microtime * 1e9),
128
+ endTimeUnixNano: null,
129
+ attributes: {
130
+ "glow.name": String(glow.name),
131
+ "glow.level": glow.messageLevel,
132
+ "glow.context": glow.metaData ?? {}
133
+ }
134
+ }));
57
135
  }
58
136
 
59
- // src/util/now.ts
137
+ //#endregion
138
+ //#region src/util/now.ts
60
139
  function now() {
61
- return Math.round(Date.now() / 1e3);
140
+ return Math.round(Date.now() / 1e3);
141
+ }
142
+
143
+ //#endregion
144
+ //#region src/util/redactUrl.ts
145
+ const DEFAULT_URL_DENYLIST = /password|passwd|pwd|token|secret|authorization|\bauth\b|bearer|oauth|credentials?|cookie|api[-_]?key|private[-_]?key|session|csrf|xsrf|\bpin\b|\bssn\b|card[-_]?number|\bcvv\b/i;
146
+ function resolveDenylist(custom, replaceDefault = false, defaultDenylist = DEFAULT_URL_DENYLIST) {
147
+ if (!custom) return defaultDenylist;
148
+ if (replaceDefault) {
149
+ const safeFlags = custom.flags.replace(/[gy]/g, "");
150
+ return new RegExp(custom.source, safeFlags);
151
+ }
152
+ const flags = unionFlags(defaultDenylist.flags, custom.flags);
153
+ return new RegExp(`(?:${defaultDenylist.source})|(?:${custom.source})`, flags);
154
+ }
155
+ function unionFlags(a, b) {
156
+ const merged = /* @__PURE__ */ new Set();
157
+ for (const flag of a + b) {
158
+ if (flag === "g" || flag === "y") continue;
159
+ merged.add(flag);
160
+ }
161
+ return [...merged].join("");
162
+ }
163
+ function redactFullPath(fullPath, denylist = DEFAULT_URL_DENYLIST) {
164
+ const queryStart = fullPath.indexOf("?");
165
+ if (queryStart === -1) return fullPath;
166
+ const hashStart = fullPath.indexOf("#", queryStart);
167
+ const queryEnd = hashStart === -1 ? fullPath.length : hashStart;
168
+ const prefix = fullPath.slice(0, queryStart + 1);
169
+ const queryString = fullPath.slice(queryStart + 1, queryEnd);
170
+ const suffix = fullPath.slice(queryEnd);
171
+ return `${prefix}${queryString.split("&").map((pair) => {
172
+ if (pair === "") return pair;
173
+ const eq = pair.indexOf("=");
174
+ const rawKey = eq === -1 ? pair : pair.slice(0, eq);
175
+ const decodedKey = safeDecode(rawKey);
176
+ if (!denylist.test(decodedKey)) return pair;
177
+ return eq === -1 ? rawKey : `${rawKey}=[redacted]`;
178
+ }).join("&")}${suffix}`;
179
+ }
180
+ function safeDecode(value) {
181
+ try {
182
+ return decodeURIComponent(value);
183
+ } catch {
184
+ return value;
185
+ }
62
186
  }
63
187
 
64
- // src/api/Api.ts
188
+ //#endregion
189
+ //#region src/api/Api.ts
65
190
  var Api = class {
66
- report(report, url, key, reportBrowserExtensionErrors) {
67
- return fetch(url, {
68
- method: "POST",
69
- headers: {
70
- "Content-Type": "application/json",
71
- "X-Api-Token": key,
72
- "X-Requested-With": "XMLHttpRequest",
73
- "X-Report-Browser-Extension-Errors": JSON.stringify(reportBrowserExtensionErrors)
74
- },
75
- body: flatJsonStringify({
76
- ...report,
77
- key
78
- })
79
- }).then(
80
- (response) => {
81
- if (response.status !== 204) {
82
- console.error(`Received response with status ${response.status} from Flare`);
83
- }
84
- },
85
- (error) => console.error(error)
86
- );
87
- }
191
+ report(report, url, key, reportBrowserExtensionErrors, debug = false) {
192
+ return fetch(url, {
193
+ method: "POST",
194
+ headers: {
195
+ "Accept": "application/json",
196
+ "Content-Type": "application/json",
197
+ "X-Api-Token": key ?? "",
198
+ "X-Report-Browser-Extension-Errors": JSON.stringify(reportBrowserExtensionErrors),
199
+ "X-Flare-Client-Version": "2"
200
+ },
201
+ body: flatJsonStringify(report)
202
+ }).then((response) => {
203
+ if (debug && response.status !== 201) console.error(`Received response with status ${response.status} from Flare`);
204
+ }, (error) => {
205
+ if (debug) console.error(error);
206
+ });
207
+ }
88
208
  };
89
209
 
90
- // src/context/cookie.ts
210
+ //#endregion
211
+ //#region src/context/cookie.ts
91
212
  function cookie() {
92
- if (!window.document.cookie) {
93
- return {};
94
- }
95
- return {
96
- cookies: window.document.cookie.split("; ").reduce(
97
- (cookies, cookie2) => {
98
- const [cookieName, cookieValue] = cookie2.split(/=/);
99
- cookies[cookieName] = cookieValue;
100
- return cookies;
101
- },
102
- {}
103
- )
104
- };
105
- }
106
-
107
- // src/context/request.ts
108
- function request() {
109
- return {
110
- request: {
111
- url: window.document.location.href,
112
- useragent: window.navigator.userAgent,
113
- referrer: window.document.referrer,
114
- readyState: window.document.readyState
115
- }
116
- };
213
+ if (!window.document.cookie) return {};
214
+ const cookies = {};
215
+ window.document.cookie.split("; ").forEach((rawCookie) => {
216
+ const idx = rawCookie.indexOf("=");
217
+ if (idx === -1) {
218
+ cookies[rawCookie] = "";
219
+ return;
220
+ }
221
+ const name = rawCookie.slice(0, idx);
222
+ cookies[name] = rawCookie.slice(idx + 1);
223
+ });
224
+ return { "http.request.cookies": cookies };
117
225
  }
118
226
 
119
- // src/context/requestData.ts
120
- function requestData() {
121
- if (!window.location.search) {
122
- return {};
123
- }
124
- const queryString = {};
125
- new URLSearchParams(window.location.search).forEach((value, key) => {
126
- queryString[key] = value;
127
- });
128
- return { request_data: { queryString } };
227
+ //#endregion
228
+ //#region src/context/request.ts
229
+ function request(urlDenylist) {
230
+ return {
231
+ "url.full": redactFullPath(window.location.href, urlDenylist),
232
+ "user_agent.original": window.navigator.userAgent,
233
+ "http.request.referrer": redactFullPath(window.document.referrer, urlDenylist),
234
+ "document.ready_state": window.document.readyState
235
+ };
129
236
  }
130
237
 
131
- // src/context/collectContext.ts
132
- function collectContext(additionalContext) {
133
- if (typeof window === "undefined") {
134
- return additionalContext;
135
- }
136
- return {
137
- ...cookie(),
138
- ...request(),
139
- ...requestData(),
140
- ...additionalContext
141
- };
238
+ //#endregion
239
+ //#region src/context/requestData.ts
240
+ function requestData(urlDenylist) {
241
+ if (!window.location.search) return {};
242
+ return { "url.query": redactFullPath(window.location.search, urlDenylist).replace(/^\?/, "") };
142
243
  }
143
244
 
144
- // src/solutions/getSolutions.ts
145
- function getSolutions(solutionProviders, error, extraSolutionParameters = {}) {
146
- return new Promise((resolve) => {
147
- const canSolves = solutionProviders.reduce(
148
- (canSolves2, provider) => {
149
- canSolves2.push(Promise.resolve(provider.canSolve(error, extraSolutionParameters)));
150
- return canSolves2;
151
- },
152
- []
153
- );
154
- Promise.all(canSolves).then((resolvedCanSolves) => {
155
- const solutionPromises = [];
156
- resolvedCanSolves.forEach((canSolve, i) => {
157
- if (canSolve) {
158
- solutionPromises.push(
159
- Promise.resolve(solutionProviders[i].getSolutions(error, extraSolutionParameters))
160
- );
161
- }
162
- });
163
- Promise.all(solutionPromises).then((solutions) => {
164
- resolve(flattenOnce(solutions));
165
- });
166
- });
167
- });
245
+ //#endregion
246
+ //#region src/context/collectAttributes.ts
247
+ function collectAttributes(urlDenylist) {
248
+ if (typeof window === "undefined") return {};
249
+ return {
250
+ ...request(urlDenylist),
251
+ ...requestData(urlDenylist),
252
+ ...cookie()
253
+ };
168
254
  }
169
255
 
170
- // src/stacktrace/createStackTrace.ts
171
- import ErrorStackParser from "error-stack-parser";
172
-
173
- // src/stacktrace/fileReader.ts
174
- var cachedFiles = {};
256
+ //#endregion
257
+ //#region src/stacktrace/fileReader.ts
258
+ const cachedFiles = {};
175
259
  function getCodeSnippet(url, lineNumber, columnNumber) {
176
- return new Promise((resolve) => {
177
- if (!url || !lineNumber) {
178
- return resolve({
179
- codeSnippet: {
180
- 0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}`
181
- },
182
- trimmedColumnNumber: null
183
- });
184
- }
185
- readFile(url).then((fileText) => {
186
- if (!fileText) {
187
- return resolve({
188
- codeSnippet: {
189
- 0: `Could not read from file: Error while opening file at URL ${url}`
190
- },
191
- trimmedColumnNumber: null
192
- });
193
- }
194
- return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
195
- });
196
- });
260
+ return new Promise((resolve) => {
261
+ if (!url || !lineNumber) return resolve({
262
+ codeSnippet: { 0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}` },
263
+ trimmedColumnNumber: null
264
+ });
265
+ if (!isFetchableUrl(url)) return resolve({
266
+ codeSnippet: { 0: `Could not read from file: unsupported URL scheme. URL: ${url}` },
267
+ trimmedColumnNumber: null
268
+ });
269
+ readFile(url).then((fileText) => {
270
+ if (!fileText) return resolve({
271
+ codeSnippet: { 0: `Could not read from file: Error while opening file at URL ${url}` },
272
+ trimmedColumnNumber: null
273
+ });
274
+ return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
275
+ });
276
+ });
277
+ }
278
+ function isFetchableUrl(url) {
279
+ return /^https?:\/\//i.test(url);
197
280
  }
198
281
  function readFile(url) {
199
- if (cachedFiles[url]) {
200
- return Promise.resolve(cachedFiles[url]);
201
- }
202
- return fetch(url).then((response) => {
203
- if (response.status !== 200) {
204
- return null;
205
- }
206
- return response.text();
207
- }).catch(() => null);
282
+ if (cachedFiles[url] !== void 0) return Promise.resolve(cachedFiles[url]);
283
+ return fetch(url).then((response) => {
284
+ if (response.status !== 200) return null;
285
+ return response.text();
286
+ }).then((text) => {
287
+ if (text !== null) cachedFiles[url] = text;
288
+ return text;
289
+ }).catch(() => null);
208
290
  }
209
291
  function readLinesFromFile(fileText, lineNumber, columnNumber, maxSnippetLineLength = 1e3, maxSnippetLines = 40) {
210
- const codeSnippet = {};
211
- let trimmedColumnNumber = null;
212
- const lines = fileText.split("\n");
213
- for (let i = -maxSnippetLines / 2; i <= maxSnippetLines / 2; i++) {
214
- const currentLineIndex = lineNumber + i;
215
- if (currentLineIndex >= 0 && lines[currentLineIndex]) {
216
- const displayLine = currentLineIndex + 1;
217
- if (lines[currentLineIndex].length > maxSnippetLineLength) {
218
- if (columnNumber && columnNumber + maxSnippetLineLength / 2 > maxSnippetLineLength) {
219
- codeSnippet[displayLine] = lines[currentLineIndex].substr(
220
- columnNumber - Math.round(maxSnippetLineLength / 2),
221
- maxSnippetLineLength
222
- );
223
- if (displayLine === lineNumber) {
224
- trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
225
- }
226
- continue;
227
- }
228
- codeSnippet[displayLine] = lines[currentLineIndex].substr(0, maxSnippetLineLength) + "\u2026";
229
- continue;
230
- }
231
- codeSnippet[displayLine] = lines[currentLineIndex];
232
- }
233
- }
234
- return { codeSnippet, trimmedColumnNumber };
292
+ const codeSnippet = {};
293
+ let trimmedColumnNumber = null;
294
+ const lines = fileText.split("\n");
295
+ const errorLineIndex = lineNumber - 1;
296
+ const half = Math.floor(maxSnippetLines / 2);
297
+ for (let i = -half; i <= half; i++) {
298
+ const currentLineIndex = errorLineIndex + i;
299
+ if (currentLineIndex < 0 || !lines[currentLineIndex]) continue;
300
+ const displayLine = currentLineIndex + 1;
301
+ const line = lines[currentLineIndex];
302
+ if (line.length > maxSnippetLineLength) {
303
+ if (columnNumber && columnNumber > maxSnippetLineLength / 2) {
304
+ const start = columnNumber - Math.round(maxSnippetLineLength / 2);
305
+ codeSnippet[displayLine] = line.slice(start, start + maxSnippetLineLength);
306
+ if (displayLine === lineNumber) trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
307
+ continue;
308
+ }
309
+ codeSnippet[displayLine] = line.slice(0, maxSnippetLineLength) + "…";
310
+ continue;
311
+ }
312
+ codeSnippet[displayLine] = line;
313
+ }
314
+ return {
315
+ codeSnippet,
316
+ trimmedColumnNumber
317
+ };
235
318
  }
236
319
 
237
- // src/stacktrace/createStackTrace.ts
320
+ //#endregion
321
+ //#region src/stacktrace/createStackTrace.ts
238
322
  function createStackTrace(error, debug) {
239
- return new Promise((resolve) => {
240
- if (!hasStack(error)) {
241
- assert(false, "Couldn't generate stacktrace of below error:", debug);
242
- if (debug) {
243
- console.error(error);
244
- }
245
- return resolve([
246
- {
247
- line_number: 0,
248
- column_number: 0,
249
- method: "unknown",
250
- file: "unknown",
251
- code_snippet: {
252
- 0: "Could not read from file: stacktrace missing"
253
- },
254
- trimmed_column_number: null,
255
- class: "unknown"
256
- }
257
- ]);
258
- }
259
- Promise.all(
260
- ErrorStackParser.parse(error).map((frame) => {
261
- return new Promise((resolve2) => {
262
- getCodeSnippet(frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => {
263
- resolve2({
264
- line_number: frame.lineNumber || 1,
265
- column_number: frame.columnNumber || 1,
266
- method: frame.functionName || "Anonymous or unknown function",
267
- file: frame.fileName || "Unknown file",
268
- code_snippet: snippet.codeSnippet,
269
- trimmed_column_number: snippet.trimmedColumnNumber,
270
- class: ""
271
- });
272
- });
273
- });
274
- })
275
- ).then(resolve);
276
- });
323
+ return new Promise((resolve) => {
324
+ if (!hasStack(error)) return resolve([fallbackFrame("stacktrace missing")]);
325
+ let parsedFrames;
326
+ try {
327
+ parsedFrames = ErrorStackParser.parse(error);
328
+ } catch (parseError) {
329
+ assert(false, "Couldn't parse stacktrace of below error:", debug);
330
+ if (debug) {
331
+ console.error(parseError);
332
+ console.error(error);
333
+ }
334
+ return resolve([fallbackFrame("stacktrace could not be parsed")]);
335
+ }
336
+ Promise.all(parsedFrames.map((frame) => {
337
+ return getCodeSnippet(frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => ({
338
+ lineNumber: frame.lineNumber || 1,
339
+ columnNumber: frame.columnNumber || 1,
340
+ method: frame.functionName || "Anonymous or unknown function",
341
+ file: frame.fileName || "Unknown file",
342
+ codeSnippet: snippet.codeSnippet,
343
+ class: "",
344
+ isApplicationFrame: isApplicationFrame(frame.fileName)
345
+ }));
346
+ })).then(resolve);
347
+ });
348
+ }
349
+ function fallbackFrame(reason) {
350
+ return {
351
+ lineNumber: 0,
352
+ columnNumber: 0,
353
+ method: "unknown",
354
+ file: "unknown",
355
+ codeSnippet: { 0: `Could not read from file: ${reason}` },
356
+ class: "unknown"
357
+ };
277
358
  }
278
359
  function hasStack(err) {
279
- return !!err && (!!err.stack || !!err.stacktrace || !!err["opera#sourceloc"]) && typeof (err.stack || err.stacktrace || err["opera#sourceloc"]) === "string" && err.stack !== `${err.name}: ${err.message}`;
360
+ if (!err || typeof err !== "object") return false;
361
+ const e = err;
362
+ const stack = e.stack ?? e.stacktrace ?? e["opera#sourceloc"];
363
+ return typeof stack === "string" && stack !== `${e.name}: ${e.message}`;
364
+ }
365
+ function isApplicationFrame(fileName) {
366
+ if (!fileName) return true;
367
+ if (/[/\\]node_modules[/\\]/.test(fileName)) return false;
368
+ if (/(^|[/\\])(vendor|vendors)[.~-][^/\\]*\.js/i.test(fileName)) return false;
369
+ return true;
280
370
  }
281
371
 
282
- // src/Flare.ts
372
+ //#endregion
373
+ //#region src/Flare.ts
374
+ const DEFAULT_SDK_NAME = "@flareapp/js";
283
375
  var Flare = class {
284
- constructor(http = new Api()) {
285
- this.http = http;
286
- this.config = {
287
- key: KEY,
288
- version: CLIENT_VERSION,
289
- sourcemapVersion: SOURCEMAP_VERSION,
290
- stage: "",
291
- maxGlowsPerReport: 30,
292
- reportingUrl: "https://reporting.flareapp.io/api/reports",
293
- reportBrowserExtensionErrors: false,
294
- debug: false,
295
- beforeEvaluate: (error) => error,
296
- beforeSubmit: (report) => report
297
- };
298
- this.glows = [];
299
- this.context = { context: {} };
300
- this.solutionProviders = [];
301
- }
302
- light(key = KEY, debug = false) {
303
- this.config.key = key;
304
- this.config.debug = debug;
305
- return this;
306
- }
307
- configure(config) {
308
- this.config = { ...this.config, ...config };
309
- return this;
310
- }
311
- test() {
312
- return this.report(new Error("The Flare client is set up correctly!"));
313
- }
314
- glow(name, level = "info", data = []) {
315
- const time = now();
316
- this.glows.push({
317
- name,
318
- message_level: level,
319
- meta_data: data,
320
- time,
321
- microtime: time
322
- });
323
- if (this.glows.length > this.config.maxGlowsPerReport) {
324
- this.glows = this.glows.slice(this.glows.length - this.config.maxGlowsPerReport);
325
- }
326
- return this;
327
- }
328
- clearGlows() {
329
- this.glows = [];
330
- return this;
331
- }
332
- addContext(name, value) {
333
- this.context.context[name] = value;
334
- return this;
335
- }
336
- addContextGroup(groupName, value) {
337
- this.context[groupName] = value;
338
- return this;
339
- }
340
- registerSolutionProvider(solutionProvider) {
341
- if (!assertSolutionProvider(solutionProvider, this.config.debug)) {
342
- return this;
343
- }
344
- this.solutionProviders.push(solutionProvider);
345
- return this;
346
- }
347
- async report(error, context = {}, extraSolutionParameters = {}) {
348
- const errorToReport = await this.config.beforeEvaluate(error);
349
- if (!errorToReport) {
350
- return;
351
- }
352
- const report = await this.createReportFromError(error, context, extraSolutionParameters);
353
- if (!report) {
354
- return;
355
- }
356
- return this.sendReport(report);
357
- }
358
- async reportMessage(message, context = {}, exceptionClass = "Log") {
359
- const stackTrace = await createStackTrace(Error(), this.config.debug);
360
- stackTrace.shift();
361
- this.sendReport({
362
- notifier: `Flare JavaScript client v${CLIENT_VERSION}`,
363
- exception_class: exceptionClass,
364
- seen_at: now(),
365
- message,
366
- language: "javascript",
367
- glows: this.glows,
368
- context: collectContext({ ...context, ...this.context }),
369
- stacktrace: stackTrace,
370
- sourcemap_version_id: this.config.sourcemapVersion,
371
- solutions: [],
372
- stage: this.config.stage
373
- });
374
- }
375
- createReportFromError(error, context = {}, extraSolutionParameters = {}) {
376
- if (!assert(error, "No error provided.", this.config.debug)) {
377
- return Promise.resolve(false);
378
- }
379
- const seenAt = now();
380
- return Promise.all([
381
- getSolutions(this.solutionProviders, error, extraSolutionParameters),
382
- createStackTrace(error, this.config.debug)
383
- ]).then((result) => {
384
- const [solutions, stacktrace] = result;
385
- assert(stacktrace.length, "Couldn't generate stacktrace of this error: " + error, this.config.debug);
386
- return {
387
- notifier: `Flare JavaScript client v${CLIENT_VERSION}`,
388
- exception_class: error.constructor && error.constructor.name ? error.constructor.name : "undefined",
389
- seen_at: seenAt,
390
- message: error.message,
391
- language: "javascript",
392
- glows: this.glows,
393
- context: collectContext({ ...context, ...this.context }),
394
- stacktrace,
395
- sourcemap_version_id: this.config.sourcemapVersion,
396
- solutions,
397
- stage: this.config.stage
398
- };
399
- });
400
- }
401
- async sendReport(report) {
402
- if (!assertKey(this.config.key, this.config.debug)) {
403
- return;
404
- }
405
- const reportToSubmit = await this.config.beforeSubmit(report);
406
- if (!reportToSubmit) {
407
- return;
408
- }
409
- return this.http.report(
410
- reportToSubmit,
411
- this.config.reportingUrl,
412
- this.config.key,
413
- this.config.reportBrowserExtensionErrors
414
- );
415
- }
416
- // Deprecated, the following methods exist for backwards compatibility.
417
- set beforeEvaluate(beforeEvaluate) {
418
- this.config.beforeEvaluate = beforeEvaluate ?? "";
419
- }
420
- set beforeSubmit(beforeSubmit) {
421
- this.config.beforeSubmit = beforeSubmit ?? "";
422
- }
423
- set stage(stage) {
424
- this.config.stage = stage ?? "";
425
- }
376
+ _config = {
377
+ key: null,
378
+ version: "",
379
+ sourcemapVersionId: SOURCEMAP_VERSION,
380
+ stage: "",
381
+ maxGlowsPerReport: 30,
382
+ ingestUrl: "https://ingress.flareapp.io/v1/errors",
383
+ reportBrowserExtensionErrors: false,
384
+ debug: false,
385
+ urlDenylist: DEFAULT_URL_DENYLIST,
386
+ replaceDefaultUrlDenylist: false,
387
+ sampleRate: 1,
388
+ beforeEvaluate: (error) => error,
389
+ beforeSubmit: (report) => report
390
+ };
391
+ _glows = [];
392
+ pendingAttributes = {};
393
+ entryPoint = null;
394
+ sdkInfo = {
395
+ name: DEFAULT_SDK_NAME,
396
+ version: CLIENT_VERSION
397
+ };
398
+ framework = null;
399
+ constructor(api = new Api()) {
400
+ this.api = api;
401
+ }
402
+ get config() {
403
+ return this._config;
404
+ }
405
+ get glows() {
406
+ return this._glows;
407
+ }
408
+ light(key = KEY, debug) {
409
+ this._config.key = key;
410
+ if (debug !== void 0) this._config.debug = debug;
411
+ return this;
412
+ }
413
+ configure(config) {
414
+ this._config = {
415
+ ...this._config,
416
+ ...config
417
+ };
418
+ if (config.sampleRate !== void 0) this._config.sampleRate = Math.max(0, Math.min(1, config.sampleRate));
419
+ this._config.urlDenylist = resolveDenylist(config.urlDenylist, config.replaceDefaultUrlDenylist ?? this._config.replaceDefaultUrlDenylist);
420
+ return this;
421
+ }
422
+ async test() {
423
+ const report = await this.createReportFromError(/* @__PURE__ */ new Error("The Flare client is set up correctly!"));
424
+ if (!report) return;
425
+ return this.sendReport(report);
426
+ }
427
+ glow(name, level = "info", data = []) {
428
+ const time = now();
429
+ this._glows.push({
430
+ name,
431
+ messageLevel: level,
432
+ metaData: data,
433
+ time,
434
+ microtime: time
435
+ });
436
+ if (this._glows.length > this._config.maxGlowsPerReport) this._glows = this._glows.slice(this._glows.length - this._config.maxGlowsPerReport);
437
+ return this;
438
+ }
439
+ clearGlows() {
440
+ this._glows = [];
441
+ return this;
442
+ }
443
+ addContext(name, value) {
444
+ const existing = this.pendingAttributes["context.custom"] ?? {};
445
+ this.pendingAttributes["context.custom"] = {
446
+ ...existing,
447
+ [name]: value
448
+ };
449
+ return this;
450
+ }
451
+ addContextGroup(groupName, value) {
452
+ this.pendingAttributes[`context.${groupName}`] = value;
453
+ return this;
454
+ }
455
+ setEntryPoint(handler) {
456
+ this.entryPoint = handler;
457
+ return this;
458
+ }
459
+ setSdkInfo(info) {
460
+ this.sdkInfo = info;
461
+ return this;
462
+ }
463
+ setFramework(framework) {
464
+ this.framework = framework;
465
+ this.addContext("framework", framework.name.toLowerCase());
466
+ return this;
467
+ }
468
+ async report(error, attributes = {}) {
469
+ if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
470
+ const seenAtUnixNano = Date.now() * 1e6;
471
+ const coerced = error instanceof Error ? error : new Error(typeof error === "string" ? error : String(error));
472
+ const errorToReport = await this._config.beforeEvaluate(coerced);
473
+ if (!errorToReport) return;
474
+ const report = await this.createReportFromError(errorToReport, attributes, seenAtUnixNano);
475
+ if (!report) return;
476
+ return this.sendReport(report);
477
+ }
478
+ reportSilently(error, attributes = {}) {
479
+ Promise.resolve(this.report(error, attributes)).catch(() => {});
480
+ }
481
+ async reportUnhandledRejection(message, attributes = {}) {
482
+ if (Math.random() >= this._config.sampleRate) return;
483
+ const seenAtUnixNano = Date.now() * 1e6;
484
+ const report = this.buildReport({
485
+ exceptionClass: "UnhandledRejection",
486
+ message,
487
+ stacktrace: [],
488
+ isLog: false,
489
+ level: void 0,
490
+ extraAttributes: attributes,
491
+ code: void 0,
492
+ seenAtUnixNano
493
+ });
494
+ return this.sendReport(report);
495
+ }
496
+ async reportMessage(message, level, attributes = {}) {
497
+ if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
498
+ const seenAtUnixNano = Date.now() * 1e6;
499
+ const stackTrace = await createStackTrace(/* @__PURE__ */ new Error(), this._config.debug);
500
+ stackTrace.shift();
501
+ const report = this.buildReport({
502
+ exceptionClass: "Log",
503
+ message,
504
+ stacktrace: stackTrace,
505
+ isLog: true,
506
+ level,
507
+ extraAttributes: attributes,
508
+ code: void 0,
509
+ seenAtUnixNano
510
+ });
511
+ return this.sendReport(report);
512
+ }
513
+ async createReportFromError(error, attributes = {}, seenAtUnixNano = Date.now() * 1e6) {
514
+ if (!assert(error, "No error provided.", this._config.debug)) return false;
515
+ const stacktrace = await createStackTrace(error, this._config.debug);
516
+ assert(stacktrace.length, "Couldn't generate stacktrace of this error: " + error, this._config.debug);
517
+ const exceptionClass = error.constructor && error.constructor.name ? error.constructor.name : "undefined";
518
+ return this.buildReport({
519
+ exceptionClass,
520
+ message: error.message,
521
+ stacktrace,
522
+ isLog: false,
523
+ level: void 0,
524
+ extraAttributes: attributes,
525
+ code: extractCode(error),
526
+ seenAtUnixNano
527
+ });
528
+ }
529
+ buildReport(input) {
530
+ const baseAttributes = {
531
+ "telemetry.sdk.language": "javascript",
532
+ "telemetry.sdk.name": this.sdkInfo.name,
533
+ "telemetry.sdk.version": this.sdkInfo.version,
534
+ "flare.language.name": "javascript",
535
+ "flare.entry_point.type": "web"
536
+ };
537
+ if (typeof window !== "undefined" && window?.location?.href) baseAttributes["flare.entry_point.value"] = redactFullPath(window.location.href, this._config.urlDenylist);
538
+ const handlerIdentifier = this.entryPoint?.identifier ?? (typeof window !== "undefined" && window?.location?.pathname ? window.location.pathname : void 0);
539
+ const handlerType = this.entryPoint?.type ?? (typeof window !== "undefined" && window ? "browser" : void 0);
540
+ if (handlerIdentifier !== void 0) baseAttributes["flare.entry_point.handler.identifier"] = handlerIdentifier;
541
+ if (handlerType !== void 0) baseAttributes["flare.entry_point.handler.type"] = handlerType;
542
+ if (this.entryPoint?.name !== void 0) baseAttributes["flare.entry_point.handler.name"] = this.entryPoint.name;
543
+ if (this.framework?.name) baseAttributes["flare.framework.name"] = this.framework.name;
544
+ if (this.framework?.version) baseAttributes["flare.framework.version"] = this.framework.version;
545
+ if (this._config.stage) baseAttributes["service.stage"] = this._config.stage;
546
+ if (this._config.version) baseAttributes["service.version"] = this._config.version;
547
+ const attributes = {
548
+ ...baseAttributes,
549
+ ...collectAttributes(this._config.urlDenylist),
550
+ ...this.pendingAttributes,
551
+ ...input.extraAttributes
552
+ };
553
+ const pendingCustom = this.pendingAttributes["context.custom"];
554
+ const extraCustom = input.extraAttributes["context.custom"];
555
+ if (pendingCustom && extraCustom && typeof pendingCustom === "object" && typeof extraCustom === "object" && !Array.isArray(pendingCustom) && !Array.isArray(extraCustom)) attributes["context.custom"] = {
556
+ ...pendingCustom,
557
+ ...extraCustom
558
+ };
559
+ const report = {
560
+ exceptionClass: input.exceptionClass,
561
+ message: input.message,
562
+ seenAtUnixNano: input.seenAtUnixNano,
563
+ stacktrace: input.stacktrace,
564
+ events: glowsToEvents(this._glows),
565
+ attributes
566
+ };
567
+ if (input.isLog) report.isLog = true;
568
+ if (input.level !== void 0) report.level = input.level;
569
+ if (this._config.sourcemapVersionId) report.sourcemapVersionId = this._config.sourcemapVersionId;
570
+ if (input.code !== void 0) report.code = input.code;
571
+ return report;
572
+ }
573
+ async sendReport(report) {
574
+ if (!assertKey(this._config.key, this._config.debug)) return;
575
+ const reportToSubmit = await this._config.beforeSubmit(report);
576
+ if (!reportToSubmit) return;
577
+ return this.api.report(reportToSubmit, this._config.ingestUrl, this._config.key, this._config.reportBrowserExtensionErrors, this._config.debug);
578
+ }
426
579
  };
427
580
 
428
- // src/browser/catchWindowErrors.ts
429
- function catchWindowErrors() {
430
- if (typeof window === "undefined") {
431
- return;
432
- }
433
- const flare2 = window.flare;
434
- if (!window || !flare2) {
435
- return;
436
- }
437
- const originalOnerrorHandler = window.onerror;
438
- const originalOnunhandledrejectionHandler = window.onunhandledrejection;
439
- window.onerror = (_1, _2, _3, _4, error) => {
440
- if (error) {
441
- flare2.report(error);
442
- }
443
- if (typeof originalOnerrorHandler === "function") {
444
- originalOnerrorHandler(_1, _2, _3, _4, error);
445
- }
446
- };
447
- window.onunhandledrejection = (error) => {
448
- if (error.reason instanceof Error) {
449
- flare2.report(error.reason);
450
- }
451
- if (typeof originalOnunhandledrejectionHandler === "function") {
452
- originalOnunhandledrejectionHandler(error);
453
- }
454
- };
455
- }
456
-
457
- // src/index.ts
458
- var flare = new Flare();
581
+ //#endregion
582
+ //#region src/index.ts
583
+ const flare = new Flare();
459
584
  if (typeof window !== "undefined" && window) {
460
- window.flare = flare;
461
- catchWindowErrors();
585
+ window.flare = flare;
586
+ catchWindowErrors();
462
587
  }
463
- export {
464
- Flare,
465
- flare
466
- };
588
+
589
+ //#endregion
590
+ export { DEFAULT_URL_DENYLIST, Flare, convertToError, flare, redactFullPath, resolveDenylist };