@kharko/dozor-react 0.1.0 → 0.2.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/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # @kharko/dozor-react
2
+
3
+ React bindings for [`@kharko/dozor`](https://www.npmjs.com/package/@kharko/dozor) — the session recording SDK for [Kharko Dozor](https://github.com/kolia-zamnius/kharko-dozor).
4
+
5
+ Provides a `<DozorProvider>` and a `useDozor()` hook to control the recorder from any React component with reactive state updates. Compatible with React 18+ and React Server Components (RSC).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @kharko/dozor @kharko/dozor-react
11
+ # or
12
+ pnpm add @kharko/dozor @kharko/dozor-react
13
+ # or
14
+ yarn add @kharko/dozor @kharko/dozor-react
15
+ ```
16
+
17
+ Both packages are required — `@kharko/dozor` is the core SDK, `@kharko/dozor-react` provides the React integration.
18
+
19
+ ## Quick start
20
+
21
+ Wrap your app (or a subtree) with `<DozorProvider>` and pass your API key via the `options` prop:
22
+
23
+ ```tsx
24
+ import { DozorProvider } from "@kharko/dozor-react";
25
+
26
+ function App() {
27
+ return (
28
+ <DozorProvider options={{ apiKey: "dp_your_public_key" }}>
29
+ <YourApp />
30
+ </DozorProvider>
31
+ );
32
+ }
33
+ ```
34
+
35
+ Recording starts automatically on mount. That's it — no extra code needed for basic usage.
36
+
37
+ ## API
38
+
39
+ ### `<DozorProvider>`
40
+
41
+ React Context provider that manages the `Dozor` singleton. Must wrap any component that calls `useDozor()`.
42
+
43
+ ```tsx
44
+ <DozorProvider options={dozorOptions}>
45
+ {children}
46
+ </DozorProvider>
47
+ ```
48
+
49
+ #### Props
50
+
51
+ | Prop | Type | Required | Description |
52
+ |---|---|---|---|
53
+ | `options` | `DozorOptions` | No | Pass to auto-initialize Dozor on mount. Omit for manual `init()` via the hook. |
54
+ | `children` | `ReactNode` | Yes | Child components that can access `useDozor()`. |
55
+
56
+ #### `DozorOptions`
57
+
58
+ All options from `@kharko/dozor` are supported:
59
+
60
+ | Option | Type | Default | Description |
61
+ |---|---|---|---|
62
+ | `apiKey` | `string` | **required** | Public project API key (`dp_...`). |
63
+ | `endpoint` | `string` | `https://dozor.kharko.dev/api/ingest` | Ingest endpoint URL. |
64
+ | `flushInterval` | `number` | `10000` | Flush interval in ms. |
65
+ | `batchSize` | `number` | `500` | Max events before auto-flush. |
66
+ | `autoStart` | `boolean` | `true` | Start recording on init. |
67
+ | `hold` | `boolean` | `false` | Start with transport held (buffer events without sending). |
68
+ | `userId` | `string` | — | Stable user identifier for cross-session analytics. |
69
+ | `pauseOnHidden` | `boolean` | `true` | Auto-pause recording when the tab is hidden, resume when visible. |
70
+ | `recordConsole` | `boolean` | `true` | Record console.log/warn/error/info/debug calls. Viewable in the dashboard replay. |
71
+
72
+ #### Auto-init vs manual init
73
+
74
+ **Auto-init** — pass `options` to start recording on mount:
75
+
76
+ ```tsx
77
+ <DozorProvider options={{ apiKey: "dp_your_key" }}>
78
+ ```
79
+
80
+ **Manual init** — omit `options` and call `init()` from a component:
81
+
82
+ ```tsx
83
+ <DozorProvider>
84
+ <ManualInitComponent />
85
+ </DozorProvider>
86
+
87
+ function ManualInitComponent() {
88
+ const dozor = useDozor();
89
+
90
+ function handleStart() {
91
+ dozor.init({ apiKey: "dp_your_key" });
92
+ }
93
+
94
+ return <button onClick={handleStart}>Start recording</button>;
95
+ }
96
+ ```
97
+
98
+ ### `useDozor()`
99
+
100
+ Hook that returns the current recorder state and control methods. Must be used within a `<DozorProvider>`.
101
+
102
+ ```ts
103
+ import { useDozor } from "@kharko/dozor-react";
104
+
105
+ function MyComponent() {
106
+ const dozor = useDozor();
107
+ // dozor.state, dozor.isRecording, dozor.hold(), etc.
108
+ }
109
+ ```
110
+
111
+ Throws an error if called outside of `<DozorProvider>`.
112
+
113
+ #### Returned properties
114
+
115
+ All properties are reactive — they update automatically when the recorder state changes.
116
+
117
+ | Property | Type | Description |
118
+ |---|---|---|
119
+ | `state` | `DozorContextState` | Current state: `"not_initialized"`, `"idle"`, `"recording"`, `"paused"`, or `"stopped"`. |
120
+ | `sessionId` | `string \| null` | Current session ID (UUID v4), or `null` before init. |
121
+ | `isRecording` | `boolean` | `true` when actively recording. |
122
+ | `isPaused` | `boolean` | `true` when paused via `pause()`. |
123
+ | `isHeld` | `boolean` | `true` when transport is held — events are buffered but not sent. |
124
+ | `userId` | `string \| null` | Current user ID, or `null` if not set. |
125
+ | `bufferSize` | `number` | Number of events currently buffered in memory (not yet sent). Useful for monitoring buffer growth during `hold()`. |
126
+
127
+ > **Note:** `state` includes `"not_initialized"` which doesn't exist in the core SDK — it indicates that `init()` hasn't been called yet.
128
+ >
129
+ > **Note:** `bufferSize` is synced after each method call. It does not update in real-time as rrweb emits events — it reflects the value at the last state sync.
130
+
131
+ #### Returned methods
132
+
133
+ | Method | Signature | Description |
134
+ |---|---|---|
135
+ | `init` | `(options: DozorOptions) => void` | Initialize the recorder. No-op if already initialized. |
136
+ | `start` | `() => void` | Start recording (only when `autoStart: false`). |
137
+ | `pause` | `() => void` | Pause recording. Keeps session and buffer alive. |
138
+ | `resume` | `() => void` | Resume recording after a pause. |
139
+ | `stop` | `() => void` | Stop recording, flush all events (even if held), destroy instance. |
140
+ | `cancel` | `() => void` | Discard session — drop buffer, delete from server, destroy instance. |
141
+ | `hold` | `() => void` | Hold transport — recording continues but events are buffered without sending. |
142
+ | `release` | `(options?: { discard?: boolean }) => void` | Release transport hold. Flushes buffer by default, or pass `{ discard: true }` to drop held events. |
143
+ | `setUserId` | `(id: string) => void` | Set or update the user ID. Triggers metadata re-send if needed. |
144
+
145
+ ## Use cases
146
+
147
+ ### Basic recording
148
+
149
+ ```tsx
150
+ import { DozorProvider } from "@kharko/dozor-react";
151
+
152
+ export default function RootLayout({ children }) {
153
+ return (
154
+ <DozorProvider options={{ apiKey: "dp_your_key" }}>
155
+ {children}
156
+ </DozorProvider>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ### Conditional recording
162
+
163
+ Record a session but only send it if the user completes a valuable action.
164
+
165
+ ```tsx
166
+ function CheckoutFlow() {
167
+ const dozor = useDozor();
168
+
169
+ async function handlePurchase() {
170
+ await submitOrder();
171
+ dozor.release(); // session was valuable — send it
172
+ }
173
+
174
+ function handleAbandon() {
175
+ dozor.cancel(); // session was not valuable — discard it
176
+ }
177
+
178
+ return (
179
+ <>
180
+ <button onClick={handlePurchase}>Complete purchase</button>
181
+ <button onClick={handleAbandon}>Leave</button>
182
+ </>
183
+ );
184
+ }
185
+
186
+ // In layout: start with hold so nothing is sent until release()
187
+ <DozorProvider options={{ apiKey: "dp_your_key", hold: true }}>
188
+ <CheckoutFlow />
189
+ </DozorProvider>
190
+ ```
191
+
192
+ ### Network-aware buffering
193
+
194
+ Pause sending during heavy network activity.
195
+
196
+ ```tsx
197
+ function DataLoader() {
198
+ const dozor = useDozor();
199
+
200
+ async function loadEverything() {
201
+ dozor.hold();
202
+ await Promise.all([fetchUsers(), fetchProducts(), fetchOrders()]);
203
+ dozor.release(); // resume sending, flush what accumulated
204
+ }
205
+
206
+ return <button onClick={loadEverything}>Load data</button>;
207
+ }
208
+ ```
209
+
210
+ ### Identify users after login
211
+
212
+ ```tsx
213
+ function LoginForm() {
214
+ const dozor = useDozor();
215
+
216
+ async function handleLogin(credentials) {
217
+ const user = await login(credentials);
218
+ dozor.setUserId(user.id); // link this session to the user
219
+ }
220
+
221
+ return <form onSubmit={handleLogin}>...</form>;
222
+ }
223
+ ```
224
+
225
+ Or, if the user is known at mount time:
226
+
227
+ ```tsx
228
+ <DozorProvider options={{ apiKey: "dp_your_key", userId: currentUser.id }}>
229
+ ```
230
+
231
+ ### Deferred start
232
+
233
+ Start recording only when the user enters a specific section.
234
+
235
+ ```tsx
236
+ function RecordingGate({ children }) {
237
+ const dozor = useDozor();
238
+
239
+ useEffect(() => {
240
+ dozor.init({ apiKey: "dp_your_key", autoStart: false });
241
+ }, []);
242
+
243
+ return (
244
+ <>
245
+ <button onClick={() => dozor.start()}>Start recording</button>
246
+ <button onClick={() => dozor.stop()}>Stop recording</button>
247
+ {children}
248
+ </>
249
+ );
250
+ }
251
+ ```
252
+
253
+ ### Pause during sensitive input
254
+
255
+ ```tsx
256
+ function CreditCardForm() {
257
+ const dozor = useDozor();
258
+
259
+ return (
260
+ <div
261
+ onFocus={() => dozor.pause()}
262
+ onBlur={() => dozor.resume()}
263
+ >
264
+ <input type="text" placeholder="Card number" />
265
+ </div>
266
+ );
267
+ }
268
+ ```
269
+
270
+ ### Status indicator
271
+
272
+ ```tsx
273
+ function RecordingStatus() {
274
+ const { state, isHeld, sessionId } = useDozor();
275
+
276
+ return (
277
+ <div>
278
+ <span>State: {state}</span>
279
+ {isHeld && <span> (transport held)</span>}
280
+ {sessionId && <span> | Session: {sessionId}</span>}
281
+ </div>
282
+ );
283
+ }
284
+ ```
285
+
286
+ ## Edge cases
287
+
288
+ | Scenario | Behavior |
289
+ |---|---|
290
+ | `useDozor()` outside `<DozorProvider>` | Throws: `"useDozor must be used within a <DozorProvider>"`. |
291
+ | `init()` called multiple times | Returns existing singleton. Does not re-initialize. |
292
+ | `start()` when already recording | No-op. |
293
+ | `pause()` when not recording | No-op. |
294
+ | `resume()` when not paused | No-op. |
295
+ | `stop()` when already stopped | No-op. |
296
+ | `cancel()` when already stopped | No-op. |
297
+ | `hold()` when already held | No-op. |
298
+ | `hold()` when stopped | No-op. |
299
+ | `release()` when not held | No-op. |
300
+ | `stop()` while held | Releases hold, flushes all events, destroys instance. |
301
+ | `cancel()` while held | Drops buffer, deletes session. |
302
+ | Methods called before `init()` | No-op (safely ignored via optional chaining). |
303
+ | `<DozorProvider>` unmounts | Provider unmounts but the Dozor singleton persists until `stop()` or `cancel()` is called. |
304
+ | Multiple `<DozorProvider>` instances | Both reference the same singleton. Avoid this — use one provider at the app root. |
305
+ | React Server Components | Safe — the `"use client"` directive is bundled into the package output. Only use `<DozorProvider>` and `useDozor()` in client components. |
306
+ | Next.js App Router | Place `<DozorProvider>` in a client component (e.g., `providers.tsx`) that wraps your layout. |
307
+ | Tab hidden with `pauseOnHidden: true` (default) | Recording pauses automatically, `isPaused` becomes `true`. Resumes when the tab is visible again. |
308
+ | Tab hidden after manual `pause()` | Auto-resume does **not** override manual pause. Only `resume()` can resume. |
309
+ | React Strict Mode (dev) | `useEffect` runs twice in development. The provider handles this — `init()` is idempotent. |
310
+
311
+ ## TypeScript
312
+
313
+ The package exports all types needed for typed usage:
314
+
315
+ ```ts
316
+ import type { DozorContextValue, DozorContextState } from "@kharko/dozor-react";
317
+ import type { DozorOptions, DozorState } from "@kharko/dozor";
318
+ ```
319
+
320
+ ### `DozorContextState`
321
+
322
+ ```ts
323
+ type DozorContextState = DozorState | "not_initialized";
324
+ // = "not_initialized" | "idle" | "recording" | "paused" | "stopped"
325
+ ```
326
+
327
+ ### `DozorContextValue`
328
+
329
+ Full type of the object returned by `useDozor()`. See [Returned properties](#returned-properties) and [Returned methods](#returned-methods) above.
330
+
331
+ ## Peer dependencies
332
+
333
+ | Package | Version |
334
+ |---|---|
335
+ | `@kharko/dozor` | `*` |
336
+ | `react` | `>=18` |
337
+
338
+ ## License
339
+
340
+ MIT
package/dist/index.cjs CHANGED
@@ -35,15 +35,24 @@ function DozorProvider({ options, children }) {
35
35
  const instanceRef = (0, import_react.useRef)(null);
36
36
  const [state, setState] = (0, import_react.useState)("not_initialized");
37
37
  const [sessionId, setSessionId] = (0, import_react.useState)(null);
38
+ const [isHeld, setIsHeld] = (0, import_react.useState)(false);
39
+ const [userId, setUserIdState] = (0, import_react.useState)(null);
40
+ const [bufferSize, setBufferSize] = (0, import_react.useState)(0);
38
41
  const syncState = (0, import_react.useCallback)(() => {
39
42
  const d = instanceRef.current;
40
43
  if (!d) {
41
44
  setState("not_initialized");
42
45
  setSessionId(null);
46
+ setIsHeld(false);
47
+ setUserIdState(null);
48
+ setBufferSize(0);
43
49
  return;
44
50
  }
45
51
  setState(d.state);
46
52
  setSessionId(d.sessionId);
53
+ setIsHeld(d.isHeld);
54
+ setUserIdState(d.userId);
55
+ setBufferSize(d.bufferSize);
47
56
  }, []);
48
57
  const init = (0, import_react.useCallback)(
49
58
  (opts) => {
@@ -75,22 +84,51 @@ function DozorProvider({ options, children }) {
75
84
  instanceRef.current = null;
76
85
  syncState();
77
86
  }, [syncState]);
87
+ const hold = (0, import_react.useCallback)(() => {
88
+ instanceRef.current?.hold();
89
+ syncState();
90
+ }, [syncState]);
91
+ const release = (0, import_react.useCallback)(
92
+ (options2) => {
93
+ instanceRef.current?.release(options2);
94
+ syncState();
95
+ },
96
+ [syncState]
97
+ );
98
+ const setUserId = (0, import_react.useCallback)(
99
+ (id) => {
100
+ instanceRef.current?.setUserId(id);
101
+ syncState();
102
+ },
103
+ [syncState]
104
+ );
78
105
  (0, import_react.useEffect)(() => {
79
106
  if (options && !instanceRef.current) {
80
107
  init(options);
81
108
  }
82
109
  }, []);
110
+ (0, import_react.useEffect)(() => {
111
+ const handler = () => syncState();
112
+ addEventListener("visibilitychange", handler);
113
+ return () => removeEventListener("visibilitychange", handler);
114
+ }, [syncState]);
83
115
  const value = {
84
116
  state,
85
117
  sessionId,
86
118
  isRecording: state === "recording",
87
119
  isPaused: state === "paused",
120
+ isHeld,
121
+ userId,
122
+ bufferSize,
88
123
  init,
89
124
  start,
90
125
  pause,
91
126
  resume,
92
127
  stop,
93
- cancel
128
+ cancel,
129
+ hold,
130
+ release,
131
+ setUserId
94
132
  };
95
133
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DozorContext.Provider, { value, children });
96
134
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/dozor-context.tsx","../src/use-dozor.ts"],"sourcesContent":["export { DozorProvider } from \"./dozor-context.js\";\nexport { useDozor } from \"./use-dozor.js\";\nexport type { DozorContextValue, DozorContextState } from \"./dozor-context.js\";\n","import { createContext, useCallback, useRef, useState, useEffect, type ReactNode } from \"react\";\nimport { Dozor } from \"@kharko/dozor\";\nimport type { DozorOptions, DozorState } from \"@kharko/dozor\";\n\nexport type DozorContextState = DozorState | \"not_initialized\";\n\nexport interface DozorContextValue {\n /** Current lifecycle state. `\"not_initialized\"` when `init()` hasn't been called yet. */\n state: DozorContextState;\n /** Current session ID, or `null` before init. */\n sessionId: string | null;\n /** `true` when actively recording. */\n isRecording: boolean;\n /** `true` when paused via `pause()`. */\n isPaused: boolean;\n\n /** Initialize the Dozor recorder. No-op if already initialized. */\n init: (options: DozorOptions) => void;\n /** Start recording (only when `autoStart: false`). */\n start: () => void;\n /** Pause recording without destroying the session. */\n pause: () => void;\n /** Resume recording after a pause. */\n resume: () => void;\n /** Stop recording, flush remaining events, destroy instance. */\n stop: () => void;\n /** Discard session — drop buffer + delete from server. */\n cancel: () => void;\n}\n\nexport const DozorContext = createContext<DozorContextValue | null>(null);\n\ninterface DozorProviderProps {\n /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */\n options?: DozorOptions;\n children: ReactNode;\n}\n\nexport function DozorProvider({ options, children }: DozorProviderProps) {\n const instanceRef = useRef<Dozor | null>(null);\n\n const [state, setState] = useState<DozorContextState>(\"not_initialized\");\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n /** Read current values from the Dozor instance and sync to React state. */\n const syncState = useCallback(() => {\n const d = instanceRef.current;\n if (!d) {\n setState(\"not_initialized\");\n setSessionId(null);\n return;\n }\n setState(d.state);\n setSessionId(d.sessionId);\n }, []);\n\n const init = useCallback(\n (opts: DozorOptions) => {\n if (instanceRef.current) return;\n instanceRef.current = Dozor.init(opts);\n syncState();\n },\n [syncState],\n );\n\n const start = useCallback(() => {\n instanceRef.current?.start();\n syncState();\n }, [syncState]);\n\n const pause = useCallback(() => {\n instanceRef.current?.pause();\n syncState();\n }, [syncState]);\n\n const resume = useCallback(() => {\n instanceRef.current?.resume();\n syncState();\n }, [syncState]);\n\n const stop = useCallback(() => {\n instanceRef.current?.stop();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const cancel = useCallback(() => {\n instanceRef.current?.cancel();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n // Auto-init on mount when options are provided\n useEffect(() => {\n if (options && !instanceRef.current) {\n init(options);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps — intentionally run once on mount\n\n const value: DozorContextValue = {\n state,\n sessionId,\n isRecording: state === \"recording\",\n isPaused: state === \"paused\",\n init,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n\n return <DozorContext.Provider value={value}>{children}</DozorContext.Provider>;\n}\n","import { useContext } from \"react\";\nimport { DozorContext, type DozorContextValue } from \"./dozor-context.js\";\n\n/**\n * Access the Dozor recorder state and controls.\n * Must be used within a `<DozorProvider>`.\n */\nexport function useDozor(): DozorContextValue {\n const ctx = useContext(DozorContext);\n if (!ctx) {\n throw new Error(\"useDozor must be used within a <DozorProvider>\");\n }\n return ctx;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAwF;AACxF,mBAAsB;AA+Gb;AAlFF,IAAM,mBAAe,4BAAwC,IAAI;AAQjE,SAAS,cAAc,EAAE,SAAS,SAAS,GAAuB;AACvE,QAAM,kBAAc,qBAAqB,IAAI;AAE7C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAA4B,iBAAiB;AACvE,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAwB,IAAI;AAG9D,QAAM,gBAAY,0BAAY,MAAM;AAClC,UAAM,IAAI,YAAY;AACtB,QAAI,CAAC,GAAG;AACN,eAAS,iBAAiB;AAC1B,mBAAa,IAAI;AACjB;AAAA,IACF;AACA,aAAS,EAAE,KAAK;AAChB,iBAAa,EAAE,SAAS;AAAA,EAC1B,GAAG,CAAC,CAAC;AAEL,QAAM,WAAO;AAAA,IACX,CAAC,SAAuB;AACtB,UAAI,YAAY,QAAS;AACzB,kBAAY,UAAU,mBAAM,KAAK,IAAI;AACrC,gBAAU;AAAA,IACZ;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,YAAQ,0BAAY,MAAM;AAC9B,gBAAY,SAAS,MAAM;AAC3B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,YAAQ,0BAAY,MAAM;AAC9B,gBAAY,SAAS,MAAM;AAC3B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,aAAS,0BAAY,MAAM;AAC/B,gBAAY,SAAS,OAAO;AAC5B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,WAAO,0BAAY,MAAM;AAC7B,gBAAY,SAAS,KAAK;AAC1B,gBAAY,UAAU;AACtB,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,aAAS,0BAAY,MAAM;AAC/B,gBAAY,SAAS,OAAO;AAC5B,gBAAY,UAAU;AACtB,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAGd,8BAAU,MAAM;AACd,QAAI,WAAW,CAAC,YAAY,SAAS;AACnC,WAAK,OAAO;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,QAA2B;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,aAAa,UAAU;AAAA,IACvB,UAAU,UAAU;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,4CAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;;;ACjHA,IAAAA,gBAA2B;AAOpB,SAAS,WAA8B;AAC5C,QAAM,UAAM,0BAAW,YAAY;AACnC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,SAAO;AACT;","names":["import_react"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/dozor-context.tsx","../src/use-dozor.ts"],"sourcesContent":["export { DozorProvider } from \"./dozor-context.js\";\nexport { useDozor } from \"./use-dozor.js\";\nexport type { DozorContextValue, DozorContextState } from \"./dozor-context.js\";\n","import { createContext, useCallback, useRef, useState, useEffect, type ReactNode } from \"react\";\nimport { Dozor } from \"@kharko/dozor\";\nimport type { DozorOptions, DozorState } from \"@kharko/dozor\";\n\nexport type DozorContextState = DozorState | \"not_initialized\";\n\nexport interface DozorContextValue {\n /** Current lifecycle state. `\"not_initialized\"` when `init()` hasn't been called yet. */\n state: DozorContextState;\n /** Current session ID, or `null` before init. */\n sessionId: string | null;\n /** `true` when actively recording. */\n isRecording: boolean;\n /** `true` when paused via `pause()`. */\n isPaused: boolean;\n /** `true` when transport is held — events are buffered locally but not sent. */\n isHeld: boolean;\n /** Current user ID, or `null` if not set. */\n userId: string | null;\n /** Number of events currently buffered in memory (not yet sent). */\n bufferSize: number;\n\n /** Initialize the Dozor recorder. No-op if already initialized. */\n init: (options: DozorOptions) => void;\n /** Start recording (only when `autoStart: false`). */\n start: () => void;\n /** Pause recording without destroying the session. */\n pause: () => void;\n /** Resume recording after a pause. */\n resume: () => void;\n /** Stop recording, flush remaining events, destroy instance. */\n stop: () => void;\n /** Discard session — drop buffer + delete from server. */\n cancel: () => void;\n /** Hold transport — recording continues but events are buffered without sending. */\n hold: () => void;\n /** Release transport hold — flush buffered events and resume sending. Pass `{ discard: true }` to drop held events. */\n release: (options?: { discard?: boolean }) => void;\n /** Set or update the user ID. Useful when the user logs in after recording has started. */\n setUserId: (id: string) => void;\n}\n\nexport const DozorContext = createContext<DozorContextValue | null>(null);\n\ninterface DozorProviderProps {\n /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */\n options?: DozorOptions;\n children: ReactNode;\n}\n\nexport function DozorProvider({ options, children }: DozorProviderProps) {\n const instanceRef = useRef<Dozor | null>(null);\n\n const [state, setState] = useState<DozorContextState>(\"not_initialized\");\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [isHeld, setIsHeld] = useState(false);\n const [userId, setUserIdState] = useState<string | null>(null);\n const [bufferSize, setBufferSize] = useState(0);\n\n /** Read current values from the Dozor instance and sync to React state. */\n const syncState = useCallback(() => {\n const d = instanceRef.current;\n if (!d) {\n setState(\"not_initialized\");\n setSessionId(null);\n setIsHeld(false);\n setUserIdState(null);\n setBufferSize(0);\n return;\n }\n setState(d.state);\n setSessionId(d.sessionId);\n setIsHeld(d.isHeld);\n setUserIdState(d.userId);\n setBufferSize(d.bufferSize);\n }, []);\n\n const init = useCallback(\n (opts: DozorOptions) => {\n if (instanceRef.current) return;\n instanceRef.current = Dozor.init(opts);\n syncState();\n },\n [syncState],\n );\n\n const start = useCallback(() => {\n instanceRef.current?.start();\n syncState();\n }, [syncState]);\n\n const pause = useCallback(() => {\n instanceRef.current?.pause();\n syncState();\n }, [syncState]);\n\n const resume = useCallback(() => {\n instanceRef.current?.resume();\n syncState();\n }, [syncState]);\n\n const stop = useCallback(() => {\n instanceRef.current?.stop();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const cancel = useCallback(() => {\n instanceRef.current?.cancel();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const hold = useCallback(() => {\n instanceRef.current?.hold();\n syncState();\n }, [syncState]);\n\n const release = useCallback(\n (options?: { discard?: boolean }) => {\n instanceRef.current?.release(options);\n syncState();\n },\n [syncState],\n );\n\n const setUserId = useCallback(\n (id: string) => {\n instanceRef.current?.setUserId(id);\n syncState();\n },\n [syncState],\n );\n\n // Auto-init on mount when options are provided\n useEffect(() => {\n if (options && !instanceRef.current) {\n init(options);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps — intentionally run once on mount\n\n // Sync React state when the tab visibility changes (the core SDK may auto-pause/resume internally)\n useEffect(() => {\n const handler = () => syncState();\n addEventListener(\"visibilitychange\", handler);\n return () => removeEventListener(\"visibilitychange\", handler);\n }, [syncState]);\n\n const value: DozorContextValue = {\n state,\n sessionId,\n isRecording: state === \"recording\",\n isPaused: state === \"paused\",\n isHeld,\n userId,\n bufferSize,\n init,\n start,\n pause,\n resume,\n stop,\n cancel,\n hold,\n release,\n setUserId,\n };\n\n return <DozorContext.Provider value={value}>{children}</DozorContext.Provider>;\n}\n","import { useContext } from \"react\";\nimport { DozorContext, type DozorContextValue } from \"./dozor-context.js\";\n\n/**\n * Access the Dozor recorder state and controls.\n * Must be used within a `<DozorProvider>`.\n */\nexport function useDozor(): DozorContextValue {\n const ctx = useContext(DozorContext);\n if (!ctx) {\n throw new Error(\"useDozor must be used within a <DozorProvider>\");\n }\n return ctx;\n}\n"],"mappings":"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","names":["options","import_react"]}
package/dist/index.d.cts CHANGED
@@ -12,6 +12,12 @@ interface DozorContextValue {
12
12
  isRecording: boolean;
13
13
  /** `true` when paused via `pause()`. */
14
14
  isPaused: boolean;
15
+ /** `true` when transport is held — events are buffered locally but not sent. */
16
+ isHeld: boolean;
17
+ /** Current user ID, or `null` if not set. */
18
+ userId: string | null;
19
+ /** Number of events currently buffered in memory (not yet sent). */
20
+ bufferSize: number;
15
21
  /** Initialize the Dozor recorder. No-op if already initialized. */
16
22
  init: (options: DozorOptions) => void;
17
23
  /** Start recording (only when `autoStart: false`). */
@@ -24,6 +30,14 @@ interface DozorContextValue {
24
30
  stop: () => void;
25
31
  /** Discard session — drop buffer + delete from server. */
26
32
  cancel: () => void;
33
+ /** Hold transport — recording continues but events are buffered without sending. */
34
+ hold: () => void;
35
+ /** Release transport hold — flush buffered events and resume sending. Pass `{ discard: true }` to drop held events. */
36
+ release: (options?: {
37
+ discard?: boolean;
38
+ }) => void;
39
+ /** Set or update the user ID. Useful when the user logs in after recording has started. */
40
+ setUserId: (id: string) => void;
27
41
  }
28
42
  interface DozorProviderProps {
29
43
  /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */
package/dist/index.d.ts CHANGED
@@ -12,6 +12,12 @@ interface DozorContextValue {
12
12
  isRecording: boolean;
13
13
  /** `true` when paused via `pause()`. */
14
14
  isPaused: boolean;
15
+ /** `true` when transport is held — events are buffered locally but not sent. */
16
+ isHeld: boolean;
17
+ /** Current user ID, or `null` if not set. */
18
+ userId: string | null;
19
+ /** Number of events currently buffered in memory (not yet sent). */
20
+ bufferSize: number;
15
21
  /** Initialize the Dozor recorder. No-op if already initialized. */
16
22
  init: (options: DozorOptions) => void;
17
23
  /** Start recording (only when `autoStart: false`). */
@@ -24,6 +30,14 @@ interface DozorContextValue {
24
30
  stop: () => void;
25
31
  /** Discard session — drop buffer + delete from server. */
26
32
  cancel: () => void;
33
+ /** Hold transport — recording continues but events are buffered without sending. */
34
+ hold: () => void;
35
+ /** Release transport hold — flush buffered events and resume sending. Pass `{ discard: true }` to drop held events. */
36
+ release: (options?: {
37
+ discard?: boolean;
38
+ }) => void;
39
+ /** Set or update the user ID. Useful when the user logs in after recording has started. */
40
+ setUserId: (id: string) => void;
27
41
  }
28
42
  interface DozorProviderProps {
29
43
  /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */
package/dist/index.js CHANGED
@@ -9,15 +9,24 @@ function DozorProvider({ options, children }) {
9
9
  const instanceRef = useRef(null);
10
10
  const [state, setState] = useState("not_initialized");
11
11
  const [sessionId, setSessionId] = useState(null);
12
+ const [isHeld, setIsHeld] = useState(false);
13
+ const [userId, setUserIdState] = useState(null);
14
+ const [bufferSize, setBufferSize] = useState(0);
12
15
  const syncState = useCallback(() => {
13
16
  const d = instanceRef.current;
14
17
  if (!d) {
15
18
  setState("not_initialized");
16
19
  setSessionId(null);
20
+ setIsHeld(false);
21
+ setUserIdState(null);
22
+ setBufferSize(0);
17
23
  return;
18
24
  }
19
25
  setState(d.state);
20
26
  setSessionId(d.sessionId);
27
+ setIsHeld(d.isHeld);
28
+ setUserIdState(d.userId);
29
+ setBufferSize(d.bufferSize);
21
30
  }, []);
22
31
  const init = useCallback(
23
32
  (opts) => {
@@ -49,22 +58,51 @@ function DozorProvider({ options, children }) {
49
58
  instanceRef.current = null;
50
59
  syncState();
51
60
  }, [syncState]);
61
+ const hold = useCallback(() => {
62
+ instanceRef.current?.hold();
63
+ syncState();
64
+ }, [syncState]);
65
+ const release = useCallback(
66
+ (options2) => {
67
+ instanceRef.current?.release(options2);
68
+ syncState();
69
+ },
70
+ [syncState]
71
+ );
72
+ const setUserId = useCallback(
73
+ (id) => {
74
+ instanceRef.current?.setUserId(id);
75
+ syncState();
76
+ },
77
+ [syncState]
78
+ );
52
79
  useEffect(() => {
53
80
  if (options && !instanceRef.current) {
54
81
  init(options);
55
82
  }
56
83
  }, []);
84
+ useEffect(() => {
85
+ const handler = () => syncState();
86
+ addEventListener("visibilitychange", handler);
87
+ return () => removeEventListener("visibilitychange", handler);
88
+ }, [syncState]);
57
89
  const value = {
58
90
  state,
59
91
  sessionId,
60
92
  isRecording: state === "recording",
61
93
  isPaused: state === "paused",
94
+ isHeld,
95
+ userId,
96
+ bufferSize,
62
97
  init,
63
98
  start,
64
99
  pause,
65
100
  resume,
66
101
  stop,
67
- cancel
102
+ cancel,
103
+ hold,
104
+ release,
105
+ setUserId
68
106
  };
69
107
  return /* @__PURE__ */ jsx(DozorContext.Provider, { value, children });
70
108
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/dozor-context.tsx","../src/use-dozor.ts"],"sourcesContent":["import { createContext, useCallback, useRef, useState, useEffect, type ReactNode } from \"react\";\nimport { Dozor } from \"@kharko/dozor\";\nimport type { DozorOptions, DozorState } from \"@kharko/dozor\";\n\nexport type DozorContextState = DozorState | \"not_initialized\";\n\nexport interface DozorContextValue {\n /** Current lifecycle state. `\"not_initialized\"` when `init()` hasn't been called yet. */\n state: DozorContextState;\n /** Current session ID, or `null` before init. */\n sessionId: string | null;\n /** `true` when actively recording. */\n isRecording: boolean;\n /** `true` when paused via `pause()`. */\n isPaused: boolean;\n\n /** Initialize the Dozor recorder. No-op if already initialized. */\n init: (options: DozorOptions) => void;\n /** Start recording (only when `autoStart: false`). */\n start: () => void;\n /** Pause recording without destroying the session. */\n pause: () => void;\n /** Resume recording after a pause. */\n resume: () => void;\n /** Stop recording, flush remaining events, destroy instance. */\n stop: () => void;\n /** Discard session — drop buffer + delete from server. */\n cancel: () => void;\n}\n\nexport const DozorContext = createContext<DozorContextValue | null>(null);\n\ninterface DozorProviderProps {\n /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */\n options?: DozorOptions;\n children: ReactNode;\n}\n\nexport function DozorProvider({ options, children }: DozorProviderProps) {\n const instanceRef = useRef<Dozor | null>(null);\n\n const [state, setState] = useState<DozorContextState>(\"not_initialized\");\n const [sessionId, setSessionId] = useState<string | null>(null);\n\n /** Read current values from the Dozor instance and sync to React state. */\n const syncState = useCallback(() => {\n const d = instanceRef.current;\n if (!d) {\n setState(\"not_initialized\");\n setSessionId(null);\n return;\n }\n setState(d.state);\n setSessionId(d.sessionId);\n }, []);\n\n const init = useCallback(\n (opts: DozorOptions) => {\n if (instanceRef.current) return;\n instanceRef.current = Dozor.init(opts);\n syncState();\n },\n [syncState],\n );\n\n const start = useCallback(() => {\n instanceRef.current?.start();\n syncState();\n }, [syncState]);\n\n const pause = useCallback(() => {\n instanceRef.current?.pause();\n syncState();\n }, [syncState]);\n\n const resume = useCallback(() => {\n instanceRef.current?.resume();\n syncState();\n }, [syncState]);\n\n const stop = useCallback(() => {\n instanceRef.current?.stop();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const cancel = useCallback(() => {\n instanceRef.current?.cancel();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n // Auto-init on mount when options are provided\n useEffect(() => {\n if (options && !instanceRef.current) {\n init(options);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps — intentionally run once on mount\n\n const value: DozorContextValue = {\n state,\n sessionId,\n isRecording: state === \"recording\",\n isPaused: state === \"paused\",\n init,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n\n return <DozorContext.Provider value={value}>{children}</DozorContext.Provider>;\n}\n","import { useContext } from \"react\";\nimport { DozorContext, type DozorContextValue } from \"./dozor-context.js\";\n\n/**\n * Access the Dozor recorder state and controls.\n * Must be used within a `<DozorProvider>`.\n */\nexport function useDozor(): DozorContextValue {\n const ctx = useContext(DozorContext);\n if (!ctx) {\n throw new Error(\"useDozor must be used within a <DozorProvider>\");\n }\n return ctx;\n}\n"],"mappings":";;;AAAA,SAAS,eAAe,aAAa,QAAQ,UAAU,iBAAiC;AACxF,SAAS,aAAa;AA+Gb;AAlFF,IAAM,eAAe,cAAwC,IAAI;AAQjE,SAAS,cAAc,EAAE,SAAS,SAAS,GAAuB;AACvE,QAAM,cAAc,OAAqB,IAAI;AAE7C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA4B,iBAAiB;AACvE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAwB,IAAI;AAG9D,QAAM,YAAY,YAAY,MAAM;AAClC,UAAM,IAAI,YAAY;AACtB,QAAI,CAAC,GAAG;AACN,eAAS,iBAAiB;AAC1B,mBAAa,IAAI;AACjB;AAAA,IACF;AACA,aAAS,EAAE,KAAK;AAChB,iBAAa,EAAE,SAAS;AAAA,EAC1B,GAAG,CAAC,CAAC;AAEL,QAAM,OAAO;AAAA,IACX,CAAC,SAAuB;AACtB,UAAI,YAAY,QAAS;AACzB,kBAAY,UAAU,MAAM,KAAK,IAAI;AACrC,gBAAU;AAAA,IACZ;AAAA,IACA,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,gBAAY,SAAS,MAAM;AAC3B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,QAAQ,YAAY,MAAM;AAC9B,gBAAY,SAAS,MAAM;AAC3B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,SAAS,YAAY,MAAM;AAC/B,gBAAY,SAAS,OAAO;AAC5B,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,OAAO,YAAY,MAAM;AAC7B,gBAAY,SAAS,KAAK;AAC1B,gBAAY,UAAU;AACtB,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,SAAS,YAAY,MAAM;AAC/B,gBAAY,SAAS,OAAO;AAC5B,gBAAY,UAAU;AACtB,cAAU;AAAA,EACZ,GAAG,CAAC,SAAS,CAAC;AAGd,YAAU,MAAM;AACd,QAAI,WAAW,CAAC,YAAY,SAAS;AACnC,WAAK,OAAO;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,QAA2B;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,aAAa,UAAU;AAAA,IACvB,UAAU,UAAU;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;;;ACjHA,SAAS,kBAAkB;AAOpB,SAAS,WAA8B;AAC5C,QAAM,MAAM,WAAW,YAAY;AACnC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/dozor-context.tsx","../src/use-dozor.ts"],"sourcesContent":["import { createContext, useCallback, useRef, useState, useEffect, type ReactNode } from \"react\";\nimport { Dozor } from \"@kharko/dozor\";\nimport type { DozorOptions, DozorState } from \"@kharko/dozor\";\n\nexport type DozorContextState = DozorState | \"not_initialized\";\n\nexport interface DozorContextValue {\n /** Current lifecycle state. `\"not_initialized\"` when `init()` hasn't been called yet. */\n state: DozorContextState;\n /** Current session ID, or `null` before init. */\n sessionId: string | null;\n /** `true` when actively recording. */\n isRecording: boolean;\n /** `true` when paused via `pause()`. */\n isPaused: boolean;\n /** `true` when transport is held — events are buffered locally but not sent. */\n isHeld: boolean;\n /** Current user ID, or `null` if not set. */\n userId: string | null;\n /** Number of events currently buffered in memory (not yet sent). */\n bufferSize: number;\n\n /** Initialize the Dozor recorder. No-op if already initialized. */\n init: (options: DozorOptions) => void;\n /** Start recording (only when `autoStart: false`). */\n start: () => void;\n /** Pause recording without destroying the session. */\n pause: () => void;\n /** Resume recording after a pause. */\n resume: () => void;\n /** Stop recording, flush remaining events, destroy instance. */\n stop: () => void;\n /** Discard session — drop buffer + delete from server. */\n cancel: () => void;\n /** Hold transport — recording continues but events are buffered without sending. */\n hold: () => void;\n /** Release transport hold — flush buffered events and resume sending. Pass `{ discard: true }` to drop held events. */\n release: (options?: { discard?: boolean }) => void;\n /** Set or update the user ID. Useful when the user logs in after recording has started. */\n setUserId: (id: string) => void;\n}\n\nexport const DozorContext = createContext<DozorContextValue | null>(null);\n\ninterface DozorProviderProps {\n /** Pass options to auto-initialize Dozor on mount. Omit for manual `init()`. */\n options?: DozorOptions;\n children: ReactNode;\n}\n\nexport function DozorProvider({ options, children }: DozorProviderProps) {\n const instanceRef = useRef<Dozor | null>(null);\n\n const [state, setState] = useState<DozorContextState>(\"not_initialized\");\n const [sessionId, setSessionId] = useState<string | null>(null);\n const [isHeld, setIsHeld] = useState(false);\n const [userId, setUserIdState] = useState<string | null>(null);\n const [bufferSize, setBufferSize] = useState(0);\n\n /** Read current values from the Dozor instance and sync to React state. */\n const syncState = useCallback(() => {\n const d = instanceRef.current;\n if (!d) {\n setState(\"not_initialized\");\n setSessionId(null);\n setIsHeld(false);\n setUserIdState(null);\n setBufferSize(0);\n return;\n }\n setState(d.state);\n setSessionId(d.sessionId);\n setIsHeld(d.isHeld);\n setUserIdState(d.userId);\n setBufferSize(d.bufferSize);\n }, []);\n\n const init = useCallback(\n (opts: DozorOptions) => {\n if (instanceRef.current) return;\n instanceRef.current = Dozor.init(opts);\n syncState();\n },\n [syncState],\n );\n\n const start = useCallback(() => {\n instanceRef.current?.start();\n syncState();\n }, [syncState]);\n\n const pause = useCallback(() => {\n instanceRef.current?.pause();\n syncState();\n }, [syncState]);\n\n const resume = useCallback(() => {\n instanceRef.current?.resume();\n syncState();\n }, [syncState]);\n\n const stop = useCallback(() => {\n instanceRef.current?.stop();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const cancel = useCallback(() => {\n instanceRef.current?.cancel();\n instanceRef.current = null;\n syncState();\n }, [syncState]);\n\n const hold = useCallback(() => {\n instanceRef.current?.hold();\n syncState();\n }, [syncState]);\n\n const release = useCallback(\n (options?: { discard?: boolean }) => {\n instanceRef.current?.release(options);\n syncState();\n },\n [syncState],\n );\n\n const setUserId = useCallback(\n (id: string) => {\n instanceRef.current?.setUserId(id);\n syncState();\n },\n [syncState],\n );\n\n // Auto-init on mount when options are provided\n useEffect(() => {\n if (options && !instanceRef.current) {\n init(options);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps — intentionally run once on mount\n\n // Sync React state when the tab visibility changes (the core SDK may auto-pause/resume internally)\n useEffect(() => {\n const handler = () => syncState();\n addEventListener(\"visibilitychange\", handler);\n return () => removeEventListener(\"visibilitychange\", handler);\n }, [syncState]);\n\n const value: DozorContextValue = {\n state,\n sessionId,\n isRecording: state === \"recording\",\n isPaused: state === \"paused\",\n isHeld,\n userId,\n bufferSize,\n init,\n start,\n pause,\n resume,\n stop,\n cancel,\n hold,\n release,\n setUserId,\n };\n\n return <DozorContext.Provider value={value}>{children}</DozorContext.Provider>;\n}\n","import { useContext } from \"react\";\nimport { DozorContext, type DozorContextValue } from \"./dozor-context.js\";\n\n/**\n * Access the Dozor recorder state and controls.\n * Must be used within a `<DozorProvider>`.\n */\nexport function useDozor(): DozorContextValue {\n const ctx = useContext(DozorContext);\n if (!ctx) {\n throw new Error(\"useDozor must be used within a <DozorProvider>\");\n }\n return ctx;\n}\n"],"mappings":"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","names":["options"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kharko/dozor-react",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "React bindings for @kharko/dozor session recording SDK",
5
5
  "license": "MIT",
6
6
  "type": "module",