@edraj/sauron-browser 1.3.0 → 1.4.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.d.cts CHANGED
@@ -24,9 +24,17 @@ interface Mechanism {
24
24
  type: string;
25
25
  handled: boolean;
26
26
  }
27
- /** The exception payload of an error item. */
27
+ /**
28
+ * The exception payload of an error item.
29
+ *
30
+ * `type` is NOT nullable: the backend's `ExceptionInfo.ty` is a non-`Option`
31
+ * `String` with no serde default, so `null` fails to deserialize and the
32
+ * gateway 400s the ENTIRE envelope — every other item in the batch with it.
33
+ * An item with no exception type omits `exception` altogether and carries its
34
+ * text in {@link ErrorItem.message}.
35
+ */
28
36
  interface ExceptionValue {
29
- type: string | null;
37
+ type: string;
30
38
  value: string | null;
31
39
  mechanism: Mechanism;
32
40
  stacktrace: Frame[];
@@ -50,8 +58,14 @@ interface ErrorItem {
50
58
  event_id?: string;
51
59
  timestamp: string;
52
60
  level: Level;
53
- exception: ExceptionValue;
54
- /** Optional human-readable summary alongside the exception. */
61
+ /**
62
+ * The captured exception. OPTIONAL a message capture omits it entirely and
63
+ * carries its text in {@link message} instead (the backend's `exception` is
64
+ * `Option<ExceptionInfo>`, and with no exception it fingerprints off
65
+ * `message`). Never emit a placeholder here just to fill the field.
66
+ */
67
+ exception?: ExceptionValue;
68
+ /** Human-readable summary; the ONLY text carrier when `exception` is absent. */
55
69
  message?: string;
56
70
  breadcrumbs: Breadcrumb[];
57
71
  fingerprint: string[] | null;
@@ -360,6 +374,24 @@ declare class Scope {
360
374
  readonly extra: Record<string, unknown>;
361
375
  constructor(maxBreadcrumbs?: number);
362
376
  setMaxBreadcrumbs(max: number): void;
377
+ /**
378
+ * Replace the scope user.
379
+ *
380
+ * `id` is coerced with `String()` for the same reason `SauronClient.
381
+ * prepareIdentify` coerces its own — a plain-JS caller can (and does) pass
382
+ * `setUser({ id: user.id })` where `user.id` is a number, and TypeScript
383
+ * cannot stop them. This path is the one that BYPASSES `identify()`'s
384
+ * coercion entirely, and the consequence is not cosmetic: the scope user
385
+ * lands in the envelope context, where the server's `distinct_id` is a
386
+ * non-`Option` Rust `String`. A JSON number there fails deserialization of
387
+ * the ENVELOPE, not of the one field — so the whole batch 400s, and a 400
388
+ * is non-retryable, so every event in it is dropped for good.
389
+ *
390
+ * Rebuilding the whole object (rather than merging into the existing one)
391
+ * is deliberate and is the behaviour the Flutter SDK was fixed to match:
392
+ * `email` and `traits` come from the input alone, so setting a new user
393
+ * never inherits the previous person's contact details.
394
+ */
363
395
  setUser(user: UserInput): void;
364
396
  /** The user context for an envelope. Never null — defaults to an empty user. */
365
397
  getUser(): UserContext;
@@ -392,7 +424,6 @@ declare class SauronClient {
392
424
  private readonly nativeFetch?;
393
425
  private enabled;
394
426
  private installed;
395
- private anonymousId;
396
427
  private beaconCleanup;
397
428
  constructor(options: ResolvedOptions);
398
429
  /** Install global handlers + auto-instrumentation and start the transport. */
@@ -410,11 +441,55 @@ declare class SauronClient {
410
441
  * null` as the no-op/disabled case before it ever reaches here.
411
442
  */
412
443
  isEnabled(): boolean;
444
+ /**
445
+ * Whether the anonymous id has actually been USED as a `distinct_id` in this
446
+ * browser session.
447
+ *
448
+ * A persisted id that has never been observed anonymously must not create a
449
+ * permanent `identities` alias row on the server: aliasing is a durable
450
+ * server-side binding of this browser profile to a named user, and an
451
+ * identify() on a first-ever page load has no anonymous history to link.
452
+ */
453
+ private anonUsed;
413
454
  /** The current distinct id: the user id when identified, else an anon id. */
414
455
  getDistinctId(): string | null;
415
- /** The anonymous id, or null if one was never needed. */
456
+ /** The anonymous id, or null when it was never actually used as an identity. */
416
457
  getAnonymousId(): string | null;
417
- private ensureAnonymousId;
458
+ /**
459
+ * Forget the current person: clear the scope user, mint a fresh anonymous
460
+ * id, forget the last identified user, and rotate the session id.
461
+ *
462
+ * MUST BE CALLED ON LOGOUT. Without it, the next anonymous visitor on this
463
+ * browser reuses the persisted anon id, and a later identify() aliases their
464
+ * activity to the previous account server-side, permanently. Rotating the
465
+ * session id matters too: the server's `bump_session` is last-write-wins on
466
+ * `distinct_id`, so without rotation one `sessions` row could otherwise
467
+ * serially represent two different people and record only whichever wrote
468
+ * last.
469
+ */
470
+ reset(): void;
471
+ /**
472
+ * Prepare for an `identify()`; returns the `anonymous_id` to send.
473
+ *
474
+ * When a DIFFERENT user identifies than last time, the current anon id
475
+ * belongs to the previous person and is already burned server-side, so it is
476
+ * replaced before anything else happens and `null` is sent instead of a
477
+ * cross-user alias. This cannot repair events already sent under the burned
478
+ * alias — nothing can — but it bounds a forgotten `reset()` to one guest
479
+ * window instead of every future one.
480
+ *
481
+ * `id` is coerced with `String()` before comparing/persisting: a plain-JS
482
+ * caller can pass a number (`Sauron.identify(user.id)`), and `Storage`
483
+ * itself applies `ToString` on write — so comparing an un-coerced `id`
484
+ * against a value that already round-tripped through storage would treat
485
+ * the SAME numeric user as a switch on every single call. The comparison
486
+ * against `last` is an explicit `!== null` (not a truthiness check) so an
487
+ * app that (unusually) identifies with `''` still has a later, different id
488
+ * correctly detected as a real switch — a falsy string is not "no identity
489
+ * yet". `last`/the persisted value are digests, not the raw id — see
490
+ * `hashIdentity`.
491
+ */
492
+ prepareIdentify(id: string): string | null;
418
493
  /** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
419
494
  makeEnvelope(items: EnvelopeItem[]): Envelope;
420
495
  /** Add a breadcrumb, running it through `beforeBreadcrumb` first. */
@@ -486,7 +561,7 @@ declare function parseError(err: unknown): Frame[];
486
561
  /** Small dependency-free helpers shared across the SDK. */
487
562
  /** SDK identity, embedded in every envelope header. */
488
563
  declare const SDK_NAME = "sauron.javascript";
489
- declare const SDK_VERSION = "1.3.0";
564
+ declare const SDK_VERSION = "1.4.1";
490
565
 
491
566
  /**
492
567
  * `@edraj/sauron-browser` — public API surface.
@@ -509,7 +584,16 @@ declare function captureException(err: unknown, hint?: Hint): void;
509
584
  declare function captureMessage(message: string, level?: Level, hint?: Hint): void;
510
585
  /** Record a product-analytics event, optionally with per-call tags/contexts/extra. */
511
586
  declare function track(name: string, properties?: Record<string, unknown>, options?: TrackOptions): void;
512
- /** Associate the session with a known user. */
587
+ /**
588
+ * Associate the session with a known user.
589
+ *
590
+ * The `anonymous_id` sent with the identify item is the current anon id — but
591
+ * only when it was actually used as a `distinct_id` this session, and never
592
+ * when it belongs to a different person than the last one who identified on
593
+ * this device. In that case a fresh anon id is minted first and `null` is
594
+ * sent instead, since the old one is already permanently bound to the
595
+ * previous person server-side (see `reset()`).
596
+ */
513
597
  declare function identify(id: string, traits?: Record<string, unknown>): void;
514
598
  /** Record a performance transaction (navigation, http, screen load, ...). */
515
599
  declare function trackTransaction(input: TransactionInput): void;
@@ -536,8 +620,20 @@ declare function cancelWorkflow(name?: string, options?: {
536
620
  declare function getWorkflow(): ActiveWorkflow | null;
537
621
  /** Record a breadcrumb. */
538
622
  declare function addBreadcrumb(breadcrumb: BreadcrumbInput, hint?: Hint): void;
539
- /** Set (or clear, with `null`) the current user. */
623
+ /**
624
+ * Set (or clear, with `null`) the current user.
625
+ *
626
+ * `setUser(null)` is a logout, so it also calls `reset()` for you — otherwise
627
+ * the next anonymous visitor on this browser inherits the previous person's
628
+ * durable id and a later identify() aliases them together server-side.
629
+ */
540
630
  declare function setUser(user: UserInput): void;
631
+ /**
632
+ * Forget the current person: clears the scope user, mints a fresh anonymous
633
+ * id, forgets the last identified user, and rotates the session id so a
634
+ * single session can never span two different people. Call this on logout.
635
+ */
636
+ declare function reset(): void;
541
637
  /** Set a single scope tag (lifted onto later errors/events). */
542
638
  declare function setTag(key: string, value: string): void;
543
639
  /** Merge a batch of scope tags (last-write-wins per key). */
@@ -561,6 +657,7 @@ declare const Sauron: {
561
657
  identify: typeof identify;
562
658
  addBreadcrumb: typeof addBreadcrumb;
563
659
  setUser: typeof setUser;
660
+ reset: typeof reset;
564
661
  setTag: typeof setTag;
565
662
  setTags: typeof setTags;
566
663
  setContext: typeof setContext;
@@ -576,4 +673,4 @@ declare const Sauron: {
576
673
  getClient: typeof getClient;
577
674
  };
578
675
 
579
- export { type ActiveWorkflow, type AppContext, type BeforeBreadcrumb, type BeforeSend, type Breadcrumb, type BreadcrumbBatchItem, type BreadcrumbInput, type CaptureOptions, type Context, type DeviceContext, type Dsn, DsnError, type Envelope, type EnvelopeHeader, type EnvelopeItem, type ErrorItem, type EventItem, type ExceptionValue, type Frame, type Hint, type IdentifyItem, type InitOptions, type ItemType, type Level, type Mechanism, type OsContext, type ResolvedOptions, type RuntimeContext, SDK_NAME, SDK_VERSION, Sauron, SauronClient, type SdkInfo, type TrackOptions, type TransactionInput, type TransactionItem, type TransactionOp, type TransportOptions, type UserContext, type UserInput, type WorkflowResult, type WorkflowStatus, addBreadcrumb, buildEnvelope, cancelWorkflow, captureException, captureMessage, close, Sauron as default, endWorkflow, flush, getClient, getScreen, getWorkflow, identify, init, isInAppFrame, parseDsn, parseError, parseStackString, setContext, setExtra, setScreen, setTag, setTags, setUser, startWorkflow, track, trackTransaction };
676
+ export { type ActiveWorkflow, type AppContext, type BeforeBreadcrumb, type BeforeSend, type Breadcrumb, type BreadcrumbBatchItem, type BreadcrumbInput, type CaptureOptions, type Context, type DeviceContext, type Dsn, DsnError, type Envelope, type EnvelopeHeader, type EnvelopeItem, type ErrorItem, type EventItem, type ExceptionValue, type Frame, type Hint, type IdentifyItem, type InitOptions, type ItemType, type Level, type Mechanism, type OsContext, type ResolvedOptions, type RuntimeContext, SDK_NAME, SDK_VERSION, Sauron, SauronClient, type SdkInfo, type TrackOptions, type TransactionInput, type TransactionItem, type TransactionOp, type TransportOptions, type UserContext, type UserInput, type WorkflowResult, type WorkflowStatus, addBreadcrumb, buildEnvelope, cancelWorkflow, captureException, captureMessage, close, Sauron as default, endWorkflow, flush, getClient, getScreen, getWorkflow, identify, init, isInAppFrame, parseDsn, parseError, parseStackString, reset, setContext, setExtra, setScreen, setTag, setTags, setUser, startWorkflow, track, trackTransaction };
package/dist/index.d.ts CHANGED
@@ -24,9 +24,17 @@ interface Mechanism {
24
24
  type: string;
25
25
  handled: boolean;
26
26
  }
27
- /** The exception payload of an error item. */
27
+ /**
28
+ * The exception payload of an error item.
29
+ *
30
+ * `type` is NOT nullable: the backend's `ExceptionInfo.ty` is a non-`Option`
31
+ * `String` with no serde default, so `null` fails to deserialize and the
32
+ * gateway 400s the ENTIRE envelope — every other item in the batch with it.
33
+ * An item with no exception type omits `exception` altogether and carries its
34
+ * text in {@link ErrorItem.message}.
35
+ */
28
36
  interface ExceptionValue {
29
- type: string | null;
37
+ type: string;
30
38
  value: string | null;
31
39
  mechanism: Mechanism;
32
40
  stacktrace: Frame[];
@@ -50,8 +58,14 @@ interface ErrorItem {
50
58
  event_id?: string;
51
59
  timestamp: string;
52
60
  level: Level;
53
- exception: ExceptionValue;
54
- /** Optional human-readable summary alongside the exception. */
61
+ /**
62
+ * The captured exception. OPTIONAL a message capture omits it entirely and
63
+ * carries its text in {@link message} instead (the backend's `exception` is
64
+ * `Option<ExceptionInfo>`, and with no exception it fingerprints off
65
+ * `message`). Never emit a placeholder here just to fill the field.
66
+ */
67
+ exception?: ExceptionValue;
68
+ /** Human-readable summary; the ONLY text carrier when `exception` is absent. */
55
69
  message?: string;
56
70
  breadcrumbs: Breadcrumb[];
57
71
  fingerprint: string[] | null;
@@ -360,6 +374,24 @@ declare class Scope {
360
374
  readonly extra: Record<string, unknown>;
361
375
  constructor(maxBreadcrumbs?: number);
362
376
  setMaxBreadcrumbs(max: number): void;
377
+ /**
378
+ * Replace the scope user.
379
+ *
380
+ * `id` is coerced with `String()` for the same reason `SauronClient.
381
+ * prepareIdentify` coerces its own — a plain-JS caller can (and does) pass
382
+ * `setUser({ id: user.id })` where `user.id` is a number, and TypeScript
383
+ * cannot stop them. This path is the one that BYPASSES `identify()`'s
384
+ * coercion entirely, and the consequence is not cosmetic: the scope user
385
+ * lands in the envelope context, where the server's `distinct_id` is a
386
+ * non-`Option` Rust `String`. A JSON number there fails deserialization of
387
+ * the ENVELOPE, not of the one field — so the whole batch 400s, and a 400
388
+ * is non-retryable, so every event in it is dropped for good.
389
+ *
390
+ * Rebuilding the whole object (rather than merging into the existing one)
391
+ * is deliberate and is the behaviour the Flutter SDK was fixed to match:
392
+ * `email` and `traits` come from the input alone, so setting a new user
393
+ * never inherits the previous person's contact details.
394
+ */
363
395
  setUser(user: UserInput): void;
364
396
  /** The user context for an envelope. Never null — defaults to an empty user. */
365
397
  getUser(): UserContext;
@@ -392,7 +424,6 @@ declare class SauronClient {
392
424
  private readonly nativeFetch?;
393
425
  private enabled;
394
426
  private installed;
395
- private anonymousId;
396
427
  private beaconCleanup;
397
428
  constructor(options: ResolvedOptions);
398
429
  /** Install global handlers + auto-instrumentation and start the transport. */
@@ -410,11 +441,55 @@ declare class SauronClient {
410
441
  * null` as the no-op/disabled case before it ever reaches here.
411
442
  */
412
443
  isEnabled(): boolean;
444
+ /**
445
+ * Whether the anonymous id has actually been USED as a `distinct_id` in this
446
+ * browser session.
447
+ *
448
+ * A persisted id that has never been observed anonymously must not create a
449
+ * permanent `identities` alias row on the server: aliasing is a durable
450
+ * server-side binding of this browser profile to a named user, and an
451
+ * identify() on a first-ever page load has no anonymous history to link.
452
+ */
453
+ private anonUsed;
413
454
  /** The current distinct id: the user id when identified, else an anon id. */
414
455
  getDistinctId(): string | null;
415
- /** The anonymous id, or null if one was never needed. */
456
+ /** The anonymous id, or null when it was never actually used as an identity. */
416
457
  getAnonymousId(): string | null;
417
- private ensureAnonymousId;
458
+ /**
459
+ * Forget the current person: clear the scope user, mint a fresh anonymous
460
+ * id, forget the last identified user, and rotate the session id.
461
+ *
462
+ * MUST BE CALLED ON LOGOUT. Without it, the next anonymous visitor on this
463
+ * browser reuses the persisted anon id, and a later identify() aliases their
464
+ * activity to the previous account server-side, permanently. Rotating the
465
+ * session id matters too: the server's `bump_session` is last-write-wins on
466
+ * `distinct_id`, so without rotation one `sessions` row could otherwise
467
+ * serially represent two different people and record only whichever wrote
468
+ * last.
469
+ */
470
+ reset(): void;
471
+ /**
472
+ * Prepare for an `identify()`; returns the `anonymous_id` to send.
473
+ *
474
+ * When a DIFFERENT user identifies than last time, the current anon id
475
+ * belongs to the previous person and is already burned server-side, so it is
476
+ * replaced before anything else happens and `null` is sent instead of a
477
+ * cross-user alias. This cannot repair events already sent under the burned
478
+ * alias — nothing can — but it bounds a forgotten `reset()` to one guest
479
+ * window instead of every future one.
480
+ *
481
+ * `id` is coerced with `String()` before comparing/persisting: a plain-JS
482
+ * caller can pass a number (`Sauron.identify(user.id)`), and `Storage`
483
+ * itself applies `ToString` on write — so comparing an un-coerced `id`
484
+ * against a value that already round-tripped through storage would treat
485
+ * the SAME numeric user as a switch on every single call. The comparison
486
+ * against `last` is an explicit `!== null` (not a truthiness check) so an
487
+ * app that (unusually) identifies with `''` still has a later, different id
488
+ * correctly detected as a real switch — a falsy string is not "no identity
489
+ * yet". `last`/the persisted value are digests, not the raw id — see
490
+ * `hashIdentity`.
491
+ */
492
+ prepareIdentify(id: string): string | null;
418
493
  /** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
419
494
  makeEnvelope(items: EnvelopeItem[]): Envelope;
420
495
  /** Add a breadcrumb, running it through `beforeBreadcrumb` first. */
@@ -486,7 +561,7 @@ declare function parseError(err: unknown): Frame[];
486
561
  /** Small dependency-free helpers shared across the SDK. */
487
562
  /** SDK identity, embedded in every envelope header. */
488
563
  declare const SDK_NAME = "sauron.javascript";
489
- declare const SDK_VERSION = "1.3.0";
564
+ declare const SDK_VERSION = "1.4.1";
490
565
 
491
566
  /**
492
567
  * `@edraj/sauron-browser` — public API surface.
@@ -509,7 +584,16 @@ declare function captureException(err: unknown, hint?: Hint): void;
509
584
  declare function captureMessage(message: string, level?: Level, hint?: Hint): void;
510
585
  /** Record a product-analytics event, optionally with per-call tags/contexts/extra. */
511
586
  declare function track(name: string, properties?: Record<string, unknown>, options?: TrackOptions): void;
512
- /** Associate the session with a known user. */
587
+ /**
588
+ * Associate the session with a known user.
589
+ *
590
+ * The `anonymous_id` sent with the identify item is the current anon id — but
591
+ * only when it was actually used as a `distinct_id` this session, and never
592
+ * when it belongs to a different person than the last one who identified on
593
+ * this device. In that case a fresh anon id is minted first and `null` is
594
+ * sent instead, since the old one is already permanently bound to the
595
+ * previous person server-side (see `reset()`).
596
+ */
513
597
  declare function identify(id: string, traits?: Record<string, unknown>): void;
514
598
  /** Record a performance transaction (navigation, http, screen load, ...). */
515
599
  declare function trackTransaction(input: TransactionInput): void;
@@ -536,8 +620,20 @@ declare function cancelWorkflow(name?: string, options?: {
536
620
  declare function getWorkflow(): ActiveWorkflow | null;
537
621
  /** Record a breadcrumb. */
538
622
  declare function addBreadcrumb(breadcrumb: BreadcrumbInput, hint?: Hint): void;
539
- /** Set (or clear, with `null`) the current user. */
623
+ /**
624
+ * Set (or clear, with `null`) the current user.
625
+ *
626
+ * `setUser(null)` is a logout, so it also calls `reset()` for you — otherwise
627
+ * the next anonymous visitor on this browser inherits the previous person's
628
+ * durable id and a later identify() aliases them together server-side.
629
+ */
540
630
  declare function setUser(user: UserInput): void;
631
+ /**
632
+ * Forget the current person: clears the scope user, mints a fresh anonymous
633
+ * id, forgets the last identified user, and rotates the session id so a
634
+ * single session can never span two different people. Call this on logout.
635
+ */
636
+ declare function reset(): void;
541
637
  /** Set a single scope tag (lifted onto later errors/events). */
542
638
  declare function setTag(key: string, value: string): void;
543
639
  /** Merge a batch of scope tags (last-write-wins per key). */
@@ -561,6 +657,7 @@ declare const Sauron: {
561
657
  identify: typeof identify;
562
658
  addBreadcrumb: typeof addBreadcrumb;
563
659
  setUser: typeof setUser;
660
+ reset: typeof reset;
564
661
  setTag: typeof setTag;
565
662
  setTags: typeof setTags;
566
663
  setContext: typeof setContext;
@@ -576,4 +673,4 @@ declare const Sauron: {
576
673
  getClient: typeof getClient;
577
674
  };
578
675
 
579
- export { type ActiveWorkflow, type AppContext, type BeforeBreadcrumb, type BeforeSend, type Breadcrumb, type BreadcrumbBatchItem, type BreadcrumbInput, type CaptureOptions, type Context, type DeviceContext, type Dsn, DsnError, type Envelope, type EnvelopeHeader, type EnvelopeItem, type ErrorItem, type EventItem, type ExceptionValue, type Frame, type Hint, type IdentifyItem, type InitOptions, type ItemType, type Level, type Mechanism, type OsContext, type ResolvedOptions, type RuntimeContext, SDK_NAME, SDK_VERSION, Sauron, SauronClient, type SdkInfo, type TrackOptions, type TransactionInput, type TransactionItem, type TransactionOp, type TransportOptions, type UserContext, type UserInput, type WorkflowResult, type WorkflowStatus, addBreadcrumb, buildEnvelope, cancelWorkflow, captureException, captureMessage, close, Sauron as default, endWorkflow, flush, getClient, getScreen, getWorkflow, identify, init, isInAppFrame, parseDsn, parseError, parseStackString, setContext, setExtra, setScreen, setTag, setTags, setUser, startWorkflow, track, trackTransaction };
676
+ export { type ActiveWorkflow, type AppContext, type BeforeBreadcrumb, type BeforeSend, type Breadcrumb, type BreadcrumbBatchItem, type BreadcrumbInput, type CaptureOptions, type Context, type DeviceContext, type Dsn, DsnError, type Envelope, type EnvelopeHeader, type EnvelopeItem, type ErrorItem, type EventItem, type ExceptionValue, type Frame, type Hint, type IdentifyItem, type InitOptions, type ItemType, type Level, type Mechanism, type OsContext, type ResolvedOptions, type RuntimeContext, SDK_NAME, SDK_VERSION, Sauron, SauronClient, type SdkInfo, type TrackOptions, type TransactionInput, type TransactionItem, type TransactionOp, type TransportOptions, type UserContext, type UserInput, type WorkflowResult, type WorkflowStatus, addBreadcrumb, buildEnvelope, cancelWorkflow, captureException, captureMessage, close, Sauron as default, endWorkflow, flush, getClient, getScreen, getWorkflow, identify, init, isInAppFrame, parseDsn, parseError, parseStackString, reset, setContext, setExtra, setScreen, setTag, setTags, setUser, startWorkflow, track, trackTransaction };