@absolutejs/voice 0.0.22-beta.649 → 0.0.22-beta.650
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/dist/angular/index.js +29 -6
- package/dist/client/htmxBootstrap.js +29 -6
- package/dist/client/index.js +29 -6
- package/dist/core/types.d.ts +5 -0
- package/dist/embed/index.js +29 -6
- package/dist/embed/voice-widget.js +8 -8
- package/dist/index.js +35 -2
- package/dist/react/index.js +29 -6
- package/dist/svelte/index.js +29 -6
- package/dist/testing/index.js +29 -6
- package/dist/vue/index.js +29 -6
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -581,6 +581,7 @@ var WS_CLOSED = 3;
|
|
|
581
581
|
var WS_NORMAL_CLOSURE = 1000;
|
|
582
582
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
583
583
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
584
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
584
585
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
585
586
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
586
587
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -660,6 +661,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
660
661
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
661
662
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
662
663
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
664
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
663
665
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
664
666
|
const state = {
|
|
665
667
|
isConnected: false,
|
|
@@ -667,6 +669,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
667
669
|
scenarioId: options.scenarioId ?? null,
|
|
668
670
|
pingInterval: null,
|
|
669
671
|
reconnectAttempts: 0,
|
|
672
|
+
reconnectResetTimeout: null,
|
|
670
673
|
reconnectTimeout: null,
|
|
671
674
|
sessionId: options.sessionId ?? createSessionId(),
|
|
672
675
|
ws: null
|
|
@@ -683,6 +686,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
683
686
|
clearTimeout(state.reconnectTimeout);
|
|
684
687
|
state.reconnectTimeout = null;
|
|
685
688
|
}
|
|
689
|
+
if (state.reconnectResetTimeout) {
|
|
690
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
691
|
+
state.reconnectResetTimeout = null;
|
|
692
|
+
}
|
|
686
693
|
};
|
|
687
694
|
const flushPendingMessages = () => {
|
|
688
695
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -728,6 +735,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
728
735
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
729
736
|
ws.binaryType = "arraybuffer";
|
|
730
737
|
ws.onopen = () => {
|
|
738
|
+
if (state.ws !== ws) {
|
|
739
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
731
742
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
732
743
|
state.isConnected = true;
|
|
733
744
|
flushPendingMessages();
|
|
@@ -741,7 +752,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
741
752
|
},
|
|
742
753
|
type: "connection"
|
|
743
754
|
});
|
|
744
|
-
state.
|
|
755
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
756
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
757
|
+
state.reconnectAttempts = 0;
|
|
758
|
+
}
|
|
759
|
+
state.reconnectResetTimeout = null;
|
|
760
|
+
}, reconnectResetAfterMs);
|
|
745
761
|
}
|
|
746
762
|
listeners.forEach((listener) => listener({
|
|
747
763
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -756,6 +772,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
756
772
|
}, pingInterval);
|
|
757
773
|
};
|
|
758
774
|
ws.onmessage = (event) => {
|
|
775
|
+
if (state.ws !== ws)
|
|
776
|
+
return;
|
|
759
777
|
const parsed = parseServerMessage(event);
|
|
760
778
|
if (!parsed) {
|
|
761
779
|
return;
|
|
@@ -767,6 +785,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
767
785
|
listeners.forEach((listener) => listener(parsed));
|
|
768
786
|
};
|
|
769
787
|
ws.onclose = (event) => {
|
|
788
|
+
if (state.ws !== ws)
|
|
789
|
+
return;
|
|
790
|
+
state.ws = null;
|
|
770
791
|
state.isConnected = false;
|
|
771
792
|
clearTimers();
|
|
772
793
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -824,11 +845,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
824
845
|
const close = (reason = "client-close") => {
|
|
825
846
|
clearTimers();
|
|
826
847
|
if (state.ws) {
|
|
827
|
-
|
|
828
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
829
|
-
}
|
|
830
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
848
|
+
const ws = state.ws;
|
|
831
849
|
state.ws = null;
|
|
850
|
+
if (ws.readyState === WS_OPEN) {
|
|
851
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
852
|
+
}
|
|
853
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
832
854
|
}
|
|
833
855
|
state.isConnected = false;
|
|
834
856
|
listeners.clear();
|
|
@@ -836,8 +858,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
836
858
|
const disconnect = () => {
|
|
837
859
|
clearTimers();
|
|
838
860
|
if (state.ws) {
|
|
839
|
-
state.ws
|
|
861
|
+
const ws = state.ws;
|
|
840
862
|
state.ws = null;
|
|
863
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
841
864
|
}
|
|
842
865
|
state.isConnected = false;
|
|
843
866
|
listeners.clear();
|
|
@@ -498,6 +498,7 @@ var WS_CLOSED = 3;
|
|
|
498
498
|
var WS_NORMAL_CLOSURE = 1000;
|
|
499
499
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
500
500
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
501
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
501
502
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
502
503
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
503
504
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -577,6 +578,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
577
578
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
578
579
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
579
580
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
581
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
580
582
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
581
583
|
const state = {
|
|
582
584
|
isConnected: false,
|
|
@@ -584,6 +586,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
584
586
|
scenarioId: options.scenarioId ?? null,
|
|
585
587
|
pingInterval: null,
|
|
586
588
|
reconnectAttempts: 0,
|
|
589
|
+
reconnectResetTimeout: null,
|
|
587
590
|
reconnectTimeout: null,
|
|
588
591
|
sessionId: options.sessionId ?? createSessionId(),
|
|
589
592
|
ws: null
|
|
@@ -600,6 +603,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
600
603
|
clearTimeout(state.reconnectTimeout);
|
|
601
604
|
state.reconnectTimeout = null;
|
|
602
605
|
}
|
|
606
|
+
if (state.reconnectResetTimeout) {
|
|
607
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
608
|
+
state.reconnectResetTimeout = null;
|
|
609
|
+
}
|
|
603
610
|
};
|
|
604
611
|
const flushPendingMessages = () => {
|
|
605
612
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -645,6 +652,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
645
652
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
646
653
|
ws.binaryType = "arraybuffer";
|
|
647
654
|
ws.onopen = () => {
|
|
655
|
+
if (state.ws !== ws) {
|
|
656
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
648
659
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
649
660
|
state.isConnected = true;
|
|
650
661
|
flushPendingMessages();
|
|
@@ -658,7 +669,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
658
669
|
},
|
|
659
670
|
type: "connection"
|
|
660
671
|
});
|
|
661
|
-
state.
|
|
672
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
673
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
674
|
+
state.reconnectAttempts = 0;
|
|
675
|
+
}
|
|
676
|
+
state.reconnectResetTimeout = null;
|
|
677
|
+
}, reconnectResetAfterMs);
|
|
662
678
|
}
|
|
663
679
|
listeners.forEach((listener) => listener({
|
|
664
680
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -673,6 +689,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
673
689
|
}, pingInterval);
|
|
674
690
|
};
|
|
675
691
|
ws.onmessage = (event) => {
|
|
692
|
+
if (state.ws !== ws)
|
|
693
|
+
return;
|
|
676
694
|
const parsed = parseServerMessage(event);
|
|
677
695
|
if (!parsed) {
|
|
678
696
|
return;
|
|
@@ -684,6 +702,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
684
702
|
listeners.forEach((listener) => listener(parsed));
|
|
685
703
|
};
|
|
686
704
|
ws.onclose = (event) => {
|
|
705
|
+
if (state.ws !== ws)
|
|
706
|
+
return;
|
|
707
|
+
state.ws = null;
|
|
687
708
|
state.isConnected = false;
|
|
688
709
|
clearTimers();
|
|
689
710
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -741,11 +762,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
741
762
|
const close = (reason = "client-close") => {
|
|
742
763
|
clearTimers();
|
|
743
764
|
if (state.ws) {
|
|
744
|
-
|
|
745
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
746
|
-
}
|
|
747
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
765
|
+
const ws = state.ws;
|
|
748
766
|
state.ws = null;
|
|
767
|
+
if (ws.readyState === WS_OPEN) {
|
|
768
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
769
|
+
}
|
|
770
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
749
771
|
}
|
|
750
772
|
state.isConnected = false;
|
|
751
773
|
listeners.clear();
|
|
@@ -753,8 +775,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
753
775
|
const disconnect = () => {
|
|
754
776
|
clearTimers();
|
|
755
777
|
if (state.ws) {
|
|
756
|
-
state.ws
|
|
778
|
+
const ws = state.ws;
|
|
757
779
|
state.ws = null;
|
|
780
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
758
781
|
}
|
|
759
782
|
state.isConnected = false;
|
|
760
783
|
listeners.clear();
|
package/dist/client/index.js
CHANGED
|
@@ -106,6 +106,7 @@ var WS_CLOSED = 3;
|
|
|
106
106
|
var WS_NORMAL_CLOSURE = 1000;
|
|
107
107
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
108
108
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
109
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
109
110
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
110
111
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
111
112
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -185,6 +186,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
185
186
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
186
187
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
187
188
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
189
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
188
190
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
189
191
|
const state = {
|
|
190
192
|
isConnected: false,
|
|
@@ -192,6 +194,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
192
194
|
scenarioId: options.scenarioId ?? null,
|
|
193
195
|
pingInterval: null,
|
|
194
196
|
reconnectAttempts: 0,
|
|
197
|
+
reconnectResetTimeout: null,
|
|
195
198
|
reconnectTimeout: null,
|
|
196
199
|
sessionId: options.sessionId ?? createSessionId(),
|
|
197
200
|
ws: null
|
|
@@ -208,6 +211,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
208
211
|
clearTimeout(state.reconnectTimeout);
|
|
209
212
|
state.reconnectTimeout = null;
|
|
210
213
|
}
|
|
214
|
+
if (state.reconnectResetTimeout) {
|
|
215
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
216
|
+
state.reconnectResetTimeout = null;
|
|
217
|
+
}
|
|
211
218
|
};
|
|
212
219
|
const flushPendingMessages = () => {
|
|
213
220
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -253,6 +260,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
253
260
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
254
261
|
ws.binaryType = "arraybuffer";
|
|
255
262
|
ws.onopen = () => {
|
|
263
|
+
if (state.ws !== ws) {
|
|
264
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
256
267
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
257
268
|
state.isConnected = true;
|
|
258
269
|
flushPendingMessages();
|
|
@@ -266,7 +277,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
266
277
|
},
|
|
267
278
|
type: "connection"
|
|
268
279
|
});
|
|
269
|
-
state.
|
|
280
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
281
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
282
|
+
state.reconnectAttempts = 0;
|
|
283
|
+
}
|
|
284
|
+
state.reconnectResetTimeout = null;
|
|
285
|
+
}, reconnectResetAfterMs);
|
|
270
286
|
}
|
|
271
287
|
listeners.forEach((listener) => listener({
|
|
272
288
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -281,6 +297,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
281
297
|
}, pingInterval);
|
|
282
298
|
};
|
|
283
299
|
ws.onmessage = (event) => {
|
|
300
|
+
if (state.ws !== ws)
|
|
301
|
+
return;
|
|
284
302
|
const parsed = parseServerMessage(event);
|
|
285
303
|
if (!parsed) {
|
|
286
304
|
return;
|
|
@@ -292,6 +310,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
292
310
|
listeners.forEach((listener) => listener(parsed));
|
|
293
311
|
};
|
|
294
312
|
ws.onclose = (event) => {
|
|
313
|
+
if (state.ws !== ws)
|
|
314
|
+
return;
|
|
315
|
+
state.ws = null;
|
|
295
316
|
state.isConnected = false;
|
|
296
317
|
clearTimers();
|
|
297
318
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -349,11 +370,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
349
370
|
const close = (reason = "client-close") => {
|
|
350
371
|
clearTimers();
|
|
351
372
|
if (state.ws) {
|
|
352
|
-
|
|
353
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
354
|
-
}
|
|
355
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
373
|
+
const ws = state.ws;
|
|
356
374
|
state.ws = null;
|
|
375
|
+
if (ws.readyState === WS_OPEN) {
|
|
376
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
377
|
+
}
|
|
378
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
357
379
|
}
|
|
358
380
|
state.isConnected = false;
|
|
359
381
|
listeners.clear();
|
|
@@ -361,8 +383,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
361
383
|
const disconnect = () => {
|
|
362
384
|
clearTimers();
|
|
363
385
|
if (state.ws) {
|
|
364
|
-
state.ws
|
|
386
|
+
const ws = state.ws;
|
|
365
387
|
state.ws = null;
|
|
388
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
366
389
|
}
|
|
367
390
|
state.isConnected = false;
|
|
368
391
|
listeners.clear();
|
package/dist/core/types.d.ts
CHANGED
|
@@ -879,6 +879,7 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
|
|
|
879
879
|
*/
|
|
880
880
|
authorizeConnection?: (input: {
|
|
881
881
|
context: TContext;
|
|
882
|
+
headers: Headers;
|
|
882
883
|
path: string;
|
|
883
884
|
query: Record<string, unknown>;
|
|
884
885
|
scenarioId?: string;
|
|
@@ -1343,6 +1344,10 @@ export type VoiceConnectionOptions = {
|
|
|
1343
1344
|
* attempts that's a ~95s window, enough to ride out a server redeploy without
|
|
1344
1345
|
* the caller losing the call. */
|
|
1345
1346
|
reconnectMaxDelayMs?: number;
|
|
1347
|
+
/** A reconnected socket must remain open this long before its retry budget is
|
|
1348
|
+
* reset. This prevents a server that accepts and immediately drops sockets
|
|
1349
|
+
* from creating an unbounded reconnect loop. Default 30000. */
|
|
1350
|
+
reconnectResetAfterMs?: number;
|
|
1346
1351
|
pingInterval?: number;
|
|
1347
1352
|
/** Additional query values sent on every initial or reconnecting socket. */
|
|
1348
1353
|
query?: Record<string, string>;
|
package/dist/embed/index.js
CHANGED
|
@@ -495,6 +495,7 @@ var WS_CLOSED = 3;
|
|
|
495
495
|
var WS_NORMAL_CLOSURE = 1000;
|
|
496
496
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
497
497
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
498
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
498
499
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
499
500
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
500
501
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -574,6 +575,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
574
575
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
575
576
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
576
577
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
578
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
577
579
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
578
580
|
const state = {
|
|
579
581
|
isConnected: false,
|
|
@@ -581,6 +583,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
581
583
|
scenarioId: options.scenarioId ?? null,
|
|
582
584
|
pingInterval: null,
|
|
583
585
|
reconnectAttempts: 0,
|
|
586
|
+
reconnectResetTimeout: null,
|
|
584
587
|
reconnectTimeout: null,
|
|
585
588
|
sessionId: options.sessionId ?? createSessionId(),
|
|
586
589
|
ws: null
|
|
@@ -597,6 +600,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
597
600
|
clearTimeout(state.reconnectTimeout);
|
|
598
601
|
state.reconnectTimeout = null;
|
|
599
602
|
}
|
|
603
|
+
if (state.reconnectResetTimeout) {
|
|
604
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
605
|
+
state.reconnectResetTimeout = null;
|
|
606
|
+
}
|
|
600
607
|
};
|
|
601
608
|
const flushPendingMessages = () => {
|
|
602
609
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -642,6 +649,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
642
649
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
643
650
|
ws.binaryType = "arraybuffer";
|
|
644
651
|
ws.onopen = () => {
|
|
652
|
+
if (state.ws !== ws) {
|
|
653
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
645
656
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
646
657
|
state.isConnected = true;
|
|
647
658
|
flushPendingMessages();
|
|
@@ -655,7 +666,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
655
666
|
},
|
|
656
667
|
type: "connection"
|
|
657
668
|
});
|
|
658
|
-
state.
|
|
669
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
670
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
671
|
+
state.reconnectAttempts = 0;
|
|
672
|
+
}
|
|
673
|
+
state.reconnectResetTimeout = null;
|
|
674
|
+
}, reconnectResetAfterMs);
|
|
659
675
|
}
|
|
660
676
|
listeners.forEach((listener) => listener({
|
|
661
677
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -670,6 +686,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
670
686
|
}, pingInterval);
|
|
671
687
|
};
|
|
672
688
|
ws.onmessage = (event) => {
|
|
689
|
+
if (state.ws !== ws)
|
|
690
|
+
return;
|
|
673
691
|
const parsed = parseServerMessage(event);
|
|
674
692
|
if (!parsed) {
|
|
675
693
|
return;
|
|
@@ -681,6 +699,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
681
699
|
listeners.forEach((listener) => listener(parsed));
|
|
682
700
|
};
|
|
683
701
|
ws.onclose = (event) => {
|
|
702
|
+
if (state.ws !== ws)
|
|
703
|
+
return;
|
|
704
|
+
state.ws = null;
|
|
684
705
|
state.isConnected = false;
|
|
685
706
|
clearTimers();
|
|
686
707
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -738,11 +759,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
738
759
|
const close = (reason = "client-close") => {
|
|
739
760
|
clearTimers();
|
|
740
761
|
if (state.ws) {
|
|
741
|
-
|
|
742
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
743
|
-
}
|
|
744
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
762
|
+
const ws = state.ws;
|
|
745
763
|
state.ws = null;
|
|
764
|
+
if (ws.readyState === WS_OPEN) {
|
|
765
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
766
|
+
}
|
|
767
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
746
768
|
}
|
|
747
769
|
state.isConnected = false;
|
|
748
770
|
listeners.clear();
|
|
@@ -750,8 +772,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
750
772
|
const disconnect = () => {
|
|
751
773
|
clearTimers();
|
|
752
774
|
if (state.ws) {
|
|
753
|
-
state.ws
|
|
775
|
+
const ws = state.ws;
|
|
754
776
|
state.ws = null;
|
|
777
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
755
778
|
}
|
|
756
779
|
state.isConnected = false;
|
|
757
780
|
listeners.clear();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
(()=>{var{defineProperty:B,getOwnPropertyNames:R0,getOwnPropertyDescriptor:_0}=Object,$0=Object.prototype.hasOwnProperty;function I0(g){return this[g]}var H0=(g)=>{var A=(k??=new WeakMap).get(g),C;if(A)return A;if(A=B({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of R0(g))if(!$0.call(A,V))B(A,V,{get:I0.bind(g,V),enumerable:!(C=_0(g,V))||C.enumerable})}return k.set(g,A),A},k;var U0=(g)=>g;function L0(g,A){this[g]=U0.bind(null,A)}var G0=(g,A)=>{for(var C in A)B(g,C,{get:A[C],enumerable:!0,configurable:!0,set:L0.bind(A,C)})};var m0={};G0(m0,{mount:()=>V0,default:()=>r0,VOICE_EMBED_VERSION:()=>C0});var W0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},w0=(g,A,C,V)=>{let R=A??g.getAttribute("hx-get")??"";if(!R)return"";let c=new URL(R,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=W0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",R=A.sessionQueryParam??"sessionId",c=()=>{let H=window,T=w0(C,A.route,R,g.sessionId);if(T)C.setAttribute("hx-get",T);H.htmx?.process?.(C),H.htmx?.trigger?.(C,V)},U=g.subscribe(c);return c(),()=>{U()}};var y0=(g)=>Math.max(-1,Math.min(1,g)),D0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=y0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},S0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let R of C){let c=R/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},X0=(g,A,C)=>{if(A===C)return g;let V=A/C,R=Math.round(g.length/V),c=new Float32Array(R),U=0,H=0;while(U<c.length){let T=Math.round((U+1)*V),G=0,$=0;for(let w=H;w<T&&w<g.length;w+=1)G+=g[w]??0,$+=1;c[U]=$>0?G/$:0,U+=1,H=T}return c},f=(g)=>{let A=null,C=null,V=null,R=null,c=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let H=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!H)throw Error("Browser microphone capture requires AudioContext support.");try{if(R=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new H,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(R),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(T)=>{let G=T.inputBuffer.getChannelData(0),$=X0(G,A?.sampleRate??48000,g.sampleRateHz??16000),w=D0($);g.onLevel?.(S0(w)),g.onAudio(w)},C.connect(V),V.connect(A.destination)}catch(T){throw U(),T}},U=()=>{V?.disconnect(),C?.disconnect(),R?.getTracks().forEach((H)=>H.stop()),A?.close(),g.onLevel?.(0),A=null,R=null,V=null,C=null};return{start:c,stop:U}};var M=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},d=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:M(g.message),type:"error"};case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var t0=Math.PI*2;var Q=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var h0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,j=(g)=>g.length===0?void 0:Math.max(...g);var y=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},b=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},S=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},x=(g)=>String(g.id??S(g,"ssrc")??y(g,"ssrc")??S(g,"trackIdentifier")??S(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var O0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((_)=>_.type==="inbound-rtp"&&S(_,"kind")!=="video"),R=A.filter((_)=>_.type==="outbound-rtp"&&S(_,"kind")!=="video"),c=A.filter((_)=>_.type==="candidate-pair"),U=A.filter((_)=>(_.type==="track"||_.type==="media-source")&&S(_,"kind")==="audio"),H=c.filter((_)=>b(_,"selected")===!0||b(_,"nominated")===!0||S(_,"state")==="succeeded").length,T=U.filter((_)=>S(_,"readyState")!=="ended"&&S(_,"trackState")!=="ended"&&b(_,"ended")!==!0).length,G=U.filter((_)=>S(_,"readyState")==="ended"||S(_,"trackState")==="ended"||b(_,"ended")===!0).length,$=V.reduce((_,D)=>_+(y(D,"packetsReceived")??0),0),w=R.reduce((_,D)=>_+(y(D,"packetsSent")??0),0),I=[...V,...R].reduce((_,D)=>_+Math.max(0,y(D,"packetsLost")??0),0),X=$+I,h=X===0?0:I/X,W=V.reduce((_,D)=>_+(y(D,"bytesReceived")??0),0),O=R.reduce((_,D)=>_+(y(D,"bytesSent")??0),0),J=j(c.map((_)=>l(y(_,"currentRoundTripTime")??y(_,"roundTripTime"))).filter((_)=>_!==void 0)),Y=j([...V,...R].map((_)=>l(y(_,"jitter"))).filter((_)=>_!==void 0)),N=j(V.map((_)=>{let D=y(_,"jitterBufferDelay"),z=y(_,"jitterBufferEmittedCount");return D!==void 0&&z!==void 0&&z>0?D/z*1000:void 0}).filter((_)=>_!==void 0)),q=U.map((_)=>y(_,"audioLevel")).filter((_)=>_!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&H===0)Q(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&T===0)Q(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&h>g.maxPacketLossRatio)Q(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(h)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&J!==void 0&&J>g.maxRoundTripTimeMs)Q(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(J)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Y!==void 0&&Y>g.maxJitterMs)Q(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Y)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:H,audioLevelAverage:h0(q),bytesReceived:W,bytesSent:O,checkedAt:Date.now(),endedAudioTracks:G,inboundPackets:$,issues:C,jitterBufferDelayMs:N,jitterMs:Y,liveAudioTracks:T,outboundPackets:w,packetLossRatio:h,packetsLost:I,roundTripTimeMs:J,status:C.some((_)=>_.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(O0)};var n=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],R=new Map(C.map((I)=>[x(I),I])),U=A.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&S(I,"kind")!=="video"&&S(I,"mediaType")!=="video").map((I)=>{let X=I.type==="outbound-rtp"?"outbound":"inbound",h=X==="outbound"?"packetsSent":"packetsReceived",W=X==="outbound"?"bytesSent":"bytesReceived",O=R.get(x(I)),J=y(I,h),Y=O?y(O,h):void 0,N=y(I,W),q=O?y(O,W):void 0,_=I.timestamp!==void 0&&O?.timestamp!==void 0?I.timestamp-O.timestamp:void 0;return{bytesDelta:N!==void 0&&q!==void 0?N-q:void 0,currentPackets:J,direction:X,id:x(I),packetDelta:J!==void 0&&Y!==void 0?J-Y:void 0,previousPackets:Y,timeDeltaMs:_}}),H=U.filter((I)=>I.direction==="inbound"),T=U.filter((I)=>I.direction==="outbound"),G=j(U.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),$=H.filter((I)=>g.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,w=T.filter((I)=>g.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(g.requireInboundAudio&&H.length===0)Q(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&T.length===0)Q(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&G!==void 0&&G>g.maxGapMs)Q(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(G)}ms above ${String(g.maxGapMs)}ms.`);if($>0)Q(V,"error","media.webrtc_inbound_stalled",`${String($)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(w>0)Q(V,"error","media.webrtc_outbound_stalled",`${String(w)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:H.length,issues:V,maxObservedGapMs:G,outboundAudioStreams:T.length,stalledInboundStreams:$,stalledOutboundStreams:w,status:V.some((I)=>I.severity==="error")?"fail":V.length>0?"warn":"pass",streams:U,totalStats:A.length}};var J0="/api/voice/browser-media",Y0=5000,Z0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,Q0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??J0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},o=(g)=>{let A=null,C=[],V=async()=>{let U=await Z0(g);if(!U)return;let H=await i({peerConnection:U}),T=v({...g,stats:H}),G=g.continuity===!1?void 0:n({...g.continuity,previousStats:C,stats:H}),$={at:Date.now(),continuity:G,report:T,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=H,g.onReport?.($),await Q0($,g),$},R=()=>{V().catch((U)=>{g.onError?.(U)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;R(),A=setInterval(R,g.intervalMs??Y0)}}};var P0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var E=()=>{},K0=()=>E,E0={callControl:E,close:E,disconnect:E,endTurn:E,send:E,sendAudio:E,simulateDisconnect:E,subscribe:K0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},N0=()=>crypto.randomUUID(),q0=(g,A,C,V)=>{let{hostname:R,port:c,protocol:U}=window.location,H=U==="https:"?"wss:":"ws:",T=c?`:${c}`:"",G=new URL(`${H}//${R}${T}${g}`);if(G.searchParams.set("sessionId",A),C)G.searchParams.set("scenarioId",C);for(let[$,w]of Object.entries(V))if($!=="sessionId"&&$!=="scenarioId"&&w)G.searchParams.set($,w);return G.toString()},z0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},b0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return z0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return E0;let C=new Set,V=A.reconnect!==!1,R=A.maxReconnectAttempts??15,c=A.reconnectMaxDelayMs??8000,U=A.pingInterval??30000,H=(L)=>P0(L,500,c),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectTimeout:null,sessionId:A.sessionId??N0(),ws:null},G=(L)=>{C.forEach((Z)=>Z(L))},$=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null},w=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let L=T.pendingMessages.shift();if(L!==void 0)T.ws.send(L)}},I=()=>{T.reconnectAttempts+=1;let L=H(T.reconnectAttempts),Z=Date.now()+L;G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,nextAttemptAt:Z,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(()=>{if(T.reconnectAttempts>R){G({reconnect:{attempts:T.reconnectAttempts,maxAttempts:R,status:"exhausted"},type:"connection"});return}X()},L)},X=()=>{let L=new WebSocket(q0(g,T.sessionId,T.scenarioId,A.query??{}));L.binaryType="arraybuffer",L.onopen=()=>{let Z=T.reconnectAttempts>0;if(T.isConnected=!0,w(),Z)G({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:R,status:"resumed"},type:"connection"}),T.reconnectAttempts=0;C.forEach((K)=>K({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(L.readyState===1)L.send(JSON.stringify({type:"ping"}))},U)},L.onmessage=(Z)=>{let K=b0(Z);if(!K)return;if(K.type==="session")T.sessionId=K.sessionId,T.scenarioId=K.scenarioId??T.scenarioId;C.forEach((T0)=>T0(K))},L.onclose=(Z)=>{if(T.isConnected=!1,$(),V&&Z.code!==1000&&T.reconnectAttempts<R)I();else if(V&&Z.code!==1000)G({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:R,status:"exhausted"},type:"connection"})},T.ws=L},h=(L)=>{if(T.ws?.readyState===1){T.ws.send(L);return}T.pendingMessages.push(L)},W=(L)=>{h(JSON.stringify(L))},O=(L={})=>{if(L.sessionId)T.sessionId=L.sessionId;if(L.scenarioId)T.scenarioId=L.scenarioId;W({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},J=(L)=>{h(L)},Y=()=>{W({type:"end_turn"})},N=(L)=>{W({...L,type:"call_control"})},q=(L="client-close")=>{if($(),T.ws){if(T.ws.readyState===1)T.ws.send(JSON.stringify({reason:L,type:"close"}));T.ws.close(1000),T.ws=null}T.isConnected=!1,C.clear()},_=()=>{if($(),T.ws)T.ws.close(1000),T.ws=null;T.isConnected=!1,C.clear()},D=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},z=(L)=>{return C.add(L),()=>{C.delete(L)}};return X(),{callControl:N,close:q,disconnect:_,endTurn:Y,send:W,sendAudio:J,simulateDisconnect:D,start:O,subscribe:z,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var j0=()=>({attempts:0,maxAttempts:0,status:"idle"}),B0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},M0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},x0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:j0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),r=()=>{let g=x0(),A="",C=new Set,V=()=>{C.forEach((c)=>c())};return{dispatch:(c)=>{switch(c.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:c.chunk,format:c.format,receivedAt:c.receivedAt,turnId:c.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,c.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${c.delta}`};break;case"complete":g={...g,sessionId:c.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:c.event.type==="end"?c.event.disposition:g.call?.disposition,endedAt:c.event.type==="end"?c.event.at:g.call?.endedAt,events:[...g.call?.events??[],c.event],lastEventAt:c.event.at,startedAt:g.call?.startedAt??c.event.at},sessionId:c.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:c.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:c.message};break;case"final":A=B0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:M0(A,c.transcript.text)};break;case"replay":A=c.partial,g={...g,assistantStreamingText:"",assistantTexts:[...c.assistantTexts],call:c.call??null,error:null,isConnected:c.status==="active",partial:c.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:c.scenarioId??g.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status,turns:[...c.turns]};break;case"session":g={...g,error:null,scenarioId:c.scenarioId??g.scenarioId,isConnected:c.status==="active",sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,c.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(c)=>{return C.add(c),()=>{C.delete(c)}}}};var u=(g,A={})=>{let C=m(g,A),V=r(),R=A.browserMedia&&typeof window<"u"?o({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,c=new Set,U=($)=>Promise.resolve().then(()=>{if(!$?.sessionId&&!$?.scenarioId)return;C.start($),R?.start()}),H=()=>{c.forEach(($)=>$())},T=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let $=V.getSnapshot(),w=JSON.stringify({at:Date.now(),reconnect:$.reconnect,scenarioId:$.scenarioId,sessionId:C.getSessionId(),turnIds:$.turns.map((I)=>I.id)});fetch(A.reconnectReportPath,{body:w,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},G=C.subscribe(($)=>{let w=d($);if(w){if(V.dispatch(w),$.type==="connection")T();H()}});return{start:U,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl($){C.callControl($)},close($){G(),R?.close(),C.close($),V.dispatch({type:"disconnected"}),H()},disconnect(){G(),R?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),H()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio($){C.sendAudio($)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe($){return c.add($),()=>{c.delete($)}},get turns(){return V.getSnapshot().turns}}};var s=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var k0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},F0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var a=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=k0[A],R=F0[C],c=g?.silenceMs??R.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??R.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??R.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??R.transcriptStabilityMs??V.transcriptStabilityMs}};var f0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},p=(g="default")=>{let A=f0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var d0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),t=(g,A={})=>{let C=p(A.preset),V=u(g,{...C.connection,...A.connection}),R=null,c=d0(V),U=new Set,H=()=>{for(let W of U)W()},T=()=>{if(c={...c,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&c.status==="completed"&&c.isRecording)R?.stop(),R=null,c={...c,isRecording:!1};H()},G=V.subscribe(T);T();let $=()=>{if(R)return R;return R=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(W)=>{if(A.capture?.onAudio){A.capture.onAudio(W,V.sendAudio);return}V.sendAudio(W)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),R},w=()=>{R?.stop(),R=null,c={...c,isRecording:!1},H()},I=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},H(),await $().start(),c={...c,isRecording:!0},H()}catch(W){throw R?.stop(),R=null,c={...c,isRecording:!1,recordingError:W instanceof Error?W.message:String(W)},H(),W}};return{close:(W)=>{G(),w(),V.close(W)},disconnect:()=>{G(),w(),V.disconnect()},startRecording:I,stopRecording:w,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(W){return F(V,W)},get call(){return c.call},callControl:(W)=>V.callControl(W),endTurn:()=>V.endTurn(),get error(){return c.error},getServerSnapshot:()=>c,getSnapshot:()=>c,get isConnected(){return c.isConnected},get isRecording(){return c.isRecording},get partial(){return c.partial},get reconnect(){return c.reconnect},get recordingError(){return c.recordingError},get scenarioId(){return c.scenarioId},sendAudio:(W)=>V.sendAudio(W),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(W)=>{return U.add(W),()=>{U.delete(W)}},toggleRecording:async()=>{if(c.isRecording){w();return}await I()},get turns(){return c.turns}}};var P=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var e=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var l0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},v0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},i0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...l0,...g.theme},C={...v0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,R=g.state.turns.at(-1)?.committedAt,c=e({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:R}),U=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,H=g.state.error?"Error":U?C.connecting:g.state.status==="completed"?C.callEnded:i0(c,C);return{agentState:c,classes:{container:`absolute-voice-widget absolute-voice-widget--${c}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:H,theme:A,title:g.title??"Voice"}},n0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${n0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,R=[];if(g.controls.canStart)R.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.startCall)}</button>`);if(g.controls.canMute)R.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.mute)}</button>`);if(g.controls.canEnd)R.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${P(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${P(g.classes.container)}" style="${C}">
|
|
1
|
+
(()=>{var{defineProperty:B,getOwnPropertyNames:R0,getOwnPropertyDescriptor:$0}=Object,I0=Object.prototype.hasOwnProperty;function H0(g){return this[g]}var U0=(g)=>{var A=(k??=new WeakMap).get(g),C;if(A)return A;if(A=B({},"__esModule",{value:!0}),g&&typeof g==="object"||typeof g==="function"){for(var V of R0(g))if(!I0.call(A,V))B(A,V,{get:H0.bind(g,V),enumerable:!(C=$0(g,V))||C.enumerable})}return k.set(g,A),A},k;var L0=(g)=>g;function G0(g,A){this[g]=L0.bind(null,A)}var W0=(g,A)=>{for(var C in A)B(g,C,{get:A[C],enumerable:!0,configurable:!0,set:G0.bind(A,C)})};var r0={};W0(r0,{mount:()=>V0,default:()=>u0,VOICE_EMBED_VERSION:()=>C0});var y0=(g)=>{if(typeof g!=="string")return g;return document.querySelector(g)},D0=(g,A,C,V)=>{let _=A??g.getAttribute("hx-get")??"";if(!_)return"";let c=new URL(_,window.location.origin);if(V)c.searchParams.set(C,V);else c.searchParams.delete(C);return`${c.pathname}${c.search}${c.hash}`},F=(g,A)=>{if(typeof window>"u"||typeof document>"u")return()=>{};let C=y0(A.element);if(!C)return()=>{};let V=A.eventName??"voice-refresh",_=A.sessionQueryParam??"sessionId",c=()=>{let U=window,W=D0(C,A.route,_,g.sessionId);if(W)C.setAttribute("hx-get",W);U.htmx?.process?.(C),U.htmx?.trigger?.(C,V)},L=g.subscribe(c);return c(),()=>{L()}};var S0=(g)=>Math.max(-1,Math.min(1,g)),X0=(g)=>{let A=new Int16Array(g.length);for(let C=0;C<g.length;C+=1){let V=S0(g[C]??0);A[C]=V<0?V*32768:V*32767}return new Uint8Array(A.buffer)},h0=(g)=>{let A=g instanceof Uint8Array?g:new Uint8Array(g);if(A.byteLength<2)return 0;let C=new Int16Array(A.buffer,A.byteOffset,Math.floor(A.byteLength/2));if(C.length===0)return 0;let V=0;for(let _ of C){let c=_/32768;V+=c*c}return Math.min(1,Math.max(0,Math.sqrt(V/C.length)*5.5))},O0=(g,A,C)=>{if(A===C)return g;let V=A/C,_=Math.round(g.length/V),c=new Float32Array(_),L=0,U=0;while(L<c.length){let W=Math.round((L+1)*V),T=0,$=0;for(let G=U;G<W&&G<g.length;G+=1)T+=g[G]??0,$+=1;c[L]=$>0?T/$:0,L+=1,U=W}return c},f=(g)=>{let A=null,C=null,V=null,_=null,c=async()=>{if(!g.stream&&(typeof navigator>"u"||!navigator.mediaDevices?.getUserMedia))throw Error("Browser microphone capture requires navigator.mediaDevices.getUserMedia.");let U=(typeof window<"u"?window.AudioContext??window.webkitAudioContext:void 0)??AudioContext;if(!U)throw Error("Browser microphone capture requires AudioContext support.");try{if(_=g.stream??await navigator.mediaDevices.getUserMedia({audio:{autoGainControl:!0,channelCount:g.channelCount??1,echoCancellation:!0,noiseSuppression:!0}}),A=new U,A.state==="suspended")await A.resume();C=A.createMediaStreamSource(_),V=A.createScriptProcessor(4096,1,1),V.onaudioprocess=(W)=>{let T=W.inputBuffer.getChannelData(0),$=O0(T,A?.sampleRate??48000,g.sampleRateHz??16000),G=X0($);g.onLevel?.(h0(G)),g.onAudio(G)},C.connect(V),V.connect(A.destination)}catch(W){throw L(),W}},L=()=>{V?.disconnect(),C?.disconnect(),_?.getTracks().forEach((U)=>U.stop()),A?.close(),g.onLevel?.(0),A=null,_=null,V=null,C=null};return{start:c,stop:L}};var M=(g)=>{if(typeof g==="string"&&g.trim())return g;if(g instanceof Error&&g.message.trim())return g.message;if(g&&typeof g==="object"){let A=g;for(let C of["message","reason","description"]){let V=A[C];if(typeof V==="string"&&V.trim())return V}if("error"in A)return M(A.error);if("cause"in A)return M(A.cause);try{return JSON.stringify(g)}catch{}}return"Unexpected error"},d=(g)=>{switch(g.type){case"audio":return{chunk:Uint8Array.from(atob(g.chunkBase64),(A)=>A.charCodeAt(0)),format:g.format,receivedAt:g.receivedAt,turnId:g.turnId,type:"audio"};case"assistant":return{text:g.text,turnId:g.turnId,type:"assistant"};case"assistant_delta":return{delta:g.delta,turnId:g.turnId,type:"assistant_delta"};case"complete":return{sessionId:g.sessionId,type:"complete"};case"connection":return{reconnect:g.reconnect,type:"connection"};case"call_lifecycle":return{event:g.event,sessionId:g.sessionId,type:"call_lifecycle"};case"error":return{message:M(g.message),type:"error"};case"final":return{transcript:g.transcript,type:"final"};case"partial":return{transcript:g.transcript,type:"partial"};case"replay":return{assistantTexts:g.assistantTexts,call:g.call,partial:g.partial,scenarioId:g.scenarioId,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,status:g.status,turns:g.turns,type:"replay"};case"session":return{sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,type:"session"};case"turn":return{turn:g.turn,type:"turn"};default:return null}};var e0=Math.PI*2;var w=(g,A,C,V)=>{g.push({code:C,message:V,severity:A})};var J0=(g)=>g.length===0?void 0:g.reduce((A,C)=>A+C,0)/g.length,j=(g)=>g.length===0?void 0:Math.max(...g);var D=(g,A)=>{let C=g[A];return typeof C==="number"&&Number.isFinite(C)?C:void 0},b=(g,A)=>{let C=g[A];return typeof C==="boolean"?C:void 0},O=(g,A)=>{let C=g[A];return typeof C==="string"?C:void 0},x=(g)=>String(g.id??O(g,"ssrc")??D(g,"ssrc")??O(g,"trackIdentifier")??O(g,"mid")??"unknown"),l=(g)=>g===void 0?void 0:g*1000;var Y0=(g)=>{let A={};for(let[C,V]of Object.entries(g))if(V===null||typeof V==="boolean"||typeof V==="number"||typeof V==="string")A[C]=V;return A};var v=(g={})=>{let A=g.stats??[],C=[],V=A.filter((R)=>R.type==="inbound-rtp"&&O(R,"kind")!=="video"),_=A.filter((R)=>R.type==="outbound-rtp"&&O(R,"kind")!=="video"),c=A.filter((R)=>R.type==="candidate-pair"),L=A.filter((R)=>(R.type==="track"||R.type==="media-source")&&O(R,"kind")==="audio"),U=c.filter((R)=>b(R,"selected")===!0||b(R,"nominated")===!0||O(R,"state")==="succeeded").length,W=L.filter((R)=>O(R,"readyState")!=="ended"&&O(R,"trackState")!=="ended"&&b(R,"ended")!==!0).length,T=L.filter((R)=>O(R,"readyState")==="ended"||O(R,"trackState")==="ended"||b(R,"ended")===!0).length,$=V.reduce((R,S)=>R+(D(S,"packetsReceived")??0),0),G=_.reduce((R,S)=>R+(D(S,"packetsSent")??0),0),I=[...V,..._].reduce((R,S)=>R+Math.max(0,D(S,"packetsLost")??0),0),P=$+I,J=P===0?0:I/P,y=V.reduce((R,S)=>R+(D(S,"bytesReceived")??0),0),h=_.reduce((R,S)=>R+(D(S,"bytesSent")??0),0),Y=j(c.map((R)=>l(D(R,"currentRoundTripTime")??D(R,"roundTripTime"))).filter((R)=>R!==void 0)),Z=j([...V,..._].map((R)=>l(D(R,"jitter"))).filter((R)=>R!==void 0)),N=j(V.map((R)=>{let S=D(R,"jitterBufferDelay"),z=D(R,"jitterBufferEmittedCount");return S!==void 0&&z!==void 0&&z>0?S/z*1000:void 0}).filter((R)=>R!==void 0)),q=L.map((R)=>D(R,"audioLevel")).filter((R)=>R!==void 0);if(g.requireConnectedCandidatePair&&c.length>0&&U===0)w(C,"error","media.webrtc_candidate_pair_missing","No active WebRTC candidate pair was observed.");if(g.requireLiveAudioTrack&&W===0)w(C,"error","media.webrtc_audio_track_missing","No live WebRTC audio track was observed.");if(g.maxPacketLossRatio!==void 0&&J>g.maxPacketLossRatio)w(C,"warning","media.webrtc_packet_loss",`Observed WebRTC packet loss ratio ${String(J)} above ${String(g.maxPacketLossRatio)}.`);if(g.maxRoundTripTimeMs!==void 0&&Y!==void 0&&Y>g.maxRoundTripTimeMs)w(C,"warning","media.webrtc_round_trip_time",`Observed WebRTC RTT ${String(Y)}ms above ${String(g.maxRoundTripTimeMs)}ms.`);if(g.maxJitterMs!==void 0&&Z!==void 0&&Z>g.maxJitterMs)w(C,"warning","media.webrtc_jitter",`Observed WebRTC jitter ${String(Z)}ms above ${String(g.maxJitterMs)}ms.`);return{activeCandidatePairs:U,audioLevelAverage:J0(q),bytesReceived:y,bytesSent:h,checkedAt:Date.now(),endedAudioTracks:T,inboundPackets:$,issues:C,jitterBufferDelayMs:N,jitterMs:Z,liveAudioTracks:W,outboundPackets:G,packetLossRatio:J,packetsLost:I,roundTripTimeMs:Y,status:C.some((R)=>R.severity==="error")?"fail":C.length>0?"warn":"pass",totalStats:A.length}},i=async(g)=>{return[...(await g.peerConnection.getStats(g.selector??null)).values()].map(Y0)};var n=(g={})=>{let A=g.stats??[],C=g.previousStats??[],V=[],_=new Map(C.map((I)=>[x(I),I])),L=A.filter((I)=>(I.type==="inbound-rtp"||I.type==="outbound-rtp")&&O(I,"kind")!=="video"&&O(I,"mediaType")!=="video").map((I)=>{let P=I.type==="outbound-rtp"?"outbound":"inbound",J=P==="outbound"?"packetsSent":"packetsReceived",y=P==="outbound"?"bytesSent":"bytesReceived",h=_.get(x(I)),Y=D(I,J),Z=h?D(h,J):void 0,N=D(I,y),q=h?D(h,y):void 0,R=I.timestamp!==void 0&&h?.timestamp!==void 0?I.timestamp-h.timestamp:void 0;return{bytesDelta:N!==void 0&&q!==void 0?N-q:void 0,currentPackets:Y,direction:P,id:x(I),packetDelta:Y!==void 0&&Z!==void 0?Y-Z:void 0,previousPackets:Z,timeDeltaMs:R}}),U=L.filter((I)=>I.direction==="inbound"),W=L.filter((I)=>I.direction==="outbound"),T=j(L.map((I)=>I.timeDeltaMs).filter((I)=>I!==void 0)),$=U.filter((I)=>g.maxInboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxInboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length,G=W.filter((I)=>g.maxOutboundPacketStallMs!==void 0&&I.timeDeltaMs!==void 0&&I.timeDeltaMs>=g.maxOutboundPacketStallMs&&I.packetDelta!==void 0&&I.packetDelta<=0).length;if(g.requireInboundAudio&&U.length===0)w(V,"error","media.webrtc_inbound_audio_missing","No inbound WebRTC audio RTP stream was observed.");if(g.requireOutboundAudio&&W.length===0)w(V,"error","media.webrtc_outbound_audio_missing","No outbound WebRTC audio RTP stream was observed.");if(g.maxGapMs!==void 0&&T!==void 0&&T>g.maxGapMs)w(V,"warning","media.webrtc_stream_gap",`Observed WebRTC stream sample gap ${String(T)}ms above ${String(g.maxGapMs)}ms.`);if($>0)w(V,"error","media.webrtc_inbound_stalled",`${String($)} inbound WebRTC audio stream(s) stopped receiving packets.`);if(G>0)w(V,"error","media.webrtc_outbound_stalled",`${String(G)} outbound WebRTC audio stream(s) stopped sending packets.`);return{checkedAt:Date.now(),inboundAudioStreams:U.length,issues:V,maxObservedGapMs:T,outboundAudioStreams:W.length,stalledInboundStreams:$,stalledOutboundStreams:G,status:V.some((I)=>I.severity==="error")?"fail":V.length>0?"warn":"pass",streams:L,totalStats:A.length}};var Z0="/api/voice/browser-media",w0=5000,Q0=async(g)=>g.peerConnection??await g.getPeerConnection?.()??null,P0=async(g,A)=>{let C=A.fetch??globalThis.fetch;if(!C)return;await C(A.path??Z0,{body:JSON.stringify(g),headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"})},o=(g)=>{let A=null,C=[],V=async()=>{let L=await Q0(g);if(!L)return;let U=await i({peerConnection:L}),W=v({...g,stats:U}),T=g.continuity===!1?void 0:n({...g.continuity,previousStats:C,stats:U}),$={at:Date.now(),continuity:T,report:W,scenarioId:g.getScenarioId?.()??null,sessionId:g.getSessionId?.()??null};return C=U,g.onReport?.($),await P0($,g),$},_=()=>{V().catch((L)=>{g.onError?.(L)})},c=()=>{if(A)clearInterval(A),A=null};return{close:c,reportOnce:V,stop:c,start:()=>{if(A)return;_(),A=setInterval(_,g.intervalMs??w0)}}};var K0=(g,A,C)=>Math.min(C,A*2**(Math.max(1,g)-1));var E=()=>{},E0=()=>E,N0={callControl:E,close:E,disconnect:E,endTurn:E,send:E,sendAudio:E,simulateDisconnect:E,subscribe:E0,getReadyState:()=>3,getScenarioId:()=>"",getSessionId:()=>"",start:()=>{}},q0=()=>crypto.randomUUID(),z0=(g,A,C,V)=>{let{hostname:_,port:c,protocol:L}=window.location,U=L==="https:"?"wss:":"ws:",W=c?`:${c}`:"",T=new URL(`${U}//${_}${W}${g}`);if(T.searchParams.set("sessionId",A),C)T.searchParams.set("scenarioId",C);for(let[$,G]of Object.entries(V))if($!=="sessionId"&&$!=="scenarioId"&&G)T.searchParams.set($,G);return T.toString()},b0=(g)=>{if(!g||typeof g!=="object"||!("type"in g))return!1;switch(g.type){case"audio":case"assistant":case"call_lifecycle":case"complete":case"connection":case"error":case"final":case"partial":case"pong":case"replay":case"session":case"turn":return!0;default:return!1}},j0=(g)=>{if(typeof g.data!=="string")return null;try{let A=JSON.parse(g.data);return b0(A)?A:null}catch{return null}},m=(g,A={})=>{if(typeof window>"u")return N0;let C=new Set,V=A.reconnect!==!1,_=A.maxReconnectAttempts??15,c=A.reconnectMaxDelayMs??8000,L=A.pingInterval??30000,U=A.reconnectResetAfterMs??30000,W=(H)=>K0(H,500,c),T={isConnected:!1,pendingMessages:[],scenarioId:A.scenarioId??null,pingInterval:null,reconnectAttempts:0,reconnectResetTimeout:null,reconnectTimeout:null,sessionId:A.sessionId??q0(),ws:null},$=(H)=>{C.forEach((X)=>X(H))},G=()=>{if(T.pingInterval)clearInterval(T.pingInterval),T.pingInterval=null;if(T.reconnectTimeout)clearTimeout(T.reconnectTimeout),T.reconnectTimeout=null;if(T.reconnectResetTimeout)clearTimeout(T.reconnectResetTimeout),T.reconnectResetTimeout=null},I=()=>{if(T.ws?.readyState!==1)return;while(T.pendingMessages.length>0){let H=T.pendingMessages.shift();if(H!==void 0)T.ws.send(H)}},P=()=>{T.reconnectAttempts+=1;let H=W(T.reconnectAttempts),X=Date.now()+H;$({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,nextAttemptAt:X,status:"reconnecting"},type:"connection"}),T.reconnectTimeout=setTimeout(()=>{if(T.reconnectAttempts>_){$({reconnect:{attempts:T.reconnectAttempts,maxAttempts:_,status:"exhausted"},type:"connection"});return}J()},H)},J=()=>{let H=new WebSocket(z0(g,T.sessionId,T.scenarioId,A.query??{}));H.binaryType="arraybuffer",H.onopen=()=>{if(T.ws!==H){H.close(1000);return}let X=T.reconnectAttempts>0;if(T.isConnected=!0,I(),X)$({reconnect:{attempts:T.reconnectAttempts,lastResumedAt:Date.now(),maxAttempts:_,status:"resumed"},type:"connection"}),T.reconnectResetTimeout=setTimeout(()=>{if(T.ws===H&&H.readyState===1)T.reconnectAttempts=0;T.reconnectResetTimeout=null},U);C.forEach((K)=>K({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,status:"active",type:"session"})),T.pingInterval=setInterval(()=>{if(H.readyState===1)H.send(JSON.stringify({type:"ping"}))},L)},H.onmessage=(X)=>{if(T.ws!==H)return;let K=j0(X);if(!K)return;if(K.type==="session")T.sessionId=K.sessionId,T.scenarioId=K.scenarioId??T.scenarioId;C.forEach((_0)=>_0(K))},H.onclose=(X)=>{if(T.ws!==H)return;if(T.ws=null,T.isConnected=!1,G(),V&&X.code!==1000&&T.reconnectAttempts<_)P();else if(V&&X.code!==1000)$({reconnect:{attempts:T.reconnectAttempts,lastDisconnectAt:Date.now(),maxAttempts:_,status:"exhausted"},type:"connection"})},T.ws=H},y=(H)=>{if(T.ws?.readyState===1){T.ws.send(H);return}T.pendingMessages.push(H)},h=(H)=>{y(JSON.stringify(H))},Y=(H={})=>{if(H.sessionId)T.sessionId=H.sessionId;if(H.scenarioId)T.scenarioId=H.scenarioId;h({scenarioId:T.scenarioId??void 0,sessionId:T.sessionId,type:"start"})},Z=(H)=>{y(H)},N=()=>{h({type:"end_turn"})},q=(H)=>{h({...H,type:"call_control"})},R=(H="client-close")=>{if(G(),T.ws){let X=T.ws;if(T.ws=null,X.readyState===1)X.send(JSON.stringify({reason:H,type:"close"}));X.close(1000)}T.isConnected=!1,C.clear()},S=()=>{if(G(),T.ws){let H=T.ws;T.ws=null,H.close(1000)}T.isConnected=!1,C.clear()},z=()=>{if(T.ws?.readyState===1)T.ws.close(4000,"absolutejs-voice-reconnect-proof")},T0=(H)=>{return C.add(H),()=>{C.delete(H)}};return J(),{callControl:q,close:R,disconnect:S,endTurn:N,send:h,sendAudio:Z,simulateDisconnect:z,start:Y,subscribe:T0,getReadyState:()=>T.ws?.readyState??3,getScenarioId:()=>T.scenarioId??"",getSessionId:()=>T.sessionId}};var B0=()=>({attempts:0,maxAttempts:0,status:"idle"}),M0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!C)return g;if(!g)return C;if(g===C||g.endsWith(C))return g;if(C.includes(g))return C;return`${g} ${C}`},x0=(g,A)=>{let C=A.trim().replace(/\s+/g," ");if(!g)return C;if(!C||g.endsWith(C))return g;return`${g} ${C}`},k0=()=>({assistantAudio:[],assistantStreamingText:"",assistantTexts:[],call:null,error:null,isConnected:!1,partial:"",reconnect:B0(),scenarioId:null,sessionId:null,sessionMetadata:null,status:"idle",turns:[]}),r=()=>{let g=k0(),A="",C=new Set,V=()=>{C.forEach((c)=>c())};return{dispatch:(c)=>{switch(c.type){case"audio":g={...g,assistantAudio:[...g.assistantAudio,{chunk:c.chunk,format:c.format,receivedAt:c.receivedAt,turnId:c.turnId}]};break;case"assistant":g={...g,assistantStreamingText:"",assistantTexts:[...g.assistantTexts,c.text]};break;case"assistant_delta":g={...g,assistantStreamingText:`${g.assistantStreamingText}${c.delta}`};break;case"complete":g={...g,sessionId:c.sessionId,status:"completed"};break;case"call_lifecycle":g={...g,call:{...g.call,disposition:c.event.type==="end"?c.event.disposition:g.call?.disposition,endedAt:c.event.type==="end"?c.event.at:g.call?.endedAt,events:[...g.call?.events??[],c.event],lastEventAt:c.event.at,startedAt:g.call?.startedAt??c.event.at},sessionId:c.sessionId};break;case"connected":g={...g,isConnected:!0,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect};break;case"connection":g={...g,reconnect:c.reconnect};break;case"disconnected":g={...g,isConnected:!1};break;case"error":g={...g,error:c.message};break;case"final":A=M0(A,c.transcript.text),g={...g,partial:A};break;case"partial":g={...g,partial:x0(A,c.transcript.text)};break;case"replay":A=c.partial,g={...g,assistantStreamingText:"",assistantTexts:[...c.assistantTexts],call:c.call??null,error:null,isConnected:c.status==="active",partial:c.partial,reconnect:g.reconnect.status==="reconnecting"?{...g.reconnect,lastResumedAt:Date.now(),nextAttemptAt:void 0,status:"resumed"}:g.reconnect,scenarioId:c.scenarioId??g.scenarioId,sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status,turns:[...c.turns]};break;case"session":g={...g,error:null,scenarioId:c.scenarioId??g.scenarioId,isConnected:c.status==="active",sessionId:c.sessionId,sessionMetadata:c.sessionMetadata??g.sessionMetadata,status:c.status};break;case"turn":A="",g={...g,partial:"",turns:[...g.turns,c.turn]};break}V()},getServerSnapshot:()=>g,getSnapshot:()=>g,subscribe:(c)=>{return C.add(c),()=>{C.delete(c)}}}};var u=(g,A={})=>{let C=m(g,A),V=r(),_=A.browserMedia&&typeof window<"u"?o({...A.browserMedia,getScenarioId:()=>A.browserMedia?A.browserMedia.getScenarioId?.()??C.getScenarioId():C.getScenarioId(),getSessionId:()=>A.browserMedia?A.browserMedia.getSessionId?.()??C.getSessionId():C.getSessionId()}):null,c=new Set,L=($)=>Promise.resolve().then(()=>{if(!$?.sessionId&&!$?.scenarioId)return;C.start($),_?.start()}),U=()=>{c.forEach(($)=>$())},W=()=>{if(!A.reconnectReportPath||typeof fetch>"u")return;let $=V.getSnapshot(),G=JSON.stringify({at:Date.now(),reconnect:$.reconnect,scenarioId:$.scenarioId,sessionId:C.getSessionId(),turnIds:$.turns.map((I)=>I.id)});fetch(A.reconnectReportPath,{body:G,headers:{"Content-Type":"application/json"},keepalive:!0,method:"POST"}).catch(()=>{})},T=C.subscribe(($)=>{let G=d($);if(G){if(V.dispatch(G),$.type==="connection")W();U()}});return{start:L,get assistantAudio(){return V.getSnapshot().assistantAudio},get assistantTexts(){return V.getSnapshot().assistantTexts},get assistantStreamingText(){return V.getSnapshot().assistantStreamingText},get call(){return V.getSnapshot().call},callControl($){C.callControl($)},close($){T(),_?.close(),C.close($),V.dispatch({type:"disconnected"}),U()},disconnect(){T(),_?.close(),C.disconnect(),V.dispatch({type:"disconnected"}),U()},endTurn(){C.endTurn()},get error(){return V.getSnapshot().error},getServerSnapshot(){return V.getServerSnapshot()},getSnapshot(){return V.getSnapshot()},get isConnected(){return V.getSnapshot().isConnected},get partial(){return V.getSnapshot().partial},get reconnect(){return V.getSnapshot().reconnect},get scenarioId(){return V.getSnapshot().scenarioId},sendAudio($){C.sendAudio($)},get sessionId(){return C.getSessionId()},get sessionMetadata(){return V.getSnapshot().sessionMetadata},simulateDisconnect(){C.simulateDisconnect()},get status(){return V.getSnapshot().status},subscribe($){return c.add($),()=>{c.delete($)}},get turns(){return V.getSnapshot().turns}}};var s=(g)=>{if(!g||g.enabled===!1)return;return{enabled:!0,maxGain:g.maxGain??3,noiseGateAttenuation:g.noiseGateAttenuation??0.15,noiseGateThreshold:g.noiseGateThreshold??0.006,targetLevel:g.targetLevel??0.08}};var F0={balanced:{qualityProfile:"general",minSilenceMs:400,silenceMs:1400,speechThreshold:0.012,transcriptStabilityMs:1000},fast:{qualityProfile:"general",minSilenceMs:300,silenceMs:700,speechThreshold:0.015,transcriptStabilityMs:450},"long-form":{qualityProfile:"general",minSilenceMs:600,silenceMs:2200,speechThreshold:0.01,transcriptStabilityMs:1500}},f0={"accent-heavy":{silenceMs:1200,speechThreshold:0.01,transcriptStabilityMs:1200},general:{},"noisy-room":{silenceMs:2000,speechThreshold:0.02,transcriptStabilityMs:1600},"short-command":{silenceMs:500,speechThreshold:0.016,transcriptStabilityMs:420}};var a=(g)=>{let A=g?.profile??"fast",C=g?.qualityProfile??"general",V=F0[A],_=f0[C],c=g?.silenceMs??_.silenceMs??V.silenceMs;return{profile:A,qualityProfile:C,minSilenceMs:Math.min(c,g?.minSilenceMs??_.minSilenceMs??V.minSilenceMs),silenceMs:c,speechThreshold:g?.speechThreshold??_.speechThreshold??V.speechThreshold,transcriptStabilityMs:g?.transcriptStabilityMs??_.transcriptStabilityMs??V.transcriptStabilityMs}};var d0={chat:{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"balanced",qualityProfile:"short-command"}},default:{capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:10,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"fast",qualityProfile:"general"}},dictation:{audioConditioning:{enabled:!0,maxGain:2.25,noiseGateAttenuation:0.05,noiseGateThreshold:0.003,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"guided-intake":{audioConditioning:{enabled:!0,maxGain:2.5,noiseGateAttenuation:0,noiseGateThreshold:0.004,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:12,pingInterval:30000,reconnect:!0},sttLifecycle:"turn-scoped",turnDetection:{profile:"long-form",qualityProfile:"accent-heavy"}},"noisy-room":{audioConditioning:{enabled:!0,maxGain:3,noiseGateAttenuation:0.12,noiseGateThreshold:0.006,targetLevel:0.085},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:2100,speechThreshold:0.02,transcriptStabilityMs:1650}},"pstn-balanced":{audioConditioning:{enabled:!0,maxGain:2.8,noiseGateAttenuation:0.07,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:660,speechThreshold:0.012,transcriptStabilityMs:300}},"pstn-fast":{audioConditioning:{enabled:!0,maxGain:2.75,noiseGateAttenuation:0.06,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room",silenceMs:620,speechThreshold:0.012,transcriptStabilityMs:280}},reliability:{audioConditioning:{enabled:!0,maxGain:2.9,noiseGateAttenuation:0.08,noiseGateThreshold:0.005,targetLevel:0.08},capture:{channelCount:1,sampleRateHz:16000},connection:{maxReconnectAttempts:14,pingInterval:45000,reconnect:!0},sttLifecycle:"continuous",turnDetection:{profile:"long-form",qualityProfile:"noisy-room"}}},p=(g="default")=>{let A=d0[g];return{audioConditioning:s(A.audioConditioning),capture:{channelCount:A.capture?.channelCount??1,sampleRateHz:A.capture?.sampleRateHz??16000},connection:{...A.connection},name:g,sttLifecycle:A.sttLifecycle??"continuous",turnDetection:a(A.turnDetection)}};var l0=(g)=>({assistantAudio:[...g.assistantAudio],assistantStreamingText:g.assistantStreamingText,assistantTexts:[...g.assistantTexts],call:g.call,error:g.error,isConnected:g.isConnected,isRecording:!1,partial:g.partial,reconnect:g.reconnect,recordingError:null,sessionId:g.sessionId,sessionMetadata:g.sessionMetadata,scenarioId:g.scenarioId,status:g.status,turns:[...g.turns]}),t=(g,A={})=>{let C=p(A.preset),V=u(g,{...C.connection,...A.connection}),_=null,c=l0(V),L=new Set,U=()=>{for(let y of L)y()},W=()=>{if(c={...c,assistantAudio:[...V.assistantAudio],assistantStreamingText:V.assistantStreamingText,assistantTexts:[...V.assistantTexts],call:V.call,error:V.error,isConnected:V.isConnected,partial:V.partial,reconnect:V.reconnect,sessionId:V.sessionId,sessionMetadata:V.sessionMetadata,scenarioId:V.scenarioId,status:V.status,turns:[...V.turns]},A.autoStopOnComplete!==!1&&c.status==="completed"&&c.isRecording)_?.stop(),_=null,c={...c,isRecording:!1};U()},T=V.subscribe(W);W();let $=()=>{if(_)return _;return _=f({channelCount:A.capture?.channelCount??C.capture.channelCount,onLevel:A.capture?.onLevel,onAudio:(y)=>{if(A.capture?.onAudio){A.capture.onAudio(y,V.sendAudio);return}V.sendAudio(y)},sampleRateHz:A.capture?.sampleRateHz??C.capture.sampleRateHz,...A.capture?.stream?{stream:A.capture.stream}:{}}),_},G=()=>{_?.stop(),_=null,c={...c,isRecording:!1},U()},I=async()=>{if(c.isRecording)return;try{c={...c,recordingError:null},U(),await $().start(),c={...c,isRecording:!0},U()}catch(y){throw _?.stop(),_=null,c={...c,isRecording:!1,recordingError:y instanceof Error?y.message:String(y)},U(),y}};return{close:(y)=>{T(),G(),V.close(y)},disconnect:()=>{T(),G(),V.disconnect()},startRecording:I,stopRecording:G,get assistantAudio(){return c.assistantAudio},get assistantTexts(){return c.assistantTexts},get assistantStreamingText(){return c.assistantStreamingText},bindHTMX(y){return F(V,y)},get call(){return c.call},callControl:(y)=>V.callControl(y),endTurn:()=>V.endTurn(),get error(){return c.error},getServerSnapshot:()=>c,getSnapshot:()=>c,get isConnected(){return c.isConnected},get isRecording(){return c.isRecording},get partial(){return c.partial},get reconnect(){return c.reconnect},get recordingError(){return c.recordingError},get scenarioId(){return c.scenarioId},sendAudio:(y)=>V.sendAudio(y),get sessionId(){return c.sessionId},get sessionMetadata(){return c.sessionMetadata},simulateDisconnect:()=>V.simulateDisconnect(),get status(){return c.status},subscribe:(y)=>{return L.add(y),()=>{L.delete(y)}},toggleRecording:async()=>{if(c.isRecording){G();return}await I()},get turns(){return c.turns}}};var Q=(g)=>String(g).replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");var e=(g)=>{if(!g.isConnected)return"idle";if(g.isPlaying)return"speaking";if(g.isRecording&&g.hasActivePartial)return"listening";if(g.isRecording)return"listening";if(g.lastTranscriptAt&&!g.lastAssistantAt)return"thinking";if(g.lastTranscriptAt&&g.lastAssistantAt&&g.lastTranscriptAt>g.lastAssistantAt)return"thinking";return"idle"};var v0={accent:"#3b82f6",background:"#0f172a",errorAccent:"#ef4444",fontFamily:'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',foreground:"#f8fafc",radius:16},i0={callEnded:"Call ended",connecting:"Connecting…",endCall:"End call",idle:"Idle",listening:"Listening",mute:"Mute",speaking:"Speaking",startCall:"Start call",thinking:"Thinking",unmute:"Unmute"},n0=(g,A)=>{switch(g){case"listening":return A.listening;case"speaking":return A.speaking;case"thinking":return A.thinking;case"idle":return A.idle}},g0=(g)=>{let A={...v0,...g.theme},C={...i0,...g.labels},V=g.state.assistantAudio.at(-1)?.receivedAt,_=g.state.turns.at(-1)?.committedAt,c=e({hasActivePartial:g.state.partial.length>0,isConnected:g.state.isConnected,isPlaying:!1,isRecording:g.state.isRecording,lastAssistantAt:V,lastTranscriptAt:_}),L=!g.state.isConnected&&g.state.status!=="idle"&&!g.state.error,U=g.state.error?"Error":L?C.connecting:g.state.status==="completed"?C.callEnded:n0(c,C);return{agentState:c,classes:{container:`absolute-voice-widget absolute-voice-widget--${c}`,dot:`absolute-voice-widget__dot${g.state.error?" absolute-voice-widget__dot--error":""}`},controls:{canEnd:g.state.isConnected,canMute:g.state.isRecording,canStart:!g.state.isRecording&&g.state.status!=="completed"},errorMessage:g.state.error??void 0,labels:C,partial:g.state.partial||void 0,statusLabel:U,theme:A,title:g.title??"Voice"}},o0=(g)=>typeof g==="number"?`${g}px`:g,A0=(g)=>{let A=g.theme,C=`background:${A.background};border-radius:${o0(A.radius)};color:${A.foreground};font-family:${A.fontFamily};min-width:240px;padding:20px 22px;`,V=`background:${g.errorMessage?A.errorAccent:g.agentState==="idle"?"rgba(148,163,184,0.6)":A.accent};border-radius:50%;height:10px;width:10px;`,_=[];if(g.controls.canStart)_.push(`<button type="button" data-action="start" style="background:${A.accent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.startCall)}</button>`);if(g.controls.canMute)_.push(`<button type="button" data-action="mute" style="background:transparent;border:1px solid rgba(255,255,255,0.18);border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.mute)}</button>`);if(g.controls.canEnd)_.push(`<button type="button" data-action="end" style="background:${A.errorAccent};border:none;border-radius:12px;color:${A.foreground};cursor:pointer;font-size:14px;font-weight:500;padding:10px 14px;">${Q(g.labels.endCall)}</button>`);return`<div role="region" aria-live="polite" data-agent-state="${g.agentState}" class="${Q(g.classes.container)}" style="${C}">
|
|
2
2
|
<div style="align-items:center;display:flex;gap:10px;margin-bottom:12px;">
|
|
3
|
-
<span aria-hidden="true" class="${
|
|
4
|
-
<strong style="font-size:15px;">${
|
|
5
|
-
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${
|
|
3
|
+
<span aria-hidden="true" class="${Q(g.classes.dot)}" style="${V}"></span>
|
|
4
|
+
<strong style="font-size:15px;">${Q(g.title)}</strong>
|
|
5
|
+
<span style="font-size:13px;margin-left:auto;opacity:0.7;">${Q(g.statusLabel)}</span>
|
|
6
6
|
</div>
|
|
7
|
-
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${
|
|
8
|
-
<div style="display:flex;gap:10px;">${
|
|
9
|
-
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${
|
|
10
|
-
</div>`};var
|
|
7
|
+
${g.partial?`<p style="font-size:13px;margin:8px 0 12px;opacity:0.85;word-break:break-word;">“${Q(g.partial)}”</p>`:""}
|
|
8
|
+
<div style="display:flex;gap:10px;">${_.join("")}</div>
|
|
9
|
+
${g.errorMessage?`<p style="color:${A.errorAccent};font-size:12px;margin-top:12px;">${Q(g.errorMessage)}</p>`:""}
|
|
10
|
+
</div>`};var m0=(g)=>{if(typeof g!=="string")return g;let A=document.querySelector(g);if(!A)throw Error(`AbsoluteVoice.mount: no element matches "${g}"`);return A},V0=(g,A={})=>{let C=m0(g),V=t(A.path??"/voice",A.controllerOptions),_=null,c=null,L=()=>{let W=g0({...A.labels!==void 0?{labels:A.labels}:{},state:{assistantAudio:V.assistantAudio,error:V.error,isConnected:V.isConnected,isRecording:V.isRecording,partial:V.partial,status:V.status,turns:V.turns},...A.theme!==void 0?{theme:A.theme}:{},...A.title!==void 0?{title:A.title}:{}});C.innerHTML=A0(W);for(let T of C.querySelectorAll("button[data-action]")){let{action:$}=T.dataset;T.addEventListener("click",()=>{if($==="start")V.startRecording();else if($==="mute")V.stopRecording();else if($==="end")V.close()})}if(V.error&&V.error!==_)_=V.error,A.onError?.(V.error);if(V.status!==c)c=V.status,A.onStatusChange?.(V.status)},U=V.subscribe(L);if(L(),A.autoStart)V.startRecording();return{controller:V,async end(){await V.close()},mute(){V.stopRecording()},async start(){await V.startRecording()},unmount(){U(),V.close(),C.innerHTML=""}}},C0="0.0.22-beta.516",c0={mount:V0,version:C0};if(typeof globalThis<"u")globalThis.AbsoluteVoice=c0;var u0=c0;})();
|
package/dist/index.js
CHANGED
|
@@ -39955,6 +39955,27 @@ var resolveSessionId3 = (runtime, ws) => {
|
|
|
39955
39955
|
return resolved;
|
|
39956
39956
|
};
|
|
39957
39957
|
var resolveSocketQuery = (ws) => ws.data && typeof ws.data === "object" && ("query" in ws.data) ? ws.data.query ?? {} : {};
|
|
39958
|
+
var resolveSocketHeaders = (ws) => {
|
|
39959
|
+
if (!ws.data || typeof ws.data !== "object")
|
|
39960
|
+
return new Headers;
|
|
39961
|
+
if ("request" in ws.data && ws.data.request instanceof Request) {
|
|
39962
|
+
return ws.data.request.headers;
|
|
39963
|
+
}
|
|
39964
|
+
if ("headers" in ws.data) {
|
|
39965
|
+
const { headers } = ws.data;
|
|
39966
|
+
if (headers instanceof Headers)
|
|
39967
|
+
return headers;
|
|
39968
|
+
if (headers && typeof headers === "object") {
|
|
39969
|
+
const resolved = new Headers;
|
|
39970
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
39971
|
+
if (typeof value === "string")
|
|
39972
|
+
resolved.set(key, value);
|
|
39973
|
+
}
|
|
39974
|
+
return resolved;
|
|
39975
|
+
}
|
|
39976
|
+
}
|
|
39977
|
+
return new Headers;
|
|
39978
|
+
};
|
|
39958
39979
|
var resolveMaybeFunction = async (value, input) => typeof value === "function" ? await value(input) : value;
|
|
39959
39980
|
var toAudioChunk = (raw) => {
|
|
39960
39981
|
if (raw instanceof ArrayBuffer) {
|
|
@@ -40120,6 +40141,7 @@ var voice = (config) => {
|
|
|
40120
40141
|
}
|
|
40121
40142
|
const monitorBindings = new Map;
|
|
40122
40143
|
const runtime = {
|
|
40144
|
+
activeSockets: new Map,
|
|
40123
40145
|
activeSessions: new Map,
|
|
40124
40146
|
pendingSessions: new Map,
|
|
40125
40147
|
logger: resolveLogger(config.logger),
|
|
@@ -40137,6 +40159,7 @@ var voice = (config) => {
|
|
|
40137
40159
|
try {
|
|
40138
40160
|
authorized = await config.authorizeConnection({
|
|
40139
40161
|
context: ws.data,
|
|
40162
|
+
headers: resolveSocketHeaders(ws),
|
|
40140
40163
|
path: config.path,
|
|
40141
40164
|
query: resolveSocketQuery(ws),
|
|
40142
40165
|
scenarioId,
|
|
@@ -40461,9 +40484,10 @@ var voice = (config) => {
|
|
|
40461
40484
|
return new Elysia70({ name: "absolutejs-voice" }).ws(config.path, {
|
|
40462
40485
|
close: async (ws, code, reason) => {
|
|
40463
40486
|
const socketState = runtime.socketSessions.get(ws);
|
|
40464
|
-
if (!socketState) {
|
|
40487
|
+
if (!socketState || runtime.activeSockets.get(socketState.sessionId) !== ws) {
|
|
40465
40488
|
return;
|
|
40466
40489
|
}
|
|
40490
|
+
runtime.activeSockets.delete(socketState.sessionId);
|
|
40467
40491
|
const session = runtime.activeSessions.get(socketState.sessionId);
|
|
40468
40492
|
runtime.activeSessions.delete(socketState.sessionId);
|
|
40469
40493
|
deregisterMonitorSession(socketState.sessionId, reason ?? `ws-close-${String(code)}`);
|
|
@@ -40493,6 +40517,7 @@ var voice = (config) => {
|
|
|
40493
40517
|
}
|
|
40494
40518
|
if (message.type === "close" && current) {
|
|
40495
40519
|
await current.close(message.reason);
|
|
40520
|
+
runtime.activeSockets.delete(sessionState.sessionId);
|
|
40496
40521
|
runtime.activeSessions.delete(sessionState.sessionId);
|
|
40497
40522
|
deregisterMonitorSession(sessionState.sessionId, message.reason);
|
|
40498
40523
|
}
|
|
@@ -40542,10 +40567,12 @@ var voice = (config) => {
|
|
|
40542
40567
|
const currentSession = runtime.activeSessions.get(sessionState.sessionId);
|
|
40543
40568
|
if (currentSession) {
|
|
40544
40569
|
await currentSession.close("session-switch");
|
|
40570
|
+
runtime.activeSockets.delete(sessionState.sessionId);
|
|
40545
40571
|
runtime.activeSessions.delete(sessionState.sessionId);
|
|
40546
40572
|
deregisterMonitorSession(sessionState.sessionId, "session-switch");
|
|
40547
40573
|
}
|
|
40548
40574
|
sessionState.sessionId = message.sessionId;
|
|
40575
|
+
runtime.activeSockets.set(message.sessionId, ws);
|
|
40549
40576
|
runtime.socketSessions.set(ws, {
|
|
40550
40577
|
...sessionState,
|
|
40551
40578
|
sessionId: message.sessionId,
|
|
@@ -40574,8 +40601,14 @@ var voice = (config) => {
|
|
|
40574
40601
|
return;
|
|
40575
40602
|
}
|
|
40576
40603
|
const existing = runtime.activeSessions.get(sessionState.sessionId);
|
|
40604
|
+
runtime.activeSockets.set(sessionState.sessionId, ws);
|
|
40577
40605
|
if (existing) {
|
|
40578
|
-
await existing.
|
|
40606
|
+
await existing.disconnect({
|
|
40607
|
+
code: 1006,
|
|
40608
|
+
reason: "superseded",
|
|
40609
|
+
recoverable: true,
|
|
40610
|
+
type: "close"
|
|
40611
|
+
});
|
|
40579
40612
|
runtime.activeSessions.delete(sessionState.sessionId);
|
|
40580
40613
|
deregisterMonitorSession(sessionState.sessionId, "superseded");
|
|
40581
40614
|
}
|
package/dist/react/index.js
CHANGED
|
@@ -11435,6 +11435,7 @@ var WS_CLOSED = 3;
|
|
|
11435
11435
|
var WS_NORMAL_CLOSURE = 1000;
|
|
11436
11436
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
11437
11437
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
11438
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
11438
11439
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
11439
11440
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
11440
11441
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -11514,6 +11515,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11514
11515
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
11515
11516
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
11516
11517
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
11518
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
11517
11519
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
11518
11520
|
const state = {
|
|
11519
11521
|
isConnected: false,
|
|
@@ -11521,6 +11523,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11521
11523
|
scenarioId: options.scenarioId ?? null,
|
|
11522
11524
|
pingInterval: null,
|
|
11523
11525
|
reconnectAttempts: 0,
|
|
11526
|
+
reconnectResetTimeout: null,
|
|
11524
11527
|
reconnectTimeout: null,
|
|
11525
11528
|
sessionId: options.sessionId ?? createSessionId(),
|
|
11526
11529
|
ws: null
|
|
@@ -11537,6 +11540,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11537
11540
|
clearTimeout(state.reconnectTimeout);
|
|
11538
11541
|
state.reconnectTimeout = null;
|
|
11539
11542
|
}
|
|
11543
|
+
if (state.reconnectResetTimeout) {
|
|
11544
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
11545
|
+
state.reconnectResetTimeout = null;
|
|
11546
|
+
}
|
|
11540
11547
|
};
|
|
11541
11548
|
const flushPendingMessages = () => {
|
|
11542
11549
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -11582,6 +11589,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11582
11589
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11583
11590
|
ws.binaryType = "arraybuffer";
|
|
11584
11591
|
ws.onopen = () => {
|
|
11592
|
+
if (state.ws !== ws) {
|
|
11593
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
11594
|
+
return;
|
|
11595
|
+
}
|
|
11585
11596
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
11586
11597
|
state.isConnected = true;
|
|
11587
11598
|
flushPendingMessages();
|
|
@@ -11595,7 +11606,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11595
11606
|
},
|
|
11596
11607
|
type: "connection"
|
|
11597
11608
|
});
|
|
11598
|
-
state.
|
|
11609
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
11610
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
11611
|
+
state.reconnectAttempts = 0;
|
|
11612
|
+
}
|
|
11613
|
+
state.reconnectResetTimeout = null;
|
|
11614
|
+
}, reconnectResetAfterMs);
|
|
11599
11615
|
}
|
|
11600
11616
|
listeners.forEach((listener) => listener({
|
|
11601
11617
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -11610,6 +11626,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11610
11626
|
}, pingInterval);
|
|
11611
11627
|
};
|
|
11612
11628
|
ws.onmessage = (event) => {
|
|
11629
|
+
if (state.ws !== ws)
|
|
11630
|
+
return;
|
|
11613
11631
|
const parsed = parseServerMessage(event);
|
|
11614
11632
|
if (!parsed) {
|
|
11615
11633
|
return;
|
|
@@ -11621,6 +11639,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11621
11639
|
listeners.forEach((listener) => listener(parsed));
|
|
11622
11640
|
};
|
|
11623
11641
|
ws.onclose = (event) => {
|
|
11642
|
+
if (state.ws !== ws)
|
|
11643
|
+
return;
|
|
11644
|
+
state.ws = null;
|
|
11624
11645
|
state.isConnected = false;
|
|
11625
11646
|
clearTimers();
|
|
11626
11647
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -11678,11 +11699,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11678
11699
|
const close = (reason = "client-close") => {
|
|
11679
11700
|
clearTimers();
|
|
11680
11701
|
if (state.ws) {
|
|
11681
|
-
|
|
11682
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
11683
|
-
}
|
|
11684
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
11702
|
+
const ws = state.ws;
|
|
11685
11703
|
state.ws = null;
|
|
11704
|
+
if (ws.readyState === WS_OPEN) {
|
|
11705
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
11706
|
+
}
|
|
11707
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
11686
11708
|
}
|
|
11687
11709
|
state.isConnected = false;
|
|
11688
11710
|
listeners.clear();
|
|
@@ -11690,8 +11712,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11690
11712
|
const disconnect = () => {
|
|
11691
11713
|
clearTimers();
|
|
11692
11714
|
if (state.ws) {
|
|
11693
|
-
state.ws
|
|
11715
|
+
const ws = state.ws;
|
|
11694
11716
|
state.ws = null;
|
|
11717
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
11695
11718
|
}
|
|
11696
11719
|
state.isConnected = false;
|
|
11697
11720
|
listeners.clear();
|
package/dist/svelte/index.js
CHANGED
|
@@ -763,6 +763,7 @@ var WS_CLOSED = 3;
|
|
|
763
763
|
var WS_NORMAL_CLOSURE = 1000;
|
|
764
764
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
765
765
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
766
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
766
767
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
767
768
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
768
769
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -842,6 +843,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
842
843
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
843
844
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
844
845
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
846
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
845
847
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
846
848
|
const state = {
|
|
847
849
|
isConnected: false,
|
|
@@ -849,6 +851,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
849
851
|
scenarioId: options.scenarioId ?? null,
|
|
850
852
|
pingInterval: null,
|
|
851
853
|
reconnectAttempts: 0,
|
|
854
|
+
reconnectResetTimeout: null,
|
|
852
855
|
reconnectTimeout: null,
|
|
853
856
|
sessionId: options.sessionId ?? createSessionId(),
|
|
854
857
|
ws: null
|
|
@@ -865,6 +868,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
865
868
|
clearTimeout(state.reconnectTimeout);
|
|
866
869
|
state.reconnectTimeout = null;
|
|
867
870
|
}
|
|
871
|
+
if (state.reconnectResetTimeout) {
|
|
872
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
873
|
+
state.reconnectResetTimeout = null;
|
|
874
|
+
}
|
|
868
875
|
};
|
|
869
876
|
const flushPendingMessages = () => {
|
|
870
877
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -910,6 +917,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
910
917
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
911
918
|
ws.binaryType = "arraybuffer";
|
|
912
919
|
ws.onopen = () => {
|
|
920
|
+
if (state.ws !== ws) {
|
|
921
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
913
924
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
914
925
|
state.isConnected = true;
|
|
915
926
|
flushPendingMessages();
|
|
@@ -923,7 +934,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
923
934
|
},
|
|
924
935
|
type: "connection"
|
|
925
936
|
});
|
|
926
|
-
state.
|
|
937
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
938
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
939
|
+
state.reconnectAttempts = 0;
|
|
940
|
+
}
|
|
941
|
+
state.reconnectResetTimeout = null;
|
|
942
|
+
}, reconnectResetAfterMs);
|
|
927
943
|
}
|
|
928
944
|
listeners.forEach((listener) => listener({
|
|
929
945
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -938,6 +954,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
938
954
|
}, pingInterval);
|
|
939
955
|
};
|
|
940
956
|
ws.onmessage = (event) => {
|
|
957
|
+
if (state.ws !== ws)
|
|
958
|
+
return;
|
|
941
959
|
const parsed = parseServerMessage(event);
|
|
942
960
|
if (!parsed) {
|
|
943
961
|
return;
|
|
@@ -949,6 +967,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
949
967
|
listeners.forEach((listener) => listener(parsed));
|
|
950
968
|
};
|
|
951
969
|
ws.onclose = (event) => {
|
|
970
|
+
if (state.ws !== ws)
|
|
971
|
+
return;
|
|
972
|
+
state.ws = null;
|
|
952
973
|
state.isConnected = false;
|
|
953
974
|
clearTimers();
|
|
954
975
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -1006,11 +1027,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1006
1027
|
const close = (reason = "client-close") => {
|
|
1007
1028
|
clearTimers();
|
|
1008
1029
|
if (state.ws) {
|
|
1009
|
-
|
|
1010
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
1011
|
-
}
|
|
1012
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
1030
|
+
const ws = state.ws;
|
|
1013
1031
|
state.ws = null;
|
|
1032
|
+
if (ws.readyState === WS_OPEN) {
|
|
1033
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
1034
|
+
}
|
|
1035
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
1014
1036
|
}
|
|
1015
1037
|
state.isConnected = false;
|
|
1016
1038
|
listeners.clear();
|
|
@@ -1018,8 +1040,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1018
1040
|
const disconnect = () => {
|
|
1019
1041
|
clearTimers();
|
|
1020
1042
|
if (state.ws) {
|
|
1021
|
-
state.ws
|
|
1043
|
+
const ws = state.ws;
|
|
1022
1044
|
state.ws = null;
|
|
1045
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
1023
1046
|
}
|
|
1024
1047
|
state.isConnected = false;
|
|
1025
1048
|
listeners.clear();
|
package/dist/testing/index.js
CHANGED
|
@@ -2984,6 +2984,7 @@ var WS_CLOSED = 3;
|
|
|
2984
2984
|
var WS_NORMAL_CLOSURE = 1000;
|
|
2985
2985
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
2986
2986
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
2987
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
2987
2988
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
2988
2989
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
2989
2990
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -3063,6 +3064,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3063
3064
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
3064
3065
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
3065
3066
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
3067
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
3066
3068
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
3067
3069
|
const state = {
|
|
3068
3070
|
isConnected: false,
|
|
@@ -3070,6 +3072,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3070
3072
|
scenarioId: options.scenarioId ?? null,
|
|
3071
3073
|
pingInterval: null,
|
|
3072
3074
|
reconnectAttempts: 0,
|
|
3075
|
+
reconnectResetTimeout: null,
|
|
3073
3076
|
reconnectTimeout: null,
|
|
3074
3077
|
sessionId: options.sessionId ?? createSessionId(),
|
|
3075
3078
|
ws: null
|
|
@@ -3086,6 +3089,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3086
3089
|
clearTimeout(state.reconnectTimeout);
|
|
3087
3090
|
state.reconnectTimeout = null;
|
|
3088
3091
|
}
|
|
3092
|
+
if (state.reconnectResetTimeout) {
|
|
3093
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
3094
|
+
state.reconnectResetTimeout = null;
|
|
3095
|
+
}
|
|
3089
3096
|
};
|
|
3090
3097
|
const flushPendingMessages = () => {
|
|
3091
3098
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -3131,6 +3138,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3131
3138
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
3132
3139
|
ws.binaryType = "arraybuffer";
|
|
3133
3140
|
ws.onopen = () => {
|
|
3141
|
+
if (state.ws !== ws) {
|
|
3142
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3134
3145
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
3135
3146
|
state.isConnected = true;
|
|
3136
3147
|
flushPendingMessages();
|
|
@@ -3144,7 +3155,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3144
3155
|
},
|
|
3145
3156
|
type: "connection"
|
|
3146
3157
|
});
|
|
3147
|
-
state.
|
|
3158
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
3159
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
3160
|
+
state.reconnectAttempts = 0;
|
|
3161
|
+
}
|
|
3162
|
+
state.reconnectResetTimeout = null;
|
|
3163
|
+
}, reconnectResetAfterMs);
|
|
3148
3164
|
}
|
|
3149
3165
|
listeners.forEach((listener) => listener({
|
|
3150
3166
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -3159,6 +3175,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3159
3175
|
}, pingInterval);
|
|
3160
3176
|
};
|
|
3161
3177
|
ws.onmessage = (event) => {
|
|
3178
|
+
if (state.ws !== ws)
|
|
3179
|
+
return;
|
|
3162
3180
|
const parsed = parseServerMessage(event);
|
|
3163
3181
|
if (!parsed) {
|
|
3164
3182
|
return;
|
|
@@ -3170,6 +3188,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3170
3188
|
listeners.forEach((listener) => listener(parsed));
|
|
3171
3189
|
};
|
|
3172
3190
|
ws.onclose = (event) => {
|
|
3191
|
+
if (state.ws !== ws)
|
|
3192
|
+
return;
|
|
3193
|
+
state.ws = null;
|
|
3173
3194
|
state.isConnected = false;
|
|
3174
3195
|
clearTimers();
|
|
3175
3196
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -3227,11 +3248,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3227
3248
|
const close = (reason = "client-close") => {
|
|
3228
3249
|
clearTimers();
|
|
3229
3250
|
if (state.ws) {
|
|
3230
|
-
|
|
3231
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
3232
|
-
}
|
|
3233
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
3251
|
+
const ws = state.ws;
|
|
3234
3252
|
state.ws = null;
|
|
3253
|
+
if (ws.readyState === WS_OPEN) {
|
|
3254
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
3255
|
+
}
|
|
3256
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
3235
3257
|
}
|
|
3236
3258
|
state.isConnected = false;
|
|
3237
3259
|
listeners.clear();
|
|
@@ -3239,8 +3261,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3239
3261
|
const disconnect = () => {
|
|
3240
3262
|
clearTimers();
|
|
3241
3263
|
if (state.ws) {
|
|
3242
|
-
state.ws
|
|
3264
|
+
const ws = state.ws;
|
|
3243
3265
|
state.ws = null;
|
|
3266
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
3244
3267
|
}
|
|
3245
3268
|
state.isConnected = false;
|
|
3246
3269
|
listeners.clear();
|
package/dist/vue/index.js
CHANGED
|
@@ -10831,6 +10831,7 @@ var WS_CLOSED = 3;
|
|
|
10831
10831
|
var WS_NORMAL_CLOSURE = 1000;
|
|
10832
10832
|
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 15;
|
|
10833
10833
|
var DEFAULT_PING_INTERVAL = 30000;
|
|
10834
|
+
var DEFAULT_RECONNECT_RESET_AFTER_MS = 30000;
|
|
10834
10835
|
var RECONNECT_BASE_DELAY_MS = 500;
|
|
10835
10836
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
10836
10837
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
@@ -10910,6 +10911,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10910
10911
|
const maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
|
|
10911
10912
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
10912
10913
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
10914
|
+
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
10913
10915
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
10914
10916
|
const state = {
|
|
10915
10917
|
isConnected: false,
|
|
@@ -10917,6 +10919,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10917
10919
|
scenarioId: options.scenarioId ?? null,
|
|
10918
10920
|
pingInterval: null,
|
|
10919
10921
|
reconnectAttempts: 0,
|
|
10922
|
+
reconnectResetTimeout: null,
|
|
10920
10923
|
reconnectTimeout: null,
|
|
10921
10924
|
sessionId: options.sessionId ?? createSessionId(),
|
|
10922
10925
|
ws: null
|
|
@@ -10933,6 +10936,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10933
10936
|
clearTimeout(state.reconnectTimeout);
|
|
10934
10937
|
state.reconnectTimeout = null;
|
|
10935
10938
|
}
|
|
10939
|
+
if (state.reconnectResetTimeout) {
|
|
10940
|
+
clearTimeout(state.reconnectResetTimeout);
|
|
10941
|
+
state.reconnectResetTimeout = null;
|
|
10942
|
+
}
|
|
10936
10943
|
};
|
|
10937
10944
|
const flushPendingMessages = () => {
|
|
10938
10945
|
if (state.ws?.readyState !== WS_OPEN) {
|
|
@@ -10978,6 +10985,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10978
10985
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
10979
10986
|
ws.binaryType = "arraybuffer";
|
|
10980
10987
|
ws.onopen = () => {
|
|
10988
|
+
if (state.ws !== ws) {
|
|
10989
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
10990
|
+
return;
|
|
10991
|
+
}
|
|
10981
10992
|
const wasReconnecting = state.reconnectAttempts > 0;
|
|
10982
10993
|
state.isConnected = true;
|
|
10983
10994
|
flushPendingMessages();
|
|
@@ -10991,7 +11002,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10991
11002
|
},
|
|
10992
11003
|
type: "connection"
|
|
10993
11004
|
});
|
|
10994
|
-
state.
|
|
11005
|
+
state.reconnectResetTimeout = setTimeout(() => {
|
|
11006
|
+
if (state.ws === ws && ws.readyState === WS_OPEN) {
|
|
11007
|
+
state.reconnectAttempts = 0;
|
|
11008
|
+
}
|
|
11009
|
+
state.reconnectResetTimeout = null;
|
|
11010
|
+
}, reconnectResetAfterMs);
|
|
10995
11011
|
}
|
|
10996
11012
|
listeners.forEach((listener) => listener({
|
|
10997
11013
|
scenarioId: state.scenarioId ?? undefined,
|
|
@@ -11006,6 +11022,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11006
11022
|
}, pingInterval);
|
|
11007
11023
|
};
|
|
11008
11024
|
ws.onmessage = (event) => {
|
|
11025
|
+
if (state.ws !== ws)
|
|
11026
|
+
return;
|
|
11009
11027
|
const parsed = parseServerMessage(event);
|
|
11010
11028
|
if (!parsed) {
|
|
11011
11029
|
return;
|
|
@@ -11017,6 +11035,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11017
11035
|
listeners.forEach((listener) => listener(parsed));
|
|
11018
11036
|
};
|
|
11019
11037
|
ws.onclose = (event) => {
|
|
11038
|
+
if (state.ws !== ws)
|
|
11039
|
+
return;
|
|
11040
|
+
state.ws = null;
|
|
11020
11041
|
state.isConnected = false;
|
|
11021
11042
|
clearTimers();
|
|
11022
11043
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && state.reconnectAttempts < maxReconnectAttempts;
|
|
@@ -11074,11 +11095,12 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11074
11095
|
const close = (reason = "client-close") => {
|
|
11075
11096
|
clearTimers();
|
|
11076
11097
|
if (state.ws) {
|
|
11077
|
-
|
|
11078
|
-
state.ws.send(JSON.stringify({ reason, type: "close" }));
|
|
11079
|
-
}
|
|
11080
|
-
state.ws.close(WS_NORMAL_CLOSURE);
|
|
11098
|
+
const ws = state.ws;
|
|
11081
11099
|
state.ws = null;
|
|
11100
|
+
if (ws.readyState === WS_OPEN) {
|
|
11101
|
+
ws.send(JSON.stringify({ reason, type: "close" }));
|
|
11102
|
+
}
|
|
11103
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
11082
11104
|
}
|
|
11083
11105
|
state.isConnected = false;
|
|
11084
11106
|
listeners.clear();
|
|
@@ -11086,8 +11108,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11086
11108
|
const disconnect = () => {
|
|
11087
11109
|
clearTimers();
|
|
11088
11110
|
if (state.ws) {
|
|
11089
|
-
state.ws
|
|
11111
|
+
const ws = state.ws;
|
|
11090
11112
|
state.ws = null;
|
|
11113
|
+
ws.close(WS_NORMAL_CLOSURE);
|
|
11091
11114
|
}
|
|
11092
11115
|
state.isConnected = false;
|
|
11093
11116
|
listeners.clear();
|