@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.
@@ -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
- emitConnection({
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
- emitConnection({
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
- emitConnection({
1039
- reconnect: {
1040
- attempts: state.reconnectAttempts,
1041
- lastDisconnectAt: Date.now(),
1042
- maxAttempts: maxReconnectAttempts,
1043
- status: "exhausted"
1044
- },
1045
- type: "connection"
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;
@@ -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
- emitConnection({
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
- emitConnection({
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
- emitConnection({
3260
- reconnect: {
3261
- attempts: state.reconnectAttempts,
3262
- lastDisconnectAt: Date.now(),
3263
- maxAttempts: maxReconnectAttempts,
3264
- status: "exhausted"
3265
- },
3266
- type: "connection"
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
- emitConnection({
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
- emitConnection({
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
- emitConnection({
11107
- reconnect: {
11108
- attempts: state.reconnectAttempts,
11109
- lastDisconnectAt: Date.now(),
11110
- maxAttempts: maxReconnectAttempts,
11111
- status: "exhausted"
11112
- },
11113
- type: "connection"
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.654",
3
+ "version": "0.0.22-beta.656",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",