@sazabi/react-native 0.0.0-stage → 0.1.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.
@@ -0,0 +1,73 @@
1
+ import { type RandomBytes } from "./random.js";
2
+ import type { ClientEvent } from "./types.js";
3
+ /**
4
+ * Pointer episodes: one row per contact from down to up, for mouse, touch,
5
+ * and pen alike. A binding feeds raw contacts in (DOM pointer events in the
6
+ * browser, the responder's touch arrays on mobile) and the tracker emits the
7
+ * episode rows plus the derived `click` and `rage_click` rows. Movement is
8
+ * sampled, never streamed: a position sample every `sampleIntervalMs` is
9
+ * packed into the row, and a contact that outlives `partialIntervalMs` emits
10
+ * a partial row at that cadence so a long scroll is not a blind spot.
11
+ */
12
+ export type PointerType = "mouse" | "touch" | "pen";
13
+ export interface PointerTarget {
14
+ /** Developer-authored name for the widget under the pointer, if any. */
15
+ name?: string;
16
+ /** Stable-ish identifier: a selector on the web, a component path on mobile. */
17
+ id?: string;
18
+ /** Element tag or component kind. */
19
+ tag?: string;
20
+ /** Whether a tap here is on something that should respond. */
21
+ interactive: boolean;
22
+ }
23
+ export interface PointerContact {
24
+ id: number | string;
25
+ type: PointerType;
26
+ primary: boolean;
27
+ x: number;
28
+ y: number;
29
+ pressure?: number;
30
+ /**
31
+ * The button that started a mouse contact (DOM `button`: 0 primary, 1
32
+ * auxiliary, 2 secondary). Absent for touch and pen. Only a primary-button
33
+ * contact can derive a click: a short right or middle press is an episode
34
+ * without a tap, matching the host's `contextmenu` and `auxclick`.
35
+ */
36
+ button?: number;
37
+ target?: PointerTarget;
38
+ }
39
+ export interface DerivedTap {
40
+ x: number;
41
+ y: number;
42
+ target?: PointerTarget;
43
+ /** Absent for a tap no contact produced (keyboard activation, script). */
44
+ gestureId?: string;
45
+ }
46
+ export interface PointerTrackerOptions {
47
+ emit(event: ClientEvent): void;
48
+ randomBytes: RandomBytes;
49
+ /** Spacing of packed position samples. Default 1000. */
50
+ sampleIntervalMs?: number;
51
+ /** Cadence of partial rows for long contacts. Default 10000. */
52
+ partialIntervalMs?: number;
53
+ /** Every derived tap, so a binding can run its own dead-tap detection. */
54
+ onTap?(tap: DerivedTap): void;
55
+ }
56
+ export interface PointerTracker {
57
+ down(contact: PointerContact): void;
58
+ move(id: number | string, x: number, y: number, pressure?: number): void;
59
+ up(id: number | string, x: number, y: number): void;
60
+ cancel(id: number | string): void;
61
+ /**
62
+ * A click that no pointer contact produced: keyboard activation or a
63
+ * script calling `click()`. Emits the `click` row and runs the same rage
64
+ * and dead-tap derivation, with no episode behind it.
65
+ */
66
+ tap(tap: {
67
+ x: number;
68
+ y: number;
69
+ target?: PointerTarget;
70
+ }): void;
71
+ teardown(): void;
72
+ }
73
+ export declare const createPointerTracker: (options: PointerTrackerOptions) => PointerTracker;
@@ -0,0 +1,11 @@
1
+ export type RandomBytes = (length: number) => Uint8Array;
2
+ /**
3
+ * Random bytes for session, trace, and gesture ids. Web Crypto where the host
4
+ * has it; a seeded fallback where it does not (React Native without a
5
+ * `crypto.getRandomValues` polyfill). The ids need uniqueness, not secrecy,
6
+ * so the fallback is acceptable there and is never used where crypto exists.
7
+ */
8
+ export declare const createRandomBytes: () => RandomBytes;
9
+ export declare const toHex: (bytes: Uint8Array) => string;
10
+ export declare const randomHex: (randomBytes: RandomBytes, byteLength: number) => string;
11
+ export declare const newPrefixedId: (randomBytes: RandomBytes, prefix: string) => string;
@@ -0,0 +1,37 @@
1
+ import type { ClientEvent, EventAttributes } from "./types.js";
2
+ /**
3
+ * What an `sdk_health` row reports: the SDK's own delivery trouble, so a
4
+ * misconfigured or unreachable app is visible to Sazabi before the customer
5
+ * reports it. Each value is a bounded enumeration, never free text.
6
+ *
7
+ * - `intake_rejected`: the intake refused a batch for good (a 4xx other
8
+ * than 408 and 429); `app.sdk.http_status` carries the status.
9
+ * - `intake_unreachable`: uploads failed on the network (a dropped
10
+ * connection, a timeout) and the intake later answered; `app.sdk.attempts`
11
+ * is how many backoff windows the outage spanned (one per failed try,
12
+ * with concurrent uploads failing together counted once).
13
+ * - `outbox_discarded`: stored crash reports were dropped, by the caps or
14
+ * because they were not valid reports; `app.sdk.outbox_discarded` counts
15
+ * what is known lost, and `app.sdk.outbox_unsaved` what a saved queue had
16
+ * left out at the caps (possible losses: their live upload may have
17
+ * finished before the app was killed) (mobile only).
18
+ * - `outbox_persist_failed`: the storage adapter refused a write of the
19
+ * crash outbox or the saved queue (mobile only).
20
+ */
21
+ export type SdkHealthDiagnostic = "intake_rejected" | "intake_unreachable" | "outbox_discarded" | "outbox_persist_failed";
22
+ export interface SdkHealthReport {
23
+ diagnostic: SdkHealthDiagnostic;
24
+ /** The counters of the condition (`app.sdk.http_status`, `app.sdk.attempts`, ...). */
25
+ attributes?: EventAttributes;
26
+ }
27
+ export type SdkHealthReporter = (report: SdkHealthReport) => void;
28
+ export declare const SDK_DIAGNOSTIC_KEY = "app.sdk.diagnostic";
29
+ /**
30
+ * Turns health reports into `sdk_health` rows, at most one per diagnostic
31
+ * for the life of the reporter (one SDK session). The bound is what keeps
32
+ * the SDK from storming its own intake: a health row the intake refuses is
33
+ * a rejection like any other and is never reported again, and a flapping
34
+ * network produces one row per launch, not one per outage. Never throws
35
+ * into the host.
36
+ */
37
+ export declare const createSdkHealthReporter: (emit: (event: ClientEvent) => void) => SdkHealthReporter;
@@ -0,0 +1,44 @@
1
+ import { type RandomBytes } from "./random.js";
2
+ /**
3
+ * Synchronous key-value access the session machine reads through on every
4
+ * call. A browser binding backs it with `localStorage` (so sibling tabs
5
+ * converge) and `sessionStorage`; a mobile binding backs it with an
6
+ * in-memory mirror hydrated from its storage adapter and written through.
7
+ */
8
+ export interface SessionStore {
9
+ get(key: string): string | null;
10
+ set(key: string, value: string): void;
11
+ remove(key: string): void;
12
+ }
13
+ /** A store that lives for the process only: the fallback on every host. */
14
+ export declare const createMemoryStore: () => SessionStore;
15
+ export interface SessionManager {
16
+ getSessionId(): string;
17
+ getWindowId(): string;
18
+ getDistinctId(): string | undefined;
19
+ /**
20
+ * Set the client-asserted identity. Switching from one identity directly
21
+ * to a different one rotates the session and window ids so two users on a
22
+ * shared device never thread into one timeline.
23
+ */
24
+ setDistinctId(distinctId: string): void;
25
+ /** Logout: clear identity and rotate both session and window ids. */
26
+ reset(): void;
27
+ /** Record user activity; may rotate the session on idle/max-age expiry. */
28
+ touch(): void;
29
+ }
30
+ export interface SessionManagerOptions {
31
+ /** Visit-scoped state: session id and identity. */
32
+ store: SessionStore;
33
+ /** Window-scoped state: one browser tab, or one app launch. */
34
+ windowStore: SessionStore;
35
+ randomBytes: RandomBytes;
36
+ }
37
+ /**
38
+ * Session identity. `session.id` is the visit (last-writer-wins on rotation
39
+ * races between windows; a split session is cosmetic, not data loss).
40
+ * `session.window_id` is this window. `session.distinct_id` is read through
41
+ * the store so identify/reset in one window is visible to siblings on their
42
+ * next event.
43
+ */
44
+ export declare const createSessionManager: (options: SessionManagerOptions) => SessionManager;
@@ -0,0 +1,33 @@
1
+ import { type RandomBytes } from "./random.js";
2
+ export interface RequestTraceContext {
3
+ /** Headers to inject (`traceparent`, and `tracestate` when present). */
4
+ headers: Record<string, string>;
5
+ traceId: string;
6
+ spanId: string;
7
+ }
8
+ export interface TraceContextManager {
9
+ /** Trace context for one outgoing request, minted at request time. */
10
+ forRequest(): RequestTraceContext;
11
+ }
12
+ /** The subset of an OTel SpanContext this module consumes. */
13
+ export interface HostSpanContext {
14
+ traceId: string;
15
+ spanId: string;
16
+ traceFlags: number;
17
+ traceState?: {
18
+ serialize(): string;
19
+ };
20
+ }
21
+ export declare const readHostSpanContext: () => HostSpanContext | undefined;
22
+ /**
23
+ * W3C trace context for outgoing requests: hand-rolled header formatting
24
+ * (equivalence-tested against OTel's W3CTraceContextPropagator), zero
25
+ * runtime dependencies. If the host app runs its own OTel and has an active
26
+ * span, that span's context (including its flags and tracestate) is reused
27
+ * so we join the customer's trace instead of forking a new one; otherwise a
28
+ * fresh root is minted with the sampled flag defaulting to off (`-00`).
29
+ */
30
+ export declare const createTraceContextManager: (options: {
31
+ sampledFlag: boolean;
32
+ randomBytes: RandomBytes;
33
+ }) => TraceContextManager;
@@ -0,0 +1,142 @@
1
+ import type { SdkHealthReport } from "./sdk-health.js";
2
+ import type { ClientEvent, EventAttributes, EventSeverity } from "./types.js";
3
+ type OtlpAnyValue = {
4
+ stringValue: string;
5
+ } | {
6
+ intValue: string;
7
+ } | {
8
+ doubleValue: number;
9
+ } | {
10
+ boolValue: boolean;
11
+ };
12
+ interface OtlpKeyValue {
13
+ key: string;
14
+ value: OtlpAnyValue;
15
+ }
16
+ export interface OtlpLogRecord {
17
+ timeUnixNano: string;
18
+ severityNumber: number;
19
+ severityText: EventSeverity;
20
+ body: {
21
+ stringValue: string;
22
+ };
23
+ attributes: OtlpKeyValue[];
24
+ /** OTLP/JSON encodes trace/span ids as hex (unlike proto3 JSON's base64). */
25
+ traceId?: string;
26
+ spanId?: string;
27
+ }
28
+ export declare const encodeLogRecord: (event: ClientEvent, baseAttributes: EventAttributes) => OtlpLogRecord;
29
+ /**
30
+ * The slice of `fetch` the transport uses. Declared structurally so the core
31
+ * needs no DOM lib; every host's real `fetch` satisfies it.
32
+ */
33
+ export type FetchLike = (url: string, init: {
34
+ method: "POST";
35
+ headers: Record<string, string>;
36
+ body: string;
37
+ keepalive: boolean;
38
+ /** An `AbortSignal` where the host has `AbortController`; aborts a timed-out upload. */
39
+ signal?: unknown;
40
+ }) => Promise<unknown>;
41
+ /**
42
+ * UTF-8 byte length of a string, without `TextEncoder` (Hermes has none).
43
+ * Request budgets are bytes on the wire; a string's `length` counts UTF-16
44
+ * units and undercounts non-ASCII text up to threefold.
45
+ */
46
+ export declare const utf8Length: (text: string) => number;
47
+ /** Stamped on the first row delivered after events were dropped: how many. */
48
+ export declare const DROPPED_EVENTS_KEY = "app.sdk.dropped_events";
49
+ /**
50
+ * Names the sending SDK on every upload, `<package>/<version>`: the input
51
+ * for the intake gateway's per-SDK request label (a separate change), which
52
+ * can then count a request by SDK even when it rejects it before the key is
53
+ * known. The gateway's CORS allow-list admits it, so a browser preflight
54
+ * passes.
55
+ */
56
+ export declare const SDK_HEADER = "x-sazabi-sdk";
57
+ export interface TransportOptions {
58
+ /** Full OTLP logs endpoint, e.g. `https://<key>.<region>.intake.<domain>/v1/logs`. */
59
+ url: string;
60
+ publicKey: string;
61
+ resourceAttributes: EventAttributes;
62
+ /** OTLP instrumentation scope: the emitting SDK's name and version. */
63
+ scope: {
64
+ name: string;
65
+ version: string;
66
+ };
67
+ /**
68
+ * The fetch to deliver with, captured before the SDK's network patch
69
+ * installs, so exporter traffic can never recurse through our own wrapper
70
+ * or appear in the captured event stream.
71
+ */
72
+ fetchImpl: FetchLike;
73
+ flushIntervalMs: number;
74
+ maxBatchBytes: number;
75
+ maxQueuedEvents: number;
76
+ /** Reports a dropped batch or a full queue; called once per reason. */
77
+ warn?(message: string): void;
78
+ /**
79
+ * The SDK's own delivery health: a batch the intake refused for good
80
+ * (`intake_rejected`, with the status) and uploads that failed on the
81
+ * network before one got through (`intake_unreachable`, with the number
82
+ * of failed tries). The binding turns these into `sdk_health` rows, once
83
+ * per condition per session.
84
+ */
85
+ onHealth?(report: SdkHealthReport): void;
86
+ /**
87
+ * Called when an upload settles with nothing queued, nothing in flight, and
88
+ * every row of the last `snapshotEnvelopes` delivered or refused for good:
89
+ * a saved row evicted from the full queue, or given up by `shutdown()`
90
+ * while the intake was unreachable, keeps it from firing until the next
91
+ * snapshot, so the host's saved copy stays.
92
+ */
93
+ onIdle?(): void;
94
+ }
95
+ export interface Transport {
96
+ enqueue(record: OtlpLogRecord): void;
97
+ /**
98
+ * Deliver everything queued. `keepalive: true` is for browser unload paths;
99
+ * each request body must stay inside the browser's ~64 KiB keepalive
100
+ * budget, which enqueue-side chunking guarantees via `maxBatchBytes`. Hosts
101
+ * without keepalive ignore the flag.
102
+ */
103
+ flush(options?: {
104
+ keepalive?: boolean;
105
+ }): Promise<void>;
106
+ /**
107
+ * Encode everything queued into standalone request bodies without sending,
108
+ * leaving the queue empty. The mobile crash outbox persists these and posts
109
+ * them on the next launch; `recordAttributes` are appended to every record
110
+ * (the outbox stamps its replay id there).
111
+ */
112
+ drainEnvelopes(recordAttributes?: EventAttributes): string[];
113
+ /**
114
+ * Encode everything queued into standalone request bodies and keep it
115
+ * queued. `recordAttributes` are stamped on the queued records themselves,
116
+ * so the live upload and the saved copy carry the same values (the mobile
117
+ * SDK saves its queue this way when the app leaves the foreground). Rows an
118
+ * earlier snapshot saved whose upload is still in flight are included as
119
+ * they were sent: the copy this one replaces held them.
120
+ */
121
+ snapshotEnvelopes(recordAttributes: EventAttributes): string[];
122
+ /**
123
+ * While held, `enqueue` never flushes on its own, neither on the byte
124
+ * budget nor by scheduling the timer: the mobile crash path holds the
125
+ * transport while it stamps the fatal row and everything buffered before
126
+ * it, then drains the whole batch into the outbox. Without this, a fatal
127
+ * row that tips the queue over the budget would be flushed out of the queue
128
+ * into a request the dying process never completes.
129
+ */
130
+ holdFlushes(hold: boolean): void;
131
+ /**
132
+ * Deliver a previously drained envelope. Resolves `true` once the envelope
133
+ * is settled: the intake accepted it (2xx) or refused it for good (a 4xx
134
+ * other than 408 and 429, dropped with a warning). Resolves `false` when it
135
+ * is worth sending again (408, 429, 5xx, a network failure, a timeout), and
136
+ * never rejects, so a persisted envelope is deleted only once settled.
137
+ */
138
+ sendEnvelope(body: string): Promise<boolean>;
139
+ shutdown(): Promise<void>;
140
+ }
141
+ export declare const createTransport: (options: TransportOptions) => Transport;
142
+ export {};
@@ -0,0 +1,76 @@
1
+ export type AttributeValue = string | number | boolean;
2
+ export type EventAttributes = Record<string, AttributeValue | undefined>;
3
+ export type EventSeverity = "DEBUG" | "INFO" | "WARN" | "ERROR";
4
+ /**
5
+ * The shared `app.event_type` vocabulary. Every value means the same thing
6
+ * on every host; a binding that cannot produce one simply never emits it.
7
+ */
8
+ export type ClientEventType = "navigation" | "pointer" | "click" | "rage_click" | "dead_click" | "input" | "network" | "error" | "custom" | "log" | "lifecycle"
9
+ /** The SDK's own delivery health, one row per condition per session (`sdk-health.ts`). */
10
+ | "sdk_health";
11
+ /** An SDK-internal semantic event, before OTLP encoding. */
12
+ export interface ClientEvent {
13
+ type: ClientEventType;
14
+ body: string;
15
+ severity: EventSeverity;
16
+ timeUnixMs: number;
17
+ /** Set on network events when trace context was injected into the request. */
18
+ traceId?: string;
19
+ spanId?: string;
20
+ attributes: EventAttributes;
21
+ }
22
+ export type ConsoleCaptureLevel = "error" | "warn" | "info" | "debug";
23
+ export interface ConsoleCaptureConfig {
24
+ /** Mirror console output into the event stream. Default true. */
25
+ capture?: boolean;
26
+ /** Console levels to mirror. Default ["error", "warn"]. */
27
+ levels?: ConsoleCaptureLevel[];
28
+ }
29
+ export interface PointerCaptureConfig {
30
+ /** Emit one `pointer` episode per contact, plus derived taps. Default true. */
31
+ capture?: boolean;
32
+ /** Spacing of the position samples packed into an episode. Default 1000. */
33
+ sampleIntervalMs?: number;
34
+ /**
35
+ * A contact open longer than this emits a partial row at this cadence so a
36
+ * long scroll or drag is not a blind spot until release. Default 10000.
37
+ */
38
+ partialIntervalMs?: number;
39
+ }
40
+ /** Network options every binding shares; a binding may extend the shape. */
41
+ export interface NetworkCaptureConfig {
42
+ /** Emit network events. Default true. */
43
+ capture?: boolean;
44
+ /** Inject W3C `traceparent` into allowlisted requests. Default true. */
45
+ propagateTraceContext?: boolean;
46
+ /**
47
+ * Response headers probed (in order) for a platform request id to attach
48
+ * as `app.network.request_id`, an exact join key against platform logs.
49
+ */
50
+ requestIdHeaders?: string[];
51
+ /**
52
+ * Requests eligible for `traceparent` injection. A string is a host
53
+ * (`api.example.com`), a domain with every subdomain (`.example.com`), or
54
+ * an origin with an optional path prefix (`https://api.example.com/v2`); a
55
+ * RegExp is tested against the full URL. An empty list falls back to the
56
+ * binding's default policy (same-origin in the browser, every request on
57
+ * mobile).
58
+ */
59
+ allowlist?: (string | RegExp)[];
60
+ /**
61
+ * Query parameter names whose values are replaced with `[redacted]` in
62
+ * every recorded URL: `url.full` of requests and navigations, the screen or
63
+ * page URL stamped on each event (`app.screen.url`), and in the browser the
64
+ * referrer (`app.navigation.from`). Beside the default list (token, access_token, refresh_token,
65
+ * id_token, api_key, apikey, key, secret, password, signature, sig,
66
+ * x-amz-signature, x-amz-credential, x-goog-signature, sentry_key,
67
+ * authorization, auth, session, sid, code). Strings match a name exactly,
68
+ * case-insensitively; RegExps are tested against the name.
69
+ */
70
+ redactQueryParams?: (string | RegExp)[];
71
+ /**
72
+ * Set the sampled flag (`-01`) on SDK-minted trace roots. Default false:
73
+ * the sampled bit steers customers' ParentBased backend samplers.
74
+ */
75
+ sampledFlag?: boolean;
76
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Absolute-URL parsing with no `URL` global. React Native's built-in `URL`
3
+ * throws "not implemented" for `hostname`, `pathname`, and `origin`, and the
4
+ * core must work there unpolyfilled, so the few fields the SDKs read are
5
+ * parsed here. Only absolute http(s)-style URLs resolve; a binding resolves
6
+ * relative references against its own base before calling in.
7
+ */
8
+ export interface ParsedUrl {
9
+ /** e.g. `https:` */
10
+ protocol: string;
11
+ /** host with port, no credentials */
12
+ host: string;
13
+ hostname: string;
14
+ pathname: string;
15
+ search: string;
16
+ hash: string;
17
+ origin: string;
18
+ /** Normalized absolute URL with credentials stripped. */
19
+ href: string;
20
+ }
21
+ export declare const parseAbsoluteUrl: (raw: string) => ParsedUrl | undefined;
22
+ /**
23
+ * Whether `url` is `base` or lies under it: the same origin (scheme, host,
24
+ * port) and, when the base has a path, that path as whole segments. A string
25
+ * prefix would also match `https://intake.example.com.evil.test/`.
26
+ */
27
+ export declare const isUnderBase: ({ url, base, }: {
28
+ url: string;
29
+ base: string;
30
+ }) => boolean;
@@ -0,0 +1,46 @@
1
+ import { type ClientEvent } from "./client-sdk-core/index.js";
2
+ import type { RejectionTracker } from "./types.js";
3
+ export interface ErrorCaptureOptions {
4
+ emit(event: ClientEvent): void;
5
+ /**
6
+ * Receives the fatal error's row instead of `emit`, before the previous
7
+ * handler runs: the crash path must stamp that row with the transport held
8
+ * so it cannot be flushed out of the queue before the outbox drains it.
9
+ */
10
+ onFatal(event: ClientEvent): void;
11
+ unhandledRejections: boolean;
12
+ /** The tracker the host says held the slot before `init()`; runs after ours. */
13
+ previousRejectionTracker?: RejectionTracker;
14
+ }
15
+ export interface ErrorCaptureHandle {
16
+ teardown(): void;
17
+ }
18
+ /**
19
+ * Where `<SazabiRoot>` reports a render or commit error it caught. React 19's
20
+ * renderer hands an error no boundary caught straight to React Native's
21
+ * `ExceptionsManager` (the renderer's `onUncaughtError`), never through
22
+ * `ErrorUtils`, and the release app dies there. The root's boundary records
23
+ * the error through the process-wide sink first (so a root from a second
24
+ * copy of the module reaches the running instance), then rethrows it so
25
+ * React Native's fatal path still runs unchanged.
26
+ */
27
+ export declare const captureRenderError: (error: unknown) => void;
28
+ /**
29
+ * Uncaught errors route through React Native's global handler; a fatal one
30
+ * terminates the app once the handler returns, which is why the crash outbox
31
+ * runs inside it. The previous handler (React Native's red box in
32
+ * development, a crash reporter's hook) always runs after ours. Render
33
+ * errors reach the same fatal path from `<SazabiRoot>` (`captureRenderError`);
34
+ * a crash reported more than once in one task is recorded once.
35
+ *
36
+ * Hermes exposes one promise-rejection tracker slot and no way to read it:
37
+ * `HermesInternal` is frozen (its methods non-writable and non-configurable,
38
+ * the object non-extensible) and the global that names it is non-writable
39
+ * and non-configurable too (`lib/VM/JSLib/HermesInternal.cpp`,
40
+ * `lib/VM/JSLib/GlobalObject.cpp`), so the installer cannot be wrapped from
41
+ * JavaScript and whoever held the slot before `init()` is unknowable. The
42
+ * host hands that tracker over as `errors.previousRejectionTracker` and ours
43
+ * runs it after recording; `errors.unhandledRejections: false` leaves the
44
+ * slot alone. Without either, a development build is told once per process.
45
+ */
46
+ export declare const installErrorCapture: (options: ErrorCaptureOptions) => ErrorCaptureHandle;
package/dist/host.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { type EventAttributes } from "./client-sdk-core/index.js";
2
+ /**
3
+ * react-native-web renders in a real browser, where `@sazabi/browser` owns
4
+ * capture. This SDK installs nothing there.
5
+ */
6
+ export declare const isWebHost: () => boolean;
7
+ /**
8
+ * Resource attributes the host can report without a native module: OTEL
9
+ * `os.*` from `Platform`, the `device.*` fields Android's
10
+ * `Platform.constants` carries (`Brand`, `Manufacturer`, `Model`), and on iOS
11
+ * the manufacturer plus `device.type` from `interfaceIdiom` (`phone`, `pad`,
12
+ * `tv`, `carplay`, `mac`, `vision`). iOS reports no model through JavaScript.
13
+ * `os.name` stays `Platform.OS` (`ios`, `android`) on both, so queries match
14
+ * one casing; iPadOS shows as `device.type: "pad"`.
15
+ */
16
+ export declare const hostResourceAttributes: () => EventAttributes;
17
+ export declare const isDevHost: () => boolean;
18
+ /**
19
+ * The Metro (or Expo) development server this bundle was loaded from, as
20
+ * `<origin>/`, read from `SourceCode.scriptURL` the way React Native's own
21
+ * `getDevServer()` does. Only under `__DEV__`, and only for an http(s)
22
+ * bundle URL: a release bundle loads from a file.
23
+ */
24
+ export declare const devServerOrigin: () => string | undefined;
25
+ /**
26
+ * A request URL as the core expects it: absolute, credentials stripped.
27
+ * Relative references are not requests on a native host, so they resolve to
28
+ * nothing and are neither captured nor injected.
29
+ */
30
+ export declare const resolveUrl: (raw: string) => string | undefined;
@@ -0,0 +1,52 @@
1
+ import { type EventSeverity } from "./client-sdk-core/index.js";
2
+ import { SazabiRoot } from "./interactions.js";
3
+ import { trackNavigation } from "./navigation.js";
4
+ import type { ReactNativeSdkConfig } from "./types.js";
5
+ export type { AttributeValue, ClientEvent, ClientEventType, ConsoleCaptureConfig, ConsoleCaptureLevel, ErrorCaptureConfig, RejectionTracker, EventAttributes, EventSeverity, NavigationContainerRefLike, NetworkCaptureConfig, PointerCaptureConfig, ReactNativeSdkConfig, StorageAdapter, } from "./types.js";
6
+ export { SazabiRoot, trackNavigation };
7
+ declare module "react-native" {
8
+ interface ViewProps {
9
+ /** Names this element in touch rows (`app.widget.name`), ahead of `accessibilityLabel` and `testID`. */
10
+ sazabiName?: string;
11
+ /** Keeps the name of this element and everything inside it out of touch rows. */
12
+ sazabiMask?: boolean;
13
+ }
14
+ interface TextProps {
15
+ /** Names this element in touch rows (`app.widget.name`), ahead of `accessibilityLabel` and `testID`. */
16
+ sazabiName?: string;
17
+ /** Keeps the name of this element and everything inside it out of touch rows. */
18
+ sazabiMask?: boolean;
19
+ }
20
+ interface TouchableWithoutFeedbackProps {
21
+ /** Names this element in touch rows (`app.widget.name`), ahead of `accessibilityLabel` and `testID`. */
22
+ sazabiName?: string;
23
+ /** Keeps the name of this element and everything inside it out of touch rows. */
24
+ sazabiMask?: boolean;
25
+ }
26
+ }
27
+ export { SDK_VERSION } from "./version.js";
28
+ /**
29
+ * Initialize the SDK. Idempotent: repeat calls are ignored. Installs nothing
30
+ * under react-native-web (use `@sazabi/browser` there). When `consent` is
31
+ * provided, nothing is installed until it resolves true.
32
+ */
33
+ export declare const init: (config: ReactNativeSdkConfig) => void;
34
+ /** Set the client-asserted user identity (`session.distinct_id`). */
35
+ export declare const identify: (distinctId: string, traits?: Record<string, unknown>) => void;
36
+ /**
37
+ * Logout: clear the client-asserted identity and rotate both session and
38
+ * window ids, so later activity on this device never threads into the
39
+ * previous user's timeline.
40
+ */
41
+ export declare const reset: () => void;
42
+ /** Emit a custom mark into the session event stream. */
43
+ export declare const addEvent: (name: string, attributes?: Record<string, unknown>) => void;
44
+ /**
45
+ * Emit an application log line (`app.event_type: "log"`) with session context
46
+ * attached. Drops silently before init/consent, like all capture.
47
+ */
48
+ export declare const log: (severity: EventSeverity, message: string, metadata?: Record<string, unknown>) => void;
49
+ /** Force-flush queued events. */
50
+ export declare const flush: () => Promise<void>;
51
+ /** Tear down all patches/listeners (restoring originals) and flush. */
52
+ export declare const shutdown: () => Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import{a as K,b as Z,c as ie,d as tt,e as he,f as be,g as nt,h as rt,i as ye,j as ot,k as G,l as it,m as st,n as at,o as dt,p as ct,q as lt,r as ut,s as j,t as ft,u as pt,w as gt,x as vt}from"./chunk-c8j7sr2j.js";var d=globalThis.__sazabiReactNative??={initialized:!1,warnedWebHost:!1,warnedRejectionSlot:!1,generation:0,pendingReset:!1,screen:{}};var ne="@sazabi/react-native",V="0.1.0",H="[@sazabi/react-native]";var ke=()=>globalThis.HermesInternal,Ee=(e)=>{try{e()}catch{}},mt=(e)=>{try{let t=typeof e==="string"?e:typeof e==="object"&&e!==null?JSON.stringify(e)??String(e):String(e);return K(t)}catch{try{return K(Object.prototype.toString.call(e))}catch{return"[unreadable thrown value]"}}},re=(e,t)=>{let n=e instanceof Error?e:void 0,r=(s)=>{try{let l=n?.[s];return typeof l==="string"?K(l):void 0}catch{return}},i=r("name")||(t==="unhandledrejection"?"UnhandledRejection":"Error"),o=n?r("message")??"":mt(e);return{type:"error",body:K(`${i}: ${o}`,512),severity:"ERROR",timeUnixMs:Date.now(),attributes:{"exception.type":i,"exception.message":o,"exception.stacktrace":r("stack")??"","app.error.kind":t}}},we=(e)=>{try{d.renderErrorSink?.(e)}catch{}},ht=(e)=>{if(e instanceof Error)return e.stack||`${e.name}: ${e.message}`;if(typeof e==="string")return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}},oe=(e)=>{if(!j())return;try{let t=e();ie(()=>console.warn(t))}catch{}},Re=(e)=>{let t=globalThis.ErrorUtils,n=t?.getGlobalHandler?.(),r=!0,i=!1,o=(l)=>{if(!r||i)return;i=!0,setTimeout(()=>{i=!1},0),e.onFatal(re(l,"fatal"))},s=(l,u)=>{let c=()=>n?.(l,u);if(!r){c();return}try{if(u)o(l);else e.emit(re(l,"uncaught"))}catch{}ie(c)};if(t?.setGlobalHandler?.(s),d.renderErrorSink=o,e.unhandledRejections){let l=e.previousRejectionTracker;if(!l&&!d.warnedRejectionSlot&&j()&&typeof ke()?.enablePromiseRejectionTracker==="function")d.warnedRejectionSlot=!0,oe(()=>`${H} init() takes over Hermes's only promise rejection tracker slot, and a tracker another library installed before it cannot be seen. If one exists, pass errors.previousRejectionTracker to keep it running, or errors.unhandledRejections: false to leave the slot alone.`);let u={allRejections:!0,onUnhandled:(c,f)=>{if(r)try{e.emit(re(f,"unhandledrejection"))}catch{}if(l){Ee(()=>l.onUnhandled(c,f));return}oe(()=>`Possible unhandled promise rejection (id: ${c}):
2
+ ${ht(f)}`)},onHandled:(c)=>{if(l){Ee(()=>l.onHandled?.(c));return}oe(()=>`Promise rejection handled (id: ${c})
3
+ This means you can ignore any previous messages of the form "Possible unhandled promise rejection (id: ${c}):"`)}};try{ke()?.enablePromiseRejectionTracker?.(u)}catch{}}return{teardown(){if(r=!1,d.renderErrorSink===o)d.renderErrorSink=void 0;try{if(n&&t?.getGlobalHandler?.()===s)t.setGlobalHandler?.(n)}catch{}}}};import{Component as yt,createElement as bt}from"react";import{View as St}from"react-native";var se=(e)=>{d.tracker=e},kt=60,Et=3,xe=64,wt="sazabiName",Rt="sazabiMask",Te=new Set(["button","link","tab","menuitem","checkbox","radio","switch","togglebutton","imagebutton","searchbox"]),X=(e)=>{if(typeof e!=="string")return;let t=e.replace(/\s+/g," ").trim();if(!t)return;return t.length>xe?`${t.slice(0,xe)}…`:t},xt=(e)=>{let t=e.type??e.elementType;if(typeof t==="function"){let n=t;return X(n.displayName||n.name)}if(typeof t==="object"&&t!==null){let n=t.render??t.type;if(typeof n==="function"){let r=n;return X(r.displayName||r.name)}}return},Tt=(e)=>{try{let t=e;if(!t||typeof t!=="object")return;let n,r=!1,i=!1,o=[];for(let s=0;t&&s<kt;s+=1){let l=t.memoizedProps??void 0;if(l){if(l[Rt])r=!0;if(n===void 0)n=X(l[wt])??X(l.accessibilityLabel)??X(l.testID);if(typeof l.onPress==="function"||typeof l.onPressIn==="function"||typeof l.accessibilityRole==="string"&&Te.has(l.accessibilityRole)||typeof l.role==="string"&&Te.has(l.role))i=!0}if(o.length<Et){let u=xt(t);if(u&&u!=="SazabiRoot")o.unshift(u)}t=t.return}return{id:o.length>0?o.join(" > "):void 0,name:r?void 0:n,tag:o[o.length-1],interactive:i}}catch{return}},Ct=(e)=>{let t=d.tracker;if(!t)return;try{let{changedTouches:n,touches:r}=e.nativeEvent,i=Tt(e._targetInst);for(let o of n)t.down({id:o.identifier,type:"touch",primary:r.length<=1,x:o.pageX,y:o.pageY,pressure:o.force||void 0,target:i})}catch{}},Pt=(e)=>{let t=d.tracker;if(!t)return;try{for(let n of e.nativeEvent.changedTouches)t.move(n.identifier,n.pageX,n.pageY,n.force||void 0)}catch{}},At=(e)=>{let t=d.tracker;if(!t)return;try{for(let n of e.nativeEvent.changedTouches)t.up(n.identifier,n.pageX,n.pageY)}catch{}},_t=(e)=>{let t=d.tracker;if(!t)return;try{for(let n of e.nativeEvent.changedTouches)t.cancel(n.identifier)}catch{}},Nt={flex:1};class Ce extends yt{static displayName="SazabiRoot";state={};static getDerivedStateFromError(e){return we(e),{error:{value:e}}}render(){let e=this.state.error;if(e)throw e.value;return bt(St,{style:Nt,collapsable:!1,onTouchStart:Ct,onTouchMove:Pt,onTouchEnd:At,onTouchCancel:_t},this.props.children)}}import{AppState as Pe}from"react-native";var Ae=(e)=>{let t=Pe.currentState,n=(i)=>{try{if(i===t)return;if(t=i,i!=="active"&&i!=="background"&&i!=="inactive")return;if(e.emit({type:"lifecycle",body:`lifecycle ${i}`,severity:"INFO",timeUnixMs:Date.now(),attributes:{"app.lifecycle.state":i}}),i!=="active")e.onBackground()}catch{}},r;try{r=Pe.addEventListener("change",n)}catch{}return{teardown(){try{r?.remove()}catch{}}}};var ae=()=>d.screen,de=(e,t)=>{d.navigationEmit=e,d.navigationRedactUrl=t},Ot=(e)=>{if(!e)return;let t=Object.keys(e);return t.length>0?t.join(","):void 0},It=(e)=>{let t=()=>{try{let r=e.getCurrentRoute();if(!r)return;let i=d.screen;if(r.key===i.key&&r.name===i.name&&r.path===i.path)return;d.screen={name:r.name,path:r.path,key:r.key},d.navigationEmit?.({type:"navigation",body:`navigation ${r.name}`,severity:"INFO",timeUnixMs:Date.now(),attributes:{"app.navigation.type":"screen","app.screen.name":r.name,"url.path":r.path===void 0?void 0:d.navigationRedactUrl?.(r.path)??r.path,"app.navigation.from":i.name,"app.navigation.params":Ot(r.params)}})}catch{}};t();let n;try{n=e.addListener("state",t)}catch{}return()=>{try{n?.()}catch{}}},_e=()=>{d.screen={},d.navigationEmit=void 0,d.navigationRedactUrl=void 0};var Ht=async(e,t)=>{try{let n=await e.getItem(t);return typeof n==="string"?n:null}catch{return null}},U=(e)=>typeof e?.then==="function",Ne=(e,t)=>{try{let n=e();return U(n)?Promise.resolve(n).then(t,()=>{return}):t(n)}catch{return}},Oe=(e,t)=>Ne(()=>e.getItem(t),(n)=>({value:typeof n==="string"?n:null})),Ie=(e)=>Ne(()=>e.getAllKeys?.(),(t)=>Array.isArray(t)?t.filter((n)=>typeof n==="string"):void 0),q=(e,t,n)=>{try{let r=e.setItem(t,n);return U(r)?Promise.resolve(r).then(()=>!0,()=>!1):!0}catch{return!1}},k=(e,t)=>{try{let n=e.removeItem(t);return U(n)?Promise.resolve(n).then(()=>!0,()=>!1):!0}catch{return!1}},He=(e,t)=>{let n=G(),r=new Set,i=(async()=>{for(let o of t){let s=await Ht(e,o);if(s!==null&&!r.has(o))n.set(o,s)}})();return{store:{get:(o)=>n.get(o),set(o,s){n.set(o,s),r.add(o),q(e,o,s)},remove(o){n.remove(o),r.add(o),k(e,o)}},ready:i}};var le="sazabi.outbox.",De="sazabi.outbox.index",ue="sazabi.outbox.index.unmerged",Be="sazabi.outbox",Fe="sazabi.outbox.unmerged",B="sazabi.outbox.queue",ze="app.outbox.id",Ke=20,je=524288,T=(e)=>`${le}${e}`,Ut=/^(?:[0-9a-f]{16}|legacy|legacy-unmerged)$/,Mt=(e,t)=>(e??[]).filter((n)=>n.startsWith(le)).map((n)=>n.slice(le.length)).filter((n)=>Ut.test(n)&&!t.has(n)),Ue=new WeakMap,P=(e)=>{let t=Ue.get(e);if(t)return t;let n={batches:[],unmerged:[],loaded:!1,inFlight:new Set,discarded:0,unsaved:0};return Ue.set(e,n),n},Xe=(e,t)=>{P(e).report=t},$e=(e,t)=>{let n=P(e);if(n.report===t)n.report=void 0},Ge=(e)=>{if(e.discarded===0&&e.unsaved===0)return;let{discarded:t,unsaved:n}=e;e.discarded=0,e.unsaved=0,e.report?.({diagnostic:"outbox_discarded",attributes:{"app.sdk.outbox_discarded":t,"app.sdk.outbox_unsaved":n}})},fe=(e,t)=>{let n=e.report;if(!n)return;w(t,(r)=>{if(!r&&e.report===n)n({diagnostic:"outbox_persist_failed"})})},w=(e,t)=>U(e)?Promise.resolve(e).then(t):t(e),W=(e)=>e.some(U)?Promise.all(e):e,Me=(e,t)=>W(t.map((n)=>Oe(e,n))),F=(e,t,n)=>n.length===0?k(e,t):q(e,t,JSON.stringify(n)),D=(e)=>e.map((t)=>t.id),Le=(e)=>{let t=new Set;return e.filter((n)=>{if(t.has(n.id))return!1;return t.add(n.id),!0})},ge=(e)=>{let t=P(e);if(!t.loaded)return t.unmergedRead!==!1&&F(e,ue,D(t.unmerged));return w(F(e,De,D(t.batches)),(n)=>n&&t.unmergedRead?w(k(e,ue),()=>n):n)},qe=(e)=>{let t=0,n=0;for(let r=e.length-1;r>=0;r-=1){if(t+=e[r].length,n===Ke||t>je&&n>0)break;n+=1}return e.slice(e.length-n)},pe=(e,t)=>{let n=0,r=0;for(let i=t.length-1;i>=0;i-=1){let o=t[i]?.envelopes??[];n+=o.length;for(let s of o)r+=s.length;if(i<t.length-1&&(n>Ke||r>je)){let s=t.splice(0,i+1);for(let l of s)e.discarded+=l.envelopes?.length??0;return s}}return[]},Q=(e)=>{try{return JSON.parse(e)}catch{return}},Ye=(e)=>Array.isArray(e)?e.filter((t)=>typeof t==="string"):[],J=(e)=>Ye(e?Q(e):void 0),ce=(e,t)=>{let n=t?Q(t):void 0,r=Ye(n).filter((o)=>{let s=Q(o);return typeof s==="object"&&s!==null}),i=!t?0:Array.isArray(n)?n.length-r.length:1;if(e.discarded+=i,i)e.warn?.("discarded a stored crash report that is not valid JSON."),e.warn=void 0;return{envelopes:r,discarded:i}},Lt=(e,t,[n,r,i,o,s],l,u)=>{if(r)t.unmergedRead=!0;else if(t.unmergedRead===void 0)t.unmergedRead=!1;let c=new Set,f=new Map,v=(m)=>m.flatMap((p)=>{let C=f.get(p);if(C)return C;let _=l.get(p),x;if(!_)x=[{id:p}];else if(_.value===null)x=[];else{let{envelopes:O,discarded:I}=ce(t,_.value);if(O.length===0)c.add(p),x=[];else{if(I)F(e,T(p),O);x=[{id:p,envelopes:O}]}}return f.set(p,x),x}),h=v(J(n?.value)),b=v(J(r?.value)),Y=new Set(D([...t.batches,...t.unmerged])),z=v(u.filter((m)=>!Y.has(m)));if(!n&&t.unmergedRead===!1){t.discarded=0,t.unsaved=0;return}let S=new Set(D([...h,...b,...z,...t.batches,...t.unmerged])),N=[],M=(m,p)=>{if(!m||p.length===0||S.has(m))return!0;return S.add(m),c.delete(m),N.push({id:m,envelopes:p}),F(e,T(m),p)},L=[];for(let[m,p,C]of[[i,"legacy",Be],[o,"legacy-unmerged",Fe]]){if(!m?.value)continue;L.push([C,M(p,ce(t,m.value).envelopes)])}let A=s?.value?Q(s.value):void 0;if(typeof A?.discarded==="number"&&A.discarded>0)t.unsaved+=Math.floor(A.discarded);let E=M(typeof A?.id==="string"?A.id:void 0,ce(t,JSON.stringify(A?.envelopes??[])).envelopes),R;if(n)t.batches=Le([...h,...b,...z,...N,...t.unmerged]),t.unmerged=[],t.loaded=!0,R=pe(t,t.batches);else t.unmerged=Le([...b,...N,...t.unmerged]),R=pe(t,t.unmerged);Ge(t),w(ge(e),(m)=>{for(let p of D(R))k(e,T(p));if(!m)return;for(let p of c)k(e,T(p));return w(W([E,...L.map(([,p])=>p)]),([p,...C])=>{for(let[_,[x]]of L.entries())if(C[_])k(e,x);if(!s||!p)return;return w(s.value?k(e,B):!0,()=>{t.queueRead=!0})})})},ve=(e,t)=>{let n=P(e);if(n.loading)return n.loading;n.warn??=t;let r=Ie(e),i=w(Me(e,[De,ue,Be,Fe,B]),(s)=>w(r,(l)=>{let u=[...new Set([...J(s[0]?.value),...J(s[1]?.value)])],c=s[0]&&s[1]?Mt(l,new Set(u)):[],f=[...u,...c];return w(Me(e,f.map(T)),(v)=>{Lt(e,n,s,new Map(f.map((h,b)=>[h,v[b]])),c)})}));if(!U(i)){if(n.loaded)n.loading=Promise.resolve();return Promise.resolve()}let o=i.then(()=>{if(!n.loaded)n.loading=void 0});return n.loading=o,o},Ve=async(e,t,n)=>{let r=P(e),i=n.filter((c)=>!r.inFlight.has(c));if(i.length===0)return;for(let c of i)r.inFlight.add(c);let o;try{o=await Promise.all(i.map((c)=>t.sendEnvelope(c)))}finally{for(let c of i)r.inFlight.delete(c)}let s=new Set(i.filter((c,f)=>o[f]));if(s.size===0)return;let l=[];for(let c of[r.batches,r.unmerged])for(let f of c){let v=f.envelopes?.filter((h)=>!s.has(h));if(!v||v.length===f.envelopes?.length)continue;if(f.envelopes=v,v.length>0)F(e,T(f.id),v);else l.push([c,f])}let u=await W(l.map(([,c])=>w(k(e,T(c.id)),(f)=>f||q(e,T(c.id),"[]"))));l.forEach(([c,f],v)=>{let h=c.indexOf(f);if(u[v]&&h>=0)c.splice(h,1)}),await ge(e)},Qe=(e,t,n)=>{let r=t.drainEnvelopes({[ze]:n}),i=qe(r);if(i.length===0)return!0;let o=P(e);o.discarded+=r.length-i.length;let s=o.loaded?o.batches:o.unmerged;s.push({id:n,envelopes:i});let l=pe(o,s),u=F(e,T(n),i),c=ge(e),f=c===!0&&u===!0;for(let v of D(l))k(e,T(v));if(Ge(o),fe(o,u),o.loaded||o.unmergedRead!==!1)fe(o,c);if(o.queueSaved===t)o.queueSaved=void 0,w(W([u,c]),(v)=>{if(v.every(Boolean)&&o.queueSaved===void 0)k(e,B)});if(!f)Ve(e,t,i);return f},me=async(e,t)=>{await ve(e);let n=P(e);await Ve(e,t,[...new Set([...n.batches,...n.unmerged].flatMap((r)=>r.envelopes??[]))])},Je=(e,t,n)=>{let r=P(e);if(!r.queueRead)return;let i=t.snapshotEnvelopes({[ze]:n}),o=qe(i);if(r.queueSaved=o.length>0?t:void 0,r.queueSaved){let s=i.length-o.length;fe(r,q(e,B,JSON.stringify(s>0?{id:n,envelopes:o,discarded:s}:{id:n,envelopes:o})))}else k(e,B)},We=(e,t)=>{let n=P(e);if(n.queueSaved!==t)return;n.queueSaved=void 0,k(e,B)};var Dt=5000,Bt=57344,Ft=500,zt=200,Kt=["x-request-id","cf-ray","x-vercel-id","x-amz-cf-id","fly-request-id"],jt=["sazabi.session","sazabi.distinctId"],Xt=2000,$t="javascript",Ze=(e,t)=>({type:"custom",body:`identify ${e}`,severity:"INFO",timeUnixMs:Date.now(),attributes:{"app.custom.name":"identify",...t?Z(t,"custom"):{}}}),Gt=(e)=>{let t=globalThis.fetch;if(typeof t!=="function")return;let n=(a,g)=>t(a,g),r=be(e);if(!r)return;let i=`${r.baseUrl}/v1/logs`,o=rt(),s=e.storage,l=(a)=>console.warn(`${H} ${a}`),u=ct({url:i,publicKey:r.publicKey,resourceAttributes:{"service.name":e.serviceName,"service.version":e.serviceVersion,"deployment.environment":e.environment,"telemetry.sdk.name":ne,"telemetry.sdk.version":V,"telemetry.sdk.language":$t,...ut()},scope:{name:ne,version:V},fetchImpl:n,flushIntervalMs:e.flushIntervalMs??Dt,maxBatchBytes:e.maxBatchBytes??Bt,maxQueuedEvents:e.maxQueuedEvents??Ft,warn:l,onHealth:(a)=>M(a),onIdle:()=>{if(s)We(s,u)}}),c,f=[],v=0,h=!1,b=e.network??{},Y=(a)=>nt(a,b.redactQueryParams),z=(a,g,y=ae())=>{g.touch();let te={"app.event_type":a.type,"session.id":g.getSessionId(),"session.window_id":g.getWindowId(),"session.distinct_id":g.getDistinctId(),"app.screen.name":y.name,"app.screen.url":y.path===void 0?void 0:Y(y.path),"derived.source":"sdk"};u.enqueue(dt(a,te))},S=(a)=>{if(h)return;try{if(c)z(a,c);else if(v<zt||a.type==="sdk_health")f.push({kind:"event",event:a,screen:ae()}),v+=1}catch{}},N=(a)=>{try{S(a())}catch{}},M=at(S);if(s)Xe(s,M),ve(s,l);let L=ft(),A=(a)=>he({url:a,base:r.baseUrl})||L!==void 0&&he({url:a,base:L})||j()&&a.includes("/symbolicate"),E=[];if(s)E.push(()=>$e(s,M));let R,m=!1;E.push(()=>{if(R!==void 0)clearTimeout(R);R=void 0});let p=Re({emit:S,unhandledRejections:e.errors?.unhandledRejections!==!1,previousRejectionTracker:e.errors?.previousRejectionTracker,onFatal:(a)=>{m=!0,u.holdFlushes(!0);try{if(ee(),S(a),s)Qe(s,u,ye(o,8))}finally{u.holdFlushes(!1)}if(!s){u.flush();return}if(R===void 0&&!h)R=setTimeout(()=>{R=void 0,me(s,u)},Xt)}});if(E.push(()=>p.teardown()),e.pointer?.capture!==!1){let a=ot({emit:S,randomBytes:o,sampleIntervalMs:e.pointer?.sampleIntervalMs,partialIntervalMs:e.pointer?.partialIntervalMs});se(a),E.push(()=>{se(void 0),a.teardown()})}if(de(S,Y),E.push(()=>de(void 0)),e.console?.capture!==!1){let a=tt({emit:S,levels:e.console?.levels??["error","warn"],selfPrefix:H});E.push(()=>a.teardown())}let C=b.capture??!0,_=b.propagateTraceContext??!0;if(C||_){let a=vt({emit:S,traceContext:st({sampledFlag:b.sampledFlag??!1,randomBytes:o}),capture:C,propagateTraceContext:_,allowlist:b.allowlist??[],redactQueryParams:b.redactQueryParams,requestIdHeaders:b.requestIdHeaders??Kt,isInternalUrl:A,resolveUrl:pt,isDefaultInjectionTarget:()=>!0});E.push(()=>a.teardown())}let x=Ae({emit:S,onBackground:()=>{if(s)Je(s,u,ye(o,8));u.flush()}});E.push(()=>x.teardown());let O,I;if(s){let a=He(s,jt);O=a.store,I=a.ready}else O=G(),I=Promise.resolve();let ee=()=>{if(c||h)return;let a=it({store:O,windowStore:G(),randomBytes:o});if(c=a,d.pendingReset)a.reset(),d.pendingReset=!1;let g=d.pendingIdentity;if(d.pendingIdentity=void 0,g)a.setDistinctId(g.distinctId),S(Ze(g.distinctId,g.traits));for(let y of f.splice(0))try{if(y.kind==="identify")a.setDistinctId(y.distinctId);else if(y.kind==="reset")a.reset();else z(y.event,a,y.screen)}catch{}if(s&&!m)me(s,u)},Se=(a)=>{let g=f.length;while(g>0&&f[g-1]?.kind!=="event")g-=1;let y=f.splice(g);if(a.kind==="reset"||y.some((te)=>te.kind==="reset"))f.push({kind:"reset"});if(a.kind==="identify")f.push(a)};d.instance={identify(a,g){N(()=>{if(c)c.setDistinctId(a);else Se({kind:"identify",distinctId:a});return Ze(a,g)})},reset(){try{if(c)c.reset();else Se({kind:"reset"})}catch{}},addEvent(a,g){N(()=>({type:"custom",body:`custom ${a}`,severity:"INFO",timeUnixMs:Date.now(),attributes:{"app.custom.name":a,...g?Z(g,"custom"):{}}}))},log(a,g,y){N(()=>({type:"log",body:g,severity:a,timeUnixMs:Date.now(),attributes:y?Z(y):{}}))},async flush(){await I,await u.flush()},async shutdown(){for(let a of E.reverse())try{a()}catch{}h=!0,gt(),_e(),await I,await u.shutdown()}},I.then(ee,ee)},et=(e)=>{try{Gt(e)}catch{}},xn=(e)=>{if(lt()){if(!d.warnedWebHost)d.warnedWebHost=!0,console.warn(`${H} this app is running in a browser; use @sazabi/browser instead. SDK disabled.`);return}if(d.initialized)return;d.initialized=!0,d.generation+=1;let t=d.generation;if(!e.serviceName||!be(e)){console.warn(`${H} init() requires intakeUrl and serviceName; SDK disabled. Pass publicKey too if the intake URL is a proxy that carries no key.`);return}if(e.consent===void 0){et(e);return}try{Promise.resolve(e.consent()).then((n)=>{if(n===!0&&t===d.generation)et(e)}).catch(()=>{})}catch{}},Tn=(e,t)=>{if(d.instance)d.instance.identify(e,t);else d.pendingIdentity={distinctId:e,traits:t}},Cn=()=>{if(d.instance)d.instance.reset();else d.pendingReset=!0,d.pendingIdentity=void 0},Pn=(e,t)=>{d.instance?.addEvent(e,t)},An=(e,t,n)=>{d.instance?.log(e,t,n)},_n=async()=>{await d.instance?.flush()},Nn=async()=>{let e=d.instance;d.instance=void 0,d.initialized=!1,d.generation+=1,d.pendingIdentity=void 0,d.pendingReset=!1,await e?.shutdown()};export{V as SDK_VERSION,Ce as SazabiRoot,Pn as addEvent,_n as flush,Tn as identify,xn as init,An as log,Cn as reset,Nn as shutdown,It as trackNavigation};
4
+
5
+ //# debugId=ACB6EA50D2850DE964756E2164756E21
6
+ //# sourceMappingURL=index.js.map