@absolutejs/voice 0.0.22-beta.654 → 0.0.22-beta.656
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 +67 -28
- package/dist/client/connection.d.ts +3 -0
- package/dist/client/htmxBootstrap.js +67 -28
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +69 -29
- package/dist/embed/index.js +67 -28
- package/dist/embed/voice-widget.js +8 -8
- package/dist/react/index.js +67 -28
- package/dist/svelte/index.js +67 -28
- package/dist/testing/index.js +67 -28
- package/dist/vue/index.js +67 -28
- package/package.json +1 -1
package/dist/svelte/index.js
CHANGED
|
@@ -775,6 +775,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
775
775
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
776
776
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
777
777
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
778
|
+
|
|
779
|
+
class VoiceReconnectRejectedError extends Error {
|
|
780
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
781
|
+
super(message);
|
|
782
|
+
this.name = "VoiceReconnectRejectedError";
|
|
783
|
+
}
|
|
784
|
+
}
|
|
778
785
|
var noop = () => {};
|
|
779
786
|
var noopAsync = () => Promise.resolve();
|
|
780
787
|
var noopUnsubscribe = () => noop;
|
|
@@ -854,6 +861,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
854
861
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
855
862
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
856
863
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
864
|
+
let lifecycleGeneration = 0;
|
|
865
|
+
let disposed = false;
|
|
857
866
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
858
867
|
const state = {
|
|
859
868
|
isConnected: false,
|
|
@@ -869,6 +878,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
869
878
|
const emitConnection = (reconnect) => {
|
|
870
879
|
listeners.forEach((listener) => listener(reconnect));
|
|
871
880
|
};
|
|
881
|
+
const emitTerminalFailure = (message) => {
|
|
882
|
+
listeners.forEach((listener) => listener({
|
|
883
|
+
message,
|
|
884
|
+
recoverable: false,
|
|
885
|
+
type: "error"
|
|
886
|
+
}));
|
|
887
|
+
emitConnection({
|
|
888
|
+
reconnect: {
|
|
889
|
+
attempts: state.reconnectAttempts,
|
|
890
|
+
maxAttempts: maxReconnectAttempts,
|
|
891
|
+
status: "exhausted"
|
|
892
|
+
},
|
|
893
|
+
type: "connection"
|
|
894
|
+
});
|
|
895
|
+
};
|
|
872
896
|
const clearTimers = () => {
|
|
873
897
|
if (state.pingInterval) {
|
|
874
898
|
clearInterval(state.pingInterval);
|
|
@@ -902,6 +926,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
902
926
|
pendingCallControls.clear();
|
|
903
927
|
};
|
|
904
928
|
const scheduleReconnect = () => {
|
|
929
|
+
if (disposed)
|
|
930
|
+
return;
|
|
931
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
905
932
|
state.reconnectAttempts += 1;
|
|
906
933
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
907
934
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -916,15 +943,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
916
943
|
type: "connection"
|
|
917
944
|
});
|
|
918
945
|
state.reconnectTimeout = setTimeout(async () => {
|
|
946
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
947
|
+
return;
|
|
919
948
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
920
|
-
|
|
921
|
-
reconnect: {
|
|
922
|
-
attempts: state.reconnectAttempts,
|
|
923
|
-
maxAttempts: maxReconnectAttempts,
|
|
924
|
-
status: "exhausted"
|
|
925
|
-
},
|
|
926
|
-
type: "connection"
|
|
927
|
-
});
|
|
949
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
928
950
|
return;
|
|
929
951
|
}
|
|
930
952
|
try {
|
|
@@ -934,25 +956,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
934
956
|
scenarioId: state.scenarioId,
|
|
935
957
|
sessionId: state.sessionId
|
|
936
958
|
});
|
|
937
|
-
} catch {
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
961
|
+
return;
|
|
962
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
963
|
+
emitTerminalFailure(error.message);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
938
966
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
939
967
|
scheduleReconnect();
|
|
940
968
|
} else {
|
|
941
|
-
|
|
942
|
-
reconnect: {
|
|
943
|
-
attempts: state.reconnectAttempts,
|
|
944
|
-
maxAttempts: maxReconnectAttempts,
|
|
945
|
-
status: "exhausted"
|
|
946
|
-
},
|
|
947
|
-
type: "connection"
|
|
948
|
-
});
|
|
969
|
+
emitTerminalFailure("Voice authorization could not be renewed for reconnect.");
|
|
949
970
|
}
|
|
950
971
|
return;
|
|
951
972
|
}
|
|
973
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
974
|
+
return;
|
|
952
975
|
connect();
|
|
953
976
|
}, delayMs);
|
|
954
977
|
};
|
|
955
978
|
const connect = () => {
|
|
979
|
+
if (disposed)
|
|
980
|
+
return;
|
|
956
981
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
957
982
|
ws.binaryType = "arraybuffer";
|
|
958
983
|
ws.onopen = () => {
|
|
@@ -1023,6 +1048,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1023
1048
|
state.ws = null;
|
|
1024
1049
|
state.isConnected = false;
|
|
1025
1050
|
clearTimers();
|
|
1051
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
1026
1052
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
1027
1053
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
1028
1054
|
if (authorizationFailure) {
|
|
@@ -1035,15 +1061,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1035
1061
|
if (reconnectable) {
|
|
1036
1062
|
scheduleReconnect();
|
|
1037
1063
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1064
|
+
if (authorizationFailure) {
|
|
1065
|
+
emitConnection({
|
|
1066
|
+
reconnect: {
|
|
1067
|
+
attempts: state.reconnectAttempts,
|
|
1068
|
+
lastDisconnectAt: Date.now(),
|
|
1069
|
+
maxAttempts: maxReconnectAttempts,
|
|
1070
|
+
status: "exhausted"
|
|
1071
|
+
},
|
|
1072
|
+
type: "connection"
|
|
1073
|
+
});
|
|
1074
|
+
} else {
|
|
1075
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
1076
|
+
}
|
|
1047
1077
|
}
|
|
1048
1078
|
};
|
|
1049
1079
|
state.ws = ws;
|
|
@@ -1084,6 +1114,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1084
1114
|
send({ type: "end_turn" });
|
|
1085
1115
|
};
|
|
1086
1116
|
const callControl = (message) => {
|
|
1117
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
1118
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
1119
|
+
}
|
|
1087
1120
|
const requestId = crypto.randomUUID();
|
|
1088
1121
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
1089
1122
|
const timeout = setTimeout(() => {
|
|
@@ -1091,16 +1124,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1091
1124
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
1092
1125
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
1093
1126
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
1094
|
-
send({
|
|
1127
|
+
state.ws.send(JSON.stringify({
|
|
1095
1128
|
...message,
|
|
1096
1129
|
requestId,
|
|
1097
1130
|
type: "call_control"
|
|
1098
|
-
});
|
|
1131
|
+
}));
|
|
1099
1132
|
return promise;
|
|
1100
1133
|
};
|
|
1101
1134
|
const close = (reason = "client-close") => {
|
|
1135
|
+
disposed = true;
|
|
1136
|
+
lifecycleGeneration += 1;
|
|
1102
1137
|
clearTimers();
|
|
1103
1138
|
rejectPendingCallControls("Voice connection closed");
|
|
1139
|
+
state.pendingMessages.length = 0;
|
|
1104
1140
|
if (state.ws) {
|
|
1105
1141
|
const ws = state.ws;
|
|
1106
1142
|
state.ws = null;
|
|
@@ -1113,8 +1149,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
1113
1149
|
listeners.clear();
|
|
1114
1150
|
};
|
|
1115
1151
|
const disconnect = () => {
|
|
1152
|
+
disposed = true;
|
|
1153
|
+
lifecycleGeneration += 1;
|
|
1116
1154
|
clearTimers();
|
|
1117
1155
|
rejectPendingCallControls("Voice connection disconnected");
|
|
1156
|
+
state.pendingMessages.length = 0;
|
|
1118
1157
|
if (state.ws) {
|
|
1119
1158
|
const ws = state.ws;
|
|
1120
1159
|
state.ws = null;
|
package/dist/testing/index.js
CHANGED
|
@@ -2996,6 +2996,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
2996
2996
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
2997
2997
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
2998
2998
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
2999
|
+
|
|
3000
|
+
class VoiceReconnectRejectedError extends Error {
|
|
3001
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
3002
|
+
super(message);
|
|
3003
|
+
this.name = "VoiceReconnectRejectedError";
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
2999
3006
|
var noop = () => {};
|
|
3000
3007
|
var noopAsync = () => Promise.resolve();
|
|
3001
3008
|
var noopUnsubscribe = () => noop;
|
|
@@ -3075,6 +3082,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3075
3082
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
3076
3083
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
3077
3084
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
3085
|
+
let lifecycleGeneration = 0;
|
|
3086
|
+
let disposed = false;
|
|
3078
3087
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
3079
3088
|
const state = {
|
|
3080
3089
|
isConnected: false,
|
|
@@ -3090,6 +3099,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3090
3099
|
const emitConnection = (reconnect) => {
|
|
3091
3100
|
listeners.forEach((listener) => listener(reconnect));
|
|
3092
3101
|
};
|
|
3102
|
+
const emitTerminalFailure = (message) => {
|
|
3103
|
+
listeners.forEach((listener) => listener({
|
|
3104
|
+
message,
|
|
3105
|
+
recoverable: false,
|
|
3106
|
+
type: "error"
|
|
3107
|
+
}));
|
|
3108
|
+
emitConnection({
|
|
3109
|
+
reconnect: {
|
|
3110
|
+
attempts: state.reconnectAttempts,
|
|
3111
|
+
maxAttempts: maxReconnectAttempts,
|
|
3112
|
+
status: "exhausted"
|
|
3113
|
+
},
|
|
3114
|
+
type: "connection"
|
|
3115
|
+
});
|
|
3116
|
+
};
|
|
3093
3117
|
const clearTimers = () => {
|
|
3094
3118
|
if (state.pingInterval) {
|
|
3095
3119
|
clearInterval(state.pingInterval);
|
|
@@ -3123,6 +3147,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3123
3147
|
pendingCallControls.clear();
|
|
3124
3148
|
};
|
|
3125
3149
|
const scheduleReconnect = () => {
|
|
3150
|
+
if (disposed)
|
|
3151
|
+
return;
|
|
3152
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
3126
3153
|
state.reconnectAttempts += 1;
|
|
3127
3154
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
3128
3155
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -3137,15 +3164,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3137
3164
|
type: "connection"
|
|
3138
3165
|
});
|
|
3139
3166
|
state.reconnectTimeout = setTimeout(async () => {
|
|
3167
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3168
|
+
return;
|
|
3140
3169
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
3141
|
-
|
|
3142
|
-
reconnect: {
|
|
3143
|
-
attempts: state.reconnectAttempts,
|
|
3144
|
-
maxAttempts: maxReconnectAttempts,
|
|
3145
|
-
status: "exhausted"
|
|
3146
|
-
},
|
|
3147
|
-
type: "connection"
|
|
3148
|
-
});
|
|
3170
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
3149
3171
|
return;
|
|
3150
3172
|
}
|
|
3151
3173
|
try {
|
|
@@ -3155,25 +3177,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3155
3177
|
scenarioId: state.scenarioId,
|
|
3156
3178
|
sessionId: state.sessionId
|
|
3157
3179
|
});
|
|
3158
|
-
} catch {
|
|
3180
|
+
} catch (error) {
|
|
3181
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3182
|
+
return;
|
|
3183
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
3184
|
+
emitTerminalFailure(error.message);
|
|
3185
|
+
return;
|
|
3186
|
+
}
|
|
3159
3187
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
3160
3188
|
scheduleReconnect();
|
|
3161
3189
|
} else {
|
|
3162
|
-
|
|
3163
|
-
reconnect: {
|
|
3164
|
-
attempts: state.reconnectAttempts,
|
|
3165
|
-
maxAttempts: maxReconnectAttempts,
|
|
3166
|
-
status: "exhausted"
|
|
3167
|
-
},
|
|
3168
|
-
type: "connection"
|
|
3169
|
-
});
|
|
3190
|
+
emitTerminalFailure("Voice authorization could not be renewed for reconnect.");
|
|
3170
3191
|
}
|
|
3171
3192
|
return;
|
|
3172
3193
|
}
|
|
3194
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
3195
|
+
return;
|
|
3173
3196
|
connect();
|
|
3174
3197
|
}, delayMs);
|
|
3175
3198
|
};
|
|
3176
3199
|
const connect = () => {
|
|
3200
|
+
if (disposed)
|
|
3201
|
+
return;
|
|
3177
3202
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
3178
3203
|
ws.binaryType = "arraybuffer";
|
|
3179
3204
|
ws.onopen = () => {
|
|
@@ -3244,6 +3269,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3244
3269
|
state.ws = null;
|
|
3245
3270
|
state.isConnected = false;
|
|
3246
3271
|
clearTimers();
|
|
3272
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
3247
3273
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
3248
3274
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
3249
3275
|
if (authorizationFailure) {
|
|
@@ -3256,15 +3282,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3256
3282
|
if (reconnectable) {
|
|
3257
3283
|
scheduleReconnect();
|
|
3258
3284
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3285
|
+
if (authorizationFailure) {
|
|
3286
|
+
emitConnection({
|
|
3287
|
+
reconnect: {
|
|
3288
|
+
attempts: state.reconnectAttempts,
|
|
3289
|
+
lastDisconnectAt: Date.now(),
|
|
3290
|
+
maxAttempts: maxReconnectAttempts,
|
|
3291
|
+
status: "exhausted"
|
|
3292
|
+
},
|
|
3293
|
+
type: "connection"
|
|
3294
|
+
});
|
|
3295
|
+
} else {
|
|
3296
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
3297
|
+
}
|
|
3268
3298
|
}
|
|
3269
3299
|
};
|
|
3270
3300
|
state.ws = ws;
|
|
@@ -3305,6 +3335,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3305
3335
|
send({ type: "end_turn" });
|
|
3306
3336
|
};
|
|
3307
3337
|
const callControl = (message) => {
|
|
3338
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
3339
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
3340
|
+
}
|
|
3308
3341
|
const requestId = crypto.randomUUID();
|
|
3309
3342
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
3310
3343
|
const timeout = setTimeout(() => {
|
|
@@ -3312,16 +3345,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3312
3345
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
3313
3346
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
3314
3347
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
3315
|
-
send({
|
|
3348
|
+
state.ws.send(JSON.stringify({
|
|
3316
3349
|
...message,
|
|
3317
3350
|
requestId,
|
|
3318
3351
|
type: "call_control"
|
|
3319
|
-
});
|
|
3352
|
+
}));
|
|
3320
3353
|
return promise;
|
|
3321
3354
|
};
|
|
3322
3355
|
const close = (reason = "client-close") => {
|
|
3356
|
+
disposed = true;
|
|
3357
|
+
lifecycleGeneration += 1;
|
|
3323
3358
|
clearTimers();
|
|
3324
3359
|
rejectPendingCallControls("Voice connection closed");
|
|
3360
|
+
state.pendingMessages.length = 0;
|
|
3325
3361
|
if (state.ws) {
|
|
3326
3362
|
const ws = state.ws;
|
|
3327
3363
|
state.ws = null;
|
|
@@ -3334,8 +3370,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
3334
3370
|
listeners.clear();
|
|
3335
3371
|
};
|
|
3336
3372
|
const disconnect = () => {
|
|
3373
|
+
disposed = true;
|
|
3374
|
+
lifecycleGeneration += 1;
|
|
3337
3375
|
clearTimers();
|
|
3338
3376
|
rejectPendingCallControls("Voice connection disconnected");
|
|
3377
|
+
state.pendingMessages.length = 0;
|
|
3339
3378
|
if (state.ws) {
|
|
3340
3379
|
const ws = state.ws;
|
|
3341
3380
|
state.ws = null;
|
package/dist/vue/index.js
CHANGED
|
@@ -10843,6 +10843,13 @@ var RECONNECT_BASE_DELAY_MS = 500;
|
|
|
10843
10843
|
var DEFAULT_RECONNECT_MAX_DELAY_MS = 8000;
|
|
10844
10844
|
var computeVoiceReconnectDelayMs = (attempt, baseMs, maxDelayMs) => Math.min(maxDelayMs, baseMs * 2 ** (Math.max(1, attempt) - 1));
|
|
10845
10845
|
var DEFAULT_SCENARIO_QUERY_PARAM = "scenarioId";
|
|
10846
|
+
|
|
10847
|
+
class VoiceReconnectRejectedError extends Error {
|
|
10848
|
+
constructor(message = "Voice authorization could not be renewed.") {
|
|
10849
|
+
super(message);
|
|
10850
|
+
this.name = "VoiceReconnectRejectedError";
|
|
10851
|
+
}
|
|
10852
|
+
}
|
|
10846
10853
|
var noop = () => {};
|
|
10847
10854
|
var noopAsync = () => Promise.resolve();
|
|
10848
10855
|
var noopUnsubscribe = () => noop;
|
|
@@ -10922,6 +10929,8 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10922
10929
|
const reconnectMaxDelayMs = options.reconnectMaxDelayMs ?? DEFAULT_RECONNECT_MAX_DELAY_MS;
|
|
10923
10930
|
const pingInterval = options.pingInterval ?? DEFAULT_PING_INTERVAL;
|
|
10924
10931
|
const reconnectResetAfterMs = options.reconnectResetAfterMs ?? DEFAULT_RECONNECT_RESET_AFTER_MS;
|
|
10932
|
+
let lifecycleGeneration = 0;
|
|
10933
|
+
let disposed = false;
|
|
10925
10934
|
const computeReconnectDelayMs = (attempt) => computeVoiceReconnectDelayMs(attempt, RECONNECT_BASE_DELAY_MS, reconnectMaxDelayMs);
|
|
10926
10935
|
const state = {
|
|
10927
10936
|
isConnected: false,
|
|
@@ -10937,6 +10946,21 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10937
10946
|
const emitConnection = (reconnect) => {
|
|
10938
10947
|
listeners.forEach((listener) => listener(reconnect));
|
|
10939
10948
|
};
|
|
10949
|
+
const emitTerminalFailure = (message) => {
|
|
10950
|
+
listeners.forEach((listener) => listener({
|
|
10951
|
+
message,
|
|
10952
|
+
recoverable: false,
|
|
10953
|
+
type: "error"
|
|
10954
|
+
}));
|
|
10955
|
+
emitConnection({
|
|
10956
|
+
reconnect: {
|
|
10957
|
+
attempts: state.reconnectAttempts,
|
|
10958
|
+
maxAttempts: maxReconnectAttempts,
|
|
10959
|
+
status: "exhausted"
|
|
10960
|
+
},
|
|
10961
|
+
type: "connection"
|
|
10962
|
+
});
|
|
10963
|
+
};
|
|
10940
10964
|
const clearTimers = () => {
|
|
10941
10965
|
if (state.pingInterval) {
|
|
10942
10966
|
clearInterval(state.pingInterval);
|
|
@@ -10970,6 +10994,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10970
10994
|
pendingCallControls.clear();
|
|
10971
10995
|
};
|
|
10972
10996
|
const scheduleReconnect = () => {
|
|
10997
|
+
if (disposed)
|
|
10998
|
+
return;
|
|
10999
|
+
const scheduledGeneration = lifecycleGeneration;
|
|
10973
11000
|
state.reconnectAttempts += 1;
|
|
10974
11001
|
const delayMs = computeReconnectDelayMs(state.reconnectAttempts);
|
|
10975
11002
|
const nextAttemptAt = Date.now() + delayMs;
|
|
@@ -10984,15 +11011,10 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
10984
11011
|
type: "connection"
|
|
10985
11012
|
});
|
|
10986
11013
|
state.reconnectTimeout = setTimeout(async () => {
|
|
11014
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11015
|
+
return;
|
|
10987
11016
|
if (state.reconnectAttempts > maxReconnectAttempts) {
|
|
10988
|
-
|
|
10989
|
-
reconnect: {
|
|
10990
|
-
attempts: state.reconnectAttempts,
|
|
10991
|
-
maxAttempts: maxReconnectAttempts,
|
|
10992
|
-
status: "exhausted"
|
|
10993
|
-
},
|
|
10994
|
-
type: "connection"
|
|
10995
|
-
});
|
|
11017
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
10996
11018
|
return;
|
|
10997
11019
|
}
|
|
10998
11020
|
try {
|
|
@@ -11002,25 +11024,28 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11002
11024
|
scenarioId: state.scenarioId,
|
|
11003
11025
|
sessionId: state.sessionId
|
|
11004
11026
|
});
|
|
11005
|
-
} catch {
|
|
11027
|
+
} catch (error) {
|
|
11028
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11029
|
+
return;
|
|
11030
|
+
if (error instanceof VoiceReconnectRejectedError) {
|
|
11031
|
+
emitTerminalFailure(error.message);
|
|
11032
|
+
return;
|
|
11033
|
+
}
|
|
11006
11034
|
if (state.reconnectAttempts < maxReconnectAttempts) {
|
|
11007
11035
|
scheduleReconnect();
|
|
11008
11036
|
} else {
|
|
11009
|
-
|
|
11010
|
-
reconnect: {
|
|
11011
|
-
attempts: state.reconnectAttempts,
|
|
11012
|
-
maxAttempts: maxReconnectAttempts,
|
|
11013
|
-
status: "exhausted"
|
|
11014
|
-
},
|
|
11015
|
-
type: "connection"
|
|
11016
|
-
});
|
|
11037
|
+
emitTerminalFailure("Voice authorization could not be renewed for reconnect.");
|
|
11017
11038
|
}
|
|
11018
11039
|
return;
|
|
11019
11040
|
}
|
|
11041
|
+
if (disposed || lifecycleGeneration !== scheduledGeneration)
|
|
11042
|
+
return;
|
|
11020
11043
|
connect();
|
|
11021
11044
|
}, delayMs);
|
|
11022
11045
|
};
|
|
11023
11046
|
const connect = () => {
|
|
11047
|
+
if (disposed)
|
|
11048
|
+
return;
|
|
11024
11049
|
const ws = new WebSocket(buildWsUrl(path, state.sessionId, state.scenarioId, options.query ?? {}));
|
|
11025
11050
|
ws.binaryType = "arraybuffer";
|
|
11026
11051
|
ws.onopen = () => {
|
|
@@ -11091,6 +11116,7 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11091
11116
|
state.ws = null;
|
|
11092
11117
|
state.isConnected = false;
|
|
11093
11118
|
clearTimers();
|
|
11119
|
+
rejectPendingCallControls("Voice connection interrupted");
|
|
11094
11120
|
const reconnectable = shouldReconnect && event.code !== WS_NORMAL_CLOSURE && event.code !== WS_UNAUTHORIZED && event.code !== WS_FORBIDDEN && state.reconnectAttempts < maxReconnectAttempts;
|
|
11095
11121
|
const authorizationFailure = event.code === WS_UNAUTHORIZED || event.code === WS_FORBIDDEN;
|
|
11096
11122
|
if (authorizationFailure) {
|
|
@@ -11103,15 +11129,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11103
11129
|
if (reconnectable) {
|
|
11104
11130
|
scheduleReconnect();
|
|
11105
11131
|
} else if (shouldReconnect && event.code !== WS_NORMAL_CLOSURE) {
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11132
|
+
if (authorizationFailure) {
|
|
11133
|
+
emitConnection({
|
|
11134
|
+
reconnect: {
|
|
11135
|
+
attempts: state.reconnectAttempts,
|
|
11136
|
+
lastDisconnectAt: Date.now(),
|
|
11137
|
+
maxAttempts: maxReconnectAttempts,
|
|
11138
|
+
status: "exhausted"
|
|
11139
|
+
},
|
|
11140
|
+
type: "connection"
|
|
11141
|
+
});
|
|
11142
|
+
} else {
|
|
11143
|
+
emitTerminalFailure("Voice connection could not be restored.");
|
|
11144
|
+
}
|
|
11115
11145
|
}
|
|
11116
11146
|
};
|
|
11117
11147
|
state.ws = ws;
|
|
@@ -11152,6 +11182,9 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11152
11182
|
send({ type: "end_turn" });
|
|
11153
11183
|
};
|
|
11154
11184
|
const callControl = (message) => {
|
|
11185
|
+
if (state.ws?.readyState !== WS_OPEN) {
|
|
11186
|
+
return Promise.reject(new Error(`${message.action} is unavailable while disconnected`));
|
|
11187
|
+
}
|
|
11155
11188
|
const requestId = crypto.randomUUID();
|
|
11156
11189
|
const { promise, reject, resolve } = Promise.withResolvers();
|
|
11157
11190
|
const timeout = setTimeout(() => {
|
|
@@ -11159,16 +11192,19 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11159
11192
|
reject(new Error(`${message.action} acknowledgement timed out`));
|
|
11160
11193
|
}, DEFAULT_CALL_CONTROL_ACK_TIMEOUT_MS);
|
|
11161
11194
|
pendingCallControls.set(requestId, { reject, resolve, timeout });
|
|
11162
|
-
send({
|
|
11195
|
+
state.ws.send(JSON.stringify({
|
|
11163
11196
|
...message,
|
|
11164
11197
|
requestId,
|
|
11165
11198
|
type: "call_control"
|
|
11166
|
-
});
|
|
11199
|
+
}));
|
|
11167
11200
|
return promise;
|
|
11168
11201
|
};
|
|
11169
11202
|
const close = (reason = "client-close") => {
|
|
11203
|
+
disposed = true;
|
|
11204
|
+
lifecycleGeneration += 1;
|
|
11170
11205
|
clearTimers();
|
|
11171
11206
|
rejectPendingCallControls("Voice connection closed");
|
|
11207
|
+
state.pendingMessages.length = 0;
|
|
11172
11208
|
if (state.ws) {
|
|
11173
11209
|
const ws = state.ws;
|
|
11174
11210
|
state.ws = null;
|
|
@@ -11181,8 +11217,11 @@ var createVoiceConnection = (path, options = {}) => {
|
|
|
11181
11217
|
listeners.clear();
|
|
11182
11218
|
};
|
|
11183
11219
|
const disconnect = () => {
|
|
11220
|
+
disposed = true;
|
|
11221
|
+
lifecycleGeneration += 1;
|
|
11184
11222
|
clearTimers();
|
|
11185
11223
|
rejectPendingCallControls("Voice connection disconnected");
|
|
11224
|
+
state.pendingMessages.length = 0;
|
|
11186
11225
|
if (state.ws) {
|
|
11187
11226
|
const ws = state.ws;
|
|
11188
11227
|
state.ws = null;
|