@flareapp/core 2.5.0 → 2.6.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.cjs +114 -8
- package/dist/index.d.cts +71 -4
- package/dist/index.d.mts +71 -4
- package/dist/index.mjs +110 -8
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,7 +30,7 @@ let error_stack_parser = require("error-stack-parser");
|
|
|
30
30
|
error_stack_parser = __toESM(error_stack_parser);
|
|
31
31
|
|
|
32
32
|
//#region src/env/index.ts
|
|
33
|
-
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.
|
|
33
|
+
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.6.0" : "?";
|
|
34
34
|
const KEY = typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY;
|
|
35
35
|
const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION;
|
|
36
36
|
|
|
@@ -171,6 +171,49 @@ function safeDecode(value) {
|
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/util/rejection.ts
|
|
176
|
+
/** Best-effort human-readable description of an arbitrary rejection reason. */
|
|
177
|
+
function describeRejectionReason(reason) {
|
|
178
|
+
if (typeof reason === "string") return reason;
|
|
179
|
+
if (reason && typeof reason === "object") {
|
|
180
|
+
const message = reason.message;
|
|
181
|
+
if (typeof message === "string" && message) return message;
|
|
182
|
+
try {
|
|
183
|
+
return JSON.stringify(reason);
|
|
184
|
+
} catch {
|
|
185
|
+
return "Unhandled promise rejection (non-serializable reason)";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return String(reason);
|
|
189
|
+
}
|
|
190
|
+
function hasStack$1(reason) {
|
|
191
|
+
return !!reason && typeof reason === "object" && typeof reason.stack === "string";
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Route a rejection reason to the reporter: an Error (or any stack-bearing
|
|
195
|
+
* object) goes to `reportSilently` so the STACK survives; only a stackless
|
|
196
|
+
* reason falls back to `reportUnhandledRejection` (string message, empty-stack
|
|
197
|
+
* `UnhandledRejection` class). Any rejection from `reportUnhandledRejection`'s
|
|
198
|
+
* returned promise is swallowed so a transport failure cannot itself surface as
|
|
199
|
+
* an unhandled rejection. `reportSilently` is assumed not to throw synchronously
|
|
200
|
+
* (core's does its work asynchronously); it is intentionally not wrapped, so a
|
|
201
|
+
* synchronous throw there would propagate.
|
|
202
|
+
*/
|
|
203
|
+
function routeRejection(reporter, reason) {
|
|
204
|
+
if (reason instanceof Error) {
|
|
205
|
+
reporter.reportSilently(reason);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (hasStack$1(reason)) {
|
|
209
|
+
const error = new Error(describeRejectionReason(reason));
|
|
210
|
+
error.stack = reason.stack;
|
|
211
|
+
reporter.reportSilently(error);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
Promise.resolve(reporter.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
|
|
215
|
+
}
|
|
216
|
+
|
|
174
217
|
//#endregion
|
|
175
218
|
//#region src/api/Api.ts
|
|
176
219
|
var Api = class {
|
|
@@ -500,6 +543,39 @@ function partitionAttributes(attributes) {
|
|
|
500
543
|
//#endregion
|
|
501
544
|
//#region src/Scope.ts
|
|
502
545
|
/**
|
|
546
|
+
* Maps each `User` identity field to the flat report attribute key it projects to.
|
|
547
|
+
* `Flare.setUser`'s set pass writes through these so the literal key strings live in
|
|
548
|
+
* exactly one place; `USER_IDENTITY_KEYS` (the clear pass) derives from them, so adding
|
|
549
|
+
* a field here can never silently leave the clear pass out of date.
|
|
550
|
+
*/
|
|
551
|
+
const USER_FIELD_KEYS = {
|
|
552
|
+
id: "user.id",
|
|
553
|
+
email: "user.email",
|
|
554
|
+
fullName: "user.full_name",
|
|
555
|
+
ipAddress: "client.address"
|
|
556
|
+
};
|
|
557
|
+
/**
|
|
558
|
+
* The report attribute keys that `Flare.setUser` owns: the four projected identity
|
|
559
|
+
* fields plus the `user.attributes` bag for extras. Single source of truth so the
|
|
560
|
+
* clear pass and the set pass in `setUser` cannot drift, and so consumers that must
|
|
561
|
+
* stamp identity outside core's report pipeline (Electron's forwarded-renderer path)
|
|
562
|
+
* pick up the exact same set instead of re-hardcoding it.
|
|
563
|
+
*/
|
|
564
|
+
const USER_IDENTITY_KEYS = [...Object.values(USER_FIELD_KEYS), "user.attributes"];
|
|
565
|
+
/**
|
|
566
|
+
* Pick the user-identity attributes currently set on a scope. Used where identity must
|
|
567
|
+
* be copied onto a report that does not flow through `Flare.report()` (which would
|
|
568
|
+
* otherwise spread `pendingAttributes` automatically).
|
|
569
|
+
*/
|
|
570
|
+
function userIdentityAttributes(scope) {
|
|
571
|
+
const attrs = {};
|
|
572
|
+
for (const key of USER_IDENTITY_KEYS) {
|
|
573
|
+
const value = scope.pendingAttributes[key];
|
|
574
|
+
if (value !== void 0) attrs[key] = value;
|
|
575
|
+
}
|
|
576
|
+
return attrs;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
503
579
|
* Holds the per-call mutable state that used to live on the `Flare` instance:
|
|
504
580
|
* breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
|
|
505
581
|
* current entry-point handler.
|
|
@@ -516,9 +592,9 @@ function partitionAttributes(attributes) {
|
|
|
516
592
|
* holding the state directly, so the per-request behavior comes from the
|
|
517
593
|
* provider, not from the class itself.
|
|
518
594
|
*
|
|
519
|
-
* `NodeScope` (in `@flareapp/node`) extends this with
|
|
520
|
-
*
|
|
521
|
-
* does not need
|
|
595
|
+
* `NodeScope` (in `@flareapp/node`) extends this with a `request` bucket
|
|
596
|
+
* (HTTP method, path, headers). User identity is written to `pendingAttributes`
|
|
597
|
+
* by `Flare.setUser`, so it needs no dedicated field. Browser does not need `request`.
|
|
522
598
|
*/
|
|
523
599
|
var Scope = class {
|
|
524
600
|
glows = [];
|
|
@@ -641,18 +717,24 @@ function createStackTrace(error, debug, fileReader) {
|
|
|
641
717
|
return resolve([fallbackFrame("stacktrace could not be parsed")]);
|
|
642
718
|
}
|
|
643
719
|
Promise.all(parsedFrames.map((frame) => {
|
|
644
|
-
|
|
720
|
+
const fileName = normalizeFileName(frame.fileName);
|
|
721
|
+
return getCodeSnippet(fileReader, fileName, frame.lineNumber, frame.columnNumber).then((snippet) => ({
|
|
645
722
|
lineNumber: frame.lineNumber || 1,
|
|
646
723
|
columnNumber: frame.columnNumber || 1,
|
|
647
724
|
method: frame.functionName || "Anonymous or unknown function",
|
|
648
|
-
file:
|
|
725
|
+
file: fileName || "Unknown file",
|
|
649
726
|
codeSnippet: snippet.codeSnippet,
|
|
650
727
|
class: "",
|
|
651
|
-
isApplicationFrame: isApplicationFrame(
|
|
728
|
+
isApplicationFrame: isApplicationFrame(fileName)
|
|
652
729
|
}));
|
|
653
730
|
})).then(resolve);
|
|
654
731
|
});
|
|
655
732
|
}
|
|
733
|
+
const HERMES_ADDRESS_PREFIX = "address at ";
|
|
734
|
+
function normalizeFileName(fileName) {
|
|
735
|
+
if (fileName?.startsWith(HERMES_ADDRESS_PREFIX)) return fileName.slice(11);
|
|
736
|
+
return fileName;
|
|
737
|
+
}
|
|
656
738
|
function fallbackFrame(reason) {
|
|
657
739
|
return {
|
|
658
740
|
lineNumber: 0,
|
|
@@ -975,6 +1057,26 @@ var Flare = class {
|
|
|
975
1057
|
this.scopeProvider.active().setAttribute(`context.${groupName}`, value);
|
|
976
1058
|
return this;
|
|
977
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Attach an identified user to the active scope. Fields are projected to the
|
|
1062
|
+
* keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
|
|
1063
|
+
* and `client.address`. Any extra keys are bundled into `user.attributes`.
|
|
1064
|
+
* Pass `null` to clear the user. Scope-aware: in Node this targets the
|
|
1065
|
+
* per-request scope via the scope provider.
|
|
1066
|
+
*/
|
|
1067
|
+
setUser(user) {
|
|
1068
|
+
const scope = this.scopeProvider.active();
|
|
1069
|
+
for (const key of USER_IDENTITY_KEYS) delete scope.pendingAttributes[key];
|
|
1070
|
+
if (!user) return this;
|
|
1071
|
+
const { id, email, fullName, ipAddress, ...rest } = user;
|
|
1072
|
+
if (id !== void 0 && id !== null) scope.setAttribute(USER_FIELD_KEYS.id, String(id));
|
|
1073
|
+
if (email !== void 0) scope.setAttribute(USER_FIELD_KEYS.email, email);
|
|
1074
|
+
if (fullName !== void 0) scope.setAttribute(USER_FIELD_KEYS.fullName, fullName);
|
|
1075
|
+
if (ipAddress !== void 0) scope.setAttribute(USER_FIELD_KEYS.ipAddress, ipAddress);
|
|
1076
|
+
const extras = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== void 0));
|
|
1077
|
+
if (Object.keys(extras).length > 0) scope.setAttribute("user.attributes", extras);
|
|
1078
|
+
return this;
|
|
1079
|
+
}
|
|
978
1080
|
setEntryPoint(handler) {
|
|
979
1081
|
this.scopeProvider.active().entryPoint = handler;
|
|
980
1082
|
return this;
|
|
@@ -1138,10 +1240,12 @@ exports.Logger = Logger;
|
|
|
1138
1240
|
exports.NoopFlushScheduler = NoopFlushScheduler;
|
|
1139
1241
|
exports.NullFileReader = NullFileReader;
|
|
1140
1242
|
exports.Scope = Scope;
|
|
1243
|
+
exports.USER_IDENTITY_KEYS = USER_IDENTITY_KEYS;
|
|
1141
1244
|
exports.assert = assert;
|
|
1142
1245
|
exports.assertKey = assertKey;
|
|
1143
1246
|
exports.convertToError = convertToError;
|
|
1144
1247
|
exports.createStackTrace = createStackTrace;
|
|
1248
|
+
exports.describeRejectionReason = describeRejectionReason;
|
|
1145
1249
|
exports.extractCode = extractCode;
|
|
1146
1250
|
exports.flatJsonStringify = flatJsonStringify;
|
|
1147
1251
|
exports.getCodeSnippet = getCodeSnippet;
|
|
@@ -1149,4 +1253,6 @@ exports.glowsToEvents = glowsToEvents;
|
|
|
1149
1253
|
exports.now = now;
|
|
1150
1254
|
exports.readLinesFromFile = readLinesFromFile;
|
|
1151
1255
|
exports.redactUrlQuery = redactUrlQuery;
|
|
1152
|
-
exports.resolveDenylist = resolveDenylist;
|
|
1256
|
+
exports.resolveDenylist = resolveDenylist;
|
|
1257
|
+
exports.routeRejection = routeRejection;
|
|
1258
|
+
exports.userIdentityAttributes = userIdentityAttributes;
|
package/dist/index.d.cts
CHANGED
|
@@ -4,6 +4,23 @@ type AttributeValue = string | number | boolean | null | AttributeValue[] | {
|
|
|
4
4
|
[key: string]: AttributeValue;
|
|
5
5
|
};
|
|
6
6
|
type Attributes = Record<string, AttributeValue>;
|
|
7
|
+
/**
|
|
8
|
+
* An identified user passed to `Flare.setUser`. The four known fields project to the
|
|
9
|
+
* report keys the Flare backend reads: `id`→`user.id`, `email`→`user.email`,
|
|
10
|
+
* `fullName`→`user.full_name`, `ipAddress`→`client.address`. Any OTHER key is bundled
|
|
11
|
+
* into `user.attributes`.
|
|
12
|
+
*
|
|
13
|
+
* Caveat: the open index signature means a misspelled known field (e.g. `fullname` or
|
|
14
|
+
* `full_name` instead of `fullName`) does NOT raise a type error — it silently lands in
|
|
15
|
+
* `user.attributes` rather than the identity key. Spell the four known fields exactly.
|
|
16
|
+
*/
|
|
17
|
+
type User = {
|
|
18
|
+
id?: string | number;
|
|
19
|
+
email?: string;
|
|
20
|
+
fullName?: string;
|
|
21
|
+
ipAddress?: string;
|
|
22
|
+
[key: string]: AttributeValue | undefined;
|
|
23
|
+
};
|
|
7
24
|
type Config = {
|
|
8
25
|
key: string | null;
|
|
9
26
|
version: string;
|
|
@@ -168,6 +185,34 @@ declare const DEFAULT_URL_DENYLIST: RegExp;
|
|
|
168
185
|
declare function resolveDenylist(custom?: RegExp, replaceDefault?: boolean, defaultDenylist?: RegExp): RegExp;
|
|
169
186
|
declare function redactUrlQuery(fullPath: string, denylist?: RegExp): string;
|
|
170
187
|
//#endregion
|
|
188
|
+
//#region src/util/rejection.d.ts
|
|
189
|
+
/**
|
|
190
|
+
* Shared unhandled-rejection routing. A rejection "reason" can be anything a
|
|
191
|
+
* promise was rejected with: an Error, an Error-like object carrying a `.stack`,
|
|
192
|
+
* a string, or a plain object. The browser `unhandledrejection` listener
|
|
193
|
+
* (`@flareapp/js`) and the React Native engine rejection tracker
|
|
194
|
+
* (`@flareapp/react-native`) need the SAME routing so a report looks identical
|
|
195
|
+
* across SDKs, so it lives here instead of being copy-pasted (and drifting) per
|
|
196
|
+
* client.
|
|
197
|
+
*/
|
|
198
|
+
type RejectionReporter = {
|
|
199
|
+
reportSilently: (error: Error) => void;
|
|
200
|
+
reportUnhandledRejection: (message: string) => unknown;
|
|
201
|
+
};
|
|
202
|
+
/** Best-effort human-readable description of an arbitrary rejection reason. */
|
|
203
|
+
declare function describeRejectionReason(reason: unknown): string;
|
|
204
|
+
/**
|
|
205
|
+
* Route a rejection reason to the reporter: an Error (or any stack-bearing
|
|
206
|
+
* object) goes to `reportSilently` so the STACK survives; only a stackless
|
|
207
|
+
* reason falls back to `reportUnhandledRejection` (string message, empty-stack
|
|
208
|
+
* `UnhandledRejection` class). Any rejection from `reportUnhandledRejection`'s
|
|
209
|
+
* returned promise is swallowed so a transport failure cannot itself surface as
|
|
210
|
+
* an unhandled rejection. `reportSilently` is assumed not to throw synchronously
|
|
211
|
+
* (core's does its work asynchronously); it is intentionally not wrapped, so a
|
|
212
|
+
* synchronous throw there would propagate.
|
|
213
|
+
*/
|
|
214
|
+
declare function routeRejection(reporter: RejectionReporter, reason: unknown): void;
|
|
215
|
+
//#endregion
|
|
171
216
|
//#region src/api/Api.d.ts
|
|
172
217
|
declare class Api {
|
|
173
218
|
report(report: Report, url: string, key: string | null, reportBrowserExtensionErrors: boolean, debug?: boolean): Promise<void>;
|
|
@@ -236,6 +281,20 @@ declare class Logger {
|
|
|
236
281
|
}
|
|
237
282
|
//#endregion
|
|
238
283
|
//#region src/Scope.d.ts
|
|
284
|
+
/**
|
|
285
|
+
* The report attribute keys that `Flare.setUser` owns: the four projected identity
|
|
286
|
+
* fields plus the `user.attributes` bag for extras. Single source of truth so the
|
|
287
|
+
* clear pass and the set pass in `setUser` cannot drift, and so consumers that must
|
|
288
|
+
* stamp identity outside core's report pipeline (Electron's forwarded-renderer path)
|
|
289
|
+
* pick up the exact same set instead of re-hardcoding it.
|
|
290
|
+
*/
|
|
291
|
+
declare const USER_IDENTITY_KEYS: readonly [...("user.id" | "user.email" | "user.full_name" | "client.address")[], "user.attributes"];
|
|
292
|
+
/**
|
|
293
|
+
* Pick the user-identity attributes currently set on a scope. Used where identity must
|
|
294
|
+
* be copied onto a report that does not flow through `Flare.report()` (which would
|
|
295
|
+
* otherwise spread `pendingAttributes` automatically).
|
|
296
|
+
*/
|
|
297
|
+
declare function userIdentityAttributes(scope: Scope): Attributes;
|
|
239
298
|
/**
|
|
240
299
|
* Holds the per-call mutable state that used to live on the `Flare` instance:
|
|
241
300
|
* breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
|
|
@@ -253,9 +312,9 @@ declare class Logger {
|
|
|
253
312
|
* holding the state directly, so the per-request behavior comes from the
|
|
254
313
|
* provider, not from the class itself.
|
|
255
314
|
*
|
|
256
|
-
* `NodeScope` (in `@flareapp/node`) extends this with
|
|
257
|
-
*
|
|
258
|
-
* does not need
|
|
315
|
+
* `NodeScope` (in `@flareapp/node`) extends this with a `request` bucket
|
|
316
|
+
* (HTTP method, path, headers). User identity is written to `pendingAttributes`
|
|
317
|
+
* by `Flare.setUser`, so it needs no dedicated field. Browser does not need `request`.
|
|
259
318
|
*/
|
|
260
319
|
declare class Scope {
|
|
261
320
|
glows: Glow[];
|
|
@@ -494,6 +553,14 @@ declare class Flare {
|
|
|
494
553
|
clearGlows(): this;
|
|
495
554
|
addContext(name: string, value: AttributeValue): this;
|
|
496
555
|
addContextGroup(groupName: string, value: Record<string, AttributeValue>): this;
|
|
556
|
+
/**
|
|
557
|
+
* Attach an identified user to the active scope. Fields are projected to the
|
|
558
|
+
* keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
|
|
559
|
+
* and `client.address`. Any extra keys are bundled into `user.attributes`.
|
|
560
|
+
* Pass `null` to clear the user. Scope-aware: in Node this targets the
|
|
561
|
+
* per-request scope via the scope provider.
|
|
562
|
+
*/
|
|
563
|
+
setUser(user: User | null): this;
|
|
497
564
|
setEntryPoint(handler: EntryPointHandler): this;
|
|
498
565
|
setSdkInfo(info: SdkInfo): this;
|
|
499
566
|
setFramework(framework: Framework): this;
|
|
@@ -541,4 +608,4 @@ declare class NullFileReader implements FileReader {
|
|
|
541
608
|
//#region src/stacktrace/createStackTrace.d.ts
|
|
542
609
|
declare function createStackTrace(error: Error, debug: boolean, fileReader: FileReader): Promise<Array<StackFrame>>;
|
|
543
610
|
//#endregion
|
|
544
|
-
export { type AnyValue, Api, type AttributeValue, type Attributes, type BufferedLog, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, assert, assertKey, convertToError, createStackTrace, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist };
|
|
611
|
+
export { type AnyValue, Api, type AttributeValue, type Attributes, type BufferedLog, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OverriddenGrouping, type RejectionReporter, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, USER_IDENTITY_KEYS, type User, assert, assertKey, convertToError, createStackTrace, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist, routeRejection, userIdentityAttributes };
|
package/dist/index.d.mts
CHANGED
|
@@ -4,6 +4,23 @@ type AttributeValue = string | number | boolean | null | AttributeValue[] | {
|
|
|
4
4
|
[key: string]: AttributeValue;
|
|
5
5
|
};
|
|
6
6
|
type Attributes = Record<string, AttributeValue>;
|
|
7
|
+
/**
|
|
8
|
+
* An identified user passed to `Flare.setUser`. The four known fields project to the
|
|
9
|
+
* report keys the Flare backend reads: `id`→`user.id`, `email`→`user.email`,
|
|
10
|
+
* `fullName`→`user.full_name`, `ipAddress`→`client.address`. Any OTHER key is bundled
|
|
11
|
+
* into `user.attributes`.
|
|
12
|
+
*
|
|
13
|
+
* Caveat: the open index signature means a misspelled known field (e.g. `fullname` or
|
|
14
|
+
* `full_name` instead of `fullName`) does NOT raise a type error — it silently lands in
|
|
15
|
+
* `user.attributes` rather than the identity key. Spell the four known fields exactly.
|
|
16
|
+
*/
|
|
17
|
+
type User = {
|
|
18
|
+
id?: string | number;
|
|
19
|
+
email?: string;
|
|
20
|
+
fullName?: string;
|
|
21
|
+
ipAddress?: string;
|
|
22
|
+
[key: string]: AttributeValue | undefined;
|
|
23
|
+
};
|
|
7
24
|
type Config = {
|
|
8
25
|
key: string | null;
|
|
9
26
|
version: string;
|
|
@@ -168,6 +185,34 @@ declare const DEFAULT_URL_DENYLIST: RegExp;
|
|
|
168
185
|
declare function resolveDenylist(custom?: RegExp, replaceDefault?: boolean, defaultDenylist?: RegExp): RegExp;
|
|
169
186
|
declare function redactUrlQuery(fullPath: string, denylist?: RegExp): string;
|
|
170
187
|
//#endregion
|
|
188
|
+
//#region src/util/rejection.d.ts
|
|
189
|
+
/**
|
|
190
|
+
* Shared unhandled-rejection routing. A rejection "reason" can be anything a
|
|
191
|
+
* promise was rejected with: an Error, an Error-like object carrying a `.stack`,
|
|
192
|
+
* a string, or a plain object. The browser `unhandledrejection` listener
|
|
193
|
+
* (`@flareapp/js`) and the React Native engine rejection tracker
|
|
194
|
+
* (`@flareapp/react-native`) need the SAME routing so a report looks identical
|
|
195
|
+
* across SDKs, so it lives here instead of being copy-pasted (and drifting) per
|
|
196
|
+
* client.
|
|
197
|
+
*/
|
|
198
|
+
type RejectionReporter = {
|
|
199
|
+
reportSilently: (error: Error) => void;
|
|
200
|
+
reportUnhandledRejection: (message: string) => unknown;
|
|
201
|
+
};
|
|
202
|
+
/** Best-effort human-readable description of an arbitrary rejection reason. */
|
|
203
|
+
declare function describeRejectionReason(reason: unknown): string;
|
|
204
|
+
/**
|
|
205
|
+
* Route a rejection reason to the reporter: an Error (or any stack-bearing
|
|
206
|
+
* object) goes to `reportSilently` so the STACK survives; only a stackless
|
|
207
|
+
* reason falls back to `reportUnhandledRejection` (string message, empty-stack
|
|
208
|
+
* `UnhandledRejection` class). Any rejection from `reportUnhandledRejection`'s
|
|
209
|
+
* returned promise is swallowed so a transport failure cannot itself surface as
|
|
210
|
+
* an unhandled rejection. `reportSilently` is assumed not to throw synchronously
|
|
211
|
+
* (core's does its work asynchronously); it is intentionally not wrapped, so a
|
|
212
|
+
* synchronous throw there would propagate.
|
|
213
|
+
*/
|
|
214
|
+
declare function routeRejection(reporter: RejectionReporter, reason: unknown): void;
|
|
215
|
+
//#endregion
|
|
171
216
|
//#region src/api/Api.d.ts
|
|
172
217
|
declare class Api {
|
|
173
218
|
report(report: Report, url: string, key: string | null, reportBrowserExtensionErrors: boolean, debug?: boolean): Promise<void>;
|
|
@@ -236,6 +281,20 @@ declare class Logger {
|
|
|
236
281
|
}
|
|
237
282
|
//#endregion
|
|
238
283
|
//#region src/Scope.d.ts
|
|
284
|
+
/**
|
|
285
|
+
* The report attribute keys that `Flare.setUser` owns: the four projected identity
|
|
286
|
+
* fields plus the `user.attributes` bag for extras. Single source of truth so the
|
|
287
|
+
* clear pass and the set pass in `setUser` cannot drift, and so consumers that must
|
|
288
|
+
* stamp identity outside core's report pipeline (Electron's forwarded-renderer path)
|
|
289
|
+
* pick up the exact same set instead of re-hardcoding it.
|
|
290
|
+
*/
|
|
291
|
+
declare const USER_IDENTITY_KEYS: readonly [...("user.id" | "user.email" | "user.full_name" | "client.address")[], "user.attributes"];
|
|
292
|
+
/**
|
|
293
|
+
* Pick the user-identity attributes currently set on a scope. Used where identity must
|
|
294
|
+
* be copied onto a report that does not flow through `Flare.report()` (which would
|
|
295
|
+
* otherwise spread `pendingAttributes` automatically).
|
|
296
|
+
*/
|
|
297
|
+
declare function userIdentityAttributes(scope: Scope): Attributes;
|
|
239
298
|
/**
|
|
240
299
|
* Holds the per-call mutable state that used to live on the `Flare` instance:
|
|
241
300
|
* breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
|
|
@@ -253,9 +312,9 @@ declare class Logger {
|
|
|
253
312
|
* holding the state directly, so the per-request behavior comes from the
|
|
254
313
|
* provider, not from the class itself.
|
|
255
314
|
*
|
|
256
|
-
* `NodeScope` (in `@flareapp/node`) extends this with
|
|
257
|
-
*
|
|
258
|
-
* does not need
|
|
315
|
+
* `NodeScope` (in `@flareapp/node`) extends this with a `request` bucket
|
|
316
|
+
* (HTTP method, path, headers). User identity is written to `pendingAttributes`
|
|
317
|
+
* by `Flare.setUser`, so it needs no dedicated field. Browser does not need `request`.
|
|
259
318
|
*/
|
|
260
319
|
declare class Scope {
|
|
261
320
|
glows: Glow[];
|
|
@@ -494,6 +553,14 @@ declare class Flare {
|
|
|
494
553
|
clearGlows(): this;
|
|
495
554
|
addContext(name: string, value: AttributeValue): this;
|
|
496
555
|
addContextGroup(groupName: string, value: Record<string, AttributeValue>): this;
|
|
556
|
+
/**
|
|
557
|
+
* Attach an identified user to the active scope. Fields are projected to the
|
|
558
|
+
* keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
|
|
559
|
+
* and `client.address`. Any extra keys are bundled into `user.attributes`.
|
|
560
|
+
* Pass `null` to clear the user. Scope-aware: in Node this targets the
|
|
561
|
+
* per-request scope via the scope provider.
|
|
562
|
+
*/
|
|
563
|
+
setUser(user: User | null): this;
|
|
497
564
|
setEntryPoint(handler: EntryPointHandler): this;
|
|
498
565
|
setSdkInfo(info: SdkInfo): this;
|
|
499
566
|
setFramework(framework: Framework): this;
|
|
@@ -541,4 +608,4 @@ declare class NullFileReader implements FileReader {
|
|
|
541
608
|
//#region src/stacktrace/createStackTrace.d.ts
|
|
542
609
|
declare function createStackTrace(error: Error, debug: boolean, fileReader: FileReader): Promise<Array<StackFrame>>;
|
|
543
610
|
//#endregion
|
|
544
|
-
export { type AnyValue, Api, type AttributeValue, type Attributes, type BufferedLog, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OverriddenGrouping, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, assert, assertKey, convertToError, createStackTrace, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist };
|
|
611
|
+
export { type AnyValue, Api, type AttributeValue, type Attributes, type BufferedLog, type Config, type ContextCollector, DEFAULT_URL_DENYLIST, type EntryPointHandler, type FileReader, Flare, type FlushFn, type FlushScheduler, type Framework, GlobalScopeProvider, type Glow, type KeyValue, Logger, type LoggerDeps, type LogsEnvelope, type MessageLevel, NoopFlushScheduler, NullFileReader, type OtelLogRecord, type OverriddenGrouping, type RejectionReporter, type Report, Scope, type ScopeProvider, type SdkInfo, type SpanEvent, type StackFrame, USER_IDENTITY_KEYS, type User, assert, assertKey, convertToError, createStackTrace, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist, routeRejection, userIdentityAttributes };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ErrorStackParser from "error-stack-parser";
|
|
2
2
|
|
|
3
3
|
//#region src/env/index.ts
|
|
4
|
-
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.
|
|
4
|
+
const CLIENT_VERSION = typeof process !== "undefined" && true ? "2.6.0" : "?";
|
|
5
5
|
const KEY = typeof FLARE_JS_KEY === "undefined" ? "" : FLARE_JS_KEY;
|
|
6
6
|
const SOURCEMAP_VERSION = typeof FLARE_SOURCEMAP_VERSION === "undefined" ? "" : FLARE_SOURCEMAP_VERSION;
|
|
7
7
|
|
|
@@ -142,6 +142,49 @@ function safeDecode(value) {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/util/rejection.ts
|
|
147
|
+
/** Best-effort human-readable description of an arbitrary rejection reason. */
|
|
148
|
+
function describeRejectionReason(reason) {
|
|
149
|
+
if (typeof reason === "string") return reason;
|
|
150
|
+
if (reason && typeof reason === "object") {
|
|
151
|
+
const message = reason.message;
|
|
152
|
+
if (typeof message === "string" && message) return message;
|
|
153
|
+
try {
|
|
154
|
+
return JSON.stringify(reason);
|
|
155
|
+
} catch {
|
|
156
|
+
return "Unhandled promise rejection (non-serializable reason)";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return String(reason);
|
|
160
|
+
}
|
|
161
|
+
function hasStack$1(reason) {
|
|
162
|
+
return !!reason && typeof reason === "object" && typeof reason.stack === "string";
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Route a rejection reason to the reporter: an Error (or any stack-bearing
|
|
166
|
+
* object) goes to `reportSilently` so the STACK survives; only a stackless
|
|
167
|
+
* reason falls back to `reportUnhandledRejection` (string message, empty-stack
|
|
168
|
+
* `UnhandledRejection` class). Any rejection from `reportUnhandledRejection`'s
|
|
169
|
+
* returned promise is swallowed so a transport failure cannot itself surface as
|
|
170
|
+
* an unhandled rejection. `reportSilently` is assumed not to throw synchronously
|
|
171
|
+
* (core's does its work asynchronously); it is intentionally not wrapped, so a
|
|
172
|
+
* synchronous throw there would propagate.
|
|
173
|
+
*/
|
|
174
|
+
function routeRejection(reporter, reason) {
|
|
175
|
+
if (reason instanceof Error) {
|
|
176
|
+
reporter.reportSilently(reason);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (hasStack$1(reason)) {
|
|
180
|
+
const error = new Error(describeRejectionReason(reason));
|
|
181
|
+
error.stack = reason.stack;
|
|
182
|
+
reporter.reportSilently(error);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
Promise.resolve(reporter.reportUnhandledRejection(describeRejectionReason(reason))).catch(() => {});
|
|
186
|
+
}
|
|
187
|
+
|
|
145
188
|
//#endregion
|
|
146
189
|
//#region src/api/Api.ts
|
|
147
190
|
var Api = class {
|
|
@@ -471,6 +514,39 @@ function partitionAttributes(attributes) {
|
|
|
471
514
|
//#endregion
|
|
472
515
|
//#region src/Scope.ts
|
|
473
516
|
/**
|
|
517
|
+
* Maps each `User` identity field to the flat report attribute key it projects to.
|
|
518
|
+
* `Flare.setUser`'s set pass writes through these so the literal key strings live in
|
|
519
|
+
* exactly one place; `USER_IDENTITY_KEYS` (the clear pass) derives from them, so adding
|
|
520
|
+
* a field here can never silently leave the clear pass out of date.
|
|
521
|
+
*/
|
|
522
|
+
const USER_FIELD_KEYS = {
|
|
523
|
+
id: "user.id",
|
|
524
|
+
email: "user.email",
|
|
525
|
+
fullName: "user.full_name",
|
|
526
|
+
ipAddress: "client.address"
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* The report attribute keys that `Flare.setUser` owns: the four projected identity
|
|
530
|
+
* fields plus the `user.attributes` bag for extras. Single source of truth so the
|
|
531
|
+
* clear pass and the set pass in `setUser` cannot drift, and so consumers that must
|
|
532
|
+
* stamp identity outside core's report pipeline (Electron's forwarded-renderer path)
|
|
533
|
+
* pick up the exact same set instead of re-hardcoding it.
|
|
534
|
+
*/
|
|
535
|
+
const USER_IDENTITY_KEYS = [...Object.values(USER_FIELD_KEYS), "user.attributes"];
|
|
536
|
+
/**
|
|
537
|
+
* Pick the user-identity attributes currently set on a scope. Used where identity must
|
|
538
|
+
* be copied onto a report that does not flow through `Flare.report()` (which would
|
|
539
|
+
* otherwise spread `pendingAttributes` automatically).
|
|
540
|
+
*/
|
|
541
|
+
function userIdentityAttributes(scope) {
|
|
542
|
+
const attrs = {};
|
|
543
|
+
for (const key of USER_IDENTITY_KEYS) {
|
|
544
|
+
const value = scope.pendingAttributes[key];
|
|
545
|
+
if (value !== void 0) attrs[key] = value;
|
|
546
|
+
}
|
|
547
|
+
return attrs;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
474
550
|
* Holds the per-call mutable state that used to live on the `Flare` instance:
|
|
475
551
|
* breadcrumbs (`glows`), custom attributes (`pendingAttributes`), and the
|
|
476
552
|
* current entry-point handler.
|
|
@@ -487,9 +563,9 @@ function partitionAttributes(attributes) {
|
|
|
487
563
|
* holding the state directly, so the per-request behavior comes from the
|
|
488
564
|
* provider, not from the class itself.
|
|
489
565
|
*
|
|
490
|
-
* `NodeScope` (in `@flareapp/node`) extends this with
|
|
491
|
-
*
|
|
492
|
-
* does not need
|
|
566
|
+
* `NodeScope` (in `@flareapp/node`) extends this with a `request` bucket
|
|
567
|
+
* (HTTP method, path, headers). User identity is written to `pendingAttributes`
|
|
568
|
+
* by `Flare.setUser`, so it needs no dedicated field. Browser does not need `request`.
|
|
493
569
|
*/
|
|
494
570
|
var Scope = class {
|
|
495
571
|
glows = [];
|
|
@@ -612,18 +688,24 @@ function createStackTrace(error, debug, fileReader) {
|
|
|
612
688
|
return resolve([fallbackFrame("stacktrace could not be parsed")]);
|
|
613
689
|
}
|
|
614
690
|
Promise.all(parsedFrames.map((frame) => {
|
|
615
|
-
|
|
691
|
+
const fileName = normalizeFileName(frame.fileName);
|
|
692
|
+
return getCodeSnippet(fileReader, fileName, frame.lineNumber, frame.columnNumber).then((snippet) => ({
|
|
616
693
|
lineNumber: frame.lineNumber || 1,
|
|
617
694
|
columnNumber: frame.columnNumber || 1,
|
|
618
695
|
method: frame.functionName || "Anonymous or unknown function",
|
|
619
|
-
file:
|
|
696
|
+
file: fileName || "Unknown file",
|
|
620
697
|
codeSnippet: snippet.codeSnippet,
|
|
621
698
|
class: "",
|
|
622
|
-
isApplicationFrame: isApplicationFrame(
|
|
699
|
+
isApplicationFrame: isApplicationFrame(fileName)
|
|
623
700
|
}));
|
|
624
701
|
})).then(resolve);
|
|
625
702
|
});
|
|
626
703
|
}
|
|
704
|
+
const HERMES_ADDRESS_PREFIX = "address at ";
|
|
705
|
+
function normalizeFileName(fileName) {
|
|
706
|
+
if (fileName?.startsWith(HERMES_ADDRESS_PREFIX)) return fileName.slice(11);
|
|
707
|
+
return fileName;
|
|
708
|
+
}
|
|
627
709
|
function fallbackFrame(reason) {
|
|
628
710
|
return {
|
|
629
711
|
lineNumber: 0,
|
|
@@ -946,6 +1028,26 @@ var Flare = class {
|
|
|
946
1028
|
this.scopeProvider.active().setAttribute(`context.${groupName}`, value);
|
|
947
1029
|
return this;
|
|
948
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Attach an identified user to the active scope. Fields are projected to the
|
|
1033
|
+
* keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
|
|
1034
|
+
* and `client.address`. Any extra keys are bundled into `user.attributes`.
|
|
1035
|
+
* Pass `null` to clear the user. Scope-aware: in Node this targets the
|
|
1036
|
+
* per-request scope via the scope provider.
|
|
1037
|
+
*/
|
|
1038
|
+
setUser(user) {
|
|
1039
|
+
const scope = this.scopeProvider.active();
|
|
1040
|
+
for (const key of USER_IDENTITY_KEYS) delete scope.pendingAttributes[key];
|
|
1041
|
+
if (!user) return this;
|
|
1042
|
+
const { id, email, fullName, ipAddress, ...rest } = user;
|
|
1043
|
+
if (id !== void 0 && id !== null) scope.setAttribute(USER_FIELD_KEYS.id, String(id));
|
|
1044
|
+
if (email !== void 0) scope.setAttribute(USER_FIELD_KEYS.email, email);
|
|
1045
|
+
if (fullName !== void 0) scope.setAttribute(USER_FIELD_KEYS.fullName, fullName);
|
|
1046
|
+
if (ipAddress !== void 0) scope.setAttribute(USER_FIELD_KEYS.ipAddress, ipAddress);
|
|
1047
|
+
const extras = Object.fromEntries(Object.entries(rest).filter(([, value]) => value !== void 0));
|
|
1048
|
+
if (Object.keys(extras).length > 0) scope.setAttribute("user.attributes", extras);
|
|
1049
|
+
return this;
|
|
1050
|
+
}
|
|
949
1051
|
setEntryPoint(handler) {
|
|
950
1052
|
this.scopeProvider.active().entryPoint = handler;
|
|
951
1053
|
return this;
|
|
@@ -1101,4 +1203,4 @@ var Flare = class {
|
|
|
1101
1203
|
};
|
|
1102
1204
|
|
|
1103
1205
|
//#endregion
|
|
1104
|
-
export { Api, DEFAULT_URL_DENYLIST, Flare, GlobalScopeProvider, Logger, NoopFlushScheduler, NullFileReader, Scope, assert, assertKey, convertToError, createStackTrace, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist };
|
|
1206
|
+
export { Api, DEFAULT_URL_DENYLIST, Flare, GlobalScopeProvider, Logger, NoopFlushScheduler, NullFileReader, Scope, USER_IDENTITY_KEYS, assert, assertKey, convertToError, createStackTrace, describeRejectionReason, extractCode, flatJsonStringify, getCodeSnippet, glowsToEvents, now, readLinesFromFile, redactUrlQuery, resolveDenylist, routeRejection, userIdentityAttributes };
|