@@ -69,11 +69,11 @@ export class Flare {
6969 private framework : Framework | null = null ;
7070
7171 /**
72- * @param contextCollector returns per-report attributes (browser DOM, Node process, etc ). No-op by default.
73- * @param fileReader reads source files for stack-trace snippets. Default returns null ( no snippets) ;
72+ * @param contextCollector per-report attributes (browser DOM, Node process). No-op by default.
73+ * @param fileReader source files for stack-trace snippets. Defaults to no snippets;
7474 * `@flareapp/js` injects a fetch reader, `@flareapp/node` a disk reader.
75- * @param scopeProvider returns the current `Scope` (glows, pendingAttributes, entryPoint) . Browser uses one
76- * global scope; Node uses an AsyncLocalStorage-backed provider so each request gets its own.
75+ * @param scopeProvider the current `Scope`. Browser uses one global scope; Node an
76+ * AsyncLocalStorage-backed provider so each request gets its own.
7777 */
7878 constructor (
7979 public api : Api = new Api ( ) ,
@@ -107,14 +107,12 @@ export class Flare {
107107 }
108108
109109 /**
110- * Register an in-flight report so `flush()` can wait for it. Called by every public report entry point, each
111- * wrapping its full async pipeline (beforeEvaluate -> stack trace -> beforeSubmit -> api.report) so the whole
112- * roundtrip is tracked, not just the HTTP send.
110+ * Register an in-flight report so `flush()` can wait for it. Every entry point wraps its whole async
111+ * pipeline (beforeEvaluate -> stack trace -> beforeSubmit -> api.report), not just the HTTP send.
113112 *
114- * Stores a shadow promise that mirrors `p`'s timing but cannot reject (the `() => undefined` rejection handler
115- * consumes any failure), so an unhandled report rejection never surfaces as a Node warning / console error. The
116- * shadow self-removes from the Set via `.finally`; `delete` never throws, so wrap any richer cleanup in try/catch.
117- * Returns the original `p` so the caller still observes real success/failure; tracking is invisible to them.
113+ * What goes in the Set is a shadow promise that mirrors `p`'s timing but cannot reject, so a failed
114+ * report never surfaces as an unhandled rejection warning. `p` itself is returned untouched, so the
115+ * caller still observes real success or failure.
118116 */
119117 private track < T > ( p : Promise < T > ) : Promise < T > {
120118 const tracked = p . then (
@@ -127,14 +125,12 @@ export class Flare {
127125 }
128126
129127 /**
130- * Wait until every in-flight report settles or `timeoutMs` elapses, whichever comes first . Always resolves, never
131- * rejects; no retry. Main consumer is `@flareapp/node`'s fatal handler, which awaits the fatal report explicitly
132- * then calls flush to drain any OTHER concurrent reports before `process.exit`.
128+ * Wait until every in-flight report settles or `timeoutMs` elapses. Always resolves, never rejects.
129+ * Written for `@flareapp/node`'s fatal handler, which awaits the fatal report itself then flushes to
130+ * drain any other concurrent reports before `process.exit`.
133131 *
134- * `[...this.inflight]` snapshots the Set: reports started after this line are NOT awaited, which bounds the wait so
135- * a handler emitting reports during shutdown cannot block the process forever. Call flush again to drain those.
136- * `allSettled` (not `all`) waits for every report regardless of HTTP success/failure; `all` would short-circuit on
137- * the first rejection. Flush does not stop new reports; the instance stays usable afterward.
132+ * Snapshotting the Set bounds the wait: reports started after this line are not awaited, so a handler
133+ * that keeps emitting during shutdown cannot block the process forever. Call flush again for those.
138134 */
139135 flush ( timeoutMs = 2000 ) : Promise < void > {
140136 this . _logger . flush ( ) ;
@@ -200,9 +196,8 @@ export class Flare {
200196 this . _config . tracesSampleRate = Math . max ( 0 , Math . min ( 1 , config . tracesSampleRate ) ) ;
201197 }
202198
203- // Only re-resolve the denylist when this call actually carries denylist config. Otherwise the spread
204- // above already preserved the previously resolved denylist, and re-resolving with an undefined `custom`
205- // would clobber a custom denylist back to the default, silently re-exposing data the user asked to redact.
199+ // Only when this call carries denylist config. Re-resolving with an undefined `custom` would reset a
200+ // custom denylist to the default, silently re-exposing data the user asked to redact.
206201 if ( config . urlDenylist !== undefined || config . replaceDefaultUrlDenylist !== undefined ) {
207202 this . _config . urlDenylist = resolveDenylist (
208203 config . urlDenylist ,
@@ -278,11 +273,8 @@ export class Flare {
278273 }
279274
280275 /**
281- * Attach an identified user to the active scope. Fields are projected to the
282- * keys the Flare backend reads: `user.id`, `user.email`, `user.full_name`,
283- * and `client.address`. Any extra keys are bundled into `user.attributes`.
284- * Pass `null` to clear the user. Scope-aware: in Node this targets the
285- * per-request scope via the scope provider.
276+ * Projects the known fields to the keys the Flare backend reads (see `USER_FIELD_KEYS`) and bundles
277+ * anything else into `user.attributes`. Pass `null` to clear. In Node this targets the per-request scope.
286278 */
287279 setUser ( user : User | null ) : this {
288280 const scope = this . scopeProvider . active ( ) ;
@@ -536,16 +528,15 @@ export class Flare {
536528 } ;
537529 }
538530
531+ /** Local roots only, snapshotted by the Tracer at span START so a long-lived root does not drift into
532+ * the next page's scope. Children get none, and no span ever runs the DOM collector. */
539533 private getScopeAttributes ( ) : Attributes {
540- // Scope-derived record a LOCAL ROOT span carries: user context, entry-point overrides,
541- // framework-in-context.custom. Tracer snapshots this at span START so a long-lived root does not drift into the
542- // next page's scope. Spans never run the DOM collector; children get no scope. Mirrors the PHP client.
543534 return this . assembleAttributes ( { } , { } , false ) ;
544535 }
545536
546537 private spanResourceAttributes ( ) : Attributes {
547- // Resource is stable per page (host.name). Keep only the collector's resource partition, dropping record-level
548- // context (cookies/url) so nothing heavy or drifting reaches spans. Evaluated once per flush, not per span.
538+ // Only the resource partition: record-level context (cookies, url) is heavy and drifts, and this is
539+ // evaluated once per flush rather than per span.
549540 return partitionAttributes ( this . contextCollector ( this . _config ) ) . resource ;
550541 }
551542
@@ -562,9 +553,8 @@ export class Flare {
562553 const activeScope = this . scopeProvider . active ( ) ;
563554 const attributes = this . assembleAttributes ( this . contextCollector ( this . _config ) , input . extraAttributes , true ) ;
564555
565- // seenAtUnixNano in real nanoseconds. Date.now() * 1_000_000 exceeds MAX_SAFE_INTEGER by ~3 bits (~256 ns), but
566- // browser clocks are millisecond-precision so the lost bits are below source resolution. PHP's json_decode
567- // reads the 19-digit literal as a 64-bit int (PHP_INT_MAX ~9.22e18 vs our ~1.78e18).
556+ // seenAtUnixNano overflows MAX_SAFE_INTEGER by ~3 bits (~256ns), which is below the millisecond
557+ // resolution browser clocks actually have. PHP reads the 19-digit literal as a 64-bit int.
568558 const report : Report = {
569559 exceptionClass : input . exceptionClass ,
570560 message : input . message ,
0 commit comments