@edraj/sauron-browser 1.4.0 → 1.5.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/CHANGELOG.md +73 -0
- package/README.md +174 -4
- package/dist/index.cjs +165 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -9
- package/dist/index.d.ts +126 -9
- package/dist/index.js +164 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -144,6 +144,19 @@ interface TransactionItem {
|
|
|
144
144
|
workflow_id?: string;
|
|
145
145
|
workflow_name?: string;
|
|
146
146
|
timestamp: string;
|
|
147
|
+
/**
|
|
148
|
+
* Developer-supplied flat string tags for THIS transaction.
|
|
149
|
+
*
|
|
150
|
+
* Unlike {@link EventItem} and the error item, this is NOT merged with the
|
|
151
|
+
* scope — see {@link TransactionInput.tags}. Omitted entirely when empty.
|
|
152
|
+
*/
|
|
153
|
+
tags?: Record<string, string>;
|
|
154
|
+
/**
|
|
155
|
+
* Developer-supplied freeform JSON for THIS transaction — the request body,
|
|
156
|
+
* the response body, a retry count. Capped and replaced with a truncation
|
|
157
|
+
* marker past {@link MAX_TRANSACTION_EXTRA_BYTES}. Omitted when empty.
|
|
158
|
+
*/
|
|
159
|
+
extra?: Record<string, unknown>;
|
|
147
160
|
}
|
|
148
161
|
/** An identity association (PostHog-style `identify`). */
|
|
149
162
|
interface IdentifyItem {
|
|
@@ -330,6 +343,29 @@ interface TransactionInput {
|
|
|
330
343
|
httpMethod?: string | null;
|
|
331
344
|
httpStatus?: number | null;
|
|
332
345
|
url?: string | null;
|
|
346
|
+
/**
|
|
347
|
+
* Flat string tags for this transaction.
|
|
348
|
+
*
|
|
349
|
+
* **Per-call only — the scope is NOT merged in**, which is the one place
|
|
350
|
+
* transactions differ from `track()` and `captureException()`. Those two
|
|
351
|
+
* merge `setTag`/`setExtra` defaults; a transaction carries only what its own
|
|
352
|
+
* call site attached. Transactions are the highest-volume signal (one per
|
|
353
|
+
* navigation and per HTTP call), so inheriting a global blob would write it
|
|
354
|
+
* onto every row.
|
|
355
|
+
*/
|
|
356
|
+
tags?: Record<string, string>;
|
|
357
|
+
/**
|
|
358
|
+
* Freeform JSON for this transaction — the request body, the response body,
|
|
359
|
+
* an order id, a retry count.
|
|
360
|
+
*
|
|
361
|
+
* Per-call only, for the reason on {@link TransactionInput.tags}. Serialized
|
|
362
|
+
* and capped at {@link MAX_TRANSACTION_EXTRA_BYTES}; past that the whole map
|
|
363
|
+
* is replaced with `{ _truncated: true, _bytes: N }` so one large body cannot
|
|
364
|
+
* take a batched envelope over the ingest limit and drop every span in it.
|
|
365
|
+
*
|
|
366
|
+
* Nothing here is scrubbed. `beforeSend` is the redaction seam.
|
|
367
|
+
*/
|
|
368
|
+
extra?: Record<string, unknown>;
|
|
333
369
|
}
|
|
334
370
|
|
|
335
371
|
/**
|
|
@@ -374,6 +410,24 @@ declare class Scope {
|
|
|
374
410
|
readonly extra: Record<string, unknown>;
|
|
375
411
|
constructor(maxBreadcrumbs?: number);
|
|
376
412
|
setMaxBreadcrumbs(max: number): void;
|
|
413
|
+
/**
|
|
414
|
+
* Replace the scope user.
|
|
415
|
+
*
|
|
416
|
+
* `id` is coerced with `String()` for the same reason `SauronClient.
|
|
417
|
+
* prepareIdentify` coerces its own — a plain-JS caller can (and does) pass
|
|
418
|
+
* `setUser({ id: user.id })` where `user.id` is a number, and TypeScript
|
|
419
|
+
* cannot stop them. This path is the one that BYPASSES `identify()`'s
|
|
420
|
+
* coercion entirely, and the consequence is not cosmetic: the scope user
|
|
421
|
+
* lands in the envelope context, where the server's `distinct_id` is a
|
|
422
|
+
* non-`Option` Rust `String`. A JSON number there fails deserialization of
|
|
423
|
+
* the ENVELOPE, not of the one field — so the whole batch 400s, and a 400
|
|
424
|
+
* is non-retryable, so every event in it is dropped for good.
|
|
425
|
+
*
|
|
426
|
+
* Rebuilding the whole object (rather than merging into the existing one)
|
|
427
|
+
* is deliberate and is the behaviour the Flutter SDK was fixed to match:
|
|
428
|
+
* `email` and `traits` come from the input alone, so setting a new user
|
|
429
|
+
* never inherits the previous person's contact details.
|
|
430
|
+
*/
|
|
377
431
|
setUser(user: UserInput): void;
|
|
378
432
|
/** The user context for an envelope. Never null — defaults to an empty user. */
|
|
379
433
|
getUser(): UserContext;
|
|
@@ -438,14 +492,40 @@ declare class SauronClient {
|
|
|
438
492
|
/** The anonymous id, or null when it was never actually used as an identity. */
|
|
439
493
|
getAnonymousId(): string | null;
|
|
440
494
|
/**
|
|
441
|
-
* Forget the current person: clear the scope user
|
|
442
|
-
* id.
|
|
495
|
+
* Forget the current person: clear the scope user, mint a fresh anonymous
|
|
496
|
+
* id, forget the last identified user, and rotate the session id.
|
|
443
497
|
*
|
|
444
498
|
* MUST BE CALLED ON LOGOUT. Without it, the next anonymous visitor on this
|
|
445
499
|
* browser reuses the persisted anon id, and a later identify() aliases their
|
|
446
|
-
* activity to the previous account server-side, permanently.
|
|
500
|
+
* activity to the previous account server-side, permanently. Rotating the
|
|
501
|
+
* session id matters too: the server's `bump_session` is last-write-wins on
|
|
502
|
+
* `distinct_id`, so without rotation one `sessions` row could otherwise
|
|
503
|
+
* serially represent two different people and record only whichever wrote
|
|
504
|
+
* last.
|
|
447
505
|
*/
|
|
448
506
|
reset(): void;
|
|
507
|
+
/**
|
|
508
|
+
* Prepare for an `identify()`; returns the `anonymous_id` to send.
|
|
509
|
+
*
|
|
510
|
+
* When a DIFFERENT user identifies than last time, the current anon id
|
|
511
|
+
* belongs to the previous person and is already burned server-side, so it is
|
|
512
|
+
* replaced before anything else happens and `null` is sent instead of a
|
|
513
|
+
* cross-user alias. This cannot repair events already sent under the burned
|
|
514
|
+
* alias — nothing can — but it bounds a forgotten `reset()` to one guest
|
|
515
|
+
* window instead of every future one.
|
|
516
|
+
*
|
|
517
|
+
* `id` is coerced with `String()` before comparing/persisting: a plain-JS
|
|
518
|
+
* caller can pass a number (`Sauron.identify(user.id)`), and `Storage`
|
|
519
|
+
* itself applies `ToString` on write — so comparing an un-coerced `id`
|
|
520
|
+
* against a value that already round-tripped through storage would treat
|
|
521
|
+
* the SAME numeric user as a switch on every single call. The comparison
|
|
522
|
+
* against `last` is an explicit `!== null` (not a truthiness check) so an
|
|
523
|
+
* app that (unusually) identifies with `''` still has a later, different id
|
|
524
|
+
* correctly detected as a real switch — a falsy string is not "no identity
|
|
525
|
+
* yet". `last`/the persisted value are digests, not the raw id — see
|
|
526
|
+
* `hashIdentity`.
|
|
527
|
+
*/
|
|
528
|
+
prepareIdentify(id: string): string | null;
|
|
449
529
|
/** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
|
|
450
530
|
makeEnvelope(items: EnvelopeItem[]): Envelope;
|
|
451
531
|
/** Add a breadcrumb, running it through `beforeBreadcrumb` first. */
|
|
@@ -517,7 +597,34 @@ declare function parseError(err: unknown): Frame[];
|
|
|
517
597
|
/** Small dependency-free helpers shared across the SDK. */
|
|
518
598
|
/** SDK identity, embedded in every envelope header. */
|
|
519
599
|
declare const SDK_NAME = "sauron.javascript";
|
|
520
|
-
declare const SDK_VERSION = "1.
|
|
600
|
+
declare const SDK_VERSION = "1.5.0";
|
|
601
|
+
/**
|
|
602
|
+
* Largest serialized `extra` a single transaction may carry, in bytes.
|
|
603
|
+
*
|
|
604
|
+
* Transactions are the highest-volume signal and they ship in BATCHED
|
|
605
|
+
* envelopes, so one oversized payload does not fail alone — ingest rejects the
|
|
606
|
+
* whole envelope past `INGEST_MAX_BODY_BYTES` (1 MiB by default) and every
|
|
607
|
+
* unrelated span batched with it is lost. Since the motivating use of
|
|
608
|
+
* transaction `extra` is request and response bodies, that is not a remote
|
|
609
|
+
* hazard.
|
|
610
|
+
*/
|
|
611
|
+
declare const MAX_TRANSACTION_EXTRA_BYTES: number;
|
|
612
|
+
/**
|
|
613
|
+
* Cap a transaction's `extra`, substituting a marker when it is too large.
|
|
614
|
+
*
|
|
615
|
+
* Replaces the WHOLE map rather than trimming keys: a half-written JSON value
|
|
616
|
+
* is worse than an honest marker, and per-key trimming would make the result
|
|
617
|
+
* depend on key iteration order, which differs across the five SDKs. The
|
|
618
|
+
* marker is deliberately readable on the dashboard — `_truncated` says data
|
|
619
|
+
* was dropped rather than silently serving a short object that looks complete.
|
|
620
|
+
*
|
|
621
|
+
* Returns the input unchanged when it fits. A value that cannot be serialized
|
|
622
|
+
* at all (a cycle, a BigInt) is replaced by the same marker with `_bytes: -1`,
|
|
623
|
+
* because the alternative is throwing from inside `trackTransaction` — and an
|
|
624
|
+
* SDK that crashes the app it is measuring is worse than one that drops a
|
|
625
|
+
* payload.
|
|
626
|
+
*/
|
|
627
|
+
declare function capTransactionExtra(extra: Record<string, unknown>, maxBytes?: number): Record<string, unknown>;
|
|
521
628
|
|
|
522
629
|
/**
|
|
523
630
|
* `@edraj/sauron-browser` — public API surface.
|
|
@@ -540,7 +647,16 @@ declare function captureException(err: unknown, hint?: Hint): void;
|
|
|
540
647
|
declare function captureMessage(message: string, level?: Level, hint?: Hint): void;
|
|
541
648
|
/** Record a product-analytics event, optionally with per-call tags/contexts/extra. */
|
|
542
649
|
declare function track(name: string, properties?: Record<string, unknown>, options?: TrackOptions): void;
|
|
543
|
-
/**
|
|
650
|
+
/**
|
|
651
|
+
* Associate the session with a known user.
|
|
652
|
+
*
|
|
653
|
+
* The `anonymous_id` sent with the identify item is the current anon id — but
|
|
654
|
+
* only when it was actually used as a `distinct_id` this session, and never
|
|
655
|
+
* when it belongs to a different person than the last one who identified on
|
|
656
|
+
* this device. In that case a fresh anon id is minted first and `null` is
|
|
657
|
+
* sent instead, since the old one is already permanently bound to the
|
|
658
|
+
* previous person server-side (see `reset()`).
|
|
659
|
+
*/
|
|
544
660
|
declare function identify(id: string, traits?: Record<string, unknown>): void;
|
|
545
661
|
/** Record a performance transaction (navigation, http, screen load, ...). */
|
|
546
662
|
declare function trackTransaction(input: TransactionInput): void;
|
|
@@ -570,14 +686,15 @@ declare function addBreadcrumb(breadcrumb: BreadcrumbInput, hint?: Hint): void;
|
|
|
570
686
|
/**
|
|
571
687
|
* Set (or clear, with `null`) the current user.
|
|
572
688
|
*
|
|
573
|
-
* `setUser(null)` is a logout, so it also
|
|
689
|
+
* `setUser(null)` is a logout, so it also calls `reset()` for you — otherwise
|
|
574
690
|
* the next anonymous visitor on this browser inherits the previous person's
|
|
575
691
|
* durable id and a later identify() aliases them together server-side.
|
|
576
692
|
*/
|
|
577
693
|
declare function setUser(user: UserInput): void;
|
|
578
694
|
/**
|
|
579
|
-
* Forget the current person: clears the scope user
|
|
580
|
-
* id
|
|
695
|
+
* Forget the current person: clears the scope user, mints a fresh anonymous
|
|
696
|
+
* id, forgets the last identified user, and rotates the session id so a
|
|
697
|
+
* single session can never span two different people. Call this on logout.
|
|
581
698
|
*/
|
|
582
699
|
declare function reset(): void;
|
|
583
700
|
/** Set a single scope tag (lifted onto later errors/events). */
|
|
@@ -619,4 +736,4 @@ declare const Sauron: {
|
|
|
619
736
|
getClient: typeof getClient;
|
|
620
737
|
};
|
|
621
738
|
|
|
622
|
-
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 };
|
|
739
|
+
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, MAX_TRANSACTION_EXTRA_BYTES, 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, capTransactionExtra, 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
|
@@ -144,6 +144,19 @@ interface TransactionItem {
|
|
|
144
144
|
workflow_id?: string;
|
|
145
145
|
workflow_name?: string;
|
|
146
146
|
timestamp: string;
|
|
147
|
+
/**
|
|
148
|
+
* Developer-supplied flat string tags for THIS transaction.
|
|
149
|
+
*
|
|
150
|
+
* Unlike {@link EventItem} and the error item, this is NOT merged with the
|
|
151
|
+
* scope — see {@link TransactionInput.tags}. Omitted entirely when empty.
|
|
152
|
+
*/
|
|
153
|
+
tags?: Record<string, string>;
|
|
154
|
+
/**
|
|
155
|
+
* Developer-supplied freeform JSON for THIS transaction — the request body,
|
|
156
|
+
* the response body, a retry count. Capped and replaced with a truncation
|
|
157
|
+
* marker past {@link MAX_TRANSACTION_EXTRA_BYTES}. Omitted when empty.
|
|
158
|
+
*/
|
|
159
|
+
extra?: Record<string, unknown>;
|
|
147
160
|
}
|
|
148
161
|
/** An identity association (PostHog-style `identify`). */
|
|
149
162
|
interface IdentifyItem {
|
|
@@ -330,6 +343,29 @@ interface TransactionInput {
|
|
|
330
343
|
httpMethod?: string | null;
|
|
331
344
|
httpStatus?: number | null;
|
|
332
345
|
url?: string | null;
|
|
346
|
+
/**
|
|
347
|
+
* Flat string tags for this transaction.
|
|
348
|
+
*
|
|
349
|
+
* **Per-call only — the scope is NOT merged in**, which is the one place
|
|
350
|
+
* transactions differ from `track()` and `captureException()`. Those two
|
|
351
|
+
* merge `setTag`/`setExtra` defaults; a transaction carries only what its own
|
|
352
|
+
* call site attached. Transactions are the highest-volume signal (one per
|
|
353
|
+
* navigation and per HTTP call), so inheriting a global blob would write it
|
|
354
|
+
* onto every row.
|
|
355
|
+
*/
|
|
356
|
+
tags?: Record<string, string>;
|
|
357
|
+
/**
|
|
358
|
+
* Freeform JSON for this transaction — the request body, the response body,
|
|
359
|
+
* an order id, a retry count.
|
|
360
|
+
*
|
|
361
|
+
* Per-call only, for the reason on {@link TransactionInput.tags}. Serialized
|
|
362
|
+
* and capped at {@link MAX_TRANSACTION_EXTRA_BYTES}; past that the whole map
|
|
363
|
+
* is replaced with `{ _truncated: true, _bytes: N }` so one large body cannot
|
|
364
|
+
* take a batched envelope over the ingest limit and drop every span in it.
|
|
365
|
+
*
|
|
366
|
+
* Nothing here is scrubbed. `beforeSend` is the redaction seam.
|
|
367
|
+
*/
|
|
368
|
+
extra?: Record<string, unknown>;
|
|
333
369
|
}
|
|
334
370
|
|
|
335
371
|
/**
|
|
@@ -374,6 +410,24 @@ declare class Scope {
|
|
|
374
410
|
readonly extra: Record<string, unknown>;
|
|
375
411
|
constructor(maxBreadcrumbs?: number);
|
|
376
412
|
setMaxBreadcrumbs(max: number): void;
|
|
413
|
+
/**
|
|
414
|
+
* Replace the scope user.
|
|
415
|
+
*
|
|
416
|
+
* `id` is coerced with `String()` for the same reason `SauronClient.
|
|
417
|
+
* prepareIdentify` coerces its own — a plain-JS caller can (and does) pass
|
|
418
|
+
* `setUser({ id: user.id })` where `user.id` is a number, and TypeScript
|
|
419
|
+
* cannot stop them. This path is the one that BYPASSES `identify()`'s
|
|
420
|
+
* coercion entirely, and the consequence is not cosmetic: the scope user
|
|
421
|
+
* lands in the envelope context, where the server's `distinct_id` is a
|
|
422
|
+
* non-`Option` Rust `String`. A JSON number there fails deserialization of
|
|
423
|
+
* the ENVELOPE, not of the one field — so the whole batch 400s, and a 400
|
|
424
|
+
* is non-retryable, so every event in it is dropped for good.
|
|
425
|
+
*
|
|
426
|
+
* Rebuilding the whole object (rather than merging into the existing one)
|
|
427
|
+
* is deliberate and is the behaviour the Flutter SDK was fixed to match:
|
|
428
|
+
* `email` and `traits` come from the input alone, so setting a new user
|
|
429
|
+
* never inherits the previous person's contact details.
|
|
430
|
+
*/
|
|
377
431
|
setUser(user: UserInput): void;
|
|
378
432
|
/** The user context for an envelope. Never null — defaults to an empty user. */
|
|
379
433
|
getUser(): UserContext;
|
|
@@ -438,14 +492,40 @@ declare class SauronClient {
|
|
|
438
492
|
/** The anonymous id, or null when it was never actually used as an identity. */
|
|
439
493
|
getAnonymousId(): string | null;
|
|
440
494
|
/**
|
|
441
|
-
* Forget the current person: clear the scope user
|
|
442
|
-
* id.
|
|
495
|
+
* Forget the current person: clear the scope user, mint a fresh anonymous
|
|
496
|
+
* id, forget the last identified user, and rotate the session id.
|
|
443
497
|
*
|
|
444
498
|
* MUST BE CALLED ON LOGOUT. Without it, the next anonymous visitor on this
|
|
445
499
|
* browser reuses the persisted anon id, and a later identify() aliases their
|
|
446
|
-
* activity to the previous account server-side, permanently.
|
|
500
|
+
* activity to the previous account server-side, permanently. Rotating the
|
|
501
|
+
* session id matters too: the server's `bump_session` is last-write-wins on
|
|
502
|
+
* `distinct_id`, so without rotation one `sessions` row could otherwise
|
|
503
|
+
* serially represent two different people and record only whichever wrote
|
|
504
|
+
* last.
|
|
447
505
|
*/
|
|
448
506
|
reset(): void;
|
|
507
|
+
/**
|
|
508
|
+
* Prepare for an `identify()`; returns the `anonymous_id` to send.
|
|
509
|
+
*
|
|
510
|
+
* When a DIFFERENT user identifies than last time, the current anon id
|
|
511
|
+
* belongs to the previous person and is already burned server-side, so it is
|
|
512
|
+
* replaced before anything else happens and `null` is sent instead of a
|
|
513
|
+
* cross-user alias. This cannot repair events already sent under the burned
|
|
514
|
+
* alias — nothing can — but it bounds a forgotten `reset()` to one guest
|
|
515
|
+
* window instead of every future one.
|
|
516
|
+
*
|
|
517
|
+
* `id` is coerced with `String()` before comparing/persisting: a plain-JS
|
|
518
|
+
* caller can pass a number (`Sauron.identify(user.id)`), and `Storage`
|
|
519
|
+
* itself applies `ToString` on write — so comparing an un-coerced `id`
|
|
520
|
+
* against a value that already round-tripped through storage would treat
|
|
521
|
+
* the SAME numeric user as a switch on every single call. The comparison
|
|
522
|
+
* against `last` is an explicit `!== null` (not a truthiness check) so an
|
|
523
|
+
* app that (unusually) identifies with `''` still has a later, different id
|
|
524
|
+
* correctly detected as a real switch — a falsy string is not "no identity
|
|
525
|
+
* yet". `last`/the persisted value are digests, not the raw id — see
|
|
526
|
+
* `hashIdentity`.
|
|
527
|
+
*/
|
|
528
|
+
prepareIdentify(id: string): string | null;
|
|
449
529
|
/** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
|
|
450
530
|
makeEnvelope(items: EnvelopeItem[]): Envelope;
|
|
451
531
|
/** Add a breadcrumb, running it through `beforeBreadcrumb` first. */
|
|
@@ -517,7 +597,34 @@ declare function parseError(err: unknown): Frame[];
|
|
|
517
597
|
/** Small dependency-free helpers shared across the SDK. */
|
|
518
598
|
/** SDK identity, embedded in every envelope header. */
|
|
519
599
|
declare const SDK_NAME = "sauron.javascript";
|
|
520
|
-
declare const SDK_VERSION = "1.
|
|
600
|
+
declare const SDK_VERSION = "1.5.0";
|
|
601
|
+
/**
|
|
602
|
+
* Largest serialized `extra` a single transaction may carry, in bytes.
|
|
603
|
+
*
|
|
604
|
+
* Transactions are the highest-volume signal and they ship in BATCHED
|
|
605
|
+
* envelopes, so one oversized payload does not fail alone — ingest rejects the
|
|
606
|
+
* whole envelope past `INGEST_MAX_BODY_BYTES` (1 MiB by default) and every
|
|
607
|
+
* unrelated span batched with it is lost. Since the motivating use of
|
|
608
|
+
* transaction `extra` is request and response bodies, that is not a remote
|
|
609
|
+
* hazard.
|
|
610
|
+
*/
|
|
611
|
+
declare const MAX_TRANSACTION_EXTRA_BYTES: number;
|
|
612
|
+
/**
|
|
613
|
+
* Cap a transaction's `extra`, substituting a marker when it is too large.
|
|
614
|
+
*
|
|
615
|
+
* Replaces the WHOLE map rather than trimming keys: a half-written JSON value
|
|
616
|
+
* is worse than an honest marker, and per-key trimming would make the result
|
|
617
|
+
* depend on key iteration order, which differs across the five SDKs. The
|
|
618
|
+
* marker is deliberately readable on the dashboard — `_truncated` says data
|
|
619
|
+
* was dropped rather than silently serving a short object that looks complete.
|
|
620
|
+
*
|
|
621
|
+
* Returns the input unchanged when it fits. A value that cannot be serialized
|
|
622
|
+
* at all (a cycle, a BigInt) is replaced by the same marker with `_bytes: -1`,
|
|
623
|
+
* because the alternative is throwing from inside `trackTransaction` — and an
|
|
624
|
+
* SDK that crashes the app it is measuring is worse than one that drops a
|
|
625
|
+
* payload.
|
|
626
|
+
*/
|
|
627
|
+
declare function capTransactionExtra(extra: Record<string, unknown>, maxBytes?: number): Record<string, unknown>;
|
|
521
628
|
|
|
522
629
|
/**
|
|
523
630
|
* `@edraj/sauron-browser` — public API surface.
|
|
@@ -540,7 +647,16 @@ declare function captureException(err: unknown, hint?: Hint): void;
|
|
|
540
647
|
declare function captureMessage(message: string, level?: Level, hint?: Hint): void;
|
|
541
648
|
/** Record a product-analytics event, optionally with per-call tags/contexts/extra. */
|
|
542
649
|
declare function track(name: string, properties?: Record<string, unknown>, options?: TrackOptions): void;
|
|
543
|
-
/**
|
|
650
|
+
/**
|
|
651
|
+
* Associate the session with a known user.
|
|
652
|
+
*
|
|
653
|
+
* The `anonymous_id` sent with the identify item is the current anon id — but
|
|
654
|
+
* only when it was actually used as a `distinct_id` this session, and never
|
|
655
|
+
* when it belongs to a different person than the last one who identified on
|
|
656
|
+
* this device. In that case a fresh anon id is minted first and `null` is
|
|
657
|
+
* sent instead, since the old one is already permanently bound to the
|
|
658
|
+
* previous person server-side (see `reset()`).
|
|
659
|
+
*/
|
|
544
660
|
declare function identify(id: string, traits?: Record<string, unknown>): void;
|
|
545
661
|
/** Record a performance transaction (navigation, http, screen load, ...). */
|
|
546
662
|
declare function trackTransaction(input: TransactionInput): void;
|
|
@@ -570,14 +686,15 @@ declare function addBreadcrumb(breadcrumb: BreadcrumbInput, hint?: Hint): void;
|
|
|
570
686
|
/**
|
|
571
687
|
* Set (or clear, with `null`) the current user.
|
|
572
688
|
*
|
|
573
|
-
* `setUser(null)` is a logout, so it also
|
|
689
|
+
* `setUser(null)` is a logout, so it also calls `reset()` for you — otherwise
|
|
574
690
|
* the next anonymous visitor on this browser inherits the previous person's
|
|
575
691
|
* durable id and a later identify() aliases them together server-side.
|
|
576
692
|
*/
|
|
577
693
|
declare function setUser(user: UserInput): void;
|
|
578
694
|
/**
|
|
579
|
-
* Forget the current person: clears the scope user
|
|
580
|
-
* id
|
|
695
|
+
* Forget the current person: clears the scope user, mints a fresh anonymous
|
|
696
|
+
* id, forgets the last identified user, and rotates the session id so a
|
|
697
|
+
* single session can never span two different people. Call this on logout.
|
|
581
698
|
*/
|
|
582
699
|
declare function reset(): void;
|
|
583
700
|
/** Set a single scope tag (lifted onto later errors/events). */
|
|
@@ -619,4 +736,4 @@ declare const Sauron: {
|
|
|
619
736
|
getClient: typeof getClient;
|
|
620
737
|
};
|
|
621
738
|
|
|
622
|
-
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 };
|
|
739
|
+
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, MAX_TRANSACTION_EXTRA_BYTES, 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, capTransactionExtra, 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.js
CHANGED
|
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4
4
|
|
|
5
5
|
// src/utils.ts
|
|
6
6
|
var SDK_NAME = "sauron.javascript";
|
|
7
|
-
var SDK_VERSION = "1.
|
|
7
|
+
var SDK_VERSION = "1.5.0";
|
|
8
8
|
function getGlobal() {
|
|
9
9
|
return globalThis;
|
|
10
10
|
}
|
|
@@ -83,11 +83,60 @@ function makeLogger(debug) {
|
|
|
83
83
|
warn: (...args) => console.warn("[sauron]", ...args)
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
var MAX_TRANSACTION_EXTRA_BYTES = 16 * 1024;
|
|
87
|
+
function capTransactionExtra(extra, maxBytes = MAX_TRANSACTION_EXTRA_BYTES) {
|
|
88
|
+
let bytes;
|
|
89
|
+
try {
|
|
90
|
+
const json = JSON.stringify(extra);
|
|
91
|
+
if (json === void 0) return { _truncated: true, _bytes: -1 };
|
|
92
|
+
bytes = utf8Length(json);
|
|
93
|
+
} catch {
|
|
94
|
+
return { _truncated: true, _bytes: -1 };
|
|
95
|
+
}
|
|
96
|
+
if (bytes <= maxBytes) return extra;
|
|
97
|
+
return { _truncated: true, _bytes: bytes };
|
|
98
|
+
}
|
|
99
|
+
function utf8Length(s) {
|
|
100
|
+
let n = 0;
|
|
101
|
+
for (let i = 0; i < s.length; i++) {
|
|
102
|
+
const c = s.charCodeAt(i);
|
|
103
|
+
if (c < 128) n += 1;
|
|
104
|
+
else if (c < 2048) n += 2;
|
|
105
|
+
else if (c >= 55296 && c <= 56319) {
|
|
106
|
+
n += 4;
|
|
107
|
+
i++;
|
|
108
|
+
} else n += 3;
|
|
109
|
+
}
|
|
110
|
+
return n;
|
|
111
|
+
}
|
|
86
112
|
|
|
87
113
|
// src/identity.ts
|
|
88
114
|
var DEVICE_ID_KEY = "sauron.device_id";
|
|
89
115
|
var SESSION_ID_KEY = "sauron.session_id";
|
|
90
116
|
var ANON_ID_KEY = "sauron.anon_id";
|
|
117
|
+
var LAST_IDENTIFIED_KEY = "sauron.last_identified";
|
|
118
|
+
var LAST_IDENTIFIED_FORMAT = "v1";
|
|
119
|
+
function encodeLastIdentified(digest) {
|
|
120
|
+
return `${LAST_IDENTIFIED_FORMAT}:${digest}`;
|
|
121
|
+
}
|
|
122
|
+
function decodeLastIdentified(raw) {
|
|
123
|
+
if (raw === null) return null;
|
|
124
|
+
const sep = raw.indexOf(":");
|
|
125
|
+
if (sep < 0 || raw.slice(0, sep) !== LAST_IDENTIFIED_FORMAT) return null;
|
|
126
|
+
const digest = raw.slice(sep + 1);
|
|
127
|
+
return digest === "" ? null : digest;
|
|
128
|
+
}
|
|
129
|
+
function fnv1a32(s) {
|
|
130
|
+
let h = 2166136261;
|
|
131
|
+
for (let i = 0; i < s.length; i++) {
|
|
132
|
+
h ^= s.charCodeAt(i);
|
|
133
|
+
h = Math.imul(h, 16777619);
|
|
134
|
+
}
|
|
135
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
136
|
+
}
|
|
137
|
+
function hashIdentity(id) {
|
|
138
|
+
return fnv1a32(id) + fnv1a32("" + id);
|
|
139
|
+
}
|
|
91
140
|
function webStorage(name) {
|
|
92
141
|
try {
|
|
93
142
|
const s = globalThis[name];
|
|
@@ -121,6 +170,7 @@ function persistentId(cached, storage, key) {
|
|
|
121
170
|
var deviceId = null;
|
|
122
171
|
var sessionId = null;
|
|
123
172
|
var anonymousId = null;
|
|
173
|
+
var lastIdentified = null;
|
|
124
174
|
function getDeviceId() {
|
|
125
175
|
deviceId = persistentId(deviceId, webStorage("localStorage"), DEVICE_ID_KEY);
|
|
126
176
|
return deviceId;
|
|
@@ -129,6 +179,17 @@ function getSessionId() {
|
|
|
129
179
|
sessionId = persistentId(sessionId, webStorage("sessionStorage"), SESSION_ID_KEY);
|
|
130
180
|
return sessionId;
|
|
131
181
|
}
|
|
182
|
+
function rotateSessionId() {
|
|
183
|
+
sessionId = null;
|
|
184
|
+
const storage = webStorage("sessionStorage");
|
|
185
|
+
if (storage) {
|
|
186
|
+
try {
|
|
187
|
+
storage.removeItem(SESSION_ID_KEY);
|
|
188
|
+
} catch {
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return getSessionId();
|
|
192
|
+
}
|
|
132
193
|
function getAnonymousId() {
|
|
133
194
|
if (anonymousId) return anonymousId;
|
|
134
195
|
const storage = webStorage("localStorage");
|
|
@@ -163,6 +224,37 @@ function resetAnonymousId() {
|
|
|
163
224
|
}
|
|
164
225
|
return getAnonymousId();
|
|
165
226
|
}
|
|
227
|
+
function getLastIdentified() {
|
|
228
|
+
const storage = webStorage("localStorage");
|
|
229
|
+
if (!storage) return decodeLastIdentified(lastIdentified);
|
|
230
|
+
try {
|
|
231
|
+
const stored = storage.getItem(LAST_IDENTIFIED_KEY);
|
|
232
|
+
return decodeLastIdentified(stored ?? lastIdentified);
|
|
233
|
+
} catch {
|
|
234
|
+
return decodeLastIdentified(lastIdentified);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function setLastIdentified(id) {
|
|
238
|
+
const encoded = encodeLastIdentified(id);
|
|
239
|
+
lastIdentified = encoded;
|
|
240
|
+
const storage = webStorage("localStorage");
|
|
241
|
+
if (storage) {
|
|
242
|
+
try {
|
|
243
|
+
storage.setItem(LAST_IDENTIFIED_KEY, encoded);
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function clearLastIdentified() {
|
|
249
|
+
lastIdentified = null;
|
|
250
|
+
const storage = webStorage("localStorage");
|
|
251
|
+
if (storage) {
|
|
252
|
+
try {
|
|
253
|
+
storage.removeItem(LAST_IDENTIFIED_KEY);
|
|
254
|
+
} catch {
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
166
258
|
|
|
167
259
|
// src/context.ts
|
|
168
260
|
function getNavigator() {
|
|
@@ -809,13 +901,31 @@ var Scope = class {
|
|
|
809
901
|
this.maxBreadcrumbs = Math.max(0, max);
|
|
810
902
|
this.trim();
|
|
811
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Replace the scope user.
|
|
906
|
+
*
|
|
907
|
+
* `id` is coerced with `String()` for the same reason `SauronClient.
|
|
908
|
+
* prepareIdentify` coerces its own — a plain-JS caller can (and does) pass
|
|
909
|
+
* `setUser({ id: user.id })` where `user.id` is a number, and TypeScript
|
|
910
|
+
* cannot stop them. This path is the one that BYPASSES `identify()`'s
|
|
911
|
+
* coercion entirely, and the consequence is not cosmetic: the scope user
|
|
912
|
+
* lands in the envelope context, where the server's `distinct_id` is a
|
|
913
|
+
* non-`Option` Rust `String`. A JSON number there fails deserialization of
|
|
914
|
+
* the ENVELOPE, not of the one field — so the whole batch 400s, and a 400
|
|
915
|
+
* is non-retryable, so every event in it is dropped for good.
|
|
916
|
+
*
|
|
917
|
+
* Rebuilding the whole object (rather than merging into the existing one)
|
|
918
|
+
* is deliberate and is the behaviour the Flutter SDK was fixed to match:
|
|
919
|
+
* `email` and `traits` come from the input alone, so setting a new user
|
|
920
|
+
* never inherits the previous person's contact details.
|
|
921
|
+
*/
|
|
812
922
|
setUser(user) {
|
|
813
923
|
if (user === null) {
|
|
814
924
|
this.user = null;
|
|
815
925
|
return;
|
|
816
926
|
}
|
|
817
927
|
this.user = {
|
|
818
|
-
id: user.id
|
|
928
|
+
id: user.id === null || user.id === void 0 ? null : String(user.id),
|
|
819
929
|
email: user.email ?? null,
|
|
820
930
|
traits: user.traits ?? {}
|
|
821
931
|
};
|
|
@@ -916,11 +1026,12 @@ function setScreen(name) {
|
|
|
916
1026
|
function identify(id, traits = {}) {
|
|
917
1027
|
const client = getClient();
|
|
918
1028
|
if (!client) return;
|
|
919
|
-
const
|
|
920
|
-
client.
|
|
1029
|
+
const distinctId = String(id);
|
|
1030
|
+
const anonymousId2 = client.prepareIdentify(distinctId);
|
|
1031
|
+
client.getScope().setUser({ id: distinctId, traits });
|
|
921
1032
|
const item = {
|
|
922
1033
|
type: "identify",
|
|
923
|
-
distinct_id:
|
|
1034
|
+
distinct_id: distinctId,
|
|
924
1035
|
anonymous_id: anonymousId2,
|
|
925
1036
|
traits: traits ?? {}
|
|
926
1037
|
};
|
|
@@ -937,7 +1048,7 @@ function normalizeOp(op) {
|
|
|
937
1048
|
return op && TRANSACTION_OPS.includes(op) ? op : "custom";
|
|
938
1049
|
}
|
|
939
1050
|
function buildTransactionItem(input, distinctId, sessionId2) {
|
|
940
|
-
|
|
1051
|
+
const item = {
|
|
941
1052
|
type: "transaction",
|
|
942
1053
|
name: input.name,
|
|
943
1054
|
op: normalizeOp(input.op),
|
|
@@ -950,6 +1061,11 @@ function buildTransactionItem(input, distinctId, sessionId2) {
|
|
|
950
1061
|
session_id: sessionId2,
|
|
951
1062
|
timestamp: nowIso()
|
|
952
1063
|
};
|
|
1064
|
+
if (input.tags && Object.keys(input.tags).length > 0) item.tags = { ...input.tags };
|
|
1065
|
+
if (input.extra && Object.keys(input.extra).length > 0) {
|
|
1066
|
+
item.extra = capTransactionExtra({ ...input.extra });
|
|
1067
|
+
}
|
|
1068
|
+
return item;
|
|
953
1069
|
}
|
|
954
1070
|
function trackTransaction(input) {
|
|
955
1071
|
const client = getClient();
|
|
@@ -1850,18 +1966,56 @@ var SauronClient = class {
|
|
|
1850
1966
|
return this.anonUsed ? getAnonymousId() : null;
|
|
1851
1967
|
}
|
|
1852
1968
|
/**
|
|
1853
|
-
* Forget the current person: clear the scope user
|
|
1854
|
-
* id.
|
|
1969
|
+
* Forget the current person: clear the scope user, mint a fresh anonymous
|
|
1970
|
+
* id, forget the last identified user, and rotate the session id.
|
|
1855
1971
|
*
|
|
1856
1972
|
* MUST BE CALLED ON LOGOUT. Without it, the next anonymous visitor on this
|
|
1857
1973
|
* browser reuses the persisted anon id, and a later identify() aliases their
|
|
1858
|
-
* activity to the previous account server-side, permanently.
|
|
1974
|
+
* activity to the previous account server-side, permanently. Rotating the
|
|
1975
|
+
* session id matters too: the server's `bump_session` is last-write-wins on
|
|
1976
|
+
* `distinct_id`, so without rotation one `sessions` row could otherwise
|
|
1977
|
+
* serially represent two different people and record only whichever wrote
|
|
1978
|
+
* last.
|
|
1859
1979
|
*/
|
|
1860
1980
|
reset() {
|
|
1861
1981
|
this.scope.setUser(null);
|
|
1862
1982
|
resetAnonymousId();
|
|
1983
|
+
clearLastIdentified();
|
|
1984
|
+
rotateSessionId();
|
|
1863
1985
|
this.anonUsed = false;
|
|
1864
1986
|
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Prepare for an `identify()`; returns the `anonymous_id` to send.
|
|
1989
|
+
*
|
|
1990
|
+
* When a DIFFERENT user identifies than last time, the current anon id
|
|
1991
|
+
* belongs to the previous person and is already burned server-side, so it is
|
|
1992
|
+
* replaced before anything else happens and `null` is sent instead of a
|
|
1993
|
+
* cross-user alias. This cannot repair events already sent under the burned
|
|
1994
|
+
* alias — nothing can — but it bounds a forgotten `reset()` to one guest
|
|
1995
|
+
* window instead of every future one.
|
|
1996
|
+
*
|
|
1997
|
+
* `id` is coerced with `String()` before comparing/persisting: a plain-JS
|
|
1998
|
+
* caller can pass a number (`Sauron.identify(user.id)`), and `Storage`
|
|
1999
|
+
* itself applies `ToString` on write — so comparing an un-coerced `id`
|
|
2000
|
+
* against a value that already round-tripped through storage would treat
|
|
2001
|
+
* the SAME numeric user as a switch on every single call. The comparison
|
|
2002
|
+
* against `last` is an explicit `!== null` (not a truthiness check) so an
|
|
2003
|
+
* app that (unusually) identifies with `''` still has a later, different id
|
|
2004
|
+
* correctly detected as a real switch — a falsy string is not "no identity
|
|
2005
|
+
* yet". `last`/the persisted value are digests, not the raw id — see
|
|
2006
|
+
* `hashIdentity`.
|
|
2007
|
+
*/
|
|
2008
|
+
prepareIdentify(id) {
|
|
2009
|
+
const digest = hashIdentity(String(id));
|
|
2010
|
+
const last = getLastIdentified();
|
|
2011
|
+
if (last !== null && last !== digest) {
|
|
2012
|
+
resetAnonymousId();
|
|
2013
|
+
rotateSessionId();
|
|
2014
|
+
this.anonUsed = false;
|
|
2015
|
+
}
|
|
2016
|
+
setLastIdentified(digest);
|
|
2017
|
+
return this.getAnonymousId();
|
|
2018
|
+
}
|
|
1865
2019
|
/** Stamp a fresh envelope (new `sent_at`, current context) around `items`. */
|
|
1866
2020
|
makeEnvelope(items) {
|
|
1867
2021
|
const header = {
|
|
@@ -2174,6 +2328,6 @@ var Sauron = {
|
|
|
2174
2328
|
};
|
|
2175
2329
|
var index_default = Sauron;
|
|
2176
2330
|
|
|
2177
|
-
export { DsnError, SDK_NAME, SDK_VERSION, Sauron, SauronClient, addBreadcrumb2 as addBreadcrumb, buildEnvelope, cancelWorkflow2 as cancelWorkflow, captureException2 as captureException, captureMessage2 as captureMessage, close, index_default as default, endWorkflow2 as endWorkflow, flush, getClient, getScreen2 as getScreen, getWorkflow2 as getWorkflow, identify2 as identify, init2 as init, isInAppFrame, parseDsn, parseError, parseStackString, reset, setContext, setExtra, setScreen2 as setScreen, setTag, setTags, setUser, startWorkflow2 as startWorkflow, track2 as track, trackTransaction2 as trackTransaction };
|
|
2331
|
+
export { DsnError, MAX_TRANSACTION_EXTRA_BYTES, SDK_NAME, SDK_VERSION, Sauron, SauronClient, addBreadcrumb2 as addBreadcrumb, buildEnvelope, cancelWorkflow2 as cancelWorkflow, capTransactionExtra, captureException2 as captureException, captureMessage2 as captureMessage, close, index_default as default, endWorkflow2 as endWorkflow, flush, getClient, getScreen2 as getScreen, getWorkflow2 as getWorkflow, identify2 as identify, init2 as init, isInAppFrame, parseDsn, parseError, parseStackString, reset, setContext, setExtra, setScreen2 as setScreen, setTag, setTags, setUser, startWorkflow2 as startWorkflow, track2 as track, trackTransaction2 as trackTransaction };
|
|
2178
2332
|
//# sourceMappingURL=index.js.map
|
|
2179
2333
|
//# sourceMappingURL=index.js.map
|