@obsrviq/tracker 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Obsrviq
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @obsrviq/tracker
2
+
3
+ The Obsrviq capture SDK. A tiny, privacy-first script that records DOM (rrweb),
4
+ console, network, errors, and web vitals, then ships them in masked, gzipped
5
+ batches. No tagging required.
6
+
7
+ ## Install
8
+
9
+ **Script tag (no build):**
10
+ ```html
11
+ <script
12
+ src="https://cdn.yourapp.com/obsrviq.global.js"
13
+ data-obsrviq-key="pk_live_xxx"
14
+ data-obsrviq-endpoint="https://in.yourapp.com"></script>
15
+ ```
16
+ The built file is `dist/obsrviq.global.js` (host it on your CDN).
17
+
18
+ **npm:**
19
+ ```bash
20
+ npm install @obsrviq/tracker
21
+ ```
22
+ ```ts
23
+ import { Obsrviq } from '@obsrviq/tracker';
24
+ Obsrviq.init({ siteKey: 'pk_live_xxx', endpoint: 'https://in.yourapp.com' });
25
+ ```
26
+
27
+ ## API
28
+
29
+ ```ts
30
+ Obsrviq.init(config); // see config below
31
+ Obsrviq.identify(userId, traits?); // raw userId (searchable) + SHA-256 hash; retro-stamps
32
+ Obsrviq.setMetadata({ plan: 'pro' }); // merge searchable session properties anytime
33
+ Obsrviq.reset(); // forget identity on logout → fresh anonymous session
34
+ Obsrviq.track('event', { conversion: true });
35
+ Obsrviq.setConsent(true | false); // gate recording (PRIV-2)
36
+ Obsrviq.stop(); // hard stop + flush
37
+ Obsrviq.getDiagnostics(); // { recording, sessionId, flushes, bytesSent, … }
38
+ ```
39
+
40
+ ## Config
41
+
42
+ | Option | Default | Notes |
43
+ |---|---|---|
44
+ | `siteKey` | — | **Required.** The `pk_` ingest key. |
45
+ | `endpoint` | `https://in.lumera.app` | Your ingest URL. |
46
+ | `maskAllInputs` | `true` | Never record input values. |
47
+ | `maskTextSelector` | — | Mask text inside matching nodes. |
48
+ | `blockSelector` | `.obsrviq-block` | Never record these nodes. |
49
+ | `captureNetworkBodies` | `false` | Opt-in, masked, size-capped. |
50
+ | `redactHeaders` | `authorization, cookie, set-cookie` | Redacted in captured headers. |
51
+ | `requireConsent` | `false` | If true, nothing records until `setConsent(true)`. |
52
+ | `sampleRate` | `1.0` | 0..1 fraction of sessions to record. |
53
+ | `flushIntervalMs` | `5000` | Batch flush cadence. |
54
+ | `beforeSend` | — | Last-chance redaction; return `null` to drop. |
55
+
56
+ ## Behavior
57
+
58
+ - **Consent & privacy:** with a consent gate and consent withheld, *zero* network
59
+ calls leave the page (honors Global Privacy Control / DNT).
60
+ - **Transport:** gzipped via fflate, sent with `sendBeacon` (or `fetch` keepalive)
61
+ on a timer, on `visibilitychange=hidden`, and on `pagehide`.
62
+ - **Capture:** rrweb for DOM (periodic full snapshots for progressive replay);
63
+ custom interceptors for `fetch` / `XHR` / `PerformanceObserver` / `sendBeacon`;
64
+ console hooks; `window.onerror` + `onunhandledrejection`; web vitals; rage/dead
65
+ click detection.
66
+
67
+ Full guide: [docs/EMBEDDING.md](../../docs/EMBEDDING.md).
@@ -0,0 +1,96 @@
1
+ import { ObsrviqInitConfig } from '@obsrviq/types';
2
+ export { ObsrviqInitConfig } from '@obsrviq/types';
3
+
4
+ interface Diagnostics {
5
+ recording: boolean;
6
+ sessionId: string | null;
7
+ flushes: number;
8
+ bytesSent: number;
9
+ eventsSent: number;
10
+ eventsQueued: number;
11
+ }
12
+ declare class ObsrviqClient {
13
+ private config;
14
+ private clock;
15
+ private transport;
16
+ private teardowns;
17
+ private meta;
18
+ private recording;
19
+ private consent;
20
+ private sampledIn;
21
+ private lastActivity;
22
+ /** The userId this session is attributed to (for the shared-device split). */
23
+ private currentUserId;
24
+ /** Next batch seq — mirrored from the transport so we can persist continuation. */
25
+ private nextSeq;
26
+ /** Open task spans keyed by task name → the span id + start time, so endTask()
27
+ * can pair with its startTask(). Re-starting the same name supersedes the
28
+ * earlier open span (the player pairs by spanId, so this only affects which
29
+ * start an unqualified end() resolves to). */
30
+ private openTasks;
31
+ private consentUnmount;
32
+ private recDotUnmount;
33
+ private diag;
34
+ /** The current session id (null until init). */
35
+ get sessionId(): string | null;
36
+ init(config: ObsrviqInitConfig): void;
37
+ /** (Re)create the session id, clock, meta, and transport. Used by init() and
38
+ * reset(). Does not start recording — callers decide via maybeStart().
39
+ * @param seedInitUser seed identity from the init-time `cfg.userId`. True for
40
+ * the initial init() session; FALSE for reset() (logout) — otherwise the fresh
41
+ * post-logout session would be re-attributed to the user who just logged out. */
42
+ private startSession;
43
+ /** Persist the session-continuation record (no-op unless continueSession). Only
44
+ * ever writes for a RECORDING session — a sampled-out or pre-consent session must
45
+ * not leave a record that a later page would resume (and force back into
46
+ * recording), and identify() can fire before recording starts. */
47
+ private persistSession;
48
+ private maybeStart;
49
+ /** Associate the session with an application user. Stores the RAW userId (for
50
+ * search + display) AND a SHA-256 identityHash, merges any traits into metadata,
51
+ * and force-flushes so the live session is retro-stamped promptly. Safe to call
52
+ * before recording starts (values ride the first batch) and after login. */
53
+ identify(userId: string, traits?: Record<string, unknown>): void;
54
+ /** Merge custom properties onto the session at any time (searchable server-side). */
55
+ setMetadata(props: Record<string, unknown>): void;
56
+ /** Forget the current identity (e.g. on logout) and rotate to a fresh anonymous
57
+ * session, so post-logout activity isn't attributed to the prior user. (Identity
58
+ * can't be cleared on the existing row — the server merge only fills nulls — so
59
+ * we start a new session instead.) */
60
+ reset(): void;
61
+ /** Set the raw userId on meta and compute its hash (async). */
62
+ private applyUserId;
63
+ private mergeMetadata;
64
+ /** Emit a custom event (e.g. conversions). */
65
+ track(name: string, props?: Record<string, unknown>): void;
66
+ /** Mark a conversion. Conversion is client-defined, so the NAME matters —
67
+ * "checkout", "signup", "trial_started" — and each is denormalised onto the
68
+ * session for per-client filtering. Sets props.conversion so the legacy
69
+ * 'conversion' flag still lights up. Sugar over track(name, {conversion:true}). */
70
+ conversion(name: string, props?: Record<string, unknown>): void;
71
+ /** Attach one or more free-form, searchable labels to the current session.
72
+ * Tags are session-level (deduped server-side) — use them to slice sessions
73
+ * however a client likes ("vip", "ab_variant_b", "mobile_app"). */
74
+ tag(...names: string[]): void;
75
+ /** Begin a named sub-event span. Pairs with end() / endTask(name) to produce a
76
+ * searchable, duration-bearing span on the session timeline. Returns a handle
77
+ * so callers can `const t = Obsrviq.startTask('checkout'); …; t.end()`. */
78
+ startTask(name: string, props?: Record<string, unknown>): {
79
+ end: (endProps?: Record<string, unknown>) => void;
80
+ };
81
+ /** Close a named sub-event span opened by startTask(name). Safe to call even
82
+ * if the start was never seen (renders as an orphan end on the timeline). */
83
+ endTask(name: string, props?: Record<string, unknown>): void;
84
+ /** Gate recording (PRIV-2). true starts; false stops + flushes. Dismisses the
85
+ * built-in consent prompt if one is showing. */
86
+ setConsent(granted: boolean): void;
87
+ /** Hard stop + flush. */
88
+ stop(): void;
89
+ /** Force a flush now (returns when send is attempted). */
90
+ flush(): Promise<boolean>;
91
+ getDiagnostics(): Diagnostics;
92
+ private teardown;
93
+ }
94
+ declare const Obsrviq: ObsrviqClient;
95
+
96
+ export { type Diagnostics, Obsrviq, Obsrviq as default };