@signalwire/js 4.0.0-dev-20260420151734 → 4.0.0-dev-20260421201955

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.
@@ -1129,9 +1129,9 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1129
1129
  var ObjectUnsubscribedError_1$1 = require_ObjectUnsubscribedError();
1130
1130
  var arrRemove_1$6 = require_arrRemove();
1131
1131
  var errorContext_1 = require_errorContext();
1132
- var Subject$2 = function(_super) {
1133
- __extends$13(Subject$3, _super);
1134
- function Subject$3() {
1132
+ var Subject$1 = function(_super) {
1133
+ __extends$13(Subject$2, _super);
1134
+ function Subject$2() {
1135
1135
  var _this = _super.call(this) || this;
1136
1136
  _this.closed = false;
1137
1137
  _this.currentObservers = null;
@@ -1141,15 +1141,15 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1141
1141
  _this.thrownError = null;
1142
1142
  return _this;
1143
1143
  }
1144
- Subject$3.prototype.lift = function(operator) {
1144
+ Subject$2.prototype.lift = function(operator) {
1145
1145
  var subject = new AnonymousSubject(this, this);
1146
1146
  subject.operator = operator;
1147
1147
  return subject;
1148
1148
  };
1149
- Subject$3.prototype._throwIfClosed = function() {
1149
+ Subject$2.prototype._throwIfClosed = function() {
1150
1150
  if (this.closed) throw new ObjectUnsubscribedError_1$1.ObjectUnsubscribedError();
1151
1151
  };
1152
- Subject$3.prototype.next = function(value) {
1152
+ Subject$2.prototype.next = function(value) {
1153
1153
  var _this = this;
1154
1154
  errorContext_1.errorContext(function() {
1155
1155
  var e_1, _a;
@@ -1170,7 +1170,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1170
1170
  }
1171
1171
  });
1172
1172
  };
1173
- Subject$3.prototype.error = function(err) {
1173
+ Subject$2.prototype.error = function(err) {
1174
1174
  var _this = this;
1175
1175
  errorContext_1.errorContext(function() {
1176
1176
  _this._throwIfClosed();
@@ -1182,7 +1182,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1182
1182
  }
1183
1183
  });
1184
1184
  };
1185
- Subject$3.prototype.complete = function() {
1185
+ Subject$2.prototype.complete = function() {
1186
1186
  var _this = this;
1187
1187
  errorContext_1.errorContext(function() {
1188
1188
  _this._throwIfClosed();
@@ -1193,11 +1193,11 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1193
1193
  }
1194
1194
  });
1195
1195
  };
1196
- Subject$3.prototype.unsubscribe = function() {
1196
+ Subject$2.prototype.unsubscribe = function() {
1197
1197
  this.isStopped = this.closed = true;
1198
1198
  this.observers = this.currentObservers = null;
1199
1199
  };
1200
- Object.defineProperty(Subject$3.prototype, "observed", {
1200
+ Object.defineProperty(Subject$2.prototype, "observed", {
1201
1201
  get: function() {
1202
1202
  var _a;
1203
1203
  return ((_a = this.observers) === null || _a === void 0 ? void 0 : _a.length) > 0;
@@ -1205,16 +1205,16 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1205
1205
  enumerable: false,
1206
1206
  configurable: true
1207
1207
  });
1208
- Subject$3.prototype._trySubscribe = function(subscriber) {
1208
+ Subject$2.prototype._trySubscribe = function(subscriber) {
1209
1209
  this._throwIfClosed();
1210
1210
  return _super.prototype._trySubscribe.call(this, subscriber);
1211
1211
  };
1212
- Subject$3.prototype._subscribe = function(subscriber) {
1212
+ Subject$2.prototype._subscribe = function(subscriber) {
1213
1213
  this._throwIfClosed();
1214
1214
  this._checkFinalizedStatuses(subscriber);
1215
1215
  return this._innerSubscribe(subscriber);
1216
1216
  };
1217
- Subject$3.prototype._innerSubscribe = function(subscriber) {
1217
+ Subject$2.prototype._innerSubscribe = function(subscriber) {
1218
1218
  var _this = this;
1219
1219
  var _a = this, hasError = _a.hasError, isStopped = _a.isStopped, observers = _a.observers;
1220
1220
  if (hasError || isStopped) return Subscription_1$6.EMPTY_SUBSCRIPTION;
@@ -1225,22 +1225,22 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1225
1225
  arrRemove_1$6.arrRemove(observers, subscriber);
1226
1226
  });
1227
1227
  };
1228
- Subject$3.prototype._checkFinalizedStatuses = function(subscriber) {
1228
+ Subject$2.prototype._checkFinalizedStatuses = function(subscriber) {
1229
1229
  var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, isStopped = _a.isStopped;
1230
1230
  if (hasError) subscriber.error(thrownError);
1231
1231
  else if (isStopped) subscriber.complete();
1232
1232
  };
1233
- Subject$3.prototype.asObservable = function() {
1233
+ Subject$2.prototype.asObservable = function() {
1234
1234
  var observable = new Observable_1$24.Observable();
1235
1235
  observable.source = this;
1236
1236
  return observable;
1237
1237
  };
1238
- Subject$3.create = function(destination, source) {
1238
+ Subject$2.create = function(destination, source) {
1239
1239
  return new AnonymousSubject(destination, source);
1240
1240
  };
1241
- return Subject$3;
1241
+ return Subject$2;
1242
1242
  }(Observable_1$24.Observable);
1243
- exports.Subject = Subject$2;
1243
+ exports.Subject = Subject$1;
1244
1244
  var AnonymousSubject = function(_super) {
1245
1245
  __extends$13(AnonymousSubject$1, _super);
1246
1246
  function AnonymousSubject$1(destination, source) {
@@ -1266,7 +1266,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1266
1266
  return (_b = (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber)) !== null && _b !== void 0 ? _b : Subscription_1$6.EMPTY_SUBSCRIPTION;
1267
1267
  };
1268
1268
  return AnonymousSubject$1;
1269
- }(Subject$2);
1269
+ }(Subject$1);
1270
1270
  exports.AnonymousSubject = AnonymousSubject;
1271
1271
  }));
1272
1272
 
@@ -1293,37 +1293,37 @@ var require_BehaviorSubject = /* @__PURE__ */ __commonJSMin(((exports) => {
1293
1293
  })();
1294
1294
  Object.defineProperty(exports, "__esModule", { value: true });
1295
1295
  exports.BehaviorSubject = void 0;
1296
- var BehaviorSubject$2 = function(_super) {
1297
- __extends$12(BehaviorSubject$3, _super);
1298
- function BehaviorSubject$3(_value) {
1296
+ var BehaviorSubject$1 = function(_super) {
1297
+ __extends$12(BehaviorSubject$2, _super);
1298
+ function BehaviorSubject$2(_value) {
1299
1299
  var _this = _super.call(this) || this;
1300
1300
  _this._value = _value;
1301
1301
  return _this;
1302
1302
  }
1303
- Object.defineProperty(BehaviorSubject$3.prototype, "value", {
1303
+ Object.defineProperty(BehaviorSubject$2.prototype, "value", {
1304
1304
  get: function() {
1305
1305
  return this.getValue();
1306
1306
  },
1307
1307
  enumerable: false,
1308
1308
  configurable: true
1309
1309
  });
1310
- BehaviorSubject$3.prototype._subscribe = function(subscriber) {
1310
+ BehaviorSubject$2.prototype._subscribe = function(subscriber) {
1311
1311
  var subscription = _super.prototype._subscribe.call(this, subscriber);
1312
1312
  !subscription.closed && subscriber.next(this._value);
1313
1313
  return subscription;
1314
1314
  };
1315
- BehaviorSubject$3.prototype.getValue = function() {
1315
+ BehaviorSubject$2.prototype.getValue = function() {
1316
1316
  var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, _value = _a._value;
1317
1317
  if (hasError) throw thrownError;
1318
1318
  this._throwIfClosed();
1319
1319
  return _value;
1320
1320
  };
1321
- BehaviorSubject$3.prototype.next = function(value) {
1321
+ BehaviorSubject$2.prototype.next = function(value) {
1322
1322
  _super.prototype.next.call(this, this._value = value);
1323
1323
  };
1324
- return BehaviorSubject$3;
1324
+ return BehaviorSubject$2;
1325
1325
  }(require_Subject().Subject);
1326
- exports.BehaviorSubject = BehaviorSubject$2;
1326
+ exports.BehaviorSubject = BehaviorSubject$1;
1327
1327
  }));
1328
1328
 
1329
1329
  //#endregion
@@ -3250,7 +3250,7 @@ var require_firstValueFrom = /* @__PURE__ */ __commonJSMin(((exports) => {
3250
3250
  exports.firstValueFrom = void 0;
3251
3251
  var EmptyError_1$5 = require_EmptyError();
3252
3252
  var Subscriber_1$2 = require_Subscriber();
3253
- function firstValueFrom$6(source, config) {
3253
+ function firstValueFrom$7(source, config) {
3254
3254
  var hasConfig = typeof config === "object";
3255
3255
  return new Promise(function(resolve, reject) {
3256
3256
  var subscriber = new Subscriber_1$2.SafeSubscriber({
@@ -3267,7 +3267,7 @@ var require_firstValueFrom = /* @__PURE__ */ __commonJSMin(((exports) => {
3267
3267
  source.subscribe(subscriber);
3268
3268
  });
3269
3269
  }
3270
- exports.firstValueFrom = firstValueFrom$6;
3270
+ exports.firstValueFrom = firstValueFrom$7;
3271
3271
  }));
3272
3272
 
3273
3273
  //#endregion
@@ -4144,7 +4144,7 @@ var require_timer = /* @__PURE__ */ __commonJSMin(((exports) => {
4144
4144
  var async_1$11 = require_async();
4145
4145
  var isScheduler_1 = require_isScheduler();
4146
4146
  var isDate_1$1 = require_isDate();
4147
- function timer$2(dueTime, intervalOrScheduler, scheduler) {
4147
+ function timer$3(dueTime, intervalOrScheduler, scheduler) {
4148
4148
  if (dueTime === void 0) dueTime = 0;
4149
4149
  if (scheduler === void 0) scheduler = async_1$11.async;
4150
4150
  var intervalDuration = -1;
@@ -4163,7 +4163,7 @@ var require_timer = /* @__PURE__ */ __commonJSMin(((exports) => {
4163
4163
  }, due);
4164
4164
  });
4165
4165
  }
4166
- exports.timer = timer$2;
4166
+ exports.timer = timer$3;
4167
4167
  }));
4168
4168
 
4169
4169
  //#endregion
@@ -4331,13 +4331,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4331
4331
  var innerFrom_1$28 = require_innerFrom();
4332
4332
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4333
4333
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4334
- function race$4() {
4334
+ function race$5() {
4335
4335
  var sources = [];
4336
4336
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4337
4337
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4338
4338
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4339
4339
  }
4340
- exports.race = race$4;
4340
+ exports.race = race$5;
4341
4341
  function raceInit(sources) {
4342
4342
  return function(subscriber) {
4343
4343
  var subscriptions = [];
@@ -5335,7 +5335,7 @@ var require_take = /* @__PURE__ */ __commonJSMin(((exports) => {
5335
5335
  var empty_1$3 = require_empty();
5336
5336
  var lift_1$46 = require_lift();
5337
5337
  var OperatorSubscriber_1$35 = require_OperatorSubscriber();
5338
- function take$8(count$1) {
5338
+ function take$9(count$1) {
5339
5339
  return count$1 <= 0 ? function() {
5340
5340
  return empty_1$3.EMPTY;
5341
5341
  } : lift_1$46.operate(function(source, subscriber) {
@@ -5348,7 +5348,7 @@ var require_take = /* @__PURE__ */ __commonJSMin(((exports) => {
5348
5348
  }));
5349
5349
  });
5350
5350
  }
5351
- exports.take = take$8;
5351
+ exports.take = take$9;
5352
5352
  }));
5353
5353
 
5354
5354
  //#endregion
@@ -8937,12 +8937,12 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
8937
8937
 
8938
8938
  //#endregion
8939
8939
  //#region src/behaviors/Destroyable.ts
8940
- var import_cjs$29 = require_cjs();
8940
+ var import_cjs$28 = require_cjs();
8941
8941
  var Destroyable = class {
8942
8942
  constructor() {
8943
8943
  this.subscriptions = [];
8944
8944
  this.subjects = [];
8945
- this._destroyed$ = new import_cjs$29.Subject();
8945
+ this._destroyed$ = new import_cjs$28.Subject();
8946
8946
  }
8947
8947
  destroy() {
8948
8948
  this._observableCache?.clear();
@@ -8978,7 +8978,7 @@ var Destroyable = class {
8978
8978
  this._observableCache ??= /* @__PURE__ */ new Map();
8979
8979
  let cached = this._observableCache.get(publicKey);
8980
8980
  if (!cached) {
8981
- cached = factory().pipe((0, import_cjs$29.observeOn)(import_cjs$29.asapScheduler));
8981
+ cached = factory().pipe((0, import_cjs$28.observeOn)(import_cjs$28.asapScheduler));
8982
8982
  this._observableCache.set(publicKey, cached);
8983
8983
  }
8984
8984
  return cached;
@@ -8992,24 +8992,24 @@ var Destroyable = class {
8992
8992
  * Do NOT use for observables consumed internally by the SDK.
8993
8993
  */
8994
8994
  deferEmission(observable) {
8995
- return observable.pipe((0, import_cjs$29.observeOn)(import_cjs$29.asapScheduler));
8995
+ return observable.pipe((0, import_cjs$28.observeOn)(import_cjs$28.asapScheduler));
8996
8996
  }
8997
8997
  subscribeTo(observable, observerOrNext) {
8998
8998
  const subscription = observable.subscribe(observerOrNext);
8999
8999
  this.subscriptions.push(subscription);
9000
9000
  }
9001
9001
  createSubject() {
9002
- const subject = new import_cjs$29.Subject();
9002
+ const subject = new import_cjs$28.Subject();
9003
9003
  this.subjects.push(subject);
9004
9004
  return subject;
9005
9005
  }
9006
9006
  createReplaySubject(bufferSize, windowTime$1) {
9007
- const subject = new import_cjs$29.ReplaySubject(bufferSize, windowTime$1);
9007
+ const subject = new import_cjs$28.ReplaySubject(bufferSize, windowTime$1);
9008
9008
  this.subjects.push(subject);
9009
9009
  return subject;
9010
9010
  }
9011
9011
  createBehaviorSubject(initialValue) {
9012
- const subject = new import_cjs$29.BehaviorSubject(initialValue);
9012
+ const subject = new import_cjs$28.BehaviorSubject(initialValue);
9013
9013
  this.subjects.push(subject);
9014
9014
  return subject;
9015
9015
  }
@@ -9606,7 +9606,6 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9606
9606
 
9607
9607
  //#endregion
9608
9608
  //#region src/controllers/HTTPRequestController.ts
9609
- var import_cjs$28 = require_cjs();
9610
9609
  const logger$29 = getLogger();
9611
9610
  const GET_PARAMS = {
9612
9611
  method: "GET",
@@ -9619,7 +9618,7 @@ const POST_PARAMS = {
9619
9618
  "Content-Type": "application/json"
9620
9619
  }
9621
9620
  };
9622
- var HTTPRequestController = class HTTPRequestController {
9621
+ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9623
9622
  static {
9624
9623
  this.defaultMaxRetries = 3;
9625
9624
  }
@@ -9640,11 +9639,12 @@ var HTTPRequestController = class HTTPRequestController {
9640
9639
  ]);
9641
9640
  }
9642
9641
  constructor(baseURL, getCredential, options = {}) {
9642
+ super();
9643
9643
  this.baseURL = baseURL;
9644
9644
  this.getCredential = getCredential;
9645
- this._responses$ = new import_cjs$28.Subject();
9646
- this._errors$ = new import_cjs$28.Subject();
9647
- this._status$ = new import_cjs$28.BehaviorSubject("idle");
9645
+ this._responses$ = this.createSubject();
9646
+ this._errors$ = this.createSubject();
9647
+ this._status$ = this.createBehaviorSubject("idle");
9648
9648
  this.maxRetries = options.maxRetries ?? HTTPRequestController.defaultMaxRetries;
9649
9649
  this.retryDelayMin = options.retryDelayMin ?? HTTPRequestController.defaultRetryDelayMinMs;
9650
9650
  this.retryDelayMax = options.retryDelayMax ?? HTTPRequestController.defaultRetryDelayMaxMs;
@@ -9954,6 +9954,12 @@ const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9954
9954
  const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9955
9955
  /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9956
9956
  const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9957
+ /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9958
+ const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9959
+ /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9960
+ const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9961
+ /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9962
+ const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9957
9963
  /** Whether to persist device selections to storage by default. */
9958
9964
  const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9959
9965
  /** Whether to auto-apply device changes to active calls by default. */
@@ -11907,7 +11913,7 @@ var PreflightRunner = class extends Destroyable {
11907
11913
  const candidateTypes = /* @__PURE__ */ new Set();
11908
11914
  const startTime = Date.now();
11909
11915
  const gatheringComplete = new Promise((resolve) => {
11910
- const timer$3 = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);
11916
+ const timer$4 = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);
11911
11917
  peerConnection.onicecandidate = (event) => {
11912
11918
  if (event.candidate) {
11913
11919
  const candidateStr = event.candidate.candidate;
@@ -11915,7 +11921,7 @@ var PreflightRunner = class extends Destroyable {
11915
11921
  if (candidateStr.includes("typ srflx")) candidateTypes.add("srflx");
11916
11922
  if (candidateStr.includes("typ relay")) candidateTypes.add("relay");
11917
11923
  } else {
11918
- clearTimeout(timer$3);
11924
+ clearTimeout(timer$4);
11919
11925
  resolve();
11920
11926
  }
11921
11927
  };
@@ -12324,13 +12330,11 @@ var AttachManager = class {
12324
12330
  this.deviceController = deviceController;
12325
12331
  this.reconnectCallsTimeout = reconnectCallsTimeout;
12326
12332
  this.attachKey = attachKey;
12333
+ this.writeQueue = Promise.resolve();
12327
12334
  }
12328
12335
  async detachAll() {
12329
- const attached = await this.readAttached();
12330
- for (const callId of Object.keys(attached)) await this.detach({
12331
- id: callId,
12332
- nodeId: attached[callId].nodeId,
12333
- mediaDirections: attached[callId].mediaDirections
12336
+ await this.mutate((attached) => {
12337
+ return {};
12334
12338
  });
12335
12339
  }
12336
12340
  setSession(session) {
@@ -12351,31 +12355,47 @@ var AttachManager = class {
12351
12355
  logger$22.warn("[AttachManager] Failed to write attached calls to storage", error);
12352
12356
  }
12353
12357
  }
12358
+ /**
12359
+ * Serialize a read-modify-write operation against the attached-calls
12360
+ * storage. The mutator receives the current state and returns the new
12361
+ * state. Concurrent calls queue behind the in-flight one so writes never
12362
+ * interleave.
12363
+ */
12364
+ async mutate(mutator) {
12365
+ const next = this.writeQueue.then(async () => {
12366
+ const updated = await mutator(await this.readAttached());
12367
+ await this.writeAttached(updated);
12368
+ });
12369
+ this.writeQueue = next.catch(() => void 0);
12370
+ return next;
12371
+ }
12354
12372
  async attach(call) {
12355
12373
  if (!call.to) {
12356
12374
  logger$22.warn("[AttachManager] Skip attach for calls with no destination");
12357
12375
  return;
12358
12376
  }
12377
+ const destination = call.to;
12359
12378
  const attachment = {
12360
12379
  nodeId: call.nodeId,
12361
- destination: call.to,
12380
+ destination,
12362
12381
  mediaDirections: call.mediaDirections,
12363
12382
  audioInputDevice: call.mediaDirections.audio !== "inactive" ? this.deviceController.selectedAudioInputDevice : null,
12364
12383
  videoInputDevice: call.mediaDirections.video !== "inactive" ? this.deviceController.selectedVideoInputDevice : null,
12365
12384
  attachedAt: Date.now()
12366
12385
  };
12367
- const updated = {
12368
- ...await this.readAttached(),
12386
+ await this.mutate((attached) => ({
12387
+ ...attached,
12369
12388
  [call.id]: attachment
12370
- };
12371
- await this.writeAttached(updated);
12389
+ }));
12372
12390
  }
12373
12391
  async detach(call) {
12374
- const { [call.id]: _, ...remaining } = await this.readAttached();
12375
- await this.writeAttached(remaining);
12392
+ await this.mutate((attached) => {
12393
+ const { [call.id]: _, ...remaining } = attached;
12394
+ return remaining;
12395
+ });
12376
12396
  }
12377
12397
  async flush() {
12378
- await this.writeAttached({});
12398
+ await this.mutate(() => ({}));
12379
12399
  }
12380
12400
  /**
12381
12401
  * Reattach to previously active calls by sending verto.invite with
@@ -12447,20 +12467,31 @@ var AttachManager = class {
12447
12467
  };
12448
12468
  }
12449
12469
  /**
12450
- * Consume stored attachment data for a pending call (used by session-level
12451
- * verto.attach handler as a future path when server supports it).
12470
+ * Look up stored attachment data for a call id and return CallOptions
12471
+ * suitable for rehydrating a reattached call. Returns undefined when no
12472
+ * matching entry exists in storage.
12473
+ *
12474
+ * Used by the session-level verto.attach handler when the server pushes
12475
+ * an attach event for a call the client doesn't have an object for yet
12476
+ * (e.g. after a reload).
12452
12477
  */
12453
- consumePendingAttachment(_callId) {}
12478
+ async consumePendingAttachment(callId) {
12479
+ const attachment = (await this.readAttached())[callId];
12480
+ if (!attachment) return;
12481
+ return this.buildCallOptions(attachment);
12482
+ }
12454
12483
  async detachExpired() {
12455
- const attached = await this.readAttached();
12456
12484
  const now = Date.now();
12457
12485
  const timeout$5 = this.reconnectCallsTimeout;
12458
- const expired = Object.entries(attached).filter(([, attachment]) => now - attachment.attachedAt > timeout$5);
12459
- if (expired.length > 0) {
12486
+ await this.mutate((attached) => {
12460
12487
  const remaining = { ...attached };
12461
- for (const [callId] of expired) delete remaining[callId];
12462
- await this.writeAttached(remaining);
12463
- }
12488
+ let changed = false;
12489
+ for (const [callId, attachment] of Object.entries(attached)) if (now - attachment.attachedAt > timeout$5) {
12490
+ delete remaining[callId];
12491
+ changed = true;
12492
+ }
12493
+ return changed ? remaining : attached;
12494
+ });
12464
12495
  }
12465
12496
  };
12466
12497
 
@@ -12507,12 +12538,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12507
12538
  exports.race = void 0;
12508
12539
  var argsOrArgArray_1 = require_argsOrArgArray();
12509
12540
  var raceWith_1$1 = require_raceWith();
12510
- function race$3() {
12541
+ function race$4() {
12511
12542
  var args = [];
12512
12543
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12513
12544
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12514
12545
  }
12515
- exports.race = race$3;
12546
+ exports.race = race$4;
12516
12547
  }));
12517
12548
 
12518
12549
  //#endregion
@@ -14916,12 +14947,12 @@ var ICEGatheringController = class extends Destroyable {
14916
14947
  ...this.peerConnection.getConfiguration(),
14917
14948
  iceTransportPolicy: "relay"
14918
14949
  });
14919
- if (!(this.peerConnection.connectionState === "connected")) this.peerConnection.restartIce();
14950
+ this.peerConnection.restartIce();
14920
14951
  }
14921
- removeTimer(timer$3) {
14922
- if (this[timer$3]) {
14923
- clearTimeout(this[timer$3]);
14924
- this[timer$3] = void 0;
14952
+ removeTimer(timer$4) {
14953
+ if (this[timer$4]) {
14954
+ clearTimeout(this[timer$4]);
14955
+ this[timer$4] = void 0;
14925
14956
  }
14926
14957
  }
14927
14958
  clearAllTimers() {
@@ -15887,9 +15918,6 @@ var RTCPeerConnectionController = class extends Destroyable {
15887
15918
  negotiationEnded() {
15888
15919
  this._isNegotiating$.next(false);
15889
15920
  }
15890
- restarIce() {
15891
- this.peerConnection?.restartIce();
15892
- }
15893
15921
  /**
15894
15922
  * Trigger an ICE restart through the existing negotiation pipeline.
15895
15923
  *
@@ -15929,7 +15957,10 @@ var RTCPeerConnectionController = class extends Destroyable {
15929
15957
  if (policyChanged) this.restoreIceTransportPolicy();
15930
15958
  throw error;
15931
15959
  }
15932
- if (policyChanged) this.restoreIceTransportPolicy();
15960
+ if (policyChanged) (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this._iceGatheringState$.pipe((0, import_cjs$15.filter)((state) => state === "complete"), (0, import_cjs$15.take)(1)), (0, import_cjs$15.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$15.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
15961
+ logger$15.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
15962
+ this.restoreIceTransportPolicy();
15963
+ });
15933
15964
  }
15934
15965
  restoreIceTransportPolicy() {
15935
15966
  try {
@@ -17097,11 +17128,11 @@ var RTCStatsMonitor = class extends Destroyable {
17097
17128
  let availableOutgoingBitrate;
17098
17129
  report.forEach((stat) => {
17099
17130
  if (isInboundRtpStat(stat)) if (stat.kind === "audio") {
17100
- audioPacketsReceived += stat.packetsReceived ?? this.lastAudioPacketsReceived;
17131
+ audioPacketsReceived += stat.packetsReceived ?? 0;
17101
17132
  audioPacketsLost += stat.packetsLost ?? 0;
17102
17133
  audioJitter = Math.max(audioJitter, (stat.jitter ?? 0) * 1e3);
17103
17134
  } else {
17104
- videoPacketsReceived += stat.packetsReceived ?? this.lastVideoPacketsReceived;
17135
+ videoPacketsReceived += stat.packetsReceived ?? 0;
17105
17136
  videoPacketsLost += stat.packetsLost ?? 0;
17106
17137
  }
17107
17138
  if (isCandidatePairStat(stat) && stat.state === "succeeded" && stat.nominated) {
@@ -18151,10 +18182,10 @@ var WebRTCCall = class extends Destroyable {
18151
18182
  try {
18152
18183
  if (this.vertoManager.requestIceRestartAll) await this.vertoManager.requestIceRestartAll(relayOnly);
18153
18184
  else await this.vertoManager.requestIceRestart?.(relayOnly);
18154
- return true;
18155
18185
  } catch {
18156
18186
  return false;
18157
18187
  }
18188
+ return this.waitForPeerConnectionConnected();
18158
18189
  },
18159
18190
  disableVideo: () => {
18160
18191
  try {
@@ -18246,6 +18277,27 @@ var WebRTCCall = class extends Destroyable {
18246
18277
  }
18247
18278
  }
18248
18279
  /**
18280
+ * Wait for the underlying RTCPeerConnection to reach 'connected' after
18281
+ * triggering an ICE restart. Resolves true on success, false on failure
18282
+ * or if the state doesn't transition within the configured timeout.
18283
+ *
18284
+ * Polls connectionState directly because the recovery manager already
18285
+ * wraps this call in its own withTimeout(); a separate listener-based
18286
+ * implementation would race the outer timeout in subtle ways.
18287
+ */
18288
+ async waitForPeerConnectionConnected() {
18289
+ const pc = this.rtcPeerConnection;
18290
+ if (!pc) return false;
18291
+ const deadline = Date.now() + PEER_CONNECTION_RECOVERY_WAIT_MS;
18292
+ while (true) {
18293
+ const state = pc.connectionState;
18294
+ if (state === "connected") return true;
18295
+ if (state === "failed" || state === "closed") return false;
18296
+ if (Date.now() >= deadline) return false;
18297
+ await new Promise((resolve) => setTimeout(resolve, PEER_CONNECTION_RECOVERY_POLL_MS));
18298
+ }
18299
+ }
18300
+ /**
18249
18301
  * @internal Stop and destroy resilience subsystems (on disconnect/destroy).
18250
18302
  * Clears references so they can be re-created on reconnect.
18251
18303
  */
@@ -18382,8 +18434,13 @@ var WebRTCCall = class extends Destroyable {
18382
18434
  const cached = this._customSubscriptions.get(eventType);
18383
18435
  if (cached) return cached;
18384
18436
  const filtered$ = this.callSessionEvents$.pipe((0, import_cjs$11.filter)((event) => event.event_type === eventType), (0, import_cjs$11.map)((event) => JSON.parse(JSON.stringify(event))), (0, import_cjs$11.takeUntil)(this._destroyed$));
18437
+ this._sendVertoSubscribe(eventType).then(() => {
18438
+ this._customSubscriptions.set(eventType, filtered$);
18439
+ }, (error) => {
18440
+ this._customSubscriptions.delete(eventType);
18441
+ logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
18442
+ });
18385
18443
  this._customSubscriptions.set(eventType, filtered$);
18386
- this._sendVertoSubscribe(eventType);
18387
18444
  return filtered$;
18388
18445
  }
18389
18446
  get webrtcMessages$() {
@@ -18509,26 +18566,21 @@ var WebRTCCall = class extends Destroyable {
18509
18566
  }
18510
18567
  /**
18511
18568
  * @internal Send a verto.subscribe message to add an event type to the
18512
- * server's subscription list for this call. Best-effort — failures are
18513
- * logged but don't prevent the filtered observable from being returned.
18569
+ * server's subscription list for this call. Returns the underlying RPC
18570
+ * promise so callers can decide whether to cache the observable on success
18571
+ * or retry on failure.
18514
18572
  */
18515
- _sendVertoSubscribe(eventType) {
18516
- try {
18517
- const message = VertoSubscribe({
18518
- sessid: this.id,
18519
- eventChannel: [eventType]
18520
- });
18521
- const params = {
18522
- callID: this.id,
18523
- node_id: this.vertoManager.nodeId ?? "",
18524
- message
18525
- };
18526
- this.clientSession.execute(WebrtcVerto(params)).catch((error) => {
18527
- logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed (non-fatal):`, error);
18528
- });
18529
- } catch (error) {
18530
- logger$11.warn(`[Call] Failed to send verto.subscribe for '${eventType}':`, error);
18531
- }
18573
+ async _sendVertoSubscribe(eventType) {
18574
+ const message = VertoSubscribe({
18575
+ sessid: this.id,
18576
+ eventChannel: [eventType]
18577
+ });
18578
+ const params = {
18579
+ callID: this.id,
18580
+ node_id: this.vertoManager.nodeId ?? "",
18581
+ message
18582
+ };
18583
+ await this.clientSession.execute(WebrtcVerto(params));
18532
18584
  }
18533
18585
  };
18534
18586
 
@@ -18545,11 +18597,21 @@ function inferCallErrorKind(error) {
18545
18597
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
18546
18598
  return "internal";
18547
18599
  }
18600
+ /** JSON-RPC error codes that ClientSessionManager treats as recoverable at the
18601
+ * session level. Surfacing one of these against an in-flight call should not
18602
+ * destroy the call, because the session will reauthenticate and any pending
18603
+ * RPC can then be retried. */
18604
+ const RECOVERABLE_RPC_CODES = new Set([
18605
+ RPC_ERROR_REQUESTER_VALIDATION_FAILED,
18606
+ RPC_ERROR_AUTHENTICATION_FAILED,
18607
+ RPC_ERROR_INVALID_PARAMS
18608
+ ]);
18548
18609
  /** Determines whether an error should be fatal (destroy the call). */
18549
18610
  function isFatalError(error) {
18550
18611
  if (error instanceof VertoPongError) return false;
18551
18612
  if (error instanceof MediaTrackError) return false;
18552
18613
  if (error instanceof RPCTimeoutError) return false;
18614
+ if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
18553
18615
  return true;
18554
18616
  }
18555
18617
  /**
@@ -18987,10 +19049,10 @@ var PendingRPC = class PendingRPC {
18987
19049
  }
18988
19050
  let isSettled = false;
18989
19051
  const subscription = (0, import_cjs$8.race)(responses$.pipe((0, import_cjs$8.filter)((result) => result.id === request.id), (0, import_cjs$8.take)(1)), new import_cjs$8.Observable((subscriber) => {
18990
- const timer$3 = setTimeout(() => {
19052
+ const timer$4 = setTimeout(() => {
18991
19053
  subscriber.error(new RPCTimeoutError(request.id, timeoutMs));
18992
19054
  }, timeoutMs);
18993
- return () => clearTimeout(timer$3);
19055
+ return () => clearTimeout(timer$4);
18994
19056
  }), signal ? new import_cjs$8.Observable((subscriber) => {
18995
19057
  const abortHandler = () => {
18996
19058
  subscriber.error(new DOMException("The operation was aborted", "AbortError"));
@@ -19056,7 +19118,7 @@ var ClientSessionManager = class extends Destroyable {
19056
19118
  this.attachManager = attachManager;
19057
19119
  this.dpopManager = dpopManager;
19058
19120
  this.callCreateTimeout = 6e3;
19059
- this.agent = `signalwire-typescript-sdk/1.0.0`;
19121
+ this.agent = `signalwire-js/4.0.0`;
19060
19122
  this.eventAcks = true;
19061
19123
  this.authorizationState$ = this.createReplaySubject(1);
19062
19124
  this.connectVersion = {
@@ -19390,7 +19452,6 @@ var ClientSessionManager = class extends Destroyable {
19390
19452
  displayDirection: invite.display_direction,
19391
19453
  userVariables: invite.userVariables
19392
19454
  });
19393
- await (0, import_cjs$7.firstValueFrom)(callSession.status$);
19394
19455
  this._calls$.next({
19395
19456
  [`${callSession.id}`]: callSession,
19396
19457
  ...this._calls$.value
@@ -19411,7 +19472,7 @@ var ClientSessionManager = class extends Destroyable {
19411
19472
  logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
19412
19473
  return;
19413
19474
  }
19414
- const storedOptions = this.attachManager.consumePendingAttachment(callID);
19475
+ const storedOptions = await this.attachManager.consumePendingAttachment(callID);
19415
19476
  logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
19416
19477
  const callSession = await this.createCall({
19417
19478
  nodeId: attach.node_id,
@@ -19423,7 +19484,6 @@ var ClientSessionManager = class extends Destroyable {
19423
19484
  reattach: true,
19424
19485
  ...storedOptions
19425
19486
  });
19426
- await (0, import_cjs$7.firstValueFrom)(callSession.status$);
19427
19487
  this._calls$.next({
19428
19488
  [`${callSession.id}`]: callSession,
19429
19489
  ...this._calls$.value
@@ -20146,10 +20206,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
20146
20206
  else this._status$.next("disconnected");
20147
20207
  }
20148
20208
  reconnect() {
20149
- if (this.shouldReconnect) {
20150
- this._status$.next("reconnecting");
20151
- this.scheduleReconnection();
20152
- } else this._status$.next("disconnected");
20209
+ this.shouldReconnect = true;
20210
+ this._status$.next("reconnecting");
20211
+ this.scheduleReconnection();
20153
20212
  }
20154
20213
  send(data) {
20155
20214
  if (this._status$.value === "connected" && this.socket?.readyState === 1) {
@@ -20710,6 +20769,7 @@ var SignalWire = class extends Destroyable {
20710
20769
  * `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.
20711
20770
  */
20712
20771
  async connect() {
20772
+ await this.teardownTransportAndSession();
20713
20773
  try {
20714
20774
  const subscriber = this._subscriber$.value;
20715
20775
  if (!subscriber) throw new UnexpectedError("Subscriber not initialized before connect");
@@ -20941,10 +21001,30 @@ var SignalWire = class extends Destroyable {
20941
21001
  this._refreshTimerId = void 0;
20942
21002
  }
20943
21003
  this._diagnosticsCollector?.record("connection", "disconnected");
20944
- await this._clientSession.disconnect();
20945
- this._clientSession.destroy();
21004
+ await this.teardownTransportAndSession();
20946
21005
  this._isConnected$.next(false);
20947
21006
  }
21007
+ /**
21008
+ * Tear down the current transport / session / attach manager. Safe to call
21009
+ * when nothing has been initialized yet (e.g. first connect()).
21010
+ */
21011
+ async teardownTransportAndSession() {
21012
+ const session = this._clientSession;
21013
+ const transport = this._transport;
21014
+ if (session) {
21015
+ try {
21016
+ await session.disconnect();
21017
+ } catch (error) {
21018
+ logger$1.warn("[SignalWire] Error disconnecting previous session:", error);
21019
+ }
21020
+ session.destroy();
21021
+ }
21022
+ if (transport) transport.destroy();
21023
+ this._clientSession = void 0;
21024
+ this._publicSession = void 0;
21025
+ this._transport = void 0;
21026
+ this._attachManager = void 0;
21027
+ }
20948
21028
  async waitAuthentication() {
20949
21029
  await (0, import_cjs$1.firstValueFrom)(this.ready$.pipe((0, import_cjs$1.filter)((ready$1) => ready$1 === true)));
20950
21030
  }
@@ -20964,22 +21044,27 @@ var SignalWire = class extends Destroyable {
20964
21044
  params: {}
20965
21045
  }));
20966
21046
  this._isRegistered$.next(true);
21047
+ return;
20967
21048
  } catch (error) {
21049
+ if (!this._deps.credential.token) {
21050
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
21051
+ throw error;
21052
+ }
20968
21053
  logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
20969
- if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
21054
+ try {
21055
+ await this._clientSession.reauthenticate(this._deps.credential.token);
20970
21056
  logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
20971
21057
  await this._transport.execute(RPCExecute({
20972
21058
  method: "subscriber.online",
20973
21059
  params: {}
20974
21060
  }));
20975
21061
  this._isRegistered$.next(true);
20976
- }).catch((reauthError) => {
21062
+ } catch (reauthError) {
20977
21063
  logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
20978
21064
  const registerError = new InvalidCredentialsError("Failed to register subscriber, and reauthentication attempt also failed. Please check your credentials.", { cause: reauthError instanceof Error ? reauthError : new Error(String(reauthError), { cause: reauthError }) });
20979
21065
  this._errors$.next(registerError);
20980
- });
20981
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
20982
- throw error;
21066
+ throw registerError;
21067
+ }
20983
21068
  }
20984
21069
  }
20985
21070
  /**