@sorisdk/web-audio 0.6.5 → 0.6.7
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 +119 -15
- package/dist/generated/window.d.ts +1 -0
- package/dist/generated/window_bg.js +73 -11
- package/dist/generated/window_bg.wasm +0 -0
- package/dist/generated/window_bg.wasm.d.ts +5 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +303 -76
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ configuration, or Node-based asset server is required:
|
|
|
22
22
|
```html
|
|
23
23
|
<script type="module">
|
|
24
24
|
import { AudioRecognizer } from
|
|
25
|
-
"https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
25
|
+
"https://cdn.iplateia.com/web/sorisdk/v0.6.7/sori-web-audio.mjs";
|
|
26
26
|
|
|
27
27
|
const recognizer = new AudioRecognizer({
|
|
28
28
|
appId: "YOUR_APP_ID",
|
|
@@ -49,13 +49,39 @@ configuration, or Node-based asset server is required:
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Static URL imports are valid inside `<script type="module">`. Dynamic
|
|
52
|
-
`await import("https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
52
|
+
`await import("https://cdn.iplateia.com/web/sorisdk/v0.6.7/sori-web-audio.mjs")`
|
|
53
53
|
is an optional alternative when the SDK should be loaded conditionally.
|
|
54
54
|
|
|
55
55
|
Pin an exact version in production. Do not construct a mutable `latest` URL.
|
|
56
56
|
The module loads its versioned WASM assets and the shared
|
|
57
57
|
`https://cdn.iplateia.com/web/sorisdk/model.pack` automatically.
|
|
58
58
|
|
|
59
|
+
The fixed model response and caller-supplied AudioPacks use the matcher's same
|
|
60
|
+
64 MiB browser transport limit by default. A valid oversized `Content-Length`
|
|
61
|
+
is rejected before the body is consumed, and an unknown-length response is
|
|
62
|
+
canceled on the first chunk that crosses the limit. The response is never
|
|
63
|
+
truncated; failures use the deterministic
|
|
64
|
+
`Pack source exceeds maximum size of <limit> bytes` error.
|
|
65
|
+
|
|
66
|
+
Set an intentional shared override through the matcher options when a larger
|
|
67
|
+
pack is required:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const recognizer = new AudioRecognizer({
|
|
71
|
+
appId: "YOUR_APP_ID",
|
|
72
|
+
ephemeralKey,
|
|
73
|
+
wasm: {
|
|
74
|
+
matcher: {
|
|
75
|
+
maxPackBytes: 96 * 1024 * 1024
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The override applies to both the standalone fixed-model response and ordinary
|
|
82
|
+
pack sources. A caller-supplied `embeddedPackLoader` remains a zero-argument
|
|
83
|
+
callback and is not replaced by the standalone loader.
|
|
84
|
+
|
|
59
85
|
The standalone module removes the frontend build/server requirement, but it
|
|
60
86
|
does not remove the trusted server requirement for ephemeral credentials.
|
|
61
87
|
Provide a backend or serverless endpoint for the `ephemeralKey` callback, and
|
|
@@ -93,7 +119,7 @@ An import map can give the standalone URL the npm package name:
|
|
|
93
119
|
<script type="importmap">
|
|
94
120
|
{
|
|
95
121
|
"imports": {
|
|
96
|
-
"@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.
|
|
122
|
+
"@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.7/sori-web-audio.mjs"
|
|
97
123
|
}
|
|
98
124
|
}
|
|
99
125
|
</script>
|
|
@@ -161,23 +187,61 @@ await recognizer.destroy();
|
|
|
161
187
|
|
|
162
188
|
## Activity refinement
|
|
163
189
|
|
|
164
|
-
The default `AudioRecognizer` activity reporter represents one
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
190
|
+
The default `AudioRecognizer` activity reporter represents one ordered,
|
|
191
|
+
continuous material segment and its later audio-marker enrichment as one
|
|
192
|
+
server activity:
|
|
193
|
+
|
|
194
|
+
1. When the coordinator accepts a successful material match, it captures one
|
|
195
|
+
UTC RFC 3339 `recognized_at` value before any asynchronous request mapping,
|
|
196
|
+
header, token, or fetch work.
|
|
197
|
+
2. The initial `POST /api/activity/` includes that timestamp and the exact
|
|
198
|
+
`DetectedMatch.position` integer in milliseconds. With valid
|
|
199
|
+
`matcherSegments` metadata, the position is local to the matched segment.
|
|
200
|
+
3. A marker-free match with an active marker-scan request also includes
|
|
201
|
+
top-level `refinement_expected: true`.
|
|
202
|
+
4. The returned `activity_id` is retained for 30 seconds.
|
|
203
|
+
5. If the same recognition gains `audioMarker.code`, the SDK sends
|
|
171
204
|
`PUT /api/activity/{activity_id}` with the same material and
|
|
172
205
|
`trait.marker`.
|
|
173
|
-
|
|
206
|
+
6. The refined `campaign` event keeps the same non-empty `activityId`, so an
|
|
174
207
|
application can replace the earlier campaign row deterministically.
|
|
175
208
|
|
|
209
|
+
A consecutive match for the same material and fingerprint type renews a local
|
|
210
|
+
30-second continuity lease without another POST or campaign event. That local
|
|
211
|
+
lease can keep one segment alive beyond the initial server refinement window,
|
|
212
|
+
but it never extends the original PUT deadline. A marker confirmed anywhere
|
|
213
|
+
inside the segment is sticky: the first confirmation can issue at most one PUT,
|
|
214
|
+
and later marker misses or repeated marker evidence issue no mutation or
|
|
215
|
+
downgrade.
|
|
216
|
+
|
|
176
217
|
A marker present on the first match is included in the POST and does not cause
|
|
177
|
-
a PUT or include `refinement_expected
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
218
|
+
a PUT or include `refinement_expected`; that marker-first segment is still
|
|
219
|
+
retained for continuity. Recognition without an active marker scan, plus
|
|
220
|
+
legacy/custom POST-only reporters, also omit the opt-in flag. Custom reporters
|
|
221
|
+
enable segment coalescing only when `refinement` is enabled, preserving legacy
|
|
222
|
+
POST-only behavior otherwise.
|
|
223
|
+
|
|
224
|
+
A different material, an emitted `nomatch`, a gap beyond the continuity lease,
|
|
225
|
+
or stop, clear, destroy, authentication/capture replacement, or reset seals the
|
|
226
|
+
segment. Returning to A after B always creates a new A activity and can never
|
|
227
|
+
refine the earlier A. Marker evidence arriving after a segment is sealed is
|
|
228
|
+
ignored for that segment; create or PUT transport that already started may
|
|
229
|
+
finish under the existing capture, deadline, and cancellation guards.
|
|
230
|
+
|
|
231
|
+
Ordinary fingerprint `match` and marker-state `audiomarker` events remain
|
|
232
|
+
independently observable; coalescing only changes activity mutation and
|
|
233
|
+
campaign delivery. An `audiomarker` event alone does not mean that server
|
|
234
|
+
activity refinement completed.
|
|
235
|
+
|
|
236
|
+
Recognition timing belongs only to the segment's first material POST. Later
|
|
237
|
+
same-material observations update only the internal `lastObservedAt` lease;
|
|
238
|
+
they never replace the first `recognized_at` or `position`. A later marker PUT
|
|
239
|
+
does not include either field, and marker-only events do not create timing
|
|
240
|
+
metadata. `recognized_at` identifies first SDK match acceptance rather than
|
|
241
|
+
HTTP receipt, marker confirmation, refinement, or webhook delivery time. Do not
|
|
242
|
+
derive playback start as `recognized_at - position`: the match is accepted
|
|
243
|
+
after its query window was collected, so that calculation lacks the query
|
|
244
|
+
window's wall-clock anchor.
|
|
181
245
|
|
|
182
246
|
Network and 5xx PUT failures are retried at most twice within the original
|
|
183
247
|
30-second deadline. Server responses 404, 409, and 410 are terminal. An
|
|
@@ -211,6 +275,31 @@ mapping, endpoint, headers, fetcher, and retry policy. The stable terminal
|
|
|
211
275
|
statuses 404, 409, and 410 are never retried even if a custom policy returns
|
|
212
276
|
`true`.
|
|
213
277
|
|
|
278
|
+
`recognized_at` and `position` are optional public request properties for
|
|
279
|
+
source compatibility, but they are SDK-owned transport values. After a custom
|
|
280
|
+
POST mapper returns an eligible material payload, the SDK overwrites any
|
|
281
|
+
conflicting values with the captured recognition timestamp and exact matcher
|
|
282
|
+
position. The SDK removes both properties from all refinement PUT payloads,
|
|
283
|
+
including custom refinement mappings. Existing one-argument POST mappers and
|
|
284
|
+
POST-only transports require no signature changes. Activity responses and
|
|
285
|
+
`campaign` events do not gain timing properties from this request contract.
|
|
286
|
+
|
|
287
|
+
## API endpoint normalization
|
|
288
|
+
|
|
289
|
+
`apiEndpoint` continues to accept a `string` or `URL`. When the SDK derives the
|
|
290
|
+
default authentication and activity URLs, it removes all trailing ASCII `/`
|
|
291
|
+
characters and appends `/auth` or `/activity/` with the existing URL shapes.
|
|
292
|
+
|
|
293
|
+
The endpoint is limited to `MAX_API_ENDPOINT_LENGTH` (32,768 UTF-16 code units).
|
|
294
|
+
The first code unit over that limit throws
|
|
295
|
+
`AudioRecognizer apiEndpoint exceeds maximum length of 32768 UTF-16 code units`
|
|
296
|
+
before trailing-slash scanning or endpoint joining. The SDK rejects the input;
|
|
297
|
+
it never truncates it. An explicitly supplied `apiEndpoint` is length-validated
|
|
298
|
+
during construction even when custom `authEndpoint` and `activityEndpoint`
|
|
299
|
+
values mean its prefix is not used for either join. This resource bound limits
|
|
300
|
+
construction work only. It is not an authentication, authorization, quota, or
|
|
301
|
+
abuse-prevention control.
|
|
302
|
+
|
|
214
303
|
## Session identifiers
|
|
215
304
|
|
|
216
305
|
`AudioRecognizer` creates one pseudonymous session identifier per application
|
|
@@ -296,6 +385,21 @@ const recognizer = new AudioRecognizer({
|
|
|
296
385
|
});
|
|
297
386
|
```
|
|
298
387
|
|
|
388
|
+
Advanced callers that import `@sorisdk/web-audio/generated/window.js`
|
|
389
|
+
directly still use `appendFingerprint(bytes: Uint8Array): void`. Each generated
|
|
390
|
+
window exposes `maxAppendFingerprintByteLength()`, which is the byte length of
|
|
391
|
+
one complete query for that instance. An append exactly at that limit is
|
|
392
|
+
accepted. A larger append throws a `RangeError` before the JavaScript array is
|
|
393
|
+
copied into WASM, with this deterministic message:
|
|
394
|
+
|
|
395
|
+
```text
|
|
396
|
+
Fingerprint append byteLength <actual> exceeds the per-instance limit of <limit> bytes
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The high-level SDK enforces the same boundary before it invokes the generated
|
|
400
|
+
class. Oversized input is rejected, never truncated or split; normal microphone
|
|
401
|
+
fingerprint chunks and the existing match-window cadence are unchanged.
|
|
402
|
+
|
|
299
403
|
## Integration flow
|
|
300
404
|
|
|
301
405
|
1. Prepare your `appId` and ephemeral key flow.
|
|
@@ -6,6 +6,7 @@ export class WasmFingerprintMatchWindow {
|
|
|
6
6
|
[Symbol.dispose](): void;
|
|
7
7
|
appendFingerprint(bytes: Uint8Array): void;
|
|
8
8
|
hasReadyQuery(): boolean;
|
|
9
|
+
maxAppendFingerprintByteLength(): number;
|
|
9
10
|
constructor(sample_rate: number, afpgen_config: any | null | undefined, match_window_ms: number, match_stride_ms: number);
|
|
10
11
|
reset(): void;
|
|
11
12
|
takeReadyQuery(): any;
|
|
@@ -13,9 +13,10 @@ export class WasmFingerprintMatchWindow {
|
|
|
13
13
|
* @param {Uint8Array} bytes
|
|
14
14
|
*/
|
|
15
15
|
appendFingerprint(bytes) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const ret = wasm.wasmfingerprintmatchwindow_appendFingerprint(this.__wbg_ptr, bytes);
|
|
17
|
+
if (ret[1]) {
|
|
18
|
+
throw takeFromExternrefTable0(ret[0]);
|
|
19
|
+
}
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
22
|
* @returns {boolean}
|
|
@@ -24,6 +25,13 @@ export class WasmFingerprintMatchWindow {
|
|
|
24
25
|
const ret = wasm.wasmfingerprintmatchwindow_hasReadyQuery(this.__wbg_ptr);
|
|
25
26
|
return ret !== 0;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* @returns {number}
|
|
30
|
+
*/
|
|
31
|
+
maxAppendFingerprintByteLength() {
|
|
32
|
+
const ret = wasm.wasmfingerprintmatchwindow_maxAppendFingerprintByteLength(this.__wbg_ptr);
|
|
33
|
+
return ret >>> 0;
|
|
34
|
+
}
|
|
27
35
|
/**
|
|
28
36
|
* @param {number} sample_rate
|
|
29
37
|
* @param {any | null | undefined} afpgen_config
|
|
@@ -47,7 +55,10 @@ export class WasmFingerprintMatchWindow {
|
|
|
47
55
|
*/
|
|
48
56
|
takeReadyQuery() {
|
|
49
57
|
const ret = wasm.wasmfingerprintmatchwindow_takeReadyQuery(this.__wbg_ptr);
|
|
50
|
-
|
|
58
|
+
if (ret[2]) {
|
|
59
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
60
|
+
}
|
|
61
|
+
return takeFromExternrefTable0(ret[0]);
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
64
|
if (Symbol.dispose) WasmFingerprintMatchWindow.prototype[Symbol.dispose] = WasmFingerprintMatchWindow.prototype.free;
|
|
@@ -82,6 +93,10 @@ export function __wbg___wbindgen_in_a5d8b22e52b24dd1(arg0, arg1) {
|
|
|
82
93
|
const ret = arg0 in arg1;
|
|
83
94
|
return ret;
|
|
84
95
|
}
|
|
96
|
+
export function __wbg___wbindgen_is_function_3baa9db1a987f47d(arg0) {
|
|
97
|
+
const ret = typeof(arg0) === 'function';
|
|
98
|
+
return ret;
|
|
99
|
+
}
|
|
85
100
|
export function __wbg___wbindgen_is_null_52ff4ec04186736f(arg0) {
|
|
86
101
|
const ret = arg0 === null;
|
|
87
102
|
return ret;
|
|
@@ -116,6 +131,26 @@ export function __wbg___wbindgen_string_get_7ed5322991caaec5(arg0, arg1) {
|
|
|
116
131
|
export function __wbg___wbindgen_throw_6b64449b9b9ed33c(arg0, arg1) {
|
|
117
132
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
118
133
|
}
|
|
134
|
+
export function __wbg_call_14b169f759b26747() { return handleError(function (arg0, arg1) {
|
|
135
|
+
const ret = arg0.call(arg1);
|
|
136
|
+
return ret;
|
|
137
|
+
}, arguments); }
|
|
138
|
+
export function __wbg_call_a24592a6f349a97e() { return handleError(function (arg0, arg1, arg2) {
|
|
139
|
+
const ret = arg0.call(arg1, arg2);
|
|
140
|
+
return ret;
|
|
141
|
+
}, arguments); }
|
|
142
|
+
export function __wbg_getOwnPropertyDescriptor_131bd582a45a6f5d(arg0, arg1) {
|
|
143
|
+
const ret = Object.getOwnPropertyDescriptor(arg0, arg1);
|
|
144
|
+
return ret;
|
|
145
|
+
}
|
|
146
|
+
export function __wbg_getPrototypeOf_a56da9261bbd1e5b(arg0) {
|
|
147
|
+
const ret = Object.getPrototypeOf(arg0);
|
|
148
|
+
return ret;
|
|
149
|
+
}
|
|
150
|
+
export function __wbg_get_6011fa3a58f61074() { return handleError(function (arg0, arg1) {
|
|
151
|
+
const ret = Reflect.get(arg0, arg1);
|
|
152
|
+
return ret;
|
|
153
|
+
}, arguments); }
|
|
119
154
|
export function __wbg_get_with_ref_key_6412cf3094599694(arg0, arg1) {
|
|
120
155
|
const ret = arg0[arg1];
|
|
121
156
|
return ret;
|
|
@@ -152,6 +187,10 @@ export function __wbg_new_0c7403db6e782f19(arg0) {
|
|
|
152
187
|
const ret = new Uint8Array(arg0);
|
|
153
188
|
return ret;
|
|
154
189
|
}
|
|
190
|
+
export function __wbg_new_191521fecb171639(arg0, arg1) {
|
|
191
|
+
const ret = new RangeError(getStringFromWasm0(arg0, arg1));
|
|
192
|
+
return ret;
|
|
193
|
+
}
|
|
155
194
|
export function __wbg_new_from_slice_b5ea43e23f6008c0(arg0, arg1) {
|
|
156
195
|
const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1));
|
|
157
196
|
return ret;
|
|
@@ -159,7 +198,28 @@ export function __wbg_new_from_slice_b5ea43e23f6008c0(arg0, arg1) {
|
|
|
159
198
|
export function __wbg_prototypesetcall_a6b02eb00b0f4ce2(arg0, arg1, arg2) {
|
|
160
199
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
161
200
|
}
|
|
201
|
+
export function __wbg_static_accessor_GLOBAL_8cfadc87a297ca02() {
|
|
202
|
+
const ret = typeof global === 'undefined' ? null : global;
|
|
203
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
204
|
+
}
|
|
205
|
+
export function __wbg_static_accessor_GLOBAL_THIS_602256ae5c8f42cf() {
|
|
206
|
+
const ret = typeof globalThis === 'undefined' ? null : globalThis;
|
|
207
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
208
|
+
}
|
|
209
|
+
export function __wbg_static_accessor_SELF_e445c1c7484aecc3() {
|
|
210
|
+
const ret = typeof self === 'undefined' ? null : self;
|
|
211
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
212
|
+
}
|
|
213
|
+
export function __wbg_static_accessor_WINDOW_f20e8576ef1e0f17() {
|
|
214
|
+
const ret = typeof window === 'undefined' ? null : window;
|
|
215
|
+
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
|
|
216
|
+
}
|
|
162
217
|
export function __wbindgen_cast_0000000000000001(arg0, arg1) {
|
|
218
|
+
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
|
|
219
|
+
const ret = getArrayU8FromWasm0(arg0, arg1);
|
|
220
|
+
return ret;
|
|
221
|
+
}
|
|
222
|
+
export function __wbindgen_cast_0000000000000002(arg0, arg1) {
|
|
163
223
|
// Cast intrinsic for `Ref(String) -> Externref`.
|
|
164
224
|
const ret = getStringFromWasm0(arg0, arg1);
|
|
165
225
|
return ret;
|
|
@@ -274,15 +334,17 @@ function getUint8ArrayMemory0() {
|
|
|
274
334
|
return cachedUint8ArrayMemory0;
|
|
275
335
|
}
|
|
276
336
|
|
|
277
|
-
function
|
|
278
|
-
|
|
337
|
+
function handleError(f, args) {
|
|
338
|
+
try {
|
|
339
|
+
return f.apply(this, args);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
const idx = addToExternrefTable0(e);
|
|
342
|
+
wasm.__wbindgen_exn_store(idx);
|
|
343
|
+
}
|
|
279
344
|
}
|
|
280
345
|
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
284
|
-
WASM_VECTOR_LEN = arg.length;
|
|
285
|
-
return ptr;
|
|
346
|
+
function isLikeNone(x) {
|
|
347
|
+
return x === undefined || x === null;
|
|
286
348
|
}
|
|
287
349
|
|
|
288
350
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
Binary file
|
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const __wbg_wasmfingerprintmatchwindow_free: (a: number, b: number) => void;
|
|
5
|
-
export const wasmfingerprintmatchwindow_appendFingerprint: (a: number, b: number,
|
|
5
|
+
export const wasmfingerprintmatchwindow_appendFingerprint: (a: number, b: any) => [number, number];
|
|
6
6
|
export const wasmfingerprintmatchwindow_hasReadyQuery: (a: number) => number;
|
|
7
|
+
export const wasmfingerprintmatchwindow_maxAppendFingerprintByteLength: (a: number) => number;
|
|
7
8
|
export const wasmfingerprintmatchwindow_new: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
8
9
|
export const wasmfingerprintmatchwindow_reset: (a: number) => void;
|
|
9
|
-
export const wasmfingerprintmatchwindow_takeReadyQuery: (a: number) =>
|
|
10
|
+
export const wasmfingerprintmatchwindow_takeReadyQuery: (a: number) => [number, number, number];
|
|
10
11
|
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
11
12
|
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
12
|
-
export const
|
|
13
|
+
export const __wbindgen_exn_store: (a: number) => void;
|
|
13
14
|
export const __externref_table_alloc: () => number;
|
|
15
|
+
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
14
16
|
export const __externref_table_dealloc: (a: number) => void;
|
|
15
17
|
export const __wbindgen_start: () => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MatcherEphemeralAuthOptions, InitMatcherOptions, MatcherEphemeralAuthResult, AudioMarkerDetection, PackSource, MatchResult, AudioFingerprintType } from '@sorisdk/matcher';
|
|
1
|
+
import { MatcherEphemeralAuthOptions, InitMatcherOptions, MatcherEphemeralAuthResult, AudioMarkerDetection, PackSource, MatchResult, MatcherSessionOptions, AudioFingerprintType } from '@sorisdk/matcher';
|
|
2
2
|
export { AudioFingerprintType, AudioMarkerConfig, AudioMarkerDetection, PackSource } from '@sorisdk/matcher';
|
|
3
3
|
import { InitAfpgenOptions } from '@sorisdk/afpgen';
|
|
4
4
|
|
|
@@ -62,6 +62,10 @@ interface AudioMarkerEvent {
|
|
|
62
62
|
interface MaterialActivityRequestPayload {
|
|
63
63
|
type: "material";
|
|
64
64
|
material_id: string;
|
|
65
|
+
/** SDK-owned UTC recognition timestamp. Custom mapper values are overwritten on POST. */
|
|
66
|
+
recognized_at?: string;
|
|
67
|
+
/** SDK-owned matcher position in milliseconds. Custom mapper values are overwritten on POST. */
|
|
68
|
+
position?: number;
|
|
65
69
|
refinement_expected?: true;
|
|
66
70
|
trait?: {
|
|
67
71
|
marker?: string;
|
|
@@ -107,7 +111,7 @@ interface MicrophoneMatcherActivityReporterOptions {
|
|
|
107
111
|
}
|
|
108
112
|
interface CreateMicrophoneMatcherWasmOptions {
|
|
109
113
|
afpgen?: InitAfpgenOptions;
|
|
110
|
-
matcher?: Omit<
|
|
114
|
+
matcher?: Omit<MatcherSessionOptions, "auth">;
|
|
111
115
|
matchWindow?: InitMatchWindowWasmOptions;
|
|
112
116
|
}
|
|
113
117
|
interface CreateMicrophoneMatcherOptions {
|
|
@@ -147,6 +151,7 @@ type MicrophoneMatcherListener<T extends MicrophoneMatcherEventName> = (payload:
|
|
|
147
151
|
type Awaitable<T> = T | Promise<T>;
|
|
148
152
|
type AudioRecognizerMatcherOptions = Omit<CreateMicrophoneMatcherOptions, "activityReporter" | "packSource">;
|
|
149
153
|
declare const DEFAULT_SORI_API_ENDPOINT = "https://console.soriapi.com/api";
|
|
154
|
+
declare const MAX_API_ENDPOINT_LENGTH: number;
|
|
150
155
|
interface AudioRecognizerAuthOptions {
|
|
151
156
|
endpoint: string | URL;
|
|
152
157
|
appId: string;
|
|
@@ -291,4 +296,4 @@ declare function requestMicrophoneStream(mediaDevices: MediaDevices, mediaConstr
|
|
|
291
296
|
preferredSampleRate?: number;
|
|
292
297
|
}): Promise<MediaStream>;
|
|
293
298
|
|
|
294
|
-
export { ACTIVITY_REFINEMENT_WINDOW_MS, type ActivityRefinementFailureContext, type ActivityRefinementRequestContext, type AudioMarkerEvent, AudioRecognizer, type AudioRecognizerAuthOptions, type AudioRecognizerOptions, type AuthenticateAndLoadAudioPackOptions, type AuthenticateAndLoadAudioPackResult, type BrowserAudioPackLoader, type BrowserAudioPackState, type BrowserAudioPackStateStore, type BrowserSessionManager, type CreateLocalStorageAudioPackStoreOptions, type CreateLocalStorageSessionManagerOptions, type CreateMicrophoneMatcherOptions, type CreateMicrophoneMatcherWasmOptions, DEFAULT_BROWSER_AUDIOPACK_VERSION, DEFAULT_SORI_API_ENDPOINT, type DetectedMatch, type MaterialActivityRequestPayload, MicrophoneMatcher, type MicrophoneMatcherActivityRefinementOptions, type MicrophoneMatcherActivityReporterOptions, type MicrophoneMatcherCampaignEvent, type MicrophoneMatcherEventMap, type MicrophoneMatcherEventName, type MicrophoneMatcherListener, authenticateAndLoadAudioPack, createLocalStorageAudioPackStore, createLocalStorageSessionManager, createMicrophoneMatcher, requestMicrophoneStream };
|
|
299
|
+
export { ACTIVITY_REFINEMENT_WINDOW_MS, type ActivityRefinementFailureContext, type ActivityRefinementRequestContext, type AudioMarkerEvent, AudioRecognizer, type AudioRecognizerAuthOptions, type AudioRecognizerOptions, type AuthenticateAndLoadAudioPackOptions, type AuthenticateAndLoadAudioPackResult, type BrowserAudioPackLoader, type BrowserAudioPackState, type BrowserAudioPackStateStore, type BrowserSessionManager, type CreateLocalStorageAudioPackStoreOptions, type CreateLocalStorageSessionManagerOptions, type CreateMicrophoneMatcherOptions, type CreateMicrophoneMatcherWasmOptions, DEFAULT_BROWSER_AUDIOPACK_VERSION, DEFAULT_SORI_API_ENDPOINT, type DetectedMatch, MAX_API_ENDPOINT_LENGTH, type MaterialActivityRequestPayload, MicrophoneMatcher, type MicrophoneMatcherActivityRefinementOptions, type MicrophoneMatcherActivityReporterOptions, type MicrophoneMatcherCampaignEvent, type MicrophoneMatcherEventMap, type MicrophoneMatcherEventName, type MicrophoneMatcherListener, authenticateAndLoadAudioPack, createLocalStorageAudioPackStore, createLocalStorageSessionManager, createMicrophoneMatcher, requestMicrophoneStream };
|
package/dist/index.js
CHANGED
|
@@ -181,6 +181,18 @@ function getBrowserMediaDevices() {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// src/match-window.ts
|
|
184
|
+
var FINGERPRINT_LEVEL_BYTE_LENGTH = 8;
|
|
185
|
+
function getTypedArrayByteLengthGetter() {
|
|
186
|
+
const getter = Object.getOwnPropertyDescriptor(
|
|
187
|
+
Object.getPrototypeOf(Uint8Array.prototype),
|
|
188
|
+
"byteLength"
|
|
189
|
+
)?.get;
|
|
190
|
+
if (!getter) {
|
|
191
|
+
throw new Error("Uint8Array byteLength intrinsic is unavailable");
|
|
192
|
+
}
|
|
193
|
+
return getter;
|
|
194
|
+
}
|
|
195
|
+
var typedArrayByteLengthGetter = getTypedArrayByteLengthGetter();
|
|
184
196
|
var cachedBindings = null;
|
|
185
197
|
var initPromise = null;
|
|
186
198
|
var testInjectedWasmModule = null;
|
|
@@ -197,18 +209,46 @@ var defaultLoader = async () => {
|
|
|
197
209
|
const loaderModule = await import("./default-loader.js");
|
|
198
210
|
return loaderModule.default();
|
|
199
211
|
};
|
|
212
|
+
function calculateMaxAppendFingerprintByteLength(options) {
|
|
213
|
+
const hopSize = options.afpgenConfig.hopSize ?? 1e3;
|
|
214
|
+
const levelCount = options.afpgenConfig.levelCount ?? 2;
|
|
215
|
+
const hopDurationMs = hopSize / options.sampleRate * 1e3;
|
|
216
|
+
const hopsInWindow = Math.ceil(options.matchWindowMs / hopDurationMs);
|
|
217
|
+
return Math.max(1, hopsInWindow) * levelCount * FINGERPRINT_LEVEL_BYTE_LENGTH;
|
|
218
|
+
}
|
|
219
|
+
function appendLimitError(byteLength, maxByteLength) {
|
|
220
|
+
return new RangeError(
|
|
221
|
+
`Fingerprint append byteLength ${byteLength} exceeds the per-instance limit of ${maxByteLength} bytes`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function intrinsicUint8ArrayByteLength(bytes) {
|
|
225
|
+
return typedArrayByteLengthGetter.call(bytes);
|
|
226
|
+
}
|
|
200
227
|
var WrappedFingerprintMatchWindow = class {
|
|
201
|
-
constructor(inner) {
|
|
228
|
+
constructor(inner, options) {
|
|
202
229
|
this.inner = inner;
|
|
230
|
+
const maxLengthFn = getCallable(this.inner, [
|
|
231
|
+
"maxAppendFingerprintByteLength",
|
|
232
|
+
"max_append_fingerprint_byte_length"
|
|
233
|
+
]);
|
|
234
|
+
this.maxAppendByteLength = maxLengthFn ? Number(maxLengthFn.call(this.inner)) : calculateMaxAppendFingerprintByteLength(options);
|
|
203
235
|
}
|
|
204
236
|
inner;
|
|
237
|
+
maxAppendByteLength;
|
|
205
238
|
appendFingerprint(bytes) {
|
|
239
|
+
const byteLength = intrinsicUint8ArrayByteLength(bytes);
|
|
240
|
+
if (byteLength > this.maxAppendByteLength) {
|
|
241
|
+
throw appendLimitError(byteLength, this.maxAppendByteLength);
|
|
242
|
+
}
|
|
206
243
|
const fn = getCallable(this.inner, ["appendFingerprint", "append_fingerprint"]);
|
|
207
244
|
if (!fn) {
|
|
208
245
|
throw new Error("web-audio wasm window is missing appendFingerprint");
|
|
209
246
|
}
|
|
210
247
|
fn.call(this.inner, bytes);
|
|
211
248
|
}
|
|
249
|
+
maxAppendFingerprintByteLength() {
|
|
250
|
+
return this.maxAppendByteLength;
|
|
251
|
+
}
|
|
212
252
|
hasReadyQuery() {
|
|
213
253
|
const fn = getCallable(this.inner, ["hasReadyQuery", "has_ready_query"]);
|
|
214
254
|
if (!fn) {
|
|
@@ -261,7 +301,8 @@ function createBindingsFromModule(moduleLike) {
|
|
|
261
301
|
options.afpgenConfig,
|
|
262
302
|
options.matchWindowMs,
|
|
263
303
|
options.matchStrideMs
|
|
264
|
-
)
|
|
304
|
+
),
|
|
305
|
+
options
|
|
265
306
|
);
|
|
266
307
|
}
|
|
267
308
|
};
|
|
@@ -357,6 +398,14 @@ function defaultRequestPayload(match) {
|
|
|
357
398
|
...typeof marker === "string" && marker.length > 0 ? { trait: { marker } } : {}
|
|
358
399
|
};
|
|
359
400
|
}
|
|
401
|
+
function captureActivityRecognitionTiming(match) {
|
|
402
|
+
const recognizedAtMillis = Date.now();
|
|
403
|
+
const position = match.position;
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
recognizedAt: new Date(recognizedAtMillis).toISOString(),
|
|
406
|
+
position
|
|
407
|
+
});
|
|
408
|
+
}
|
|
360
409
|
function validateRequestPayload(payload) {
|
|
361
410
|
if (payload === null) {
|
|
362
411
|
return null;
|
|
@@ -373,7 +422,12 @@ async function resolveCreateRequestPayload(reporter, match) {
|
|
|
373
422
|
if (!payload) {
|
|
374
423
|
return null;
|
|
375
424
|
}
|
|
376
|
-
const {
|
|
425
|
+
const {
|
|
426
|
+
position: _ignoredPosition,
|
|
427
|
+
recognized_at: _ignoredRecognizedAt,
|
|
428
|
+
refinement_expected: _ignoredRefinementExpected,
|
|
429
|
+
...requestPayload
|
|
430
|
+
} = payload;
|
|
377
431
|
return requestPayload;
|
|
378
432
|
}
|
|
379
433
|
async function resolveRefineRequestPayload(reporter, match, context) {
|
|
@@ -387,7 +441,12 @@ async function resolveRefineRequestPayload(reporter, match, context) {
|
|
|
387
441
|
if (!payload) {
|
|
388
442
|
return null;
|
|
389
443
|
}
|
|
390
|
-
const {
|
|
444
|
+
const {
|
|
445
|
+
position: _ignoredPosition,
|
|
446
|
+
recognized_at: _ignoredRecognizedAt,
|
|
447
|
+
refinement_expected: _ignoredRefinementExpected,
|
|
448
|
+
...requestPayload
|
|
449
|
+
} = payload;
|
|
391
450
|
return requestPayload;
|
|
392
451
|
}
|
|
393
452
|
function defaultCampaignMapper(payload, match) {
|
|
@@ -457,10 +516,20 @@ function responseActivityId(data, campaign) {
|
|
|
457
516
|
}
|
|
458
517
|
async function mapCreateResponse(reporter, data, match, refinementExpected) {
|
|
459
518
|
if (!data) {
|
|
460
|
-
return {
|
|
519
|
+
return {
|
|
520
|
+
activityId: null,
|
|
521
|
+
campaign: null,
|
|
522
|
+
creationAttempted: true,
|
|
523
|
+
refinementExpected
|
|
524
|
+
};
|
|
461
525
|
}
|
|
462
526
|
const campaign = reporter.mapResponseToCampaign ? await reporter.mapResponseToCampaign(data, match) : defaultCampaignMapper(data, match);
|
|
463
|
-
return {
|
|
527
|
+
return {
|
|
528
|
+
activityId: responseActivityId(data, campaign),
|
|
529
|
+
campaign,
|
|
530
|
+
creationAttempted: true,
|
|
531
|
+
refinementExpected
|
|
532
|
+
};
|
|
464
533
|
}
|
|
465
534
|
async function resolveRefinementEndpoint(reporter, activityId) {
|
|
466
535
|
const configured = reporter.refinement && reporter.refinement.endpoint;
|
|
@@ -470,10 +539,15 @@ async function resolveRefinementEndpoint(reporter, activityId) {
|
|
|
470
539
|
const prefix = String(configured ?? reporter.endpoint);
|
|
471
540
|
return `${prefix.endsWith("/") ? prefix : `${prefix}/`}${encodeURIComponent(activityId)}`;
|
|
472
541
|
}
|
|
473
|
-
async function createMatchedMaterialActivity(reporter, match, refinementExpected = false) {
|
|
542
|
+
async function createMatchedMaterialActivity(reporter, match, refinementExpected = false, recognitionTiming = captureActivityRecognitionTiming(match)) {
|
|
474
543
|
const payload = await resolveCreateRequestPayload(reporter, match);
|
|
475
544
|
if (!payload) {
|
|
476
|
-
return {
|
|
545
|
+
return {
|
|
546
|
+
activityId: null,
|
|
547
|
+
campaign: null,
|
|
548
|
+
creationAttempted: false,
|
|
549
|
+
refinementExpected: false
|
|
550
|
+
};
|
|
477
551
|
}
|
|
478
552
|
const headers = await resolveHeaders(reporter);
|
|
479
553
|
const actualMarker = match.audioMarker?.code;
|
|
@@ -481,7 +555,12 @@ async function createMatchedMaterialActivity(reporter, match, refinementExpected
|
|
|
481
555
|
const hasMarker = typeof actualMarker === "string" && actualMarker.length > 0 || typeof payloadMarker === "string" && payloadMarker.length > 0;
|
|
482
556
|
const lifecycleOpen = typeof refinementExpected === "function" ? refinementExpected() : refinementExpected;
|
|
483
557
|
const requestRefinementExpected = Boolean(reporter.refinement && lifecycleOpen && !hasMarker);
|
|
484
|
-
const requestPayload =
|
|
558
|
+
const requestPayload = {
|
|
559
|
+
...payload,
|
|
560
|
+
recognized_at: recognitionTiming.recognizedAt,
|
|
561
|
+
position: recognitionTiming.position,
|
|
562
|
+
...requestRefinementExpected ? { refinement_expected: true } : {}
|
|
563
|
+
};
|
|
485
564
|
const response = await resolveFetch(reporter.fetch)(reporter.endpoint, {
|
|
486
565
|
method: "POST",
|
|
487
566
|
headers,
|
|
@@ -531,6 +610,7 @@ async function refineMatchedMaterialActivity(reporter, match, context, isActive
|
|
|
531
610
|
|
|
532
611
|
// src/activity-refinement.ts
|
|
533
612
|
var ACTIVITY_REFINEMENT_WINDOW_MS = 3e4;
|
|
613
|
+
var ACTIVITY_CONTINUITY_GAP_MS = 3e4;
|
|
534
614
|
var DEFAULT_MAX_REFINEMENT_ATTEMPTS = 2;
|
|
535
615
|
var MAX_REFINEMENT_ATTEMPTS = 10;
|
|
536
616
|
function normalizeError(error) {
|
|
@@ -566,9 +646,6 @@ var ActivityRefinementCoordinator = class {
|
|
|
566
646
|
}
|
|
567
647
|
report(recognitionId, match, captureGeneration, requestId = null) {
|
|
568
648
|
const coordinatorGeneration = this.generation;
|
|
569
|
-
if (this.activeLifecycle && this.activeLifecycle.recognitionId !== recognitionId) {
|
|
570
|
-
this.deleteLifecycle(this.activeLifecycle);
|
|
571
|
-
}
|
|
572
649
|
const hasMarker = typeof match.audioMarker?.code === "string" && match.audioMarker.code.length > 0;
|
|
573
650
|
const refinement = this.reporter.refinement;
|
|
574
651
|
const requestCanRefine = requestId !== null && !this.settledRequests.has(requestId);
|
|
@@ -578,39 +655,71 @@ var ActivityRefinementCoordinator = class {
|
|
|
578
655
|
this.registeredRequests.delete(requestId);
|
|
579
656
|
this.settledRequests.delete(requestId);
|
|
580
657
|
}
|
|
658
|
+
if (refinement && this.activeLifecycle) {
|
|
659
|
+
const now = this.now();
|
|
660
|
+
const activeLifecycle = this.activeLifecycle;
|
|
661
|
+
if (this.isContinuousMatch(activeLifecycle, match, captureGeneration, now)) {
|
|
662
|
+
this.renewContinuity(activeLifecycle, now);
|
|
663
|
+
if (requestId !== null) {
|
|
664
|
+
if (requestCanRefine && this.canAcceptMarker(activeLifecycle)) {
|
|
665
|
+
this.requestLifecycles.set(requestId, activeLifecycle);
|
|
666
|
+
} else {
|
|
667
|
+
this.requestLifecycles.delete(requestId);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (pendingMarker) {
|
|
671
|
+
this.claimMarker(activeLifecycle, pendingMarker);
|
|
672
|
+
}
|
|
673
|
+
if (hasMarker && match.audioMarker) {
|
|
674
|
+
this.claimMarker(activeLifecycle, match.audioMarker);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
this.sealLifecycle(activeLifecycle);
|
|
679
|
+
}
|
|
581
680
|
const refinementExpected = Boolean(refinement && !hasMarker && requestCanRefine);
|
|
582
681
|
let lifecycle = null;
|
|
583
|
-
if (
|
|
584
|
-
const now =
|
|
682
|
+
if (refinement) {
|
|
683
|
+
const now = this.now();
|
|
585
684
|
lifecycle = {
|
|
586
685
|
recognitionId,
|
|
587
686
|
captureGeneration,
|
|
588
687
|
match,
|
|
589
688
|
deadline: now + ACTIVITY_REFINEMENT_WINDOW_MS,
|
|
689
|
+
lastObservedAt: now,
|
|
590
690
|
activityId: null,
|
|
591
|
-
markerMatch: null,
|
|
592
|
-
markerCode: null,
|
|
593
|
-
|
|
691
|
+
markerMatch: hasMarker ? match : null,
|
|
692
|
+
markerCode: hasMarker ? match.audioMarker?.code ?? null : null,
|
|
693
|
+
refinementTimer: null,
|
|
694
|
+
continuityTimer: null,
|
|
594
695
|
createSettled: false,
|
|
595
|
-
refinementStarted: false
|
|
696
|
+
refinementStarted: false,
|
|
697
|
+
refinementInFlight: false,
|
|
698
|
+
refinementClosed: !refinementExpected,
|
|
699
|
+
sealed: false
|
|
596
700
|
};
|
|
597
701
|
const createdLifecycle = lifecycle;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
702
|
+
if (refinementExpected) {
|
|
703
|
+
lifecycle.refinementTimer = setTimeout(() => {
|
|
704
|
+
this.closeRefinement(createdLifecycle);
|
|
705
|
+
}, ACTIVITY_REFINEMENT_WINDOW_MS);
|
|
706
|
+
}
|
|
707
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
601
708
|
this.lifecycles.set(recognitionId, lifecycle);
|
|
602
|
-
if (requestId !== null) {
|
|
709
|
+
if (requestId !== null && refinementExpected) {
|
|
603
710
|
this.requestLifecycles.set(requestId, lifecycle);
|
|
604
711
|
}
|
|
605
712
|
this.activeLifecycle = lifecycle;
|
|
606
|
-
if (pendingMarker) {
|
|
607
|
-
this.
|
|
713
|
+
if (pendingMarker && refinementExpected) {
|
|
714
|
+
this.claimMarker(lifecycle, pendingMarker);
|
|
608
715
|
}
|
|
609
716
|
}
|
|
717
|
+
const recognitionTiming = captureActivityRecognitionTiming(match);
|
|
610
718
|
void createMatchedMaterialActivity(
|
|
611
719
|
this.reporter,
|
|
612
720
|
match,
|
|
613
|
-
() => Boolean(lifecycle && this.isRefinementActive(lifecycle))
|
|
721
|
+
() => Boolean(lifecycle && this.isRefinementActive(lifecycle)),
|
|
722
|
+
recognitionTiming
|
|
614
723
|
).then(
|
|
615
724
|
(result) => {
|
|
616
725
|
if (coordinatorGeneration === this.generation && result.campaign && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
@@ -619,12 +728,17 @@ var ActivityRefinementCoordinator = class {
|
|
|
619
728
|
if (coordinatorGeneration !== this.generation || !lifecycle || this.lifecycles.get(recognitionId) !== lifecycle) {
|
|
620
729
|
return;
|
|
621
730
|
}
|
|
622
|
-
|
|
731
|
+
lifecycle.createSettled = true;
|
|
732
|
+
lifecycle.activityId = result.activityId;
|
|
733
|
+
if (!result.creationAttempted) {
|
|
623
734
|
this.deleteLifecycle(lifecycle);
|
|
624
735
|
return;
|
|
625
736
|
}
|
|
626
|
-
|
|
627
|
-
|
|
737
|
+
if (!result.refinementExpected) {
|
|
738
|
+
this.closeRefinement(lifecycle);
|
|
739
|
+
this.maybeDeleteSealed(lifecycle);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
628
742
|
if (lifecycle.markerMatch) {
|
|
629
743
|
if (lifecycle.activityId) {
|
|
630
744
|
this.startRefinement(lifecycle);
|
|
@@ -632,9 +746,11 @@ var ActivityRefinementCoordinator = class {
|
|
|
632
746
|
this.failMissingActivityId(lifecycle);
|
|
633
747
|
}
|
|
634
748
|
}
|
|
749
|
+
this.maybeDeleteSealed(lifecycle);
|
|
635
750
|
},
|
|
636
751
|
(error) => {
|
|
637
752
|
if (lifecycle) {
|
|
753
|
+
lifecycle.createSettled = true;
|
|
638
754
|
this.deleteLifecycle(lifecycle);
|
|
639
755
|
}
|
|
640
756
|
if (coordinatorGeneration === this.generation && this.callbacks.isCurrentCapture(captureGeneration)) {
|
|
@@ -646,14 +762,26 @@ var ActivityRefinementCoordinator = class {
|
|
|
646
762
|
associate(requestId, match, captureGeneration) {
|
|
647
763
|
const lifecycle = this.activeLifecycle;
|
|
648
764
|
this.registeredRequests.delete(requestId);
|
|
649
|
-
if (!lifecycle || !match
|
|
765
|
+
if (!lifecycle || !match) {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
const now = this.now();
|
|
769
|
+
if (!this.isContinuousMatch(lifecycle, match, captureGeneration, now)) {
|
|
770
|
+
this.sealLifecycle(lifecycle);
|
|
650
771
|
return false;
|
|
651
772
|
}
|
|
652
|
-
this.
|
|
773
|
+
this.renewContinuity(lifecycle, now);
|
|
653
774
|
const pendingMarker = this.pendingMarkers.get(requestId);
|
|
654
|
-
if (
|
|
775
|
+
if (this.canAcceptMarker(lifecycle)) {
|
|
776
|
+
this.requestLifecycles.set(requestId, lifecycle);
|
|
777
|
+
} else {
|
|
778
|
+
this.requestLifecycles.delete(requestId);
|
|
779
|
+
}
|
|
780
|
+
if (pendingMarker && this.canAcceptMarker(lifecycle)) {
|
|
781
|
+
this.pendingMarkers.delete(requestId);
|
|
782
|
+
this.claimMarker(lifecycle, pendingMarker);
|
|
783
|
+
} else if (pendingMarker) {
|
|
655
784
|
this.pendingMarkers.delete(requestId);
|
|
656
|
-
this.refine(requestId, pendingMarker, captureGeneration);
|
|
657
785
|
}
|
|
658
786
|
return true;
|
|
659
787
|
}
|
|
@@ -666,18 +794,14 @@ var ActivityRefinementCoordinator = class {
|
|
|
666
794
|
settleRequest(requestId) {
|
|
667
795
|
const awaitingAssociation = this.registeredRequests.delete(requestId);
|
|
668
796
|
this.pendingMarkers.delete(requestId);
|
|
669
|
-
|
|
670
|
-
if (lifecycle) {
|
|
671
|
-
this.deleteLifecycle(lifecycle);
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
797
|
+
this.requestLifecycles.delete(requestId);
|
|
674
798
|
if (awaitingAssociation) {
|
|
675
799
|
this.settledRequests.add(requestId);
|
|
676
800
|
}
|
|
677
801
|
}
|
|
678
802
|
clearActive(captureGeneration) {
|
|
679
803
|
if (this.activeLifecycle?.captureGeneration === captureGeneration) {
|
|
680
|
-
this.
|
|
804
|
+
this.sealLifecycle(this.activeLifecycle);
|
|
681
805
|
}
|
|
682
806
|
}
|
|
683
807
|
refine(recognitionId, marker, captureGeneration) {
|
|
@@ -696,24 +820,19 @@ var ActivityRefinementCoordinator = class {
|
|
|
696
820
|
}
|
|
697
821
|
return;
|
|
698
822
|
}
|
|
699
|
-
|
|
700
|
-
if (now > lifecycle.deadline) {
|
|
701
|
-
this.deleteLifecycle(lifecycle);
|
|
823
|
+
if (!this.canAcceptMarker(lifecycle)) {
|
|
702
824
|
return;
|
|
703
825
|
}
|
|
704
|
-
lifecycle
|
|
705
|
-
lifecycle.markerMatch = { ...lifecycle.match, audioMarker: marker };
|
|
706
|
-
if (lifecycle.activityId) {
|
|
707
|
-
this.startRefinement(lifecycle);
|
|
708
|
-
} else if (lifecycle.createSettled) {
|
|
709
|
-
this.failMissingActivityId(lifecycle);
|
|
710
|
-
}
|
|
826
|
+
this.claimMarker(lifecycle, marker);
|
|
711
827
|
}
|
|
712
828
|
reset() {
|
|
713
829
|
this.generation += 1;
|
|
714
830
|
for (const lifecycle of this.lifecycles.values()) {
|
|
715
|
-
if (lifecycle.
|
|
716
|
-
clearTimeout(lifecycle.
|
|
831
|
+
if (lifecycle.refinementTimer) {
|
|
832
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
833
|
+
}
|
|
834
|
+
if (lifecycle.continuityTimer) {
|
|
835
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
717
836
|
}
|
|
718
837
|
}
|
|
719
838
|
this.lifecycles.clear();
|
|
@@ -724,17 +843,21 @@ var ActivityRefinementCoordinator = class {
|
|
|
724
843
|
this.activeLifecycle = null;
|
|
725
844
|
}
|
|
726
845
|
startRefinement(lifecycle) {
|
|
727
|
-
if (lifecycle.refinementStarted || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
846
|
+
if (lifecycle.refinementStarted || lifecycle.refinementClosed || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
728
847
|
return;
|
|
729
848
|
}
|
|
730
849
|
lifecycle.refinementStarted = true;
|
|
731
|
-
|
|
850
|
+
lifecycle.refinementInFlight = true;
|
|
851
|
+
void this.runRefinement(lifecycle).finally(() => {
|
|
852
|
+
lifecycle.refinementInFlight = false;
|
|
853
|
+
this.maybeDeleteSealed(lifecycle);
|
|
854
|
+
});
|
|
732
855
|
}
|
|
733
856
|
failMissingActivityId(lifecycle) {
|
|
734
857
|
if (!this.isRefinementActive(lifecycle)) {
|
|
735
858
|
return;
|
|
736
859
|
}
|
|
737
|
-
this.
|
|
860
|
+
this.closeRefinement(lifecycle);
|
|
738
861
|
this.callbacks.emitError(
|
|
739
862
|
"activity-refinement",
|
|
740
863
|
new Error("Activity refinement requires a non-empty activity_id from the create response")
|
|
@@ -743,7 +866,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
743
866
|
async runRefinement(lifecycle) {
|
|
744
867
|
const refinement = this.reporter.refinement;
|
|
745
868
|
if (!refinement || !lifecycle.activityId || !lifecycle.markerMatch) {
|
|
746
|
-
this.
|
|
869
|
+
this.closeRefinement(lifecycle);
|
|
747
870
|
return;
|
|
748
871
|
}
|
|
749
872
|
const maxAttempts = Math.min(
|
|
@@ -754,12 +877,12 @@ var ActivityRefinementCoordinator = class {
|
|
|
754
877
|
)
|
|
755
878
|
);
|
|
756
879
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
757
|
-
if (!this.
|
|
880
|
+
if (!this.isRefinementActive(lifecycle)) {
|
|
758
881
|
return;
|
|
759
882
|
}
|
|
760
|
-
const now =
|
|
883
|
+
const now = this.now();
|
|
761
884
|
if (now > lifecycle.deadline) {
|
|
762
|
-
this.
|
|
885
|
+
this.closeRefinement(lifecycle);
|
|
763
886
|
return;
|
|
764
887
|
}
|
|
765
888
|
try {
|
|
@@ -777,7 +900,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
777
900
|
if (!this.isRefinementActive(lifecycle)) {
|
|
778
901
|
return;
|
|
779
902
|
}
|
|
780
|
-
this.
|
|
903
|
+
this.closeRefinement(lifecycle);
|
|
781
904
|
if (result.campaign) {
|
|
782
905
|
this.callbacks.emitCampaign(result.campaign);
|
|
783
906
|
}
|
|
@@ -807,7 +930,7 @@ var ActivityRefinementCoordinator = class {
|
|
|
807
930
|
if (!this.isRefinementActive(lifecycle)) {
|
|
808
931
|
return;
|
|
809
932
|
}
|
|
810
|
-
this.
|
|
933
|
+
this.closeRefinement(lifecycle);
|
|
811
934
|
this.callbacks.emitError("activity-refinement", policyError);
|
|
812
935
|
return;
|
|
813
936
|
}
|
|
@@ -817,35 +940,95 @@ var ActivityRefinementCoordinator = class {
|
|
|
817
940
|
if (retry) {
|
|
818
941
|
continue;
|
|
819
942
|
}
|
|
820
|
-
this.
|
|
943
|
+
this.closeRefinement(lifecycle);
|
|
821
944
|
this.callbacks.emitError("activity-refinement", error);
|
|
822
945
|
return;
|
|
823
946
|
}
|
|
824
947
|
}
|
|
825
948
|
}
|
|
826
|
-
|
|
949
|
+
isTransportActive(lifecycle) {
|
|
827
950
|
return this.lifecycles.get(lifecycle.recognitionId) === lifecycle && this.callbacks.isCurrentCapture(lifecycle.captureGeneration);
|
|
828
951
|
}
|
|
829
952
|
isRefinementActive(lifecycle) {
|
|
830
|
-
if (!this.
|
|
953
|
+
if (!this.isTransportActive(lifecycle) || lifecycle.refinementClosed) {
|
|
831
954
|
return false;
|
|
832
955
|
}
|
|
833
|
-
const
|
|
834
|
-
const now = refinement && refinement.now ? refinement.now() : Date.now();
|
|
956
|
+
const now = this.now();
|
|
835
957
|
if (now > lifecycle.deadline) {
|
|
836
|
-
this.
|
|
958
|
+
this.closeRefinement(lifecycle);
|
|
837
959
|
return false;
|
|
838
960
|
}
|
|
839
961
|
return true;
|
|
840
962
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
963
|
+
canAcceptMarker(lifecycle) {
|
|
964
|
+
return lifecycle.markerCode === null && this.isRefinementActive(lifecycle);
|
|
965
|
+
}
|
|
966
|
+
claimMarker(lifecycle, marker) {
|
|
967
|
+
const markerCode = marker.code;
|
|
968
|
+
if (typeof markerCode !== "string" || markerCode.length === 0 || !this.canAcceptMarker(lifecycle)) {
|
|
969
|
+
return;
|
|
845
970
|
}
|
|
846
|
-
|
|
847
|
-
|
|
971
|
+
lifecycle.markerCode = markerCode;
|
|
972
|
+
lifecycle.markerMatch = { ...lifecycle.match, audioMarker: marker };
|
|
973
|
+
if (lifecycle.activityId) {
|
|
974
|
+
this.startRefinement(lifecycle);
|
|
975
|
+
} else if (lifecycle.createSettled) {
|
|
976
|
+
this.failMissingActivityId(lifecycle);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
isContinuousMatch(lifecycle, match, captureGeneration, now) {
|
|
980
|
+
return !lifecycle.sealed && this.activeLifecycle === lifecycle && lifecycle.captureGeneration === captureGeneration && lifecycle.match.name === match.name && lifecycle.match.afpType === match.afpType && this.isTransportActive(lifecycle) && now - lifecycle.lastObservedAt <= ACTIVITY_CONTINUITY_GAP_MS;
|
|
981
|
+
}
|
|
982
|
+
renewContinuity(lifecycle, now) {
|
|
983
|
+
lifecycle.lastObservedAt = now;
|
|
984
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
985
|
+
}
|
|
986
|
+
scheduleContinuityExpiry(lifecycle) {
|
|
987
|
+
if (lifecycle.continuityTimer) {
|
|
988
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
989
|
+
}
|
|
990
|
+
const remainingMs = lifecycle.lastObservedAt + ACTIVITY_CONTINUITY_GAP_MS - this.now();
|
|
991
|
+
lifecycle.continuityTimer = setTimeout(() => {
|
|
992
|
+
lifecycle.continuityTimer = null;
|
|
993
|
+
if (this.activeLifecycle !== lifecycle || lifecycle.sealed) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const now = this.now();
|
|
997
|
+
if (now - lifecycle.lastObservedAt <= ACTIVITY_CONTINUITY_GAP_MS) {
|
|
998
|
+
this.scheduleContinuityExpiry(lifecycle);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
this.sealLifecycle(lifecycle);
|
|
1002
|
+
}, Math.max(1, remainingMs + 1));
|
|
1003
|
+
}
|
|
1004
|
+
sealLifecycle(lifecycle) {
|
|
1005
|
+
if (lifecycle.sealed) {
|
|
1006
|
+
return;
|
|
848
1007
|
}
|
|
1008
|
+
lifecycle.sealed = true;
|
|
1009
|
+
if (lifecycle.continuityTimer) {
|
|
1010
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
1011
|
+
lifecycle.continuityTimer = null;
|
|
1012
|
+
}
|
|
1013
|
+
if (this.activeLifecycle === lifecycle) {
|
|
1014
|
+
this.activeLifecycle = null;
|
|
1015
|
+
}
|
|
1016
|
+
this.removeRequestAssociations(lifecycle);
|
|
1017
|
+
this.maybeDeleteSealed(lifecycle);
|
|
1018
|
+
}
|
|
1019
|
+
closeRefinement(lifecycle) {
|
|
1020
|
+
if (lifecycle.refinementClosed) {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
lifecycle.refinementClosed = true;
|
|
1024
|
+
if (lifecycle.refinementTimer) {
|
|
1025
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
1026
|
+
lifecycle.refinementTimer = null;
|
|
1027
|
+
}
|
|
1028
|
+
this.removeRequestAssociations(lifecycle);
|
|
1029
|
+
this.maybeDeleteSealed(lifecycle);
|
|
1030
|
+
}
|
|
1031
|
+
removeRequestAssociations(lifecycle) {
|
|
849
1032
|
for (const [requestId, requestLifecycle] of this.requestLifecycles) {
|
|
850
1033
|
if (requestLifecycle === lifecycle) {
|
|
851
1034
|
this.requestLifecycles.delete(requestId);
|
|
@@ -853,6 +1036,28 @@ var ActivityRefinementCoordinator = class {
|
|
|
853
1036
|
this.registeredRequests.delete(requestId);
|
|
854
1037
|
}
|
|
855
1038
|
}
|
|
1039
|
+
}
|
|
1040
|
+
maybeDeleteSealed(lifecycle) {
|
|
1041
|
+
if (lifecycle.sealed && lifecycle.createSettled && !lifecycle.refinementInFlight) {
|
|
1042
|
+
this.deleteLifecycle(lifecycle);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
now() {
|
|
1046
|
+
return this.reporter.refinement && this.reporter.refinement.now ? this.reporter.refinement.now() : Date.now();
|
|
1047
|
+
}
|
|
1048
|
+
deleteLifecycle(lifecycle) {
|
|
1049
|
+
if (lifecycle.refinementTimer) {
|
|
1050
|
+
clearTimeout(lifecycle.refinementTimer);
|
|
1051
|
+
lifecycle.refinementTimer = null;
|
|
1052
|
+
}
|
|
1053
|
+
if (lifecycle.continuityTimer) {
|
|
1054
|
+
clearTimeout(lifecycle.continuityTimer);
|
|
1055
|
+
lifecycle.continuityTimer = null;
|
|
1056
|
+
}
|
|
1057
|
+
if (this.lifecycles.get(lifecycle.recognitionId) === lifecycle) {
|
|
1058
|
+
this.lifecycles.delete(lifecycle.recognitionId);
|
|
1059
|
+
}
|
|
1060
|
+
this.removeRequestAssociations(lifecycle);
|
|
856
1061
|
if (this.activeLifecycle === lifecycle) {
|
|
857
1062
|
this.activeLifecycle = null;
|
|
858
1063
|
}
|
|
@@ -1576,6 +1781,8 @@ function createLocalStorageSessionManager(options) {
|
|
|
1576
1781
|
|
|
1577
1782
|
// src/audio-recognizer.ts
|
|
1578
1783
|
var DEFAULT_SORI_API_ENDPOINT = "https://console.soriapi.com/api";
|
|
1784
|
+
var MAX_API_ENDPOINT_LENGTH = 32 * 1024;
|
|
1785
|
+
var ASCII_SLASH_CODE_UNIT = 47;
|
|
1579
1786
|
var FORWARDED_EVENTS = [
|
|
1580
1787
|
"ready",
|
|
1581
1788
|
"microphoneready",
|
|
@@ -1603,12 +1810,24 @@ function ensureNonEmptyString(name, value) {
|
|
|
1603
1810
|
}
|
|
1604
1811
|
return value;
|
|
1605
1812
|
}
|
|
1606
|
-
function
|
|
1607
|
-
const value = String(endpoint)
|
|
1813
|
+
function stringifyAndValidateApiEndpoint(endpoint) {
|
|
1814
|
+
const value = String(endpoint);
|
|
1815
|
+
if (value.length > MAX_API_ENDPOINT_LENGTH) {
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
`AudioRecognizer apiEndpoint exceeds maximum length of ${MAX_API_ENDPOINT_LENGTH} UTF-16 code units`
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1608
1820
|
return value;
|
|
1609
1821
|
}
|
|
1822
|
+
function normalizeValidatedEndpointPrefix(value) {
|
|
1823
|
+
let end = value.length;
|
|
1824
|
+
while (end > 0 && value.charCodeAt(end - 1) === ASCII_SLASH_CODE_UNIT) {
|
|
1825
|
+
end -= 1;
|
|
1826
|
+
}
|
|
1827
|
+
return end === value.length ? value : value.slice(0, end);
|
|
1828
|
+
}
|
|
1610
1829
|
function joinEndpoint(prefix, path) {
|
|
1611
|
-
return `${
|
|
1830
|
+
return `${prefix}${path.startsWith("/") ? path : `/${path}`}`;
|
|
1612
1831
|
}
|
|
1613
1832
|
function createEphemeralKeyResolver(endpoint, fetcher, requestInit) {
|
|
1614
1833
|
return async () => {
|
|
@@ -1636,8 +1855,15 @@ function resolveRecognizerOptions(options) {
|
|
|
1636
1855
|
const activityReportingDisabled = options.activityReporter === false;
|
|
1637
1856
|
const customActivityReporter = activityReportingDisabled ? void 0 : options.activityReporter;
|
|
1638
1857
|
const appId = legacyAuth?.appId ?? options.appId;
|
|
1639
|
-
const
|
|
1640
|
-
const
|
|
1858
|
+
const configuredAuthEndpoint = legacyAuth?.endpoint ?? options.authEndpoint;
|
|
1859
|
+
const configuredActivityEndpoint = activityReportingDisabled ? void 0 : customActivityReporter?.endpoint ?? options.activityEndpoint;
|
|
1860
|
+
const derivesAuthEndpoint = configuredAuthEndpoint == null;
|
|
1861
|
+
const derivesActivityEndpoint = !activityReportingDisabled && configuredActivityEndpoint == null;
|
|
1862
|
+
const derivesApiEndpoint = derivesAuthEndpoint || derivesActivityEndpoint;
|
|
1863
|
+
const validatedApiEndpoint = options.apiEndpoint !== void 0 || derivesApiEndpoint ? stringifyAndValidateApiEndpoint(apiEndpoint) : void 0;
|
|
1864
|
+
const normalizedApiEndpoint = derivesApiEndpoint ? normalizeValidatedEndpointPrefix(validatedApiEndpoint) : void 0;
|
|
1865
|
+
const authEndpoint = configuredAuthEndpoint ?? joinEndpoint(normalizedApiEndpoint, "/auth");
|
|
1866
|
+
const activityEndpoint = activityReportingDisabled ? void 0 : configuredActivityEndpoint ?? joinEndpoint(normalizedApiEndpoint, "/activity/");
|
|
1641
1867
|
const ephemeralKey = legacyAuth?.ephemeralKey ?? options.ephemeralKey ?? options.ephemeraKey ?? (legacyAuth?.ephemeralKeyEndpoint ?? options.ephemeralKeyEndpoint ? createEphemeralKeyResolver(
|
|
1642
1868
|
legacyAuth?.ephemeralKeyEndpoint ?? options.ephemeralKeyEndpoint,
|
|
1643
1869
|
legacyAuth?.ephemeralKeyFetch ?? options.ephemeralKeyFetch ?? legacyAuth?.fetch ?? sharedFetch,
|
|
@@ -1825,6 +2051,7 @@ export {
|
|
|
1825
2051
|
AudioRecognizer,
|
|
1826
2052
|
DEFAULT_BROWSER_AUDIOPACK_VERSION,
|
|
1827
2053
|
DEFAULT_SORI_API_ENDPOINT,
|
|
2054
|
+
MAX_API_ENDPOINT_LENGTH,
|
|
1828
2055
|
MicrophoneMatcher,
|
|
1829
2056
|
authenticateAndLoadAudioPack,
|
|
1830
2057
|
createLocalStorageAudioPackStore,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorisdk/web-audio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
4
4
|
"description": "Web SDK for browser-based audio recognition with SORI API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
35
35
|
"homepage": "https://docs.soriapi.com/ko/integration/web",
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@sorisdk/
|
|
38
|
-
"@sorisdk/
|
|
37
|
+
"@sorisdk/matcher": "0.6.7",
|
|
38
|
+
"@sorisdk/afpgen": "0.6.7"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"tsup": "^8.5.1",
|