@burdenoff/website-sdk 2026.1105.2 → 2026.1105.3

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.
@@ -2,6 +2,457 @@
2
2
 
3
3
  var chunkZSTFX7FV_js = require('../chunk-ZSTFX7FV.js');
4
4
 
5
+ // src/avatar-mic/browser-press.ts
6
+ var BROWSER_START_WAIT_MS = 15e3;
7
+ var BROWSER_FINAL_WAIT_MS = 3e3;
8
+ var BROWSER_RESTART_COOLDOWN_MS = 2e3;
9
+ var BROWSER_TOUCH_RESTART_MS = 120;
10
+ var BROWSER_MAX_QUICK_ENDS = 8;
11
+ var BROWSER_MAX_RESTARTS = 60;
12
+ var BROWSER_PRIME_SETTLE_MS = 250;
13
+ var BROWSER_GRANT_RETRY_MS = 350;
14
+ var QUICK_END_MS = 1e3;
15
+ var NETWORK_FAILURES_FOR_GOOD = 2;
16
+ var IDLE = { name: "idle" };
17
+ function isRecognitionConstructor(value) {
18
+ return typeof value === "function";
19
+ }
20
+ function newBrowserRecognition() {
21
+ const found = Reflect.get(globalThis, "SpeechRecognition") ?? Reflect.get(globalThis, "webkitSpeechRecognition");
22
+ return isRecognitionConstructor(found) ? new found() : null;
23
+ }
24
+ function primeBrowserMicrophone() {
25
+ const media = typeof navigator === "undefined" ? void 0 : navigator.mediaDevices;
26
+ if (typeof media?.getUserMedia !== "function") return null;
27
+ return media.getUserMedia({ audio: true }).then((stream) => {
28
+ for (const track of stream.getTracks()) track.stop();
29
+ });
30
+ }
31
+ function watchBrowserMicPermission(onState) {
32
+ const permissions = typeof navigator === "undefined" ? void 0 : navigator.permissions;
33
+ if (typeof permissions?.query !== "function") return;
34
+ try {
35
+ void permissions.query({ name: "microphone" }).then(
36
+ (status) => {
37
+ onState(status.state);
38
+ status.addEventListener("change", () => onState(status.state));
39
+ },
40
+ () => void 0
41
+ );
42
+ } catch {
43
+ }
44
+ }
45
+ function hasCoarsePointer() {
46
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
47
+ return false;
48
+ }
49
+ try {
50
+ return window.matchMedia("(pointer: coarse)").matches;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ var BROWSER = {
56
+ createRecognition: newBrowserRecognition,
57
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
58
+ primeMicrophone: primeBrowserMicrophone,
59
+ watchMicPermission: watchBrowserMicPermission,
60
+ touch: hasCoarsePointer
61
+ };
62
+ function primeFailure(error) {
63
+ const name = typeof error === "object" && error !== null && "name" in error ? String(error.name) : "";
64
+ if (name === "NotAllowedError" || name === "SecurityError") return "denied";
65
+ if (name === "NotFoundError" || name === "DevicesNotFoundError") {
66
+ return "unsupported";
67
+ }
68
+ return "failed";
69
+ }
70
+ function tidy(text) {
71
+ return text.replace(/\s+/g, " ").trim();
72
+ }
73
+ function readRecognitionResults(results) {
74
+ const finals = [];
75
+ const interim = [];
76
+ for (let index = 0; index < results.length; index += 1) {
77
+ const result = results[index];
78
+ const text = tidy(result?.[0]?.transcript ?? "");
79
+ if (result === void 0 || text === "") continue;
80
+ if (!result.isFinal) {
81
+ interim.push(text);
82
+ continue;
83
+ }
84
+ const last = finals.length > 0 ? finals[finals.length - 1] : void 0;
85
+ const lower = text.toLowerCase();
86
+ if (last !== void 0 && (lower === last.toLowerCase() || lower.startsWith(`${last.toLowerCase()} `))) {
87
+ finals[finals.length - 1] = text;
88
+ } else {
89
+ finals.push(text);
90
+ }
91
+ }
92
+ return { finals, interim: interim.join(" ") };
93
+ }
94
+ function createBrowserAvatarMic(options, seams = {}) {
95
+ const { createRecognition, now, primeMicrophone, watchMicPermission, touch } = { ...BROWSER, ...seams };
96
+ let press = null;
97
+ let epoch = 0;
98
+ let state = IDLE;
99
+ let networkFailures = 0;
100
+ let permission = null;
101
+ let hadMicrophone = false;
102
+ watchMicPermission((next) => {
103
+ permission = next;
104
+ });
105
+ const report = (next) => {
106
+ state = next;
107
+ options.onState(next);
108
+ };
109
+ const heard = (p) => [...p.earlier, ...p.instance?.finals ?? [], p.instance?.interim ?? ""].filter((part) => part !== "").join(" ");
110
+ const show = (p) => {
111
+ if (p.stage === "listening") {
112
+ report({
113
+ name: "listening",
114
+ epoch: p.epoch,
115
+ startedAt: p.startedAt,
116
+ text: heard(p)
117
+ });
118
+ } else if (p.stage === "finishing") {
119
+ report({
120
+ name: "finishing",
121
+ epoch: p.epoch,
122
+ text: heard(p),
123
+ limit: p.limit
124
+ });
125
+ }
126
+ };
127
+ const release = (instance) => {
128
+ const { recognition } = instance;
129
+ recognition.onstart = null;
130
+ recognition.onresult = null;
131
+ recognition.onerror = null;
132
+ recognition.onend = null;
133
+ try {
134
+ recognition.abort();
135
+ } catch {
136
+ }
137
+ };
138
+ const clearTimers = (p) => {
139
+ for (const timer of [
140
+ p.startTimer,
141
+ p.limitTimer,
142
+ p.finalTimer,
143
+ p.launchTimer
144
+ ]) {
145
+ if (timer !== null) clearTimeout(timer);
146
+ }
147
+ p.startTimer = null;
148
+ p.limitTimer = null;
149
+ p.finalTimer = null;
150
+ p.launchTimer = null;
151
+ };
152
+ const settle = (p, next) => {
153
+ if (p.stage === "ended") return;
154
+ p.stage = "ended";
155
+ clearTimers(p);
156
+ const instance = p.instance;
157
+ p.instance = null;
158
+ if (instance !== null) release(instance);
159
+ if (press === p) press = null;
160
+ report(next);
161
+ };
162
+ const complete = (p) => {
163
+ const text = heard(p);
164
+ if (text !== "") networkFailures = 0;
165
+ settle(p, { name: "done", epoch: p.epoch, text, limit: p.limit });
166
+ };
167
+ const fail = (p, reason) => settle(p, { name: "failed", epoch: p.epoch, reason, text: heard(p) });
168
+ const current = (p, instance) => press === p && p.stage !== "ended" && p.instance === instance;
169
+ const began = (p, instance) => {
170
+ instance.started = true;
171
+ instance.startedAt = now();
172
+ hadMicrophone = true;
173
+ if (p.stage !== "starting") return;
174
+ if (p.startTimer !== null) clearTimeout(p.startTimer);
175
+ p.startTimer = null;
176
+ p.stage = "listening";
177
+ p.startedAt = Date.now();
178
+ p.limitTimer = setTimeout(() => {
179
+ p.limitTimer = null;
180
+ if (press === p) stopRecording(p, true);
181
+ }, options.maxPressMs);
182
+ };
183
+ const launchIn = (p, delay) => {
184
+ if (delay <= 0) {
185
+ launch(p);
186
+ return;
187
+ }
188
+ const stage = p.stage;
189
+ p.launchTimer = setTimeout(() => {
190
+ p.launchTimer = null;
191
+ if (press === p && p.stage === stage) launch(p);
192
+ }, delay);
193
+ };
194
+ const restart = (p, end) => {
195
+ p.quickEnds = end === "listened" ? 0 : p.quickEnds + 1;
196
+ if (p.restarts >= BROWSER_MAX_RESTARTS || p.quickEnds >= BROWSER_MAX_QUICK_ENDS) {
197
+ complete(p);
198
+ return;
199
+ }
200
+ p.restarts += 1;
201
+ launchIn(
202
+ p,
203
+ end === "threw" || end === "quick" && !p.touch ? BROWSER_RESTART_COOLDOWN_MS : p.touch ? BROWSER_TOUCH_RESTART_MS : 0
204
+ );
205
+ };
206
+ const keep = (p, instance) => {
207
+ for (const text of [...instance.finals, instance.interim]) {
208
+ if (text !== "") p.earlier.push(text);
209
+ }
210
+ };
211
+ const onError = (p, instance, code) => {
212
+ if (code === "no-speech") {
213
+ instance.noSpeech = true;
214
+ return;
215
+ }
216
+ const going = p.stage === "starting" || p.stage === "listening";
217
+ if (code === "language-not-supported" && going && !p.langRetried) {
218
+ p.langRetried = true;
219
+ if (options.fallbackLang !== p.lang) {
220
+ p.lang = options.fallbackLang;
221
+ keep(p, instance);
222
+ p.instance = null;
223
+ release(instance);
224
+ launch(p);
225
+ return;
226
+ }
227
+ }
228
+ const refused = code === "not-allowed" || code === "service-not-allowed";
229
+ if (refused && going && p.confirmed && !p.grantRetried) {
230
+ p.grantRetried = true;
231
+ keep(p, instance);
232
+ p.instance = null;
233
+ release(instance);
234
+ launchIn(p, BROWSER_GRANT_RETRY_MS);
235
+ return;
236
+ }
237
+ if (refused && !p.confirmed) hadMicrophone = false;
238
+ if (heard(p) !== "") {
239
+ complete(p);
240
+ return;
241
+ }
242
+ switch (code) {
243
+ case "not-allowed":
244
+ fail(p, p.confirmed ? "unsupported" : "denied");
245
+ return;
246
+ case "service-not-allowed":
247
+ case "audio-capture":
248
+ case "language-not-supported":
249
+ fail(p, "unsupported");
250
+ return;
251
+ case "network":
252
+ networkFailures += 1;
253
+ fail(
254
+ p,
255
+ networkFailures >= NETWORK_FAILURES_FOR_GOOD ? "unsupported" : "failed"
256
+ );
257
+ return;
258
+ default:
259
+ fail(p, "failed");
260
+ }
261
+ };
262
+ const onEnd = (p, instance) => {
263
+ keep(p, instance);
264
+ p.instance = null;
265
+ instance.recognition.onstart = null;
266
+ instance.recognition.onresult = null;
267
+ instance.recognition.onerror = null;
268
+ instance.recognition.onend = null;
269
+ switch (p.stage) {
270
+ case "starting":
271
+ if (instance.noSpeech) complete(p);
272
+ else fail(p, "failed");
273
+ return;
274
+ case "listening": {
275
+ const quick = !instance.started || now() - instance.startedAt < QUICK_END_MS && instance.finals.length === 0 && instance.interim === "";
276
+ restart(p, quick ? "quick" : "listened");
277
+ return;
278
+ }
279
+ case "finishing":
280
+ complete(p);
281
+ return;
282
+ default:
283
+ return;
284
+ }
285
+ };
286
+ function launch(p) {
287
+ let recognition;
288
+ try {
289
+ recognition = createRecognition();
290
+ } catch {
291
+ recognition = null;
292
+ }
293
+ if (recognition === null) {
294
+ if (p.stage === "starting") fail(p, "unsupported");
295
+ else complete(p);
296
+ return;
297
+ }
298
+ const instance = {
299
+ recognition,
300
+ started: false,
301
+ startedAt: 0,
302
+ finals: [],
303
+ interim: "",
304
+ noSpeech: false
305
+ };
306
+ recognition.lang = p.lang;
307
+ recognition.continuous = !p.touch;
308
+ recognition.interimResults = true;
309
+ recognition.maxAlternatives = 1;
310
+ recognition.onstart = () => {
311
+ if (!current(p, instance)) return;
312
+ began(p, instance);
313
+ show(p);
314
+ };
315
+ recognition.onresult = (event) => {
316
+ if (!current(p, instance)) return;
317
+ if (!instance.started) began(p, instance);
318
+ const read = readRecognitionResults(event.results);
319
+ instance.finals = read.finals;
320
+ instance.interim = read.interim;
321
+ show(p);
322
+ };
323
+ recognition.onerror = (event) => {
324
+ if (current(p, instance)) onError(p, instance, event.error);
325
+ };
326
+ recognition.onend = () => {
327
+ if (current(p, instance)) onEnd(p, instance);
328
+ };
329
+ p.instance = instance;
330
+ try {
331
+ recognition.start();
332
+ } catch {
333
+ p.instance = null;
334
+ release(instance);
335
+ if (p.stage === "starting") fail(p, "failed");
336
+ else if (p.stage === "listening") restart(p, "threw");
337
+ else complete(p);
338
+ }
339
+ }
340
+ function prime(p) {
341
+ let opening;
342
+ try {
343
+ opening = primeMicrophone();
344
+ } catch (error) {
345
+ fail(p, primeFailure(error));
346
+ return;
347
+ }
348
+ if (opening === null) {
349
+ launch(p);
350
+ return;
351
+ }
352
+ void opening.then(
353
+ () => {
354
+ hadMicrophone = true;
355
+ if (press !== p || p.stage !== "starting") return;
356
+ p.confirmed = true;
357
+ launchIn(p, BROWSER_PRIME_SETTLE_MS);
358
+ },
359
+ (error) => {
360
+ if (press === p && p.stage === "starting") fail(p, primeFailure(error));
361
+ }
362
+ );
363
+ }
364
+ function stopRecording(p, limit) {
365
+ if (p.stage !== "listening") return;
366
+ if (p.limitTimer !== null) clearTimeout(p.limitTimer);
367
+ if (p.launchTimer !== null) clearTimeout(p.launchTimer);
368
+ p.limitTimer = null;
369
+ p.launchTimer = null;
370
+ p.stage = "finishing";
371
+ p.limit = limit;
372
+ const instance = p.instance;
373
+ if (instance === null || !instance.started) {
374
+ complete(p);
375
+ return;
376
+ }
377
+ try {
378
+ instance.recognition.stop();
379
+ } catch {
380
+ complete(p);
381
+ return;
382
+ }
383
+ p.finalTimer = setTimeout(() => {
384
+ p.finalTimer = null;
385
+ if (press === p && p.stage === "finishing") complete(p);
386
+ }, BROWSER_FINAL_WAIT_MS);
387
+ show(p);
388
+ }
389
+ const cancel = () => {
390
+ const p = press;
391
+ press = null;
392
+ if (p !== null && p.stage !== "ended") {
393
+ p.stage = "ended";
394
+ clearTimers(p);
395
+ const instance = p.instance;
396
+ p.instance = null;
397
+ if (instance !== null) release(instance);
398
+ }
399
+ if (state.name !== "idle") report(IDLE);
400
+ };
401
+ const start = () => {
402
+ const held = press;
403
+ if (held !== null && (held.stage === "starting" || held.stage === "listening")) {
404
+ return;
405
+ }
406
+ if (held !== null && held.stage === "finishing") {
407
+ held.stage = "ended";
408
+ clearTimers(held);
409
+ const instance = held.instance;
410
+ held.instance = null;
411
+ if (instance !== null) release(instance);
412
+ press = null;
413
+ }
414
+ epoch += 1;
415
+ const p = {
416
+ epoch,
417
+ stage: "starting",
418
+ instance: null,
419
+ earlier: [],
420
+ lang: options.lang,
421
+ langRetried: false,
422
+ restarts: 0,
423
+ quickEnds: 0,
424
+ confirmed: permission === "granted",
425
+ grantRetried: false,
426
+ touch: touch(),
427
+ startedAt: 0,
428
+ limit: false,
429
+ startTimer: null,
430
+ limitTimer: null,
431
+ finalTimer: null,
432
+ launchTimer: null
433
+ };
434
+ press = p;
435
+ report({ name: "starting", epoch });
436
+ p.startTimer = setTimeout(() => {
437
+ p.startTimer = null;
438
+ if (press === p && p.stage === "starting") fail(p, "failed");
439
+ }, BROWSER_START_WAIT_MS);
440
+ if (p.confirmed || permission === null && hadMicrophone) launch(p);
441
+ else prime(p);
442
+ };
443
+ return {
444
+ start,
445
+ stop() {
446
+ const p = press;
447
+ if (p === null) return;
448
+ if (p.stage === "starting") cancel();
449
+ else stopRecording(p, false);
450
+ },
451
+ cancel,
452
+ state: () => state
453
+ };
454
+ }
455
+
5
456
  // src/avatar-mic/capture-worklet.ts
6
457
  var CAPTURE_FRAME_SAMPLES = 320;
7
458
  var CAPTURE_SAMPLE_RATE = 16e3;
@@ -324,7 +775,7 @@ var FINAL_WAIT_MS = 12e3;
324
775
  var OFFER_TTL_MS = 12e4;
325
776
  var OFFER_MARGIN_MS = 5e3;
326
777
  var CLOCK_TOLERANCE_MS = 5e3;
327
- var IDLE = { name: "idle" };
778
+ var IDLE2 = { name: "idle" };
328
779
  function offerFreshUntil(offer) {
329
780
  const ahead = offer.expiresAt - offer.receivedAt;
330
781
  const life = ahead > 0 && ahead <= OFFER_TTL_MS + CLOCK_TOLERANCE_MS ? Math.min(ahead, OFFER_TTL_MS) : OFFER_TTL_MS;
@@ -357,20 +808,20 @@ function randomUtteranceId() {
357
808
  const bytes = crypto.getRandomValues(new Uint8Array(16));
358
809
  return `web-${Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
359
810
  }
360
- var BROWSER = {
811
+ var BROWSER2 = {
361
812
  createSocket: openBrowserSocket,
362
813
  startCapture: startMicCapture,
363
814
  newUtteranceId: randomUtteranceId
364
815
  };
365
816
  function createAvatarMic(options, seams = {}) {
366
817
  const { createSocket, startCapture, newUtteranceId } = {
367
- ...BROWSER,
818
+ ...BROWSER2,
368
819
  ...seams
369
820
  };
370
821
  let press = null;
371
822
  let connection = null;
372
823
  let epoch = 0;
373
- let state = IDLE;
824
+ let state = IDLE2;
374
825
  const report = (next) => {
375
826
  state = next;
376
827
  options.onState(next);
@@ -652,7 +1103,7 @@ function createAvatarMic(options, seams = {}) {
652
1103
  clearTimers(p);
653
1104
  }
654
1105
  closeConnection();
655
- if (state.name !== "idle") report(IDLE);
1106
+ if (state.name !== "idle") report(IDLE2);
656
1107
  };
657
1108
  const start = () => {
658
1109
  const current = press;
@@ -734,5 +1185,6 @@ function createAvatarMic(options, seams = {}) {
734
1185
  }
735
1186
 
736
1187
  exports.createAvatarMic = createAvatarMic;
1188
+ exports.createBrowserAvatarMic = createBrowserAvatarMic;
737
1189
  //# sourceMappingURL=index.js.map
738
1190
  //# sourceMappingURL=index.js.map