@flareapp/js 2.1.0 → 2.2.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,4 +1,4 @@
1
- import ErrorStackParser from "error-stack-parser";
1
+ import { Api, DEFAULT_URL_DENYLIST, Flare as Flare$1, GlobalScopeProvider, GlobalScopeProvider as GlobalScopeProvider$1, NullFileReader, Scope, convertToError, redactUrlQuery, redactUrlQuery as redactFullPath, redactUrlQuery as redactUrlQuery$1, resolveDenylist } from "@flareapp/core";
2
2
 
3
3
  //#region src/browser/catchWindowErrors.ts
4
4
  function catchWindowErrors() {
@@ -16,7 +16,7 @@ function catchWindowErrors() {
16
16
  flare.reportSilently(reason);
17
17
  return;
18
18
  }
19
- if (hasStack$1(reason)) {
19
+ if (hasStack(reason)) {
20
20
  const error = new Error(describeRejectionReason(reason));
21
21
  error.stack = reason.stack;
22
22
  flare.reportSilently(error);
@@ -38,177 +38,12 @@ function describeRejectionReason(reason) {
38
38
  }
39
39
  return String(reason);
40
40
  }
41
- function hasStack$1(value) {
41
+ function hasStack(value) {
42
42
  return typeof value === "object" && value !== null && "stack" in value && typeof value.stack === "string";
43
43
  }
44
44
 
45
45
  //#endregion
46
- //#region src/env/index.ts
47
- const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.1.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
53
- function assert(value, message, debug) {
54
- if (debug && !value) console.error(`Flare JavaScript client v${CLIENT_VERSION}: ${message}`);
55
- return !!value;
56
- }
57
-
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
76
- function assertKey(key, debug) {
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);
78
- }
79
-
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);
87
- }
88
-
89
- //#endregion
90
- //#region src/util/flatJsonStringify.ts
91
- function flatJsonStringify(json) {
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);
120
- }
121
-
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
- }));
135
- }
136
-
137
- //#endregion
138
- //#region src/util/now.ts
139
- function now() {
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
- }
186
- }
187
-
188
- //#endregion
189
- //#region src/api/Api.ts
190
- var Api = class {
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
- }
208
- };
209
-
210
- //#endregion
211
- //#region src/context/cookie.ts
46
+ //#region src/browser/context/cookie.ts
212
47
  function cookie() {
213
48
  if (!window.document.cookie) return {};
214
49
  const cookies = {};
@@ -225,384 +60,92 @@ function cookie() {
225
60
  }
226
61
 
227
62
  //#endregion
228
- //#region src/context/request.ts
63
+ //#region src/browser/context/request.ts
229
64
  function request(urlDenylist) {
230
65
  return {
231
- "url.full": redactFullPath(window.location.href, urlDenylist),
66
+ "url.full": redactUrlQuery$1(window.location.href, urlDenylist),
232
67
  "user_agent.original": window.navigator.userAgent,
233
- "http.request.referrer": redactFullPath(window.document.referrer, urlDenylist),
68
+ "http.request.referrer": redactUrlQuery$1(window.document.referrer, urlDenylist),
234
69
  "document.ready_state": window.document.readyState
235
70
  };
236
71
  }
237
72
 
238
73
  //#endregion
239
- //#region src/context/requestData.ts
74
+ //#region src/browser/context/requestData.ts
240
75
  function requestData(urlDenylist) {
241
76
  if (!window.location.search) return {};
242
- return { "url.query": redactFullPath(window.location.search, urlDenylist).replace(/^\?/, "") };
77
+ return { "url.query": redactUrlQuery$1(window.location.search, urlDenylist).replace(/^\?/, "") };
243
78
  }
244
79
 
245
80
  //#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
- };
254
- }
255
-
256
- //#endregion
257
- //#region src/stacktrace/nativeImport.ts
258
- let cached = null;
259
- function nativeImport(specifier) {
260
- if (!cached) cached = new Function("specifier", "return import(specifier)");
261
- return cached(specifier);
262
- }
81
+ //#region src/browser/context/collectBrowser.ts
82
+ const collectBrowser = (config) => {
83
+ if (typeof window === "undefined") return { "flare.entry_point.type": "server" };
84
+ const attrs = { "flare.entry_point.type": "web" };
85
+ if (window?.location?.href) {
86
+ attrs["flare.entry_point.value"] = redactUrlQuery$1(window.location.href, config.urlDenylist);
87
+ if (window.location.pathname) {
88
+ attrs["flare.entry_point.handler.identifier"] = window.location.pathname;
89
+ attrs["flare.entry_point.handler.type"] = "browser";
90
+ }
91
+ }
92
+ Object.assign(attrs, request(config.urlDenylist));
93
+ Object.assign(attrs, requestData(config.urlDenylist));
94
+ Object.assign(attrs, cookie());
95
+ return attrs;
96
+ };
263
97
 
