@capturly/capture-sdk 2.10.2

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/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # @capturly/capture-sdk
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2025 Corey Alan LLC. All rights reserved.
2
+
3
+ PROPRIETARY SOFTWARE LICENSE
4
+
5
+ This software and associated documentation files (the "Software") are
6
+ proprietary and confidential. Unauthorized copying, modification,
7
+ distribution, or use of this Software, via any medium, is strictly
8
+ prohibited without the express written permission of Corey Alan LLC.
9
+
10
+ The Software is licensed, not sold. This license does not grant you any
11
+ rights to use the Software except as expressly set forth in a separate
12
+ written agreement between you and Corey Alan LLC.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
+ COREY ALAN LLC BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19
+ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
21
+
22
+ For licensing inquiries, contact: contact@capturly.app
23
+
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @capturly/capture-sdk
2
+
3
+ Record full-quality audio and video on your user's own device, from your own
4
+ product. The recording happens locally and uploads afterwards, so a weak
5
+ connection costs your user a slow upload rather than a ruined take. What lands
6
+ in your Capturly library is the original file, not a re-encode of a stream
7
+ that was fighting their WiFi.
8
+
9
+ This is the browser half of Capturly's capture-request flow. Your backend
10
+ creates the request and mints a per-person link; this package records against
11
+ it.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @capturly/capture-sdk
17
+ ```
18
+
19
+ Public package. Nothing here needs npm scope access; the recording engine it
20
+ uses is bundled in.
21
+
22
+ ## Environment
23
+
24
+ Browsers only. It needs `MediaRecorder` or WebCodecs, OPFS
25
+ (`navigator.storage`), and `getUserMedia`: roughly Chrome/Edge 111+,
26
+ Firefox 113+, Safari 16.4+. Call `CaptureClient.isSupported()` before you show
27
+ a record button; a browser that fails it cannot be talked into working, so
28
+ offer another browser rather than a retry.
29
+
30
+ `getUserMedia` also requires a secure context, so this will not work on a page
31
+ served over plain HTTP (localhost excepted).
32
+
33
+ ## The shape of an integration
34
+
35
+ Three things happen on your server and one in the browser.
36
+
37
+ **1. Your backend creates a capture request** with your secret API key. Never
38
+ put that key in a browser; it carries your whole organization's access.
39
+
40
+ ```js
41
+ const res = await fetch('https://capturly.app/api/v1/capture-requests', {
42
+ method: 'POST',
43
+ headers: { authorization: `Bearer ${process.env.CAPTURLY_API_KEY}`, 'content-type': 'application/json' },
44
+ body: JSON.stringify({ title: 'Customer stories', prompt: 'How did it go?', status: 'active' }),
45
+ });
46
+ const { captureRequest } = await res.json();
47
+ ```
48
+
49
+ **2. Your backend adds the person** and gets their personal link back. Adding a
50
+ contact does not email them. Sending is yours, if you want to send anything at
51
+ all.
52
+
53
+ ```js
54
+ const res = await fetch(
55
+ `https://capturly.app/api/v1/capture-requests/${captureRequest.id}/contacts`,
56
+ {
57
+ method: 'POST',
58
+ headers: { authorization: `Bearer ${process.env.CAPTURLY_API_KEY}`, 'content-type': 'application/json' },
59
+ body: JSON.stringify({ contacts: [{ email: 'jane@example.com', name: 'Jane Doe' }] }),
60
+ },
61
+ );
62
+ const { contacts } = await res.json();
63
+ // contacts[0].url is https://capturly.app/c/<contactId>?k=<key>
64
+ ```
65
+
66
+ **3. Your frontend records** with the id and key from that URL.
67
+
68
+ ```js
69
+ import { CaptureClient } from '@capturly/capture-sdk';
70
+
71
+ const client = new CaptureClient({
72
+ access: { mode: 'contact', contactId, key },
73
+ });
74
+
75
+ const stream = await client.preview(); // shows the permission prompt
76
+ videoEl.srcObject = stream; // render it however you like
77
+
78
+ await client.start();
79
+ // ...
80
+ const take = await client.stop(); // nothing has left the browser yet
81
+ previewEl.src = take.previewUrl; // let them watch it back
82
+
83
+ await client.submit({ releaseAccepted: true, onProgress: (p) => setBar(p) });
84
+ client.destroy();
85
+ ```
86
+
87
+ **4. Your backend learns it landed**, from the
88
+ `capture_request.submission_received` webhook, or by polling
89
+ `GET /v1/capture-requests/{id}/submissions`. Download the master through
90
+ `GET /v1/recordings/{id}`, where it arrives with its capture manifest: codecs,
91
+ resolution, frame rate, dropped frames, checksum, and the consent that was
92
+ accepted.
93
+
94
+ The recording id is not returned to the browser. Trust your backend for it.
95
+
96
+ ## Consent
97
+
98
+ If the capture request requires a release, read its `release.text` from the API
99
+ and show it before recording, then pass `releaseAccepted: true` only if the
100
+ person actually accepted it. That stores a consent record with the text
101
+ verbatim as it was displayed. Editing the request's wording later never rewrites what a
102
+ past respondent appears to have agreed to; passing `true` without showing them
103
+ anything produces a record that says something untrue.
104
+
105
+ ## Errors
106
+
107
+ Every failure is a `CaptureError` with a `code`. The split that matters is
108
+ between what your user can fix and what only you can:
109
+
110
+ | Code | Who can act |
111
+ | --- | --- |
112
+ | `unsupported_browser`, `permission_denied`, `no_devices` | your user |
113
+ | `link_expired`, `request_closed`, `quota_exceeded` | you |
114
+ | `network`, `upload_failed` | retryable |
115
+
116
+ A retry for the middle row can never succeed.
117
+
118
+ ## What this does not do
119
+
120
+ **Multi-participant sessions.** This records one person at a time. Hosting a
121
+ room where several people record at once needs signaling, TURN and a join
122
+ ceremony, and is not part of this package.
123
+
124
+ **UI.** You get a `MediaStream` and state events; you render the buttons.
125
+
126
+ **Transcription.** Capturly delivers masters. Transcription is your pipeline's
127
+ job.
128
+
129
+ ## Reference
130
+
131
+ - `CaptureClient.isSupported()` probes before you show a record button
132
+ - `listDevices()` lists cameras and microphones; labels only populate after the
133
+ first permission grant
134
+ - `preview(options)` requests devices and returns the `MediaStream` to render
135
+ - `start()` and `stop()` drive recording; `stop()` returns the take
136
+ - `submit(options)` uploads and records the submission
137
+ - `discard()` throws the take away so you can record again
138
+ - `destroy()` releases the camera and microphone; call it on unmount
139
+ - `onStateChange(fn)` reports `idle`, `starting`, `recording`, `stopping`,
140
+ `stopped`
141
+
142
+ ## Licence
143
+
144
+ Proprietary. See [LICENSE](./LICENSE).
@@ -0,0 +1,47 @@
1
+ import { type CaptureClientOptions, type CaptureDevice, type CaptureState, type CaptureTake, type PreviewOptions, type SubmitOptions, type SubmitResult } from './types.js';
2
+ type Listener<T> = (value: T) => void;
3
+ export declare class CaptureClient {
4
+ #private;
5
+ constructor(options: CaptureClientOptions);
6
+ /** What the recorder is doing right now. */
7
+ get state(): CaptureState;
8
+ /** Subscribe to state changes. Returns an unsubscribe function. */
9
+ onStateChange(listener: Listener<CaptureState>): () => void;
10
+ /**
11
+ * Whether this browser can record. Check before showing a record button; a
12
+ * browser that fails cannot be retried into working, so offer another one.
13
+ */
14
+ static isSupported(): boolean;
15
+ /**
16
+ * Cameras and microphones. Labels are blank until permission has been
17
+ * granted once, so call this again after `preview()` to fill a picker.
18
+ */
19
+ listDevices(): Promise<CaptureDevice[]>;
20
+ /**
21
+ * Request the camera and microphone and return the stream to render.
22
+ * Separate from `start()` so people can see themselves before recording.
23
+ */
24
+ preview(options?: PreviewOptions): Promise<MediaStream>;
25
+ /** Begin recording the current preview stream. */
26
+ start(): Promise<void>;
27
+ /**
28
+ * Stop and return the take. Nothing has left the browser yet — the file sits
29
+ * in OPFS until `submit()`, so it can be reviewed or re-recorded first.
30
+ */
31
+ stop(): Promise<CaptureTake>;
32
+ /**
33
+ * Upload the take and record the submission: request a signed target, PUT
34
+ * the bytes straight to storage, then confirm. The API never sees the bytes.
35
+ * Confirming is keyed on the object name, so a retry after a lost response
36
+ * resolves to the same submission.
37
+ */
38
+ submit(options: SubmitOptions): Promise<SubmitResult>;
39
+ /** Throw the take away without submitting, so someone can record again. */
40
+ discard(): void;
41
+ /**
42
+ * Release the camera, microphone and recorder. Call it on unmount, or the
43
+ * camera light stays on after the person has navigated away.
44
+ */
45
+ destroy(): void;
46
+ }
47
+ export {};
@@ -0,0 +1,2 @@
1
+ export { CaptureClient } from './CaptureClient.js';
2
+ export { CaptureError, type CaptureAccess, type CaptureClientOptions, type CaptureDevice, type CaptureErrorCode, type CaptureState, type CaptureTake, type PreviewOptions, type SubmitOptions, type SubmitResult, } from './types.js';