@flareapp/js 1.2.1 → 2.0.0

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