@flareapp/core 2.2.0 → 2.3.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.
Files changed (55) hide show
  1. package/README.md +4 -1
  2. package/dist/index.cjs +1152 -0
  3. package/dist/index.d.cts +544 -0
  4. package/dist/index.d.mts +544 -0
  5. package/dist/index.mjs +1104 -0
  6. package/package.json +4 -1
  7. package/.oxlintrc.json +0 -7
  8. package/.release-it.json +0 -13
  9. package/CHANGELOG.md +0 -16
  10. package/src/Flare.ts +0 -543
  11. package/src/Scope.ts +0 -96
  12. package/src/api/Api.ts +0 -35
  13. package/src/api/index.ts +0 -1
  14. package/src/env/index.ts +0 -14
  15. package/src/index.ts +0 -41
  16. package/src/stacktrace/NullFileReader.ts +0 -28
  17. package/src/stacktrace/createStackTrace.ts +0 -74
  18. package/src/stacktrace/fileReader.ts +0 -96
  19. package/src/stacktrace/index.ts +0 -4
  20. package/src/types.ts +0 -81
  21. package/src/util/assert.ts +0 -9
  22. package/src/util/assertKey.ts +0 -11
  23. package/src/util/convertToError.ts +0 -22
  24. package/src/util/extractCode.ts +0 -11
  25. package/src/util/flatJsonStringify.ts +0 -45
  26. package/src/util/glowsToEvents.ts +0 -16
  27. package/src/util/index.ts +0 -8
  28. package/src/util/now.ts +0 -3
  29. package/src/util/redactUrl.ts +0 -83
  30. package/tests/api.test.ts +0 -95
  31. package/tests/configure.test.ts +0 -16
  32. package/tests/contextCollector.test.ts +0 -37
  33. package/tests/convertToError.test.ts +0 -95
  34. package/tests/createStackTrace.test.ts +0 -54
  35. package/tests/extractCode.test.ts +0 -30
  36. package/tests/fileReader.test.ts +0 -51
  37. package/tests/flatJsonStringify.test.ts +0 -31
  38. package/tests/flush.test.ts +0 -47
  39. package/tests/glows.test.ts +0 -47
  40. package/tests/glowsToEvents.test.ts +0 -41
  41. package/tests/helpers/FakeApi.ts +0 -20
  42. package/tests/helpers/index.ts +0 -1
  43. package/tests/hooks.test.ts +0 -123
  44. package/tests/light.test.ts +0 -25
  45. package/tests/nullFileReader.test.ts +0 -11
  46. package/tests/publicExports.test.ts +0 -17
  47. package/tests/redactUrl.test.ts +0 -151
  48. package/tests/report.test.ts +0 -146
  49. package/tests/sampleRate.test.ts +0 -88
  50. package/tests/scope.test.ts +0 -64
  51. package/tests/setEntryPoint.test.ts +0 -79
  52. package/tests/setFramework.test.ts +0 -48
  53. package/tests/setSdkInfo.test.ts +0 -62
  54. package/tsconfig.json +0 -4
  55. package/vitest.config.ts +0 -17