264
98
  //#endregion
265
- //#region src/stacktrace/fileReader.ts
266
- const cachedFiles = {};
267
- function getCodeSnippet(url, lineNumber, columnNumber) {
268
- return new Promise((resolve) => {
269
- if (!url || !lineNumber) return resolve({
270
- codeSnippet: { 0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}` },
271
- trimmedColumnNumber: null
272
- });
273
- if (!isFetchableUrl(url) && !(isNode() && isLocalFileUrl(url))) return resolve({
274
- codeSnippet: { 0: `Could not read from file: unsupported URL scheme. URL: ${url}` },
275
- trimmedColumnNumber: null
276
- });
277
- readFile(url).then((fileText) => {
278
- if (!fileText) return resolve({
279
- codeSnippet: { 0: `Could not read from file: Error while opening file at URL ${url}` },
280
- trimmedColumnNumber: null
281
- });
282
- return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
283
- });
284
- });
285
- }
286
- function isFetchableUrl(url) {
287
- return /^https?:\/\//i.test(url);
288
- }
289
- function isNode() {
290
- return typeof process !== "undefined" && process.release?.name === "node";
291
- }
292
- function isLocalFileUrl(url) {
293
- return /^file:\/\//i.test(url) || url.startsWith("/") || /^[a-z]:[\\/]/i.test(url) || url.startsWith("\\\\");
294
- }
295
- function readFile(url) {
296
- if (cachedFiles[url] !== void 0) return Promise.resolve(cachedFiles[url]);
297
- return (isNode() && isLocalFileUrl(url) ? readFileFromDisk(url) : readFileWithFetch(url)).then((text) => {
298
- if (text !== null) cachedFiles[url] = text;
299
- return text;
300
- });
301
- }
302
- function readFileWithFetch(url) {
303
- return fetch(url).then((response) => {
304
- if (response.status !== 200) return null;
305
- return response.text();
306
- }).catch(() => null);
307
- }
308
- function readFileFromDisk(url) {
309
- return nativeImport("node:url").then(({ fileURLToPath }) => {
310
- const path = /^file:\/\//i.test(url) ? fileURLToPath(url) : url;
311
- return nativeImport("node:fs/promises").then(({ readFile: readFileAsync }) => readFileAsync(path, "utf-8"));
312
- }).catch(() => null);
313
- }
314
- function readLinesFromFile(fileText, lineNumber, columnNumber, maxSnippetLineLength = 1e3, maxSnippetLines = 40) {
315
- const codeSnippet = {};
316
- let trimmedColumnNumber = null;
317
- const lines = fileText.split("\n");
318
- const errorLineIndex = lineNumber - 1;
319
- const half = Math.floor(maxSnippetLines / 2);
320
- for (let i = -half; i <= half; i++) {
321
- const currentLineIndex = errorLineIndex + i;
322
- if (currentLineIndex < 0 || !lines[currentLineIndex]) continue;
323
- const displayLine = currentLineIndex + 1;
324
- const line = lines[currentLineIndex];
325
- if (line.length > maxSnippetLineLength) {
326
- if (columnNumber && columnNumber > maxSnippetLineLength / 2) {
327
- const start = columnNumber - Math.round(maxSnippetLineLength / 2);
328
- codeSnippet[displayLine] = line.slice(start, start + maxSnippetLineLength);
329
- if (displayLine === lineNumber) trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
330
- continue;
331
- }
332
- codeSnippet[displayLine] = line.slice(0, maxSnippetLineLength) + "…";
333
- continue;
334
- }
335
- codeSnippet[displayLine] = line;
99
+ //#region src/browser/FetchFileReader.ts
100
+ /**
101
+ * Browser `FileReader` implementation that fetches source files over HTTP(S).
102
+ *
103
+ * Wired into `@flareapp/js`'s singleton so the stack-trace builder can pull
104
+ * the original source for each frame and render a code snippet around the
105
+ * offending line. The source URL comes from the frame (usually the URL of
106
+ * the JS bundle that produced the error, post-sourcemap resolution).
107
+ *
108
+ * Three safety gates:
109
+ *
110
+ * 1. **Scheme allowlist.** Only `http:` and `https:` URLs are fetched. Other
111
+ * schemes (`chrome-extension://`, `file://`, `blob:`, `data:`) return
112
+ * `null` immediately. This avoids surprise privilege boundaries (e.g.,
113
+ * pages should not read extension-internal files) and dodges CORS/CSP
114
+ * walls that would error noisily.
115
+ * 2. **Status check.** Only `200 OK` responses are used. 3xx redirects are
116
+ * handled transparently by `fetch`; 4xx/5xx fall back to `null` so the
117
+ * snippet is simply omitted from the report.
118
+ * 3. **Catch-all.** Network failures, CORS errors, and aborted requests
119
+ * return `null`. We never let a source-fetch failure leak as an error
120
+ * that the consumer page would see.
121
+ *
122
+ * The `read()` contract returns `null` on any failure path, never throws.
123
+ */
124
+ var FetchFileReader = class {
125
+ read(url) {
126
+ if (!/^https?:\/\//i.test(url)) return Promise.resolve(null);
127
+ return fetch(url).then((response) => {
128
+ if (response.status !== 200) return null;
129
+ return response.text();
130
+ }).catch(() => null);
336
131
  }
337
- return {
338
- codeSnippet,
339
- trimmedColumnNumber
340
- };
341
- }
132
+ };
342
133
 
343
134
  //#endregion
344
- //#region src/stacktrace/createStackTrace.ts
345
- function createStackTrace(error, debug) {
346
- return new Promise((resolve) => {
347
- if (!hasStack(error)) return resolve([fallbackFrame("stacktrace missing")]);
348
- let parsedFrames;
349
- try {
350
- parsedFrames = ErrorStackParser.parse(error);
351
- } catch (parseError) {
352
- assert(false, "Couldn't parse stacktrace of below error:", debug);
353
- if (debug) {
354
- console.error(parseError);
355
- console.error(error);
356
- }
357
- return resolve([fallbackFrame("stacktrace could not be parsed")]);
358
- }
359
- Promise.all(parsedFrames.map((frame) => {
360
- return getCodeSnippet(frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => ({
361
- lineNumber: frame.lineNumber || 1,
362
- columnNumber: frame.columnNumber || 1,
363
- method: frame.functionName || "Anonymous or unknown function",
364
- file: frame.fileName || "Unknown file",
365
- codeSnippet: snippet.codeSnippet,
366
- class: "",
367
- isApplicationFrame: isApplicationFrame(frame.fileName)
368
- }));
369
- })).then(resolve);
370
- });
371
- }
372
- function fallbackFrame(reason) {
373
- return {
374
- lineNumber: 0,
375
- columnNumber: 0,
376
- method: "unknown",
377
- file: "unknown",
378
- codeSnippet: { 0: `Could not read from file: ${reason}` },
379
- class: "unknown"
380
- };
381
- }
382
- function hasStack(err) {
383
- if (!err || typeof err !== "object") return false;
384
- const e = err;
385
- const stack = e.stack ?? e.stacktrace ?? e["opera#sourceloc"];
386
- return typeof stack === "string" && stack !== `${e.name}: ${e.message}`;
387
- }
388
- function isApplicationFrame(fileName) {
389
- if (!fileName) return true;
390
- if (/[/\\]node_modules[/\\]/.test(fileName)) return false;
391
- if (/(^|[/\\])(vendor|vendors)[.~-][^/\\]*\.js/i.test(fileName)) return false;
392
- return true;
393
- }
135
+ //#region src/env/index.ts
136
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.2.1" : "?";
394
137
 
395
138
  //#endregion
396
- //#region src/Flare.ts
397
- const DEFAULT_SDK_NAME = "@flareapp/js";
398
- var Flare = class {
399
- _config = {
400
- key: null,
401
- version: "",
402
- sourcemapVersionId: SOURCEMAP_VERSION,
403
- stage: "",
404
- maxGlowsPerReport: 30,
405
- ingestUrl: "https://ingress.flareapp.io/v1/errors",
406
- reportBrowserExtensionErrors: false,
407
- debug: false,
408
- urlDenylist: DEFAULT_URL_DENYLIST,
409
- replaceDefaultUrlDenylist: false,
410
- sampleRate: 1,
411
- beforeEvaluate: (error) => error,
412
- beforeSubmit: (report) => report
413
- };
414
- _glows = [];
415
- pendingAttributes = {};
416
- entryPoint = null;
417
- sdkInfo = {
418
- name: DEFAULT_SDK_NAME,
419
- version: CLIENT_VERSION
420
- };
421
- framework = null;
422
- constructor(api = new Api()) {
423
- this.api = api;
424
- }
425
- get config() {
426
- return this._config;
427
- }
428
- get glows() {
429
- return this._glows;
430
- }
431
- light(key = KEY, debug) {
432
- this._config.key = key;
433
- if (debug !== void 0) this._config.debug = debug;
434
- return this;
435
- }
436
- configure(config) {
437
- this._config = {
438
- ...this._config,
439
- ...config
440
- };
441
- if (config.sampleRate !== void 0) this._config.sampleRate = Math.max(0, Math.min(1, config.sampleRate));
442
- this._config.urlDenylist = resolveDenylist(config.urlDenylist, config.replaceDefaultUrlDenylist ?? this._config.replaceDefaultUrlDenylist);
443
- return this;
444
- }
445
- async test() {
446
- const report = await this.createReportFromError(/* @__PURE__ */ new Error("The Flare client is set up correctly!"));
447
- if (!report) return;
448
- return this.sendReport(report);
449
- }
450
- glow(name, level = "info", data = []) {
451
- const time = now();
452
- this._glows.push({
453
- name,
454
- messageLevel: level,
455
- metaData: data,
456
- time,
457
- microtime: time
458
- });
459
- if (this._glows.length > this._config.maxGlowsPerReport) this._glows = this._glows.slice(this._glows.length - this._config.maxGlowsPerReport);
460
- return this;
461
- }
462
- clearGlows() {
463
- this._glows = [];
464
- return this;
465
- }
466
- addContext(name, value) {
467
- const existing = this.pendingAttributes["context.custom"] ?? {};
468
- this.pendingAttributes["context.custom"] = {
469
- ...existing,
470
- [name]: value
471
- };
472
- return this;
473
- }
474
- addContextGroup(groupName, value) {
475
- this.pendingAttributes[`context.${groupName}`] = value;
476
- return this;
477
- }
478
- setEntryPoint(handler) {
479
- this.entryPoint = handler;
480
- return this;
481
- }
482
- setSdkInfo(info) {
483
- this.sdkInfo = info;
484
- return this;
485
- }
486
- setFramework(framework) {
487
- this.framework = framework;
488
- this.addContext("framework", framework.name.toLowerCase());
489
- return this;
490
- }
491
- async report(error, attributes = {}) {
492
- if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
493
- const seenAtUnixNano = Date.now() * 1e6;
494
- const coerced = error instanceof Error ? error : new Error(typeof error === "string" ? error : String(error));
495
- const errorToReport = await this._config.beforeEvaluate(coerced);
496
- if (!errorToReport) return;
497
- const report = await this.createReportFromError(errorToReport, attributes, seenAtUnixNano);
498
- if (!report) return;
499
- return this.sendReport(report);
500
- }
501
- reportSilently(error, attributes = {}) {
502
- Promise.resolve(this.report(error, attributes)).catch(() => {});
503
- }
504
- async reportUnhandledRejection(message, attributes = {}) {
505
- if (Math.random() >= this._config.sampleRate) return;
506
- const seenAtUnixNano = Date.now() * 1e6;
507
- const report = this.buildReport({
508
- exceptionClass: "UnhandledRejection",
509
- message,
510
- stacktrace: [],
511
- isLog: false,
512
- level: void 0,
513
- extraAttributes: attributes,
514
- code: void 0,
515
- seenAtUnixNano
516
- });
517
- return this.sendReport(report);
518
- }
519
- async reportMessage(message, level, attributes = {}) {
520
- if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
521
- const seenAtUnixNano = Date.now() * 1e6;
522
- const stackTrace = await createStackTrace(/* @__PURE__ */ new Error(), this._config.debug);
523
- stackTrace.shift();
524
- const report = this.buildReport({
525
- exceptionClass: "Log",
526
- message,
527
- stacktrace: stackTrace,
528
- isLog: true,
529
- level,
530
- extraAttributes: attributes,
531
- code: void 0,
532
- seenAtUnixNano
533
- });
534
- return this.sendReport(report);
535
- }
536
- async createReportFromError(error, attributes = {}, seenAtUnixNano = Date.now() * 1e6) {
537
- if (!assert(error, "No error provided.", this._config.debug)) return false;
538
- const stacktrace = await createStackTrace(error, this._config.debug);
539
- assert(stacktrace.length, "Couldn't generate stacktrace of this error: " + error, this._config.debug);
540
- const exceptionClass = error.constructor && error.constructor.name ? error.constructor.name : "undefined";
541
- return this.buildReport({
542
- exceptionClass,
543
- message: error.message,
544
- stacktrace,
545
- isLog: false,
546
- level: void 0,
547
- extraAttributes: attributes,
548
- code: extractCode(error),
549
- seenAtUnixNano
139
+ //#region src/index.ts
140
+ var Flare = class extends Flare$1 {
141
+ constructor(api = new Api(), contextCollector = collectBrowser, fileReader = new FetchFileReader(), scopeProvider = new GlobalScopeProvider$1()) {
142
+ super(api, contextCollector, fileReader, scopeProvider);
143
+ this.setSdkInfo({
144
+ name: "@flareapp/js",
145
+ version: CLIENT_VERSION
550
146
  });
551
147
  }
552
- buildReport(input) {
553
- const baseAttributes = {
554
- "telemetry.sdk.language": "javascript",
555
- "telemetry.sdk.name": this.sdkInfo.name,
556
- "telemetry.sdk.version": this.sdkInfo.version,
557
- "flare.language.name": "javascript",
558
- "flare.entry_point.type": "web"
559
- };
560
- if (typeof window !== "undefined" && window?.location?.href) baseAttributes["flare.entry_point.value"] = redactFullPath(window.location.href, this._config.urlDenylist);
561
- const handlerIdentifier = this.entryPoint?.identifier ?? (typeof window !== "undefined" && window?.location?.pathname ? window.location.pathname : void 0);
562
- const handlerType = this.entryPoint?.type ?? (typeof window !== "undefined" && window ? "browser" : void 0);
563
- if (handlerIdentifier !== void 0) baseAttributes["flare.entry_point.handler.identifier"] = handlerIdentifier;
564
- if (handlerType !== void 0) baseAttributes["flare.entry_point.handler.type"] = handlerType;
565
- if (this.entryPoint?.name !== void 0) baseAttributes["flare.entry_point.handler.name"] = this.entryPoint.name;
566
- if (this.framework?.name) baseAttributes["flare.framework.name"] = this.framework.name;
567
- if (this.framework?.version) baseAttributes["flare.framework.version"] = this.framework.version;
568
- if (this._config.stage) baseAttributes["service.stage"] = this._config.stage;
569
- if (this._config.version) baseAttributes["service.version"] = this._config.version;
570
- const attributes = {
571
- ...baseAttributes,
572
- ...collectAttributes(this._config.urlDenylist),
573
- ...this.pendingAttributes,
574
- ...input.extraAttributes
575
- };
576
- const pendingCustom = this.pendingAttributes["context.custom"];
577
- const extraCustom = input.extraAttributes["context.custom"];
578
- if (pendingCustom && extraCustom && typeof pendingCustom === "object" && typeof extraCustom === "object" && !Array.isArray(pendingCustom) && !Array.isArray(extraCustom)) attributes["context.custom"] = {
579
- ...pendingCustom,
580
- ...extraCustom
581
- };
582
- const report = {
583
- exceptionClass: input.exceptionClass,
584
- message: input.message,
585
- seenAtUnixNano: input.seenAtUnixNano,
586
- stacktrace: input.stacktrace,
587
- events: glowsToEvents(this._glows),
588
- attributes
589
- };
590
- if (input.isLog) report.isLog = true;
591
- if (input.level !== void 0) report.level = input.level;
592
- if (this._config.sourcemapVersionId) report.sourcemapVersionId = this._config.sourcemapVersionId;
593
- if (input.code !== void 0) report.code = input.code;
594
- return report;
595
- }
596
- async sendReport(report) {
597
- if (!assertKey(this._config.key, this._config.debug)) return;
598
- const reportToSubmit = await this._config.beforeSubmit(report);
599
- if (!reportToSubmit) return;
600
- return this.api.report(reportToSubmit, this._config.ingestUrl, this._config.key, this._config.reportBrowserExtensionErrors, this._config.debug);
601
- }
602
148
  };
603
-
604
- //#endregion
605
- //#region src/index.ts
606
149
  const flare = new Flare();
607
150
  if (typeof window !== "undefined" && window) {
608
151
  window.flare = flare;
@@ -610,4 +153,4 @@ if (typeof window !== "undefined" && window) {
610
153
  }
611
154
 
612
155
  //#endregion
613
- export { DEFAULT_URL_DENYLIST, Flare, convertToError, flare, redactFullPath, resolveDenylist };
156
+ export { DEFAULT_URL_DENYLIST, Flare, GlobalScopeProvider, NullFileReader, Scope, convertToError, flare, redactFullPath, redactUrlQuery, resolveDenylist };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flareapp/js",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "JavaScript client for flareapp.io",
5
5
  "homepage": "https://flareapp.io",
6
6
  "bugs": {
@@ -23,6 +23,9 @@
23
23
  "Sebastian De Deyne <sebastian@spatie.be>",
24
24
  "Sébastien Henau <seba@spatie.be>"
25
25
  ],
26
+ "files": [
27
+ "dist"
28
+ ],
26
29
  "main": "./dist/index.cjs",
27
30
  "module": "./dist/index.mjs",
28
31
  "types": "./dist/index.d.cts",
@@ -46,9 +49,10 @@
46
49
  "release": "release-it"
47
50
  },
48
51
  "dependencies": {
49
- "error-stack-parser": "^2.0.2"
52
+ "@flareapp/core": "2.2.1"
50
53
  },
51
54
  "devDependencies": {
55
+ "error-stack-parser": "^2.0.2",
52
56
  "tsdown": "^0.20.3",
53
57
  "typescript": "^5.7.0",
54
58
  "vitest": "^4.0.18"