@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 +340 -0
- package/dist/index.cjs +39 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"]}
|