package/dist/index.cjs ADDED
@@ -0,0 +1,1152 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
12
+ key = keys[i];
13
+ if (!__hasOwnProp.call(to, key) && key !== except) {
14
+ __defProp(to, key, {
15
+ get: ((k) => from[k]).bind(null, key),
16
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
17
+ });
18
+ }
19
+ }
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
24
+ value: mod,
25
+ enumerable: true
26
+ }) : target, mod));
27
+
28
+ //#endregion
29
+ let error_stack_parser = require("error-stack-parser");
30
+ error_stack_parser = __toESM(error_stack_parser);
31
+
32
+ //#region src/env/index.ts
33
+ const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.3.0" : "?";
34
+ const KEY = typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY;
35
+ const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION;
36
+
37
+ //#endregion
38
+ //#region src/util/assert.ts
39
+ function assert(value, message, debug) {
40
+ if (debug && !value) console.error(`Flare JavaScript client v${CLIENT_VERSION}: ${message}`);
41
+ return !!value;
42
+ }
43
+
44
+ //#endregion
45
+ //#region src/util/assertKey.ts
46
+ function assertKey(key, debug) {
47
+ 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);
48
+ }
49
+
50
+ //#endregion
51
+ //#region src/util/convertToError.ts
52
+ function convertToError(error) {
53
+ if (error instanceof Error) return error;
54
+ if (typeof error === "string") return new Error(error);
55
+ if (typeof error === "object" && error !== null) {
56
+ const obj = error;
57
+ const message = typeof obj.message === "string" ? obj.message : String(error);
58
+ const converted = new Error(message);
59
+ if (typeof obj.stack === "string") converted.stack = obj.stack;
60
+ if (typeof obj.name === "string") converted.name = obj.name;
61
+ return converted;
62
+ }
63
+ return new Error(String(error));
64
+ }
65
+
66
+ //#endregion
67
+ //#region src/util/extractCode.ts
68
+ const MAX_CODE_LENGTH = 64;
69
+ function extractCode(error) {
70
+ const code = error.code;
71
+ if (typeof code !== "string" || code.length === 0) return;
72
+ return code.slice(0, MAX_CODE_LENGTH);
73
+ }
74
+
75
+ //#endregion
76
+ //#region src/util/flatJsonStringify.ts
77
+ function flatJsonStringify(json) {
78
+ return JSON.stringify(decycle(json));
79
+ }
80
+ function isPlainObject(value) {
81
+ if (typeof value !== "object" || value === null) return false;
82
+ const proto = Object.getPrototypeOf(value);
83
+ return proto === Object.prototype || proto === null;
84
+ }
85
+ function decycle(root) {
86
+ const inPath = /* @__PURE__ */ new WeakSet();
87
+ function clone(node) {
88
+ if (Array.isArray(node)) {
89
+ if (inPath.has(node)) return "[Circular]";
90
+ inPath.add(node);
91
+ const result = node.map(clone);
92
+ inPath.delete(node);
93
+ return result;
94
+ }
95
+ if (isPlainObject(node)) {
96
+ if (inPath.has(node)) return "[Circular]";
97
+ inPath.add(node);
98
+ const result = {};
99
+ for (const [k, v] of Object.entries(node)) result[k] = clone(v);
100
+ inPath.delete(node);
101
+ return result;
102
+ }
103
+ return node;
104
+ }
105
+ return clone(root);
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/util/glowsToEvents.ts
110
+ function glowsToEvents(glows) {
111
+ return glows.map((glow) => ({
112
+ type: "php_glow",
113
+ startTimeUnixNano: Math.round(glow.microtime * 1e9),
114
+ endTimeUnixNano: null,
115
+ attributes: {
116
+ "glow.name": String(glow.name),
117
+ "glow.level": glow.messageLevel,
118
+ "glow.context": glow.metaData ?? {}
119
+ }
120
+ }));
121
+ }
122
+
123
+ //#endregion
124
+ //#region src/util/now.ts
125
+ function now() {
126
+ return Math.round(Date.now() / 1e3);
127
+ }
128
+
129
+ //#endregion
130
+ //#region src/util/redactUrl.ts
131
+ 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;
132
+ function resolveDenylist(custom, replaceDefault = false, defaultDenylist = DEFAULT_URL_DENYLIST) {
133
+ if (!custom) return defaultDenylist;
134
+ if (replaceDefault) {
135
+ const safeFlags = custom.flags.replace(/[gy]/g, "");
136
+ return new RegExp(custom.source, safeFlags);
137
+ }
138
+ const flags = unionFlags(defaultDenylist.flags, custom.flags);
139
+ return new RegExp(`(?:${defaultDenylist.source})|(?:${custom.source})`, flags);
140
+ }
141
+ function unionFlags(a, b) {
142
+ const merged = /* @__PURE__ */ new Set();
143
+ for (const flag of a + b) {
144
+ if (flag === "g" || flag === "y") continue;
145
+ merged.add(flag);
146
+ }
147
+ return [...merged].join("");
148
+ }
149
+ function redactUrlQuery(fullPath, denylist = DEFAULT_URL_DENYLIST) {
150
+ const queryStart = fullPath.indexOf("?");
151
+ if (queryStart === -1) return fullPath;
152
+ const hashStart = fullPath.indexOf("#", queryStart);
153
+ const queryEnd = hashStart === -1 ? fullPath.length : hashStart;
154
+ const prefix = fullPath.slice(0, queryStart + 1);
155
+ const queryString = fullPath.slice(queryStart + 1, queryEnd);
156
+ const suffix = fullPath.slice(queryEnd);
157
+ return `${prefix}${queryString.split("&").map((pair) => {
158
+ if (pair === "") return pair;
159
+ const eq = pair.indexOf("=");
160
+ const rawKey = eq === -1 ? pair : pair.slice(0, eq);
161
+ const decodedKey = safeDecode(rawKey);
162
+ if (!denylist.test(decodedKey)) return pair;
163
+ return eq === -1 ? rawKey : `${rawKey}=[redacted]`;
164
+ }).join("&")}${suffix}`;
165
+ }
166
+ function safeDecode(value) {
167
+ try {
168
+ return decodeURIComponent(value);
169
+ } catch {
170
+ return value;
171
+ }
172
+ }
173
+
174
+ //#endregion
175
+ //#region src/api/Api.ts
176
+ var Api = class {
177
+ report(report, url, key, reportBrowserExtensionErrors, debug = false) {
178
+ return fetch(url, {
179
+ method: "POST",
180
+ headers: {
181
+ "Accept": "application/json",
182
+ "Content-Type": "application/json",
183
+ "X-Api-Token": key ?? "",
184
+ "X-Report-Browser-Extension-Errors": JSON.stringify(reportBrowserExtensionErrors),
185
+ "X-Flare-Client-Version": "2"
186
+ },
187
+ body: flatJsonStringify(report)
188
+ }).then((response) => {
189
+ if (debug && response.status !== 201) console.error(`Received response with status ${response.status} from Flare`);
190
+ }, (error) => {
191
+ if (debug) console.error(error);
192
+ });
193
+ }
194
+ logs(envelope, url, key, debug = false, keepalive = false) {
195
+ return fetch(url, {
196
+ method: "POST",
197
+ keepalive,
198
+ headers: {
199
+ "Accept": "application/json",
200
+ "Content-Type": "application/json",
201
+ "x-api-token": key ?? ""
202
+ },
203
+ body: flatJsonStringify(envelope)
204
+ }).then((response) => {
205
+ if (debug && response.status !== 201) console.error(`Received response with status ${response.status} from Flare logs`);
206
+ }, (error) => {
207
+ if (debug) console.error(error);
208
+ });
209
+ }
210
+ };
211
+
212
+ //#endregion
213
+ //#region src/logging/otel.ts
214
+ function valueToOpenTelemetry(value, inPath = /* @__PURE__ */ new WeakSet()) {
215
+ if (typeof value === "string") return { stringValue: value };
216
+ if (typeof value === "boolean") return { boolValue: value };
217
+ if (typeof value === "number") {
218
+ if (!Number.isFinite(value)) return null;
219
+ return Number.isInteger(value) ? { intValue: value } : { doubleValue: value };
220
+ }
221
+ if (value === null || value === void 0) return null;
222
+ if (Array.isArray(value)) {
223
+ if (inPath.has(value)) return { stringValue: "[Circular]" };
224
+ inPath.add(value);
225
+ const values = [];
226
+ for (const item of value) {
227
+ const mapped = valueToOpenTelemetry(item, inPath);
228
+ if (mapped !== null) values.push(mapped);
229
+ }
230
+ inPath.delete(value);
231
+ return { arrayValue: { values } };
232
+ }
233
+ if (typeof value === "object") {
234
+ if (inPath.has(value)) return { stringValue: "[Circular]" };
235
+ inPath.add(value);
236
+ const values = [];
237
+ for (const [key, item] of Object.entries(value)) {
238
+ const mapped = valueToOpenTelemetry(item, inPath);
239
+ if (mapped !== null) values.push({
240
+ key,
241
+ value: mapped
242
+ });
243
+ }
244
+ inPath.delete(value);
245
+ return { kvlistValue: { values } };
246
+ }
247
+ return null;
248
+ }
249
+ function attributesToOpenTelemetry(attributes) {
250
+ const out = [];
251
+ for (const [key, value] of Object.entries(attributes)) {
252
+ const mapped = valueToOpenTelemetry(value);
253
+ if (mapped !== null) out.push({
254
+ key,
255
+ value: mapped
256
+ });
257
+ }
258
+ return out;
259
+ }
260
+
261
+ //#endregion
262
+ //#region src/logging/envelope.ts
263
+ function buildLogsEnvelope(records, resourceAttributes, scopeName, scopeVersion) {
264
+ return { resourceLogs: [{
265
+ resource: {
266
+ attributes: attributesToOpenTelemetry(resourceAttributes),
267
+ droppedAttributesCount: 0
268
+ },
269
+ scopeLogs: [{
270
+ scope: {
271
+ name: scopeName,
272
+ version: scopeVersion,
273
+ attributes: [],
274
+ droppedAttributesCount: 0
275
+ },
276
+ logRecords: records.map((record) => ({
277
+ timeUnixNano: record.timeUnixNano,
278
+ observedTimeUnixNano: record.timeUnixNano,
279
+ severityNumber: record.severityNumber,
280
+ severityText: record.severityText,
281
+ body: { stringValue: record.message },
282
+ attributes: record.recordAttributes,
283
+ flags: 0,
284
+ droppedAttributesCount: 0
285
+ }))
286
+ }]
287
+ }] };
288
+ }
289
+
290
+ //#endregion
291
+ //#region src/logging/severity.ts
292
+ const SEVERITY_NUMBERS = {
293
+ debug: 5,
294
+ info: 9,
295
+ notice: 10,
296
+ warning: 13,
297
+ error: 17,
298
+ critical: 18,
299
+ alert: 19,
300
+ emergency: 21
301
+ };
302
+ function severityNumber(level) {
303
+ return SEVERITY_NUMBERS[level];
304
+ }
305
+ function severityText(level) {
306
+ return level.toUpperCase();
307
+ }
308
+ function isAtOrAboveMinimum(level, minimum) {
309
+ return severityNumber(level) >= severityNumber(minimum);
310
+ }
311
+
312
+ //#endregion
313
+ //#region src/logging/Logger.ts
314
+ var Logger = class {
315
+ buffer = [];
316
+ resourceAttributes = {};
317
+ timer;
318
+ timerActive = false;
319
+ constructor(deps) {
320
+ this.deps = deps;
321
+ const flush = (opts) => this.flush(opts);
322
+ this.deps.scheduler.register(flush);
323
+ }
324
+ debug(message, context = {}, attributes = {}) {
325
+ this.record("debug", message, context, attributes);
326
+ }
327
+ info(message, context = {}, attributes = {}) {
328
+ this.record("info", message, context, attributes);
329
+ }
330
+ notice(message, context = {}, attributes = {}) {
331
+ this.record("notice", message, context, attributes);
332
+ }
333
+ warning(message, context = {}, attributes = {}) {
334
+ this.record("warning", message, context, attributes);
335
+ }
336
+ error(message, context = {}, attributes = {}) {
337
+ this.record("error", message, context, attributes);
338
+ }
339
+ critical(message, context = {}, attributes = {}) {
340
+ this.record("critical", message, context, attributes);
341
+ }
342
+ alert(message, context = {}, attributes = {}) {
343
+ this.record("alert", message, context, attributes);
344
+ }
345
+ emergency(message, context = {}, attributes = {}) {
346
+ this.record("emergency", message, context, attributes);
347
+ }
348
+ bufferLength() {
349
+ return this.buffer.length;
350
+ }
351
+ record(level, message, context, attributes) {
352
+ const config = this.deps.getConfig();
353
+ if (!config.enableLogs) return;
354
+ if (config.minimumLogLevel && !isAtOrAboveMinimum(level, config.minimumLogLevel)) return;
355
+ const userAttributes = {
356
+ "log.context": context,
357
+ ...attributes
358
+ };
359
+ const { record, resource } = this.deps.buildLogAttributes(userAttributes);
360
+ const buffered = {
361
+ timeUnixNano: String(Date.now()) + "000000",
362
+ severityNumber: severityNumber(level),
363
+ severityText: severityText(level),
364
+ message,
365
+ recordAttributes: attributesToOpenTelemetry(record),
366
+ resourceAttributes: resource
367
+ };
368
+ if (this.estimateBytes(buffered) > config.logFlushMaxBytes) {
369
+ if (config.debug) console.error("Flare: dropping oversized log record");
370
+ return;
371
+ }
372
+ this.buffer.push(buffered);
373
+ this.resourceAttributes = resource;
374
+ this.evaluateTriggers(config);
375
+ this.trim(config);
376
+ }
377
+ evaluateTriggers(config) {
378
+ if (this.buffer.length >= config.maxLogBufferSize) {
379
+ this.flush();
380
+ return;
381
+ }
382
+ if (this.bufferBytes() >= config.logFlushMaxBytes) {
383
+ this.flush();
384
+ return;
385
+ }
386
+ this.armTimer(config);
387
+ }
388
+ armTimer(config) {
389
+ if (this.timerActive) return;
390
+ this.timerActive = true;
391
+ this.timer = setTimeout(() => this.flush(), config.logFlushIntervalMs);
392
+ this.timer.unref?.();
393
+ }
394
+ trim(config) {
395
+ if (this.buffer.length > config.maxLogBufferSize) this.buffer = this.buffer.slice(this.buffer.length - config.maxLogBufferSize);
396
+ while (this.buffer.length > 1 && this.bufferBytes() > config.logFlushMaxBytes) this.buffer.shift();
397
+ }
398
+ flush(opts) {
399
+ const config = this.deps.getConfig();
400
+ if (!config.enableLogs) return;
401
+ if (this.buffer.length === 0) return;
402
+ if (!assertKey(config.key, config.debug)) {
403
+ this.clearTimer();
404
+ return;
405
+ }
406
+ this.clearTimer();
407
+ let records;
408
+ if (opts?.keepalive) {
409
+ records = this.packForKeepalive(config);
410
+ this.buffer = this.buffer.filter((log) => !records.includes(log));
411
+ if (this.buffer.length > 0) this.armTimer(config);
412
+ } else {
413
+ records = this.buffer;
414
+ this.buffer = [];
415
+ }
416
+ if (records.length === 0) return;
417
+ this.deps.track(this.deps.api.logs(this.buildEnvelope(records), config.logsIngestUrl, config.key, config.debug, !!opts?.keepalive));
418
+ }
419
+ clear() {
420
+ this.buffer = [];
421
+ this.clearTimer();
422
+ }
423
+ packForKeepalive(config) {
424
+ let selected = [];
425
+ for (let i = this.buffer.length - 1; i >= 0; i--) {
426
+ const trial = [this.buffer[i], ...selected];
427
+ if (new TextEncoder().encode(flatJsonStringify(this.buildEnvelope(trial))).length <= config.keepaliveMaxBytes) selected = trial;
428
+ else if (config.debug) console.error("Flare: dropping log record from keepalive envelope (over budget)");
429
+ }
430
+ return selected;
431
+ }
432
+ buildEnvelope(records) {
433
+ const sdk = this.deps.getSdkInfo();
434
+ return buildLogsEnvelope(records, this.resourceForFlush(), sdk.name, sdk.version);
435
+ }
436
+ resourceForFlush() {
437
+ const config = this.deps.getConfig();
438
+ const sdk = this.deps.getSdkInfo();
439
+ const framework = this.deps.getFramework();
440
+ const identity = {
441
+ "telemetry.sdk.language": "javascript",
442
+ "telemetry.sdk.name": sdk.name,
443
+ "telemetry.sdk.version": sdk.version,
444
+ "flare.language.name": "javascript"
445
+ };
446
+ if (config.serviceName) identity["service.name"] = config.serviceName;
447
+ if (config.version) identity["service.version"] = config.version;
448
+ if (config.stage) identity["service.stage"] = config.stage;
449
+ if (framework?.name) identity["flare.framework.name"] = framework.name;
450
+ if (framework?.version) identity["flare.framework.version"] = framework.version;
451
+ return {
452
+ ...this.resourceAttributes,
453
+ ...identity
454
+ };
455
+ }
456
+ clearTimer() {
457
+ if (this.timer) {
458
+ clearTimeout(this.timer);
459
+ this.timer = void 0;
460
+ }
461
+ this.timerActive = false;
462
+ }
463
+ estimateBytes(log) {
464
+ return flatJsonStringify(log).length;
465
+ }
466
+ bufferBytes() {
467
+ return this.buffer.reduce((sum, log) => sum + this.estimateBytes(log), 0);
468
+ }
469
+ };
470
+
471
+ //#endregion
472
+ //#region src/logging/FlushScheduler.ts
473
+ var NoopFlushScheduler = class {
474
+ register() {}
475
+ };
476
+
477
+ //#endregion
478
+ //#region src/logging/partition.ts
479
+ const RESOURCE_PREFIXES = [
480
+ "service.",
481
+ "telemetry.",
482
+ "host.",
483
+ "os.",
484
+ "process.",
485
+ "flare.framework.",
486
+ "flare.language."
487
+ ];
488
+ const RECORD_LEVEL_EXCEPTIONS = new Set(["process.uptime"]);
489
+ function partitionAttributes(attributes) {
490
+ const resource = {};
491
+ const record = {};
492
+ for (const [key, value] of Object.entries(attributes)) if (!RECORD_LEVEL_EXCEPTIONS.has(key) && RESOURCE_PREFIXES.some((prefix) => key.startsWith(prefix))) resource[key] = value;
493
+ else record[key] = value;
494
+ return {
495
+ resource,
496
+ record
497
+ };
498
+ }
499
+
500
+ //#endregion
501
+ //#region src/Scope.ts
502
+ /**
503
+ * Holds the per-call mutable state that used to live on the `Flare` instance:
504
+ * breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
505
+ * current entry-point handler.
506
+ *
507
+ * Why this exists as its own class: in the browser there is one `Flare` per
508
+ * page and one user at a time, so a single shared bag of state is fine. In
509
+ * Node, a single `Flare` instance serves many concurrent requests, and each
510
+ * request wants its own breadcrumbs and its own custom context that do NOT
511
+ * leak into other requests. Splitting this state out of `Flare` lets the
512
+ * consumer choose: one global `Scope` (browser) or one `Scope` per request
513
+ * via AsyncLocalStorage (Node).
514
+ *
515
+ * `Flare` reads and writes this through `scopeProvider.active()` instead of
516
+ * holding the state directly, so the per-request behavior comes from the
517
+ * provider, not from the class itself.
518
+ *
519
+ * `NodeScope` (in `@flareapp/node`) extends this with two more buckets:
520
+ * `request` (HTTP method, path, headers) and `user` (id, email, ...). Browser
521
+ * does not need those.
522
+ */
523
+ var Scope = class {
524
+ glows = [];
525
+ pendingAttributes = {};
526
+ entryPoint = null;
527
+ /**
528
+ * Append a breadcrumb. Caps the list at `maxGlowsPerReport` by dropping the
529
+ * OLDEST entries when the limit is exceeded; this keeps reports below a
530
+ * payload-size threshold while preserving the most recent events leading
531
+ * up to an error.
532
+ *
533
+ * `slice(length - max)` returns the trailing `max` items, which is the
534
+ * shortest way to drop from the front and keep insertion order.
535
+ */
536
+ addGlow(glow, maxGlowsPerReport) {
537
+ this.glows.push(glow);
538
+ if (this.glows.length > maxGlowsPerReport) this.glows = this.glows.slice(this.glows.length - maxGlowsPerReport);
539
+ }
540
+ clearGlows() {
541
+ this.glows = [];
542
+ }
543
+ /**
544
+ * Set a single attribute on this scope. Called from `Flare.addContext` and
545
+ * `Flare.addContextGroup`. Last write wins.
546
+ */
547
+ setAttribute(key, value) {
548
+ this.pendingAttributes[key] = value;
549
+ }
550
+ /**
551
+ * Shallow-merge a bag of attributes into this scope. Used by Node's
552
+ * AsyncLocalStorage provider when patching the live request context via
553
+ * `flare.mergeContext({ ... })`. Last write wins per key; nested objects
554
+ * are NOT deep-merged.
555
+ */
556
+ mergeAttributes(partial) {
557
+ Object.assign(this.pendingAttributes, partial);
558
+ }
559
+ };
560
+ /**
561
+ * The simplest provider: one `Scope` for the lifetime of the provider, shared
562
+ * by every caller. This is the right default for environments with a single
563
+ * logical context (browser tab, CLI script, etc.) and is the default that
564
+ * `Flare`'s constructor falls back to when no provider is supplied.
565
+ */
566
+ var GlobalScopeProvider = class {
567
+ scope = new Scope();
568
+ active() {
569
+ return this.scope;
570
+ }
571
+ };
572
+
573
+ //#endregion
574
+ //#region src/stacktrace/fileReader.ts
575
+ const cachedFiles = {};
576
+ function getCodeSnippet(fileReader, url, lineNumber, columnNumber) {
577
+ return new Promise((resolve) => {
578
+ if (!url || !lineNumber) return resolve({
579
+ codeSnippet: { 0: `Could not read from file: missing file URL or line number. URL: ${url} lineNumber: ${lineNumber}` },
580
+ trimmedColumnNumber: null
581
+ });
582
+ readFile(fileReader, url).then((fileText) => {
583
+ if (!fileText) return resolve({
584
+ codeSnippet: { 0: `Could not read from file: Error while opening file at URL ${url}` },
585
+ trimmedColumnNumber: null
586
+ });
587
+ return resolve(readLinesFromFile(fileText, lineNumber, columnNumber));
588
+ });
589
+ });
590
+ }
591
+ function readFile(fileReader, url) {
592
+ if (cachedFiles[url] !== void 0) return Promise.resolve(cachedFiles[url]);
593
+ return fileReader.read(url).then((text) => {
594
+ if (text !== null) cachedFiles[url] = text;
595
+ return text;
596
+ });
597
+ }
598
+ function readLinesFromFile(fileText, lineNumber, columnNumber, maxSnippetLineLength = 1e3, maxSnippetLines = 40) {
599
+ const codeSnippet = {};
600
+ let trimmedColumnNumber = null;
601
+ const lines = fileText.split("\n");
602
+ const errorLineIndex = lineNumber - 1;
603
+ const half = Math.floor(maxSnippetLines / 2);
604
+ for (let i = -half; i <= half; i++) {
605
+ const currentLineIndex = errorLineIndex + i;
606
+ if (currentLineIndex < 0 || !lines[currentLineIndex]) continue;
607
+ const displayLine = currentLineIndex + 1;
608
+ const line = lines[currentLineIndex];
609
+ if (line.length > maxSnippetLineLength) {
610
+ if (columnNumber && columnNumber > maxSnippetLineLength / 2) {
611
+ const start = columnNumber - Math.round(maxSnippetLineLength / 2);
612
+ codeSnippet[displayLine] = line.slice(start, start + maxSnippetLineLength);
613
+ if (displayLine === lineNumber) trimmedColumnNumber = Math.round(maxSnippetLineLength / 2);
614
+ continue;
615
+ }
616
+ codeSnippet[displayLine] = line.slice(0, maxSnippetLineLength) + "…";
617
+ continue;
618
+ }
619
+ codeSnippet[displayLine] = line;
620
+ }
621
+ return {
622
+ codeSnippet,
623
+ trimmedColumnNumber
624
+ };
625
+ }
626
+
627
+ //#endregion
628
+ //#region src/stacktrace/createStackTrace.ts
629
+ function createStackTrace(error, debug, fileReader) {
630
+ return new Promise((resolve) => {
631
+ if (!hasStack(error)) return resolve([fallbackFrame("stacktrace missing")]);
632
+ let parsedFrames;
633
+ try {
634
+ parsedFrames = error_stack_parser.default.parse(error);
635
+ } catch (parseError) {
636
+ assert(false, "Couldn't parse stacktrace of below error:", debug);
637
+ if (debug) {
638
+ console.error(parseError);
639
+ console.error(error);
640
+ }
641
+ return resolve([fallbackFrame("stacktrace could not be parsed")]);
642
+ }
643
+ Promise.all(parsedFrames.map((frame) => {
644
+ return getCodeSnippet(fileReader, frame.fileName, frame.lineNumber, frame.columnNumber).then((snippet) => ({
645
+ lineNumber: frame.lineNumber || 1,
646
+ columnNumber: frame.columnNumber || 1,
647
+ method: frame.functionName || "Anonymous or unknown function",
648
+ file: frame.fileName || "Unknown file",
649
+ codeSnippet: snippet.codeSnippet,
650
+ class: "",
651
+ isApplicationFrame: isApplicationFrame(frame.fileName)
652
+ }));
653
+ })).then(resolve);
654
+ });
655
+ }
656
+ function fallbackFrame(reason) {
657
+ return {
658
+ lineNumber: 0,
659
+ columnNumber: 0,
660
+ method: "unknown",
661
+ file: "unknown",
662
+ codeSnippet: { 0: `Could not read from file: ${reason}` },
663
+ class: "unknown"
664
+ };
665
+ }
666
+ function hasStack(err) {
667
+ if (!err || typeof err !== "object") return false;
668
+ const e = err;
669
+ const stack = e.stack ?? e.stacktrace ?? e["opera#sourceloc"];
670
+ return typeof stack === "string" && stack !== `${e.name}: ${e.message}`;
671
+ }
672
+ function isApplicationFrame(fileName) {
673
+ if (!fileName) return true;
674
+ if (/[/\\]node_modules[/\\]/.test(fileName)) return false;
675
+ if (/(^|[/\\])(vendor|vendors)[.~-][^/\\]*\.js/i.test(fileName)) return false;
676
+ return true;
677
+ }
678
+
679
+ //#endregion
680
+ //#region src/stacktrace/NullFileReader.ts
681
+ /**
682
+ * No-op `FileReader` that returns `null` for every URL it is asked to read.
683
+ *
684
+ * Used as the default for `Flare`'s `fileReader` constructor parameter so the
685
+ * class is usable without picking a side: instantiated bare (`new Flare()`),
686
+ * reports still build, but stack frames omit source-code snippets — which is
687
+ * the correct, safe behavior in an environment we know nothing about.
688
+ *
689
+ * The two real implementations live in the consumer packages and take their
690
+ * place once the right environment is established:
691
+ *
692
+ * - `@flareapp/js` injects `FetchFileReader`, which `fetch()`s source maps
693
+ * and original files over HTTP for browser stack frames.
694
+ * - `@flareapp/node` injects `DiskFileReader`, which reads files from disk
695
+ * via `node:fs/promises` for server stack frames.
696
+ *
697
+ * The interface (`read(url) -> Promise<string | null>`) lets the stack-trace
698
+ * builder treat all three the same way: ask for a URL, render the snippet
699
+ * when text comes back, gracefully skip it when `null` does. No environment
700
+ * checks anywhere in core.
701
+ */
702
+ var NullFileReader = class {
703
+ read(_url) {
704
+ return Promise.resolve(null);
705
+ }
706
+ };
707
+
708
+ //#endregion
709
+ //#region src/Flare.ts
710
+ const DEFAULT_SDK_NAME = "@flareapp/core";
711
+ var Flare = class {
712
+ inflight = /* @__PURE__ */ new Set();
713
+ _logger;
714
+ _config = {
715
+ key: null,
716
+ version: "",
717
+ sourcemapVersionId: SOURCEMAP_VERSION,
718
+ stage: "",
719
+ maxGlowsPerReport: 30,
720
+ ingestUrl: "https://ingress.flareapp.io/v1/errors",
721
+ reportBrowserExtensionErrors: false,
722
+ debug: false,
723
+ urlDenylist: DEFAULT_URL_DENYLIST,
724
+ replaceDefaultUrlDenylist: false,
725
+ sampleRate: 1,
726
+ beforeEvaluate: (error) => error,
727
+ beforeSubmit: (report) => report,
728
+ enableLogs: false,
729
+ logsIngestUrl: "https://ingress.flareapp.io/v1/logs",
730
+ maxLogBufferSize: 100,
731
+ logFlushIntervalMs: 5e3,
732
+ logFlushMaxBytes: 8e5,
733
+ keepaliveMaxBytes: 6e4
734
+ };
735
+ sdkInfo = {
736
+ name: DEFAULT_SDK_NAME,
737
+ version: CLIENT_VERSION
738
+ };
739
+ framework = null;
740
+ /**
741
+ * @param api sends the report over HTTP.
742
+ * @param contextCollector returns per-report attributes (browser DOM info, Node
743
+ * process info, etc). Default is a no-op.
744
+ * @param fileReader reads source files for stack-trace snippets. Default
745
+ * returns null (no snippets); `@flareapp/js` injects a
746
+ * fetch-based reader, `@flareapp/node` injects a disk reader.
747
+ * @param scopeProvider returns the current `Scope` (per-call mutable state:
748
+ * glows, pendingAttributes, entryPoint). Browser uses a
749
+ * single global scope; Node uses an AsyncLocalStorage-
750
+ * backed provider so each request gets its own.
751
+ */
752
+ constructor(api = new Api(), contextCollector = () => ({}), fileReader = new NullFileReader(), scopeProvider = new GlobalScopeProvider(), scheduler = new NoopFlushScheduler()) {
753
+ this.api = api;
754
+ this.contextCollector = contextCollector;
755
+ this.fileReader = fileReader;
756
+ this.scopeProvider = scopeProvider;
757
+ this._logger = new Logger({
758
+ api: this.api,
759
+ getConfig: () => this._config,
760
+ getSdkInfo: () => this.sdkInfo,
761
+ getFramework: () => this.framework,
762
+ buildLogAttributes: (userAttributes) => this.buildLogAttributes(userAttributes),
763
+ track: (p) => this.track(p),
764
+ scheduler
765
+ });
766
+ }
767
+ /**
768
+ * Register an in-flight report so `flush()` can wait for it. Called by
769
+ * every public report entry point (`report`, `reportSilently`,
770
+ * `reportMessage`, `reportUnhandledRejection`, `test`); each wraps its
771
+ * full async pipeline (beforeEvaluate -> stack trace + source snippets ->
772
+ * beforeSubmit -> `api.report()`) so the entire roundtrip is what's
773
+ * tracked, not just the HTTP send at the end.
774
+ *
775
+ * Two problems this method solves at once.
776
+ *
777
+ * Problem 1: hold a reference to the work without leaking rejections.
778
+ *
779
+ * `p` is the real report pipeline; it can reject (network failure,
780
+ * `beforeSubmit` throws, etc). If we stored `p` directly in `inflight`
781
+ * and no caller attached a `.catch` (the global error listeners use
782
+ * `reportSilently` which DOES catch, but the path is still subtle), an
783
+ * eventual rejection would surface as an unhandled-rejection warning
784
+ * on Node and a console error in the browser. Bad citizen.
785
+ *
786
+ * So we build a SHADOW promise that mirrors `p`'s timing but cannot
787
+ * reject:
788
+ *
789
+ * p.then(
790
+ * () => undefined, // on fulfilment, value is undefined
791
+ * () => undefined, // on rejection, ALSO resolve with undefined
792
+ * )
793
+ *
794
+ * Providing the second argument means we have "handled" any rejection
795
+ * from `p`. The shadow always resolves with `undefined`, and `p`'s
796
+ * rejection is consumed at the boundary. From the runtime's point of
797
+ * view, the shadow is well-behaved.
798
+ *
799
+ * Problem 2: self-cleaning entry.
800
+ *
801
+ * `tracked.finally(() => this.inflight.delete(tracked))`. `finally`
802
+ * fires whether the shadow resolves or rejects, but the shadow can no
803
+ * longer reject (problem 1 normalized it), so this is effectively
804
+ * "when the underlying report has settled, remove me from the Set."
805
+ * No GC magic, no external cleanup, no race window.
806
+ *
807
+ * Note that `.finally` itself returns a new promise that we drop on
808
+ * the floor. If the cleanup callback ever throws, that would surface
809
+ * as an unhandled rejection on the dropped promise; `delete` does not
810
+ * throw so we are safe today, but anything more elaborate added here
811
+ * should be wrapped in try/catch.
812
+ *
813
+ * The return value is the ORIGINAL `p`. The caller awaits real success
814
+ * or failure; the tracking is completely invisible to them. This is why
815
+ * `await flare.report(err)` inside a fatal handler observes network
816
+ * errors the same as before tracking was added.
817
+ */
818
+ track(p) {
819
+ const tracked = p.then(() => void 0, () => void 0);
820
+ this.inflight.add(tracked);
821
+ tracked.finally(() => this.inflight.delete(tracked));
822
+ return p;
823
+ }
824
+ /**
825
+ * Wait until every in-flight report settles, or until `timeoutMs`
826
+ * elapses, whichever comes first. Always resolves; never rejects.
827
+ *
828
+ * The main consumer is `@flareapp/node`'s fatal handler:
829
+ *
830
+ * process.on('uncaughtException', async (err) => {
831
+ * process.exitCode = 1;
832
+ * try { await flare.report(err); } catch {}
833
+ * await flare.flush(shutdownTimeoutMs);
834
+ * process.exit(1);
835
+ * });
836
+ *
837
+ * The fatal `report` is awaited explicitly; `flush` then drains any
838
+ * OTHER reports that were already in flight (a request handler that
839
+ * fired `flare.report(...)` concurrently with the crash). The timeout
840
+ * caps the wait so a hung HTTP request cannot indefinitely block
841
+ * shutdown.
842
+ *
843
+ * Walking the implementation:
844
+ *
845
+ * const pending = [...this.inflight];
846
+ *
847
+ * Spread takes a SNAPSHOT of the Set at this instant. Reports that
848
+ * start AFTER this line are not included in `pending`, so they are
849
+ * not awaited by THIS flush call. This is intentional: it bounds
850
+ * the wait. Without the snapshot, a handler that kept emitting
851
+ * reports during shutdown could keep flush alive forever and block
852
+ * the process from exiting.
853
+ *
854
+ * if (pending.length === 0) return Promise.resolve();
855
+ *
856
+ * Fast path. No timer scheduled, no promise constructor needed.
857
+ * Resolves on the microtask queue. Cheap.
858
+ *
859
+ * return new Promise<void>((resolve) => {
860
+ * const timer = setTimeout(resolve, timeoutMs);
861
+ * Promise.allSettled(pending).then(() => {
862
+ * clearTimeout(timer);
863
+ * resolve();
864
+ * });
865
+ * });
866
+ *
867
+ * The race between two outcomes, both calling the same `resolve`:
868
+ *
869
+ * 1. `setTimeout(resolve, timeoutMs)` schedules a "give up" call.
870
+ * After `timeoutMs` it fires, calling `resolve()` from the
871
+ * timer-queue side. The outer promise resolves immediately,
872
+ * even if reports are still pending. Those reports are abandoned
873
+ * (they continue running but the process is about to die).
874
+ *
875
+ * 2. `Promise.allSettled(pending)` returns a promise that resolves
876
+ * when every promise in `pending` has either fulfilled or
877
+ * rejected. It NEVER rejects on its own. We use `allSettled`
878
+ * rather than `Promise.all` because `all` short-circuits on the
879
+ * first rejection -- we want to wait for everyone regardless of
880
+ * whether their HTTP calls succeed or fail. (Our shadows cannot
881
+ * reject anyway because `track` normalized them, but using
882
+ * `allSettled` documents the intent and survives future changes
883
+ * to shadow construction.) When it resolves, we call
884
+ * `clearTimeout(timer)` to cancel the pending timer (so it does
885
+ * not fire later and call `resolve` a second time -- a no-op,
886
+ * but wasted work) and then `resolve()` ourselves.
887
+ *
888
+ * Resolve can only meaningfully fire once. Subsequent calls to the
889
+ * same `resolve` are silently ignored by the Promise spec, so the
890
+ * race is safe even if for some reason both branches fired together.
891
+ *
892
+ * Things flush() deliberately does NOT do:
893
+ *
894
+ * - It does not reject. Even if every report failed, allSettled
895
+ * resolves. Callers do not need a `.catch`.
896
+ * - It does not retry. One pipeline attempt per report, then move on.
897
+ * - It does not stop new reports from starting. The Flare instance
898
+ * is still usable after flush resolves. flush is "wait for what is
899
+ * in flight," not "freeze the SDK."
900
+ * - It does not drain reports started after the snapshot. Call flush
901
+ * again if you need to wait for those too.
902
+ */
903
+ flush(timeoutMs = 2e3) {
904
+ this._logger.flush();
905
+ const pending = [...this.inflight];
906
+ if (pending.length === 0) return Promise.resolve();
907
+ return new Promise((resolve) => {
908
+ const timer = setTimeout(resolve, timeoutMs);
909
+ Promise.allSettled(pending).then(() => {
910
+ clearTimeout(timer);
911
+ resolve();
912
+ });
913
+ });
914
+ }
915
+ get config() {
916
+ return this._config;
917
+ }
918
+ get glows() {
919
+ return this.scopeProvider.active().glows;
920
+ }
921
+ get logger() {
922
+ return this._logger;
923
+ }
924
+ light(key = KEY, debug) {
925
+ this._config.key = key;
926
+ if (debug !== void 0) this._config.debug = debug;
927
+ this._logger.flush();
928
+ return this;
929
+ }
930
+ configure(config) {
931
+ const wasLogsEnabled = this._config.enableLogs;
932
+ this._config = {
933
+ ...this._config,
934
+ ...config
935
+ };
936
+ if (config.sampleRate !== void 0) this._config.sampleRate = Math.max(0, Math.min(1, config.sampleRate));
937
+ this._config.urlDenylist = resolveDenylist(config.urlDenylist, config.replaceDefaultUrlDenylist ?? this._config.replaceDefaultUrlDenylist);
938
+ if (wasLogsEnabled && this._config.enableLogs === false) this._logger.clear();
939
+ if (config.key !== void 0) this._logger.flush();
940
+ return this;
941
+ }
942
+ test() {
943
+ return this.track(this.testInternal());
944
+ }
945
+ async testInternal() {
946
+ const report = await this.createReportFromError(/* @__PURE__ */ new Error("The Flare client is set up correctly!"));
947
+ if (!report) return;
948
+ return this.sendReport(report);
949
+ }
950
+ glow(name, level = "info", data = []) {
951
+ const time = now();
952
+ this.scopeProvider.active().addGlow({
953
+ name,
954
+ messageLevel: level,
955
+ metaData: data,
956
+ time,
957
+ microtime: time
958
+ }, this._config.maxGlowsPerReport);
959
+ return this;
960
+ }
961
+ clearGlows() {
962
+ this.scopeProvider.active().clearGlows();
963
+ return this;
964
+ }
965
+ addContext(name, value) {
966
+ const scope = this.scopeProvider.active();
967
+ const existing = scope.pendingAttributes["context.custom"] ?? {};
968
+ scope.setAttribute("context.custom", {
969
+ ...existing,
970
+ [name]: value
971
+ });
972
+ return this;
973
+ }
974
+ addContextGroup(groupName, value) {
975
+ this.scopeProvider.active().setAttribute(`context.${groupName}`, value);
976
+ return this;
977
+ }
978
+ setEntryPoint(handler) {
979
+ this.scopeProvider.active().entryPoint = handler;
980
+ return this;
981
+ }
982
+ setSdkInfo(info) {
983
+ this.sdkInfo = info;
984
+ return this;
985
+ }
986
+ setFramework(framework) {
987
+ this.framework = framework;
988
+ return this;
989
+ }
990
+ report(error, attributes = {}) {
991
+ return this.track(this.reportInternal(error, attributes));
992
+ }
993
+ async reportInternal(error, attributes = {}) {
994
+ if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
995
+ const seenAtUnixNano = Date.now() * 1e6;
996
+ const coerced = error instanceof Error ? error : new Error(typeof error === "string" ? error : String(error));
997
+ const errorToReport = await this._config.beforeEvaluate(coerced);
998
+ if (!errorToReport) return;
999
+ const report = await this.createReportFromError(errorToReport, attributes, seenAtUnixNano);
1000
+ if (!report) return;
1001
+ return this.sendReport(report);
1002
+ }
1003
+ reportSilently(error, attributes = {}) {
1004
+ this.track(this.reportInternal(error, attributes).catch(() => {}));
1005
+ }
1006
+ reportUnhandledRejection(message, attributes = {}) {
1007
+ return this.track(this.reportUnhandledRejectionInternal(message, attributes));
1008
+ }
1009
+ async reportUnhandledRejectionInternal(message, attributes = {}) {
1010
+ if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
1011
+ const seenAtUnixNano = Date.now() * 1e6;
1012
+ const report = this.buildReport({
1013
+ exceptionClass: "UnhandledRejection",
1014
+ message,
1015
+ stacktrace: [],
1016
+ isLog: false,
1017
+ level: void 0,
1018
+ extraAttributes: attributes,
1019
+ code: void 0,
1020
+ seenAtUnixNano
1021
+ });
1022
+ return this.sendReport(report);
1023
+ }
1024
+ reportMessage(message, level, attributes = {}) {
1025
+ return this.track(this.reportMessageInternal(message, level, attributes));
1026
+ }
1027
+ async reportMessageInternal(message, level, attributes = {}) {
1028
+ if (this._config.sampleRate < 1 && Math.random() >= this._config.sampleRate) return;
1029
+ const seenAtUnixNano = Date.now() * 1e6;
1030
+ const stackTrace = await createStackTrace(/* @__PURE__ */ new Error(), this._config.debug, this.fileReader);
1031
+ stackTrace.shift();
1032
+ const report = this.buildReport({
1033
+ exceptionClass: "Log",
1034
+ message,
1035
+ stacktrace: stackTrace,
1036
+ isLog: true,
1037
+ level,
1038
+ extraAttributes: attributes,
1039
+ code: void 0,
1040
+ seenAtUnixNano
1041
+ });
1042
+ return this.sendReport(report);
1043
+ }
1044
+ async createReportFromError(error, attributes = {}, seenAtUnixNano = Date.now() * 1e6) {
1045
+ if (!assert(error, "No error provided.", this._config.debug)) return false;
1046
+ const stacktrace = await createStackTrace(error, this._config.debug, this.fileReader);
1047
+ assert(stacktrace.length, "Couldn't generate stacktrace of this error: " + error, this._config.debug);
1048
+ const exceptionClass = error.constructor && error.constructor.name ? error.constructor.name : "undefined";
1049
+ return this.buildReport({
1050
+ exceptionClass,
1051
+ message: error.message,
1052
+ stacktrace,
1053
+ isLog: false,
1054
+ level: void 0,
1055
+ extraAttributes: attributes,
1056
+ code: extractCode(error),
1057
+ seenAtUnixNano
1058
+ });
1059
+ }
1060
+ buildBaseAttributes() {
1061
+ const baseAttributes = {
1062
+ "telemetry.sdk.language": "javascript",
1063
+ "telemetry.sdk.name": this.sdkInfo.name,
1064
+ "telemetry.sdk.version": this.sdkInfo.version,
1065
+ "flare.language.name": "javascript"
1066
+ };
1067
+ if (this._config.stage) baseAttributes["service.stage"] = this._config.stage;
1068
+ if (this._config.version) baseAttributes["service.version"] = this._config.version;
1069
+ if (this.framework?.name) baseAttributes["flare.framework.name"] = this.framework.name;
1070
+ if (this.framework?.version) baseAttributes["flare.framework.version"] = this.framework.version;
1071
+ return baseAttributes;
1072
+ }
1073
+ assembleAttributes(collectorAttributes, extraAttributes, includeBase) {
1074
+ const activeScope = this.scopeProvider.active();
1075
+ const baseAttributes = includeBase ? this.buildBaseAttributes() : {};
1076
+ const entryPoint = activeScope.entryPoint;
1077
+ const entryPointOverrides = {};
1078
+ if (entryPoint?.identifier !== void 0) entryPointOverrides["flare.entry_point.handler.identifier"] = entryPoint.identifier;
1079
+ if (entryPoint?.type !== void 0) entryPointOverrides["flare.entry_point.handler.type"] = entryPoint.type;
1080
+ if (entryPoint?.name !== void 0) entryPointOverrides["flare.entry_point.handler.name"] = entryPoint.name;
1081
+ const attributes = {
1082
+ ...baseAttributes,
1083
+ ...collectorAttributes,
1084
+ ...entryPointOverrides,
1085
+ ...activeScope.pendingAttributes,
1086
+ ...extraAttributes
1087
+ };
1088
+ const pendingCustom = activeScope.pendingAttributes["context.custom"];
1089
+ const extraCustom = extraAttributes["context.custom"];
1090
+ if (pendingCustom && extraCustom && typeof pendingCustom === "object" && typeof extraCustom === "object" && !Array.isArray(pendingCustom) && !Array.isArray(extraCustom)) attributes["context.custom"] = {
1091
+ ...pendingCustom,
1092
+ ...extraCustom
1093
+ };
1094
+ if (this.framework?.name) attributes["context.custom"] = {
1095
+ ...attributes["context.custom"] ?? {},
1096
+ framework: this.framework.name.toLowerCase()
1097
+ };
1098
+ return attributes;
1099
+ }
1100
+ buildLogAttributes(userAttributes) {
1101
+ const { resource, record: collectorRecord } = partitionAttributes(this.contextCollector(this._config));
1102
+ return {
1103
+ resource,
1104
+ record: this.assembleAttributes(collectorRecord, userAttributes, false)
1105
+ };
1106
+ }
1107
+ buildReport(input) {
1108
+ const activeScope = this.scopeProvider.active();
1109
+ const attributes = this.assembleAttributes(this.contextCollector(this._config), input.extraAttributes, true);
1110
+ const report = {
1111
+ exceptionClass: input.exceptionClass,
1112
+ message: input.message,
1113
+ seenAtUnixNano: input.seenAtUnixNano,
1114
+ stacktrace: input.stacktrace,
1115
+ events: glowsToEvents(activeScope.glows),
1116
+ attributes
1117
+ };
1118
+ if (input.isLog) report.isLog = true;
1119
+ if (input.level !== void 0) report.level = input.level;
1120
+ if (this._config.sourcemapVersionId) report.sourcemapVersionId = this._config.sourcemapVersionId;
1121
+ if (input.code !== void 0) report.code = input.code;
1122
+ return report;
1123
+ }
1124
+ async sendReport(report) {
1125
+ if (!assertKey(this._config.key, this._config.debug)) return;
1126
+ const reportToSubmit = await this._config.beforeSubmit(report);
1127
+ if (!reportToSubmit) return;
1128
+ return this.api.report(reportToSubmit, this._config.ingestUrl, this._config.key, this._config.reportBrowserExtensionErrors, this._config.debug);
1129
+ }
1130
+ };
1131
+
1132
+ //#endregion
1133
+ exports.Api = Api;
1134
+ exports.DEFAULT_URL_DENYLIST = DEFAULT_URL_DENYLIST;
1135
+ exports.Flare = Flare;
1136
+ exports.GlobalScopeProvider = GlobalScopeProvider;
1137
+ exports.Logger = Logger;
1138
+ exports.NoopFlushScheduler = NoopFlushScheduler;
1139
+ exports.NullFileReader = NullFileReader;
1140
+ exports.Scope = Scope;
1141
+ exports.assert = assert;
1142
+ exports.assertKey = assertKey;
1143
+ exports.convertToError = convertToError;
1144
+ exports.createStackTrace = createStackTrace;
1145
+ exports.extractCode = extractCode;
1146
+ exports.flatJsonStringify = flatJsonStringify;
1147
+ exports.getCodeSnippet = getCodeSnippet;
1148
+ exports.glowsToEvents = glowsToEvents;
1149
+ exports.now = now;
1150
+ exports.readLinesFromFile = readLinesFromFile;
1151
+ exports.redactUrlQuery = redactUrlQuery;
1152
+ exports.resolveDenylist = resolveDenylist;