@sazabi/react-native 0.0.0-stage → 0.1.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/LICENSE +202 -0
- package/README.md +286 -2
- package/dist/chunk-h2nxjrr9.js +5 -0
- package/dist/chunk-h2nxjrr9.js.map +15 -0
- package/dist/client-sdk-core/attributes.d.ts +8 -0
- package/dist/client-sdk-core/before-send.d.ts +15 -0
- package/dist/client-sdk-core/clock.d.ts +6 -0
- package/dist/client-sdk-core/console.d.ts +23 -0
- package/dist/client-sdk-core/index.d.ts +22 -0
- package/dist/client-sdk-core/intake-url.d.ts +38 -0
- package/dist/client-sdk-core/network-capture.d.ts +90 -0
- package/dist/client-sdk-core/patch-marker.d.ts +8 -0
- package/dist/client-sdk-core/pointer.d.ts +73 -0
- package/dist/client-sdk-core/random.d.ts +11 -0
- package/dist/client-sdk-core/sdk-health.d.ts +37 -0
- package/dist/client-sdk-core/session.d.ts +44 -0
- package/dist/client-sdk-core/trace-context.d.ts +33 -0
- package/dist/client-sdk-core/transport.d.ts +142 -0
- package/dist/client-sdk-core/types.d.ts +82 -0
- package/dist/client-sdk-core/url.d.ts +30 -0
- package/dist/errors.d.ts +46 -0
- package/dist/host.d.ts +30 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +28 -0
- package/dist/interactions.d.ts +40 -0
- package/dist/lifecycle.d.ts +16 -0
- package/dist/navigation.d.ts +24 -0
- package/dist/network.d.ts +12 -0
- package/dist/outbox.d.ts +53 -0
- package/dist/process-state.d.ts +49 -0
- package/dist/register.d.ts +1 -0
- package/dist/register.js +4 -0
- package/dist/register.js.map +10 -0
- package/dist/storage.d.ts +42 -0
- package/dist/types.d.ts +117 -0
- package/dist/version.d.ts +4 -0
- package/package.json +62 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BeforeSend, ClientEvent } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The host's `beforeSend` hook, run so that it can never break the SDK: a
|
|
4
|
+
* hook that throws or returns something other than an event drops the event
|
|
5
|
+
* (a hook that redacts must not fail open) and warns once per reason. An
|
|
6
|
+
* event captured synchronously while the hook runs (a `log()` or
|
|
7
|
+
* `addEvent()` call inside it) is dropped rather than sent around the hook
|
|
8
|
+
* or fed back into it, so a hook can never recurse. A request the hook
|
|
9
|
+
* starts completes later and produces an ordinary network event the hook
|
|
10
|
+
* sees like any other; the hook is responsible for excluding it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createBeforeSendGate: (hook: BeforeSend | undefined, warnOnce: (warning: {
|
|
13
|
+
reason: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}) => void) => ((event: ClientEvent) => ClientEvent | null);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monotonic milliseconds for durations and ordering. `performance.now()` where
|
|
3
|
+
* the host has it (every browser, React Native on Hermes); `Date.now()` as the
|
|
4
|
+
* fallback so the core never assumes a global.
|
|
5
|
+
*/
|
|
6
|
+
export declare const monotonicNow: () => number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ClientEvent, ConsoleCaptureLevel } from "./types.js";
|
|
2
|
+
export interface ConsoleCaptureOptions {
|
|
3
|
+
emit(event: ClientEvent): void;
|
|
4
|
+
levels: ConsoleCaptureLevel[];
|
|
5
|
+
/** Lines the SDK itself prints start with this and are never re-captured. */
|
|
6
|
+
selfPrefix: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ConsoleCaptureHandle {
|
|
9
|
+
teardown(): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Print through the console without mirroring the line into the event
|
|
13
|
+
* stream: for a host-facing message the SDK prints on another component's
|
|
14
|
+
* behalf (the dev-mode rejection warning it relays for React Native).
|
|
15
|
+
*/
|
|
16
|
+
export declare const withoutConsoleCapture: (print: () => void) => void;
|
|
17
|
+
/**
|
|
18
|
+
* Mirror selected console levels into the event stream as `log` events.
|
|
19
|
+
* The original console method always runs first (devtools behavior is never
|
|
20
|
+
* altered) and capture failures are swallowed. Lines the SDK itself prints
|
|
21
|
+
* (prefixed with `selfPrefix`) are skipped to avoid self-capture.
|
|
22
|
+
*/
|
|
23
|
+
export declare const installConsoleCapture: (options: ConsoleCaptureOptions) => ConsoleCaptureHandle;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sazabi/client-sdk-core: the platform-neutral half of the Sazabi client
|
|
3
|
+
* SDKs. Private and bundled into @sazabi/browser and @sazabi/react-native at
|
|
4
|
+
* build time; never published, never imported by anything else. Compiles
|
|
5
|
+
* without the DOM lib (see host-globals.d.ts), so a browser global here is a
|
|
6
|
+
* typecheck error.
|
|
7
|
+
*/
|
|
8
|
+
export { flattenAttributes, truncateValue } from "./attributes.js";
|
|
9
|
+
export { monotonicNow } from "./clock.js";
|
|
10
|
+
export { type ConsoleCaptureHandle, type ConsoleCaptureOptions, installConsoleCapture, withoutConsoleCapture, } from "./console.js";
|
|
11
|
+
export { intakeBaseFromUrl, publicKeyFromIntakeUrl, type ResolvedIntakeEndpoint, resolveIntakeEndpoint, } from "./intake-url.js";
|
|
12
|
+
export { activateNetworkCapture, consumeWrapperSeen, deactivateNetworkCapture, emitNetworkEvent, ensureXhrPatch, getActiveNetworkCapture, isAborted, isActiveInjectionTarget, isAllowedForInjection, type NetworkCaptureOptions, type NetworkEventDetails, readRequestId, recordWrapperRequest, redactUrl, resetWrapperLedger, severityForStatus, uninstallXhrPatch, urlPath, } from "./network-capture.js";
|
|
13
|
+
export { isWrapped, markWrapped, WRAPPED_MARKER } from "./patch-marker.js";
|
|
14
|
+
export { createPointerTracker, type DerivedTap, type PointerContact, type PointerTarget, type PointerTracker, type PointerTrackerOptions, type PointerType, } from "./pointer.js";
|
|
15
|
+
export { createRandomBytes, newPrefixedId, type RandomBytes, randomHex, toHex, } from "./random.js";
|
|
16
|
+
export { createMemoryStore, createSessionManager, type SessionManager, type SessionManagerOptions, type SessionStore, } from "./session.js";
|
|
17
|
+
export { createTraceContextManager, type HostSpanContext, readHostSpanContext, type RequestTraceContext, type TraceContextManager, } from "./trace-context.js";
|
|
18
|
+
export { createSdkHealthReporter, SDK_DIAGNOSTIC_KEY, type SdkHealthDiagnostic, type SdkHealthReport, type SdkHealthReporter, } from "./sdk-health.js";
|
|
19
|
+
export { createTransport, encodeLogRecord, type FetchLike, type OtlpLogRecord, type Transport, type TransportOptions, } from "./transport.js";
|
|
20
|
+
export { isUnderBase, type ParsedUrl, parseAbsoluteUrl } from "./url.js";
|
|
21
|
+
export { createBeforeSendGate } from "./before-send.js";
|
|
22
|
+
export type { AttributeValue, BeforeSend, ClientEvent, ClientEventType, ConsoleCaptureConfig, ConsoleCaptureLevel, EventAttributes, EventSeverity, NetworkCaptureConfig, PointerCaptureConfig, } from "./types.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving the intake endpoint from a single URL.
|
|
3
|
+
*
|
|
4
|
+
* Every Sazabi log source is configured with one intake URL, and public keys
|
|
5
|
+
* stay a low-level detail customers never handle. The key is already in the
|
|
6
|
+
* URL: the intake edge reconstructs it from the hostname
|
|
7
|
+
* (terraform/main/cloudfront-functions/intake-router.js), so the SDK does the
|
|
8
|
+
* same reconstruction locally and sends the Bearer header itself.
|
|
9
|
+
*
|
|
10
|
+
* Two keyed shapes exist, matching the edge's two grammars:
|
|
11
|
+
* {hex}.{region}.intake.{domain} prefixless (what log sources issue)
|
|
12
|
+
* {adapter}-{hex}.{region}.intake.{domain} keyed-adapter (legacy, still served)
|
|
13
|
+
*
|
|
14
|
+
* A URL in neither shape is not an error: it may be a proxy or a custom
|
|
15
|
+
* endpoint. The SDK then requires an explicit `publicKey`, so a
|
|
16
|
+
* misconfiguration surfaces as a clear warning rather than silent no-auth.
|
|
17
|
+
*/
|
|
18
|
+
export interface ResolvedIntakeEndpoint {
|
|
19
|
+
/**
|
|
20
|
+
* Base to POST to: origin plus any proxy path prefix, no trailing slash.
|
|
21
|
+
* The transport appends `/v1/logs`.
|
|
22
|
+
*/
|
|
23
|
+
baseUrl: string;
|
|
24
|
+
/** Credential for the Authorization header, derived or explicit. */
|
|
25
|
+
publicKey: string;
|
|
26
|
+
}
|
|
27
|
+
/** Public key embedded in a keyed intake hostname, if it carries one. */
|
|
28
|
+
export declare const publicKeyFromIntakeUrl: (url: string) => string | undefined;
|
|
29
|
+
export declare const intakeBaseFromUrl: (url: string) => string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the endpoint from config. An explicit `publicKey` always wins, so a
|
|
32
|
+
* non-keyed or proxied endpoint stays configurable.
|
|
33
|
+
*/
|
|
34
|
+
export declare const resolveIntakeEndpoint: (config: {
|
|
35
|
+
intakeUrl?: string;
|
|
36
|
+
intakeHost?: string;
|
|
37
|
+
publicKey?: string;
|
|
38
|
+
}) => ResolvedIntakeEndpoint | undefined;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { RequestTraceContext, TraceContextManager } from "./trace-context.js";
|
|
2
|
+
import type { ClientEvent, EventSeverity } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Everything the network patches need at request time. A binding builds one
|
|
5
|
+
* at init and activates it; the patches themselves install dormant (possibly
|
|
6
|
+
* before init, via a register entrypoint) and consult the active slot per
|
|
7
|
+
* call. Undefined slot = transparent passthrough.
|
|
8
|
+
*/
|
|
9
|
+
export interface NetworkCaptureOptions {
|
|
10
|
+
emit(event: ClientEvent): void;
|
|
11
|
+
traceContext: TraceContextManager;
|
|
12
|
+
/** Emit network events. */
|
|
13
|
+
capture: boolean;
|
|
14
|
+
/** Inject `traceparent` into allowlisted requests. */
|
|
15
|
+
propagateTraceContext: boolean;
|
|
16
|
+
/** Injection allowlist; empty defers to `isDefaultInjectionTarget`. */
|
|
17
|
+
allowlist: (string | RegExp)[];
|
|
18
|
+
/** Query parameter names redacted from `url.full` beside the default list. */
|
|
19
|
+
redactQueryParams?: (string | RegExp)[];
|
|
20
|
+
/** Response headers probed for a platform request id, in priority order. */
|
|
21
|
+
requestIdHeaders: string[];
|
|
22
|
+
/** SDK-internal endpoints: never captured, never injected. */
|
|
23
|
+
isInternalUrl(url: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Absolute URL for a request target as the host resolves it, with embedded
|
|
26
|
+
* credentials stripped; undefined when the input cannot be parsed.
|
|
27
|
+
*/
|
|
28
|
+
resolveUrl(raw: string): string | undefined;
|
|
29
|
+
/** Injection policy when the allowlist is empty (same-origin on the web). */
|
|
30
|
+
isDefaultInjectionTarget(resolved: string): boolean;
|
|
31
|
+
/** Origin elided from the display path in event bodies, if the host has one. */
|
|
32
|
+
displayOrigin?: string;
|
|
33
|
+
/** Host-reported offline state; a failure while offline reads as an abort. */
|
|
34
|
+
isOffline?(): boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare const activateNetworkCapture: (options: NetworkCaptureOptions) => void;
|
|
37
|
+
export declare const deactivateNetworkCapture: () => void;
|
|
38
|
+
export declare const getActiveNetworkCapture: () => NetworkCaptureOptions | undefined;
|
|
39
|
+
/** Compact display path: pathname when on the display origin, host+path otherwise. */
|
|
40
|
+
export declare const urlPath: (resolved: string, displayOrigin?: string) => string;
|
|
41
|
+
/** Status 0 means transport failure. */
|
|
42
|
+
export declare const severityForStatus: (status: number) => EventSeverity;
|
|
43
|
+
/**
|
|
44
|
+
* A recorded URL: the value of every sensitive parameter after the path
|
|
45
|
+
* replaced with `[redacted]`, in the query and in the fragment. `?` separates
|
|
46
|
+
* inside the fragment as well, so a hash router's `#/reset?token=…` is
|
|
47
|
+
* covered. Credentials in the authority are already gone (`resolveUrl`
|
|
48
|
+
* strips them). Applied to `url.full` of requests, resource rows and
|
|
49
|
+
* navigations, and to the page URL the browser SDK stamps on every event.
|
|
50
|
+
*/
|
|
51
|
+
export declare const redactUrl: (url: string, extra?: (string | RegExp)[]) => string;
|
|
52
|
+
export declare const isAllowedForInjection: (options: NetworkCaptureOptions, resolved: string) => boolean;
|
|
53
|
+
/** Injection eligibility for the active config; used by the browser's resource
|
|
54
|
+
* observer to decide whether a bypassed request is a diagnosable gap. */
|
|
55
|
+
export declare const isActiveInjectionTarget: (resolved: string) => boolean;
|
|
56
|
+
export declare const recordWrapperRequest: (url: string) => void;
|
|
57
|
+
/** Consume a wrapper-seen record matching this request, if any. */
|
|
58
|
+
export declare const consumeWrapperSeen: (url: string, startTime: number) => boolean;
|
|
59
|
+
export declare const resetWrapperLedger: () => void;
|
|
60
|
+
export interface NetworkEventDetails {
|
|
61
|
+
kind: "fetch" | "xhr";
|
|
62
|
+
method: string;
|
|
63
|
+
resolvedUrl: string;
|
|
64
|
+
status: number;
|
|
65
|
+
durationMs: number;
|
|
66
|
+
severity?: EventSeverity;
|
|
67
|
+
failureMessage?: string;
|
|
68
|
+
injected?: RequestTraceContext;
|
|
69
|
+
requestId?: string;
|
|
70
|
+
requestIdHeader?: string;
|
|
71
|
+
}
|
|
72
|
+
export declare const emitNetworkEvent: (options: NetworkCaptureOptions, details: NetworkEventDetails) => void;
|
|
73
|
+
export declare const readRequestId: (options: NetworkCaptureOptions, getHeader: (name: string) => string | null | undefined) => {
|
|
74
|
+
requestId?: string;
|
|
75
|
+
requestIdHeader?: string;
|
|
76
|
+
};
|
|
77
|
+
export declare const isAborted: (error: unknown, signalAborted?: boolean, offline?: boolean) => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Install the dormant XMLHttpRequest patch around whatever implementation is
|
|
80
|
+
* currently live. Idempotent, and re-wraps when a later actor replaced the
|
|
81
|
+
* prototype methods after an earlier install (the displaced wrapper is an
|
|
82
|
+
* inert passthrough, so single-processing holds).
|
|
83
|
+
*/
|
|
84
|
+
export declare const ensureXhrPatch: () => void;
|
|
85
|
+
/**
|
|
86
|
+
* Restore the natives if the current implementations are still ours; if
|
|
87
|
+
* someone patched on top of us, restoring would clobber them, and the
|
|
88
|
+
* inactive wrappers pass through.
|
|
89
|
+
*/
|
|
90
|
+
export declare const uninstallXhrPatch: () => void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared across every Sazabi client SDK so two SDKs bundled into one app
|
|
3
|
+
* recognize each other's wrappers and never double-wrap a primitive.
|
|
4
|
+
*/
|
|
5
|
+
export declare const WRAPPED_MARKER = "__sazabiBrowserWrapped";
|
|
6
|
+
/** Best-effort idempotency marker on wrapper functions. */
|
|
7
|
+
export declare const markWrapped: (fn: object) => void;
|
|
8
|
+
export declare const isWrapped: (fn: unknown) => boolean;
|
|
@@ -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,82 @@
|
|
|
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
|
+
/**
|
|
23
|
+
* Runs on every event before it is queued. Return the event (edited or as
|
|
24
|
+
* given) to send it, or `null` to drop it. The hook sees the event's own
|
|
25
|
+
* attributes; the session and page fields are stamped afterwards.
|
|
26
|
+
*/
|
|
27
|
+
export type BeforeSend = (event: ClientEvent) => ClientEvent | null;
|
|
28
|
+
export type ConsoleCaptureLevel = "error" | "warn" | "info" | "debug";
|
|
29
|
+
export interface ConsoleCaptureConfig {
|
|
30
|
+
/** Mirror console output into the event stream. Default true. */
|
|
31
|
+
capture?: boolean;
|
|
32
|
+
/** Console levels to mirror. Default ["error", "warn"]. */
|
|
33
|
+
levels?: ConsoleCaptureLevel[];
|
|
34
|
+
}
|
|
35
|
+
export interface PointerCaptureConfig {
|
|
36
|
+
/** Emit one `pointer` episode per contact, plus derived taps. Default true. */
|
|
37
|
+
capture?: boolean;
|
|
38
|
+
/** Spacing of the position samples packed into an episode. Default 1000. */
|
|
39
|
+
sampleIntervalMs?: number;
|
|
40
|
+
/**
|
|
41
|
+
* A contact open longer than this emits a partial row at this cadence so a
|
|
42
|
+
* long scroll or drag is not a blind spot until release. Default 10000.
|
|
43
|
+
*/
|
|
44
|
+
partialIntervalMs?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Network options every binding shares; a binding may extend the shape. */
|
|
47
|
+
export interface NetworkCaptureConfig {
|
|
48
|
+
/** Emit network events. Default true. */
|
|
49
|
+
capture?: boolean;
|
|
50
|
+
/** Inject W3C `traceparent` into allowlisted requests. Default true. */
|
|
51
|
+
propagateTraceContext?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Response headers probed (in order) for a platform request id to attach
|
|
54
|
+
* as `app.network.request_id`, an exact join key against platform logs.
|
|
55
|
+
*/
|
|
56
|
+
requestIdHeaders?: string[];
|
|
57
|
+
/**
|
|
58
|
+
* Requests eligible for `traceparent` injection. A string is a host
|
|
59
|
+
* (`api.example.com`), a domain with every subdomain (`.example.com`), or
|
|
60
|
+
* an origin with an optional path prefix (`https://api.example.com/v2`); a
|
|
61
|
+
* RegExp is tested against the full URL. An empty list falls back to the
|
|
62
|
+
* binding's default policy (same-origin in the browser, every request on
|
|
63
|
+
* mobile).
|
|
64
|
+
*/
|
|
65
|
+
allowlist?: (string | RegExp)[];
|
|
66
|
+
/**
|
|
67
|
+
* Query parameter names whose values are replaced with `[redacted]` in
|
|
68
|
+
* every recorded URL: `url.full` of requests and navigations, the screen or
|
|
69
|
+
* page URL stamped on each event (`app.screen.url`), and in the browser the
|
|
70
|
+
* referrer (`app.navigation.from`). Beside the default list (token, access_token, refresh_token,
|
|
71
|
+
* id_token, api_key, apikey, key, secret, password, signature, sig,
|
|
72
|
+
* x-amz-signature, x-amz-credential, x-goog-signature, sentry_key,
|
|
73
|
+
* authorization, auth, session, sid, code). Strings match a name exactly,
|
|
74
|
+
* case-insensitively; RegExps are tested against the name.
|
|
75
|
+
*/
|
|
76
|
+
redactQueryParams?: (string | RegExp)[];
|
|
77
|
+
/**
|
|
78
|
+
* Set the sampled flag (`-01`) on SDK-minted trace roots. Default false:
|
|
79
|
+
* the sampled bit steers customers' ParentBased backend samplers.
|
|
80
|
+
*/
|
|
81
|
+
sampledFlag?: boolean;
|
|
82
|
+
}
|