@signalwire/js 4.0.0-dev-20260417161936 → 4.0.0-dev-20260421180202

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/browser.mjs CHANGED
@@ -1125,9 +1125,9 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1125
1125
  var ObjectUnsubscribedError_1$1 = require_ObjectUnsubscribedError();
1126
1126
  var arrRemove_1$6 = require_arrRemove();
1127
1127
  var errorContext_1 = require_errorContext();
1128
- var Subject$2 = function(_super) {
1129
- __extends$13(Subject$3, _super);
1130
- function Subject$3() {
1128
+ var Subject$1 = function(_super) {
1129
+ __extends$13(Subject$2, _super);
1130
+ function Subject$2() {
1131
1131
  var _this = _super.call(this) || this;
1132
1132
  _this.closed = false;
1133
1133
  _this.currentObservers = null;
@@ -1137,15 +1137,15 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1137
1137
  _this.thrownError = null;
1138
1138
  return _this;
1139
1139
  }
1140
- Subject$3.prototype.lift = function(operator) {
1140
+ Subject$2.prototype.lift = function(operator) {
1141
1141
  var subject = new AnonymousSubject(this, this);
1142
1142
  subject.operator = operator;
1143
1143
  return subject;
1144
1144
  };
1145
- Subject$3.prototype._throwIfClosed = function() {
1145
+ Subject$2.prototype._throwIfClosed = function() {
1146
1146
  if (this.closed) throw new ObjectUnsubscribedError_1$1.ObjectUnsubscribedError();
1147
1147
  };
1148
- Subject$3.prototype.next = function(value) {
1148
+ Subject$2.prototype.next = function(value) {
1149
1149
  var _this = this;
1150
1150
  errorContext_1.errorContext(function() {
1151
1151
  var e_1, _a;
@@ -1166,7 +1166,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1166
1166
  }
1167
1167
  });
1168
1168
  };
1169
- Subject$3.prototype.error = function(err) {
1169
+ Subject$2.prototype.error = function(err) {
1170
1170
  var _this = this;
1171
1171
  errorContext_1.errorContext(function() {
1172
1172
  _this._throwIfClosed();
@@ -1178,7 +1178,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1178
1178
  }
1179
1179
  });
1180
1180
  };
1181
- Subject$3.prototype.complete = function() {
1181
+ Subject$2.prototype.complete = function() {
1182
1182
  var _this = this;
1183
1183
  errorContext_1.errorContext(function() {
1184
1184
  _this._throwIfClosed();
@@ -1189,11 +1189,11 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1189
1189
  }
1190
1190
  });
1191
1191
  };
1192
- Subject$3.prototype.unsubscribe = function() {
1192
+ Subject$2.prototype.unsubscribe = function() {
1193
1193
  this.isStopped = this.closed = true;
1194
1194
  this.observers = this.currentObservers = null;
1195
1195
  };
1196
- Object.defineProperty(Subject$3.prototype, "observed", {
1196
+ Object.defineProperty(Subject$2.prototype, "observed", {
1197
1197
  get: function() {
1198
1198
  var _a;
1199
1199
  return ((_a = this.observers) === null || _a === void 0 ? void 0 : _a.length) > 0;
@@ -1201,16 +1201,16 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1201
1201
  enumerable: false,
1202
1202
  configurable: true
1203
1203
  });
1204
- Subject$3.prototype._trySubscribe = function(subscriber) {
1204
+ Subject$2.prototype._trySubscribe = function(subscriber) {
1205
1205
  this._throwIfClosed();
1206
1206
  return _super.prototype._trySubscribe.call(this, subscriber);
1207
1207
  };
1208
- Subject$3.prototype._subscribe = function(subscriber) {
1208
+ Subject$2.prototype._subscribe = function(subscriber) {
1209
1209
  this._throwIfClosed();
1210
1210
  this._checkFinalizedStatuses(subscriber);
1211
1211
  return this._innerSubscribe(subscriber);
1212
1212
  };
1213
- Subject$3.prototype._innerSubscribe = function(subscriber) {
1213
+ Subject$2.prototype._innerSubscribe = function(subscriber) {
1214
1214
  var _this = this;
1215
1215
  var _a = this, hasError = _a.hasError, isStopped = _a.isStopped, observers = _a.observers;
1216
1216
  if (hasError || isStopped) return Subscription_1$6.EMPTY_SUBSCRIPTION;
@@ -1221,22 +1221,22 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1221
1221
  arrRemove_1$6.arrRemove(observers, subscriber);
1222
1222
  });
1223
1223
  };
1224
- Subject$3.prototype._checkFinalizedStatuses = function(subscriber) {
1224
+ Subject$2.prototype._checkFinalizedStatuses = function(subscriber) {
1225
1225
  var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, isStopped = _a.isStopped;
1226
1226
  if (hasError) subscriber.error(thrownError);
1227
1227
  else if (isStopped) subscriber.complete();
1228
1228
  };
1229
- Subject$3.prototype.asObservable = function() {
1229
+ Subject$2.prototype.asObservable = function() {
1230
1230
  var observable = new Observable_1$24.Observable();
1231
1231
  observable.source = this;
1232
1232
  return observable;
1233
1233
  };
1234
- Subject$3.create = function(destination, source) {
1234
+ Subject$2.create = function(destination, source) {
1235
1235
  return new AnonymousSubject(destination, source);
1236
1236
  };
1237
- return Subject$3;
1237
+ return Subject$2;
1238
1238
  }(Observable_1$24.Observable);
1239
- exports.Subject = Subject$2;
1239
+ exports.Subject = Subject$1;
1240
1240
  var AnonymousSubject = function(_super) {
1241
1241
  __extends$13(AnonymousSubject$1, _super);
1242
1242
  function AnonymousSubject$1(destination, source) {
@@ -1262,7 +1262,7 @@ var require_Subject = /* @__PURE__ */ __commonJSMin(((exports) => {
1262
1262
  return (_b = (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber)) !== null && _b !== void 0 ? _b : Subscription_1$6.EMPTY_SUBSCRIPTION;
1263
1263
  };
1264
1264
  return AnonymousSubject$1;
1265
- }(Subject$2);
1265
+ }(Subject$1);
1266
1266
  exports.AnonymousSubject = AnonymousSubject;
1267
1267
  }));
1268
1268
 
@@ -1289,37 +1289,37 @@ var require_BehaviorSubject = /* @__PURE__ */ __commonJSMin(((exports) => {
1289
1289
  })();
1290
1290
  Object.defineProperty(exports, "__esModule", { value: true });
1291
1291
  exports.BehaviorSubject = void 0;
1292
- var BehaviorSubject$2 = function(_super) {
1293
- __extends$12(BehaviorSubject$3, _super);
1294
- function BehaviorSubject$3(_value) {
1292
+ var BehaviorSubject$1 = function(_super) {
1293
+ __extends$12(BehaviorSubject$2, _super);
1294
+ function BehaviorSubject$2(_value) {
1295
1295
  var _this = _super.call(this) || this;
1296
1296
  _this._value = _value;
1297
1297
  return _this;
1298
1298
  }
1299
- Object.defineProperty(BehaviorSubject$3.prototype, "value", {
1299
+ Object.defineProperty(BehaviorSubject$2.prototype, "value", {
1300
1300
  get: function() {
1301
1301
  return this.getValue();
1302
1302
  },
1303
1303
  enumerable: false,
1304
1304
  configurable: true
1305
1305
  });
1306
- BehaviorSubject$3.prototype._subscribe = function(subscriber) {
1306
+ BehaviorSubject$2.prototype._subscribe = function(subscriber) {
1307
1307
  var subscription = _super.prototype._subscribe.call(this, subscriber);
1308
1308
  !subscription.closed && subscriber.next(this._value);
1309
1309
  return subscription;
1310
1310
  };
1311
- BehaviorSubject$3.prototype.getValue = function() {
1311
+ BehaviorSubject$2.prototype.getValue = function() {
1312
1312
  var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, _value = _a._value;
1313
1313
  if (hasError) throw thrownError;
1314
1314
  this._throwIfClosed();
1315
1315
  return _value;
1316
1316
  };
1317
- BehaviorSubject$3.prototype.next = function(value) {
1317
+ BehaviorSubject$2.prototype.next = function(value) {
1318
1318
  _super.prototype.next.call(this, this._value = value);
1319
1319
  };
1320
- return BehaviorSubject$3;
1320
+ return BehaviorSubject$2;
1321
1321
  }(require_Subject().Subject);
1322
- exports.BehaviorSubject = BehaviorSubject$2;
1322
+ exports.BehaviorSubject = BehaviorSubject$1;
1323
1323
  }));
1324
1324
 
1325
1325
  //#endregion
@@ -3246,7 +3246,7 @@ var require_firstValueFrom = /* @__PURE__ */ __commonJSMin(((exports) => {
3246
3246
  exports.firstValueFrom = void 0;
3247
3247
  var EmptyError_1$5 = require_EmptyError();
3248
3248
  var Subscriber_1$2 = require_Subscriber();
3249
- function firstValueFrom$6(source, config) {
3249
+ function firstValueFrom$7(source, config) {
3250
3250
  var hasConfig = typeof config === "object";
3251
3251
  return new Promise(function(resolve, reject) {
3252
3252
  var subscriber = new Subscriber_1$2.SafeSubscriber({
@@ -3263,7 +3263,7 @@ var require_firstValueFrom = /* @__PURE__ */ __commonJSMin(((exports) => {
3263
3263
  source.subscribe(subscriber);
3264
3264
  });
3265
3265
  }
3266
- exports.firstValueFrom = firstValueFrom$6;
3266
+ exports.firstValueFrom = firstValueFrom$7;
3267
3267
  }));
3268
3268
 
3269
3269
  //#endregion
@@ -4140,7 +4140,7 @@ var require_timer = /* @__PURE__ */ __commonJSMin(((exports) => {
4140
4140
  var async_1$11 = require_async();
4141
4141
  var isScheduler_1 = require_isScheduler();
4142
4142
  var isDate_1$1 = require_isDate();
4143
- function timer$2(dueTime, intervalOrScheduler, scheduler) {
4143
+ function timer$3(dueTime, intervalOrScheduler, scheduler) {
4144
4144
  if (dueTime === void 0) dueTime = 0;
4145
4145
  if (scheduler === void 0) scheduler = async_1$11.async;
4146
4146
  var intervalDuration = -1;
@@ -4159,7 +4159,7 @@ var require_timer = /* @__PURE__ */ __commonJSMin(((exports) => {
4159
4159
  }, due);
4160
4160
  });
4161
4161
  }
4162
- exports.timer = timer$2;
4162
+ exports.timer = timer$3;
4163
4163
  }));
4164
4164
 
4165
4165
  //#endregion
@@ -4327,13 +4327,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4327
4327
  var innerFrom_1$28 = require_innerFrom();
4328
4328
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4329
4329
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4330
- function race$4() {
4330
+ function race$5() {
4331
4331
  var sources = [];
4332
4332
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4333
4333
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4334
4334
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4335
4335
  }
4336
- exports.race = race$4;
4336
+ exports.race = race$5;
4337
4337
  function raceInit(sources) {
4338
4338
  return function(subscriber) {
4339
4339
  var subscriptions = [];
@@ -5331,7 +5331,7 @@ var require_take = /* @__PURE__ */ __commonJSMin(((exports) => {
5331
5331
  var empty_1$3 = require_empty();
5332
5332
  var lift_1$46 = require_lift();
5333
5333
  var OperatorSubscriber_1$35 = require_OperatorSubscriber();
5334
- function take$8(count$1) {
5334
+ function take$9(count$1) {
5335
5335
  return count$1 <= 0 ? function() {
5336
5336
  return empty_1$3.EMPTY;
5337
5337
  } : lift_1$46.operate(function(source, subscriber) {
@@ -5344,7 +5344,7 @@ var require_take = /* @__PURE__ */ __commonJSMin(((exports) => {
5344
5344
  }));
5345
5345
  });
5346
5346
  }
5347
- exports.take = take$8;
5347
+ exports.take = take$9;
5348
5348
  }));
5349
5349
 
5350
5350
  //#endregion
@@ -8933,12 +8933,12 @@ var require_cjs = /* @__PURE__ */ __commonJSMin(((exports) => {
8933
8933
 
8934
8934
  //#endregion
8935
8935
  //#region src/behaviors/Destroyable.ts
8936
- var import_cjs$29 = require_cjs();
8936
+ var import_cjs$28 = require_cjs();
8937
8937
  var Destroyable = class {
8938
8938
  constructor() {
8939
8939
  this.subscriptions = [];
8940
8940
  this.subjects = [];
8941
- this._destroyed$ = new import_cjs$29.Subject();
8941
+ this._destroyed$ = new import_cjs$28.Subject();
8942
8942
  }
8943
8943
  destroy() {
8944
8944
  this._observableCache?.clear();
@@ -8974,7 +8974,7 @@ var Destroyable = class {
8974
8974
  this._observableCache ??= /* @__PURE__ */ new Map();
8975
8975
  let cached = this._observableCache.get(publicKey);
8976
8976
  if (!cached) {
8977
- cached = factory().pipe((0, import_cjs$29.observeOn)(import_cjs$29.asapScheduler));
8977
+ cached = factory().pipe((0, import_cjs$28.observeOn)(import_cjs$28.asapScheduler));
8978
8978
  this._observableCache.set(publicKey, cached);
8979
8979
  }
8980
8980
  return cached;
@@ -8988,24 +8988,24 @@ var Destroyable = class {
8988
8988
  * Do NOT use for observables consumed internally by the SDK.
8989
8989
  */
8990
8990
  deferEmission(observable) {
8991
- return observable.pipe((0, import_cjs$29.observeOn)(import_cjs$29.asapScheduler));
8991
+ return observable.pipe((0, import_cjs$28.observeOn)(import_cjs$28.asapScheduler));
8992
8992
  }
8993
8993
  subscribeTo(observable, observerOrNext) {
8994
8994
  const subscription = observable.subscribe(observerOrNext);
8995
8995
  this.subscriptions.push(subscription);
8996
8996
  }
8997
8997
  createSubject() {
8998
- const subject = new import_cjs$29.Subject();
8998
+ const subject = new import_cjs$28.Subject();
8999
8999
  this.subjects.push(subject);
9000
9000
  return subject;
9001
9001
  }
9002
9002
  createReplaySubject(bufferSize, windowTime$1) {
9003
- const subject = new import_cjs$29.ReplaySubject(bufferSize, windowTime$1);
9003
+ const subject = new import_cjs$28.ReplaySubject(bufferSize, windowTime$1);
9004
9004
  this.subjects.push(subject);
9005
9005
  return subject;
9006
9006
  }
9007
9007
  createBehaviorSubject(initialValue) {
9008
- const subject = new import_cjs$29.BehaviorSubject(initialValue);
9008
+ const subject = new import_cjs$28.BehaviorSubject(initialValue);
9009
9009
  this.subjects.push(subject);
9010
9010
  return subject;
9011
9011
  }
@@ -9602,7 +9602,6 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
9602
9602
 
9603
9603
  //#endregion
9604
9604
  //#region src/controllers/HTTPRequestController.ts
9605
- var import_cjs$28 = require_cjs();
9606
9605
  const logger$29 = getLogger();
9607
9606
  const GET_PARAMS = {
9608
9607
  method: "GET",
@@ -9615,7 +9614,7 @@ const POST_PARAMS = {
9615
9614
  "Content-Type": "application/json"
9616
9615
  }
9617
9616
  };
9618
- var HTTPRequestController = class HTTPRequestController {
9617
+ var HTTPRequestController = class HTTPRequestController extends Destroyable {
9619
9618
  static {
9620
9619
  this.defaultMaxRetries = 3;
9621
9620
  }
@@ -9636,11 +9635,12 @@ var HTTPRequestController = class HTTPRequestController {
9636
9635
  ]);
9637
9636
  }
9638
9637
  constructor(baseURL, getCredential, options = {}) {
9638
+ super();
9639
9639
  this.baseURL = baseURL;
9640
9640
  this.getCredential = getCredential;
9641
- this._responses$ = new import_cjs$28.Subject();
9642
- this._errors$ = new import_cjs$28.Subject();
9643
- this._status$ = new import_cjs$28.BehaviorSubject("idle");
9641
+ this._responses$ = this.createSubject();
9642
+ this._errors$ = this.createSubject();
9643
+ this._status$ = this.createBehaviorSubject("idle");
9644
9644
  this.maxRetries = options.maxRetries ?? HTTPRequestController.defaultMaxRetries;
9645
9645
  this.retryDelayMin = options.retryDelayMin ?? HTTPRequestController.defaultRetryDelayMinMs;
9646
9646
  this.retryDelayMax = options.retryDelayMax ?? HTTPRequestController.defaultRetryDelayMaxMs;
@@ -9950,6 +9950,12 @@ const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
9950
9950
  const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
9951
9951
  /** Maximum recovery attempts before emitting 'max_attempts_reached'. */
9952
9952
  const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
9953
+ /** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
9954
+ const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
9955
+ /** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
9956
+ const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
9957
+ /** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
9958
+ const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
9953
9959
  /** Whether to persist device selections to storage by default. */
9954
9960
  const DEFAULT_PERSIST_DEVICE_SELECTION = true;
9955
9961
  /** Whether to auto-apply device changes to active calls by default. */
@@ -11903,7 +11909,7 @@ var PreflightRunner = class extends Destroyable {
11903
11909
  const candidateTypes = /* @__PURE__ */ new Set();
11904
11910
  const startTime = Date.now();
11905
11911
  const gatheringComplete = new Promise((resolve) => {
11906
- const timer$3 = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);
11912
+ const timer$4 = setTimeout(resolve, ICE_GATHERING_TIMEOUT_MS);
11907
11913
  peerConnection.onicecandidate = (event) => {
11908
11914
  if (event.candidate) {
11909
11915
  const candidateStr = event.candidate.candidate;
@@ -11911,7 +11917,7 @@ var PreflightRunner = class extends Destroyable {
11911
11917
  if (candidateStr.includes("typ srflx")) candidateTypes.add("srflx");
11912
11918
  if (candidateStr.includes("typ relay")) candidateTypes.add("relay");
11913
11919
  } else {
11914
- clearTimeout(timer$3);
11920
+ clearTimeout(timer$4);
11915
11921
  resolve();
11916
11922
  }
11917
11923
  };
@@ -12320,13 +12326,11 @@ var AttachManager = class {
12320
12326
  this.deviceController = deviceController;
12321
12327
  this.reconnectCallsTimeout = reconnectCallsTimeout;
12322
12328
  this.attachKey = attachKey;
12329
+ this.writeQueue = Promise.resolve();
12323
12330
  }
12324
12331
  async detachAll() {
12325
- const attached = await this.readAttached();
12326
- for (const callId of Object.keys(attached)) await this.detach({
12327
- id: callId,
12328
- nodeId: attached[callId].nodeId,
12329
- mediaDirections: attached[callId].mediaDirections
12332
+ await this.mutate((attached) => {
12333
+ return {};
12330
12334
  });
12331
12335
  }
12332
12336
  setSession(session) {
@@ -12347,31 +12351,47 @@ var AttachManager = class {
12347
12351
  logger$22.warn("[AttachManager] Failed to write attached calls to storage", error);
12348
12352
  }
12349
12353
  }
12354
+ /**
12355
+ * Serialize a read-modify-write operation against the attached-calls
12356
+ * storage. The mutator receives the current state and returns the new
12357
+ * state. Concurrent calls queue behind the in-flight one so writes never
12358
+ * interleave.
12359
+ */
12360
+ async mutate(mutator) {
12361
+ const next = this.writeQueue.then(async () => {
12362
+ const updated = await mutator(await this.readAttached());
12363
+ await this.writeAttached(updated);
12364
+ });
12365
+ this.writeQueue = next.catch(() => void 0);
12366
+ return next;
12367
+ }
12350
12368
  async attach(call) {
12351
12369
  if (!call.to) {
12352
12370
  logger$22.warn("[AttachManager] Skip attach for calls with no destination");
12353
12371
  return;
12354
12372
  }
12373
+ const destination = call.to;
12355
12374
  const attachment = {
12356
12375
  nodeId: call.nodeId,
12357
- destination: call.to,
12376
+ destination,
12358
12377
  mediaDirections: call.mediaDirections,
12359
12378
  audioInputDevice: call.mediaDirections.audio !== "inactive" ? this.deviceController.selectedAudioInputDevice : null,
12360
12379
  videoInputDevice: call.mediaDirections.video !== "inactive" ? this.deviceController.selectedVideoInputDevice : null,
12361
12380
  attachedAt: Date.now()
12362
12381
  };
12363
- const updated = {
12364
- ...await this.readAttached(),
12382
+ await this.mutate((attached) => ({
12383
+ ...attached,
12365
12384
  [call.id]: attachment
12366
- };
12367
- await this.writeAttached(updated);
12385
+ }));
12368
12386
  }
12369
12387
  async detach(call) {
12370
- const { [call.id]: _, ...remaining } = await this.readAttached();
12371
- await this.writeAttached(remaining);
12388
+ await this.mutate((attached) => {
12389
+ const { [call.id]: _, ...remaining } = attached;
12390
+ return remaining;
12391
+ });
12372
12392
  }
12373
12393
  async flush() {
12374
- await this.writeAttached({});
12394
+ await this.mutate(() => ({}));
12375
12395
  }
12376
12396
  /**
12377
12397
  * Reattach to previously active calls by sending verto.invite with
@@ -12443,20 +12463,31 @@ var AttachManager = class {
12443
12463
  };
12444
12464
  }
12445
12465
  /**
12446
- * Consume stored attachment data for a pending call (used by session-level
12447
- * verto.attach handler as a future path when server supports it).
12466
+ * Look up stored attachment data for a call id and return CallOptions
12467
+ * suitable for rehydrating a reattached call. Returns undefined when no
12468
+ * matching entry exists in storage.
12469
+ *
12470
+ * Used by the session-level verto.attach handler when the server pushes
12471
+ * an attach event for a call the client doesn't have an object for yet
12472
+ * (e.g. after a reload).
12448
12473
  */
12449
- consumePendingAttachment(_callId) {}
12474
+ async consumePendingAttachment(callId) {
12475
+ const attachment = (await this.readAttached())[callId];
12476
+ if (!attachment) return;
12477
+ return this.buildCallOptions(attachment);
12478
+ }
12450
12479
  async detachExpired() {
12451
- const attached = await this.readAttached();
12452
12480
  const now = Date.now();
12453
12481
  const timeout$5 = this.reconnectCallsTimeout;
12454
- const expired = Object.entries(attached).filter(([, attachment]) => now - attachment.attachedAt > timeout$5);
12455
- if (expired.length > 0) {
12482
+ await this.mutate((attached) => {
12456
12483
  const remaining = { ...attached };
12457
- for (const [callId] of expired) delete remaining[callId];
12458
- await this.writeAttached(remaining);
12459
- }
12484
+ let changed = false;
12485
+ for (const [callId, attachment] of Object.entries(attached)) if (now - attachment.attachedAt > timeout$5) {
12486
+ delete remaining[callId];
12487
+ changed = true;
12488
+ }
12489
+ return changed ? remaining : attached;
12490
+ });
12460
12491
  }
12461
12492
  };
12462
12493
 
@@ -12503,12 +12534,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12503
12534
  exports.race = void 0;
12504
12535
  var argsOrArgArray_1 = require_argsOrArgArray();
12505
12536
  var raceWith_1$1 = require_raceWith();
12506
- function race$3() {
12537
+ function race$4() {
12507
12538
  var args = [];
12508
12539
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12509
12540
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12510
12541
  }
12511
- exports.race = race$3;
12542
+ exports.race = race$4;
12512
12543
  }));
12513
12544
 
12514
12545
  //#endregion
@@ -14912,12 +14943,12 @@ var ICEGatheringController = class extends Destroyable {
14912
14943
  ...this.peerConnection.getConfiguration(),
14913
14944
  iceTransportPolicy: "relay"
14914
14945
  });
14915
- if (!(this.peerConnection.connectionState === "connected")) this.peerConnection.restartIce();
14946
+ this.peerConnection.restartIce();
14916
14947
  }
14917
- removeTimer(timer$3) {
14918
- if (this[timer$3]) {
14919
- clearTimeout(this[timer$3]);
14920
- this[timer$3] = void 0;
14948
+ removeTimer(timer$4) {
14949
+ if (this[timer$4]) {
14950
+ clearTimeout(this[timer$4]);
14951
+ this[timer$4] = void 0;
14921
14952
  }
14922
14953
  }
14923
14954
  clearAllTimers() {
@@ -15883,9 +15914,6 @@ var RTCPeerConnectionController = class extends Destroyable {
15883
15914
  negotiationEnded() {
15884
15915
  this._isNegotiating$.next(false);
15885
15916
  }
15886
- restarIce() {
15887
- this.peerConnection?.restartIce();
15888
- }
15889
15917
  /**
15890
15918
  * Trigger an ICE restart through the existing negotiation pipeline.
15891
15919
  *
@@ -15925,7 +15953,10 @@ var RTCPeerConnectionController = class extends Destroyable {
15925
15953
  if (policyChanged) this.restoreIceTransportPolicy();
15926
15954
  throw error;
15927
15955
  }
15928
- if (policyChanged) this.restoreIceTransportPolicy();
15956
+ 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) => {
15957
+ logger$15.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
15958
+ this.restoreIceTransportPolicy();
15959
+ });
15929
15960
  }
15930
15961
  restoreIceTransportPolicy() {
15931
15962
  try {
@@ -17093,11 +17124,11 @@ var RTCStatsMonitor = class extends Destroyable {
17093
17124
  let availableOutgoingBitrate;
17094
17125
  report.forEach((stat) => {
17095
17126
  if (isInboundRtpStat(stat)) if (stat.kind === "audio") {
17096
- audioPacketsReceived += stat.packetsReceived ?? this.lastAudioPacketsReceived;
17127
+ audioPacketsReceived += stat.packetsReceived ?? 0;
17097
17128
  audioPacketsLost += stat.packetsLost ?? 0;
17098
17129
  audioJitter = Math.max(audioJitter, (stat.jitter ?? 0) * 1e3);
17099
17130
  } else {
17100
- videoPacketsReceived += stat.packetsReceived ?? this.lastVideoPacketsReceived;
17131
+ videoPacketsReceived += stat.packetsReceived ?? 0;
17101
17132
  videoPacketsLost += stat.packetsLost ?? 0;
17102
17133
  }
17103
17134
  if (isCandidatePairStat(stat) && stat.state === "succeeded" && stat.nominated) {
@@ -18147,10 +18178,10 @@ var WebRTCCall = class extends Destroyable {
18147
18178
  try {
18148
18179
  if (this.vertoManager.requestIceRestartAll) await this.vertoManager.requestIceRestartAll(relayOnly);
18149
18180
  else await this.vertoManager.requestIceRestart?.(relayOnly);
18150
- return true;
18151
18181
  } catch {
18152
18182
  return false;
18153
18183
  }
18184
+ return this.waitForPeerConnectionConnected();
18154
18185
  },
18155
18186
  disableVideo: () => {
18156
18187
  try {
@@ -18242,6 +18273,27 @@ var WebRTCCall = class extends Destroyable {
18242
18273
  }
18243
18274
  }
18244
18275
  /**
18276
+ * Wait for the underlying RTCPeerConnection to reach 'connected' after
18277
+ * triggering an ICE restart. Resolves true on success, false on failure
18278
+ * or if the state doesn't transition within the configured timeout.
18279
+ *
18280
+ * Polls connectionState directly because the recovery manager already
18281
+ * wraps this call in its own withTimeout(); a separate listener-based
18282
+ * implementation would race the outer timeout in subtle ways.
18283
+ */
18284
+ async waitForPeerConnectionConnected() {
18285
+ const pc = this.rtcPeerConnection;
18286
+ if (!pc) return false;
18287
+ const deadline = Date.now() + PEER_CONNECTION_RECOVERY_WAIT_MS;
18288
+ while (true) {
18289
+ const state = pc.connectionState;
18290
+ if (state === "connected") return true;
18291
+ if (state === "failed" || state === "closed") return false;
18292
+ if (Date.now() >= deadline) return false;
18293
+ await new Promise((resolve) => setTimeout(resolve, PEER_CONNECTION_RECOVERY_POLL_MS));
18294
+ }
18295
+ }
18296
+ /**
18245
18297
  * @internal Stop and destroy resilience subsystems (on disconnect/destroy).
18246
18298
  * Clears references so they can be re-created on reconnect.
18247
18299
  */
@@ -18378,8 +18430,13 @@ var WebRTCCall = class extends Destroyable {
18378
18430
  const cached = this._customSubscriptions.get(eventType);
18379
18431
  if (cached) return cached;
18380
18432
  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$));
18433
+ this._sendVertoSubscribe(eventType).then(() => {
18434
+ this._customSubscriptions.set(eventType, filtered$);
18435
+ }, (error) => {
18436
+ this._customSubscriptions.delete(eventType);
18437
+ logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
18438
+ });
18381
18439
  this._customSubscriptions.set(eventType, filtered$);
18382
- this._sendVertoSubscribe(eventType);
18383
18440
  return filtered$;
18384
18441
  }
18385
18442
  get webrtcMessages$() {
@@ -18505,26 +18562,21 @@ var WebRTCCall = class extends Destroyable {
18505
18562
  }
18506
18563
  /**
18507
18564
  * @internal Send a verto.subscribe message to add an event type to the
18508
- * server's subscription list for this call. Best-effort failures are
18509
- * logged but don't prevent the filtered observable from being returned.
18565
+ * server's subscription list for this call. Returns the underlying RPC
18566
+ * promise so callers can decide whether to cache the observable on success
18567
+ * or retry on failure.
18510
18568
  */
18511
- _sendVertoSubscribe(eventType) {
18512
- try {
18513
- const message = VertoSubscribe({
18514
- sessid: this.id,
18515
- eventChannel: [eventType]
18516
- });
18517
- const params = {
18518
- callID: this.id,
18519
- node_id: this.vertoManager.nodeId ?? "",
18520
- message
18521
- };
18522
- this.clientSession.execute(WebrtcVerto(params)).catch((error) => {
18523
- logger$11.warn(`[Call] verto.subscribe for '${eventType}' failed (non-fatal):`, error);
18524
- });
18525
- } catch (error) {
18526
- logger$11.warn(`[Call] Failed to send verto.subscribe for '${eventType}':`, error);
18527
- }
18569
+ async _sendVertoSubscribe(eventType) {
18570
+ const message = VertoSubscribe({
18571
+ sessid: this.id,
18572
+ eventChannel: [eventType]
18573
+ });
18574
+ const params = {
18575
+ callID: this.id,
18576
+ node_id: this.vertoManager.nodeId ?? "",
18577
+ message
18578
+ };
18579
+ await this.clientSession.execute(WebrtcVerto(params));
18528
18580
  }
18529
18581
  };
18530
18582
 
@@ -18541,11 +18593,21 @@ function inferCallErrorKind(error) {
18541
18593
  if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
18542
18594
  return "internal";
18543
18595
  }
18596
+ /** JSON-RPC error codes that ClientSessionManager treats as recoverable at the
18597
+ * session level. Surfacing one of these against an in-flight call should not
18598
+ * destroy the call, because the session will reauthenticate and any pending
18599
+ * RPC can then be retried. */
18600
+ const RECOVERABLE_RPC_CODES = new Set([
18601
+ RPC_ERROR_REQUESTER_VALIDATION_FAILED,
18602
+ RPC_ERROR_AUTHENTICATION_FAILED,
18603
+ RPC_ERROR_INVALID_PARAMS
18604
+ ]);
18544
18605
  /** Determines whether an error should be fatal (destroy the call). */
18545
18606
  function isFatalError(error) {
18546
18607
  if (error instanceof VertoPongError) return false;
18547
18608
  if (error instanceof MediaTrackError) return false;
18548
18609
  if (error instanceof RPCTimeoutError) return false;
18610
+ if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
18549
18611
  return true;
18550
18612
  }
18551
18613
  /**
@@ -18983,10 +19045,10 @@ var PendingRPC = class PendingRPC {
18983
19045
  }
18984
19046
  let isSettled = false;
18985
19047
  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) => {
18986
- const timer$3 = setTimeout(() => {
19048
+ const timer$4 = setTimeout(() => {
18987
19049
  subscriber.error(new RPCTimeoutError(request.id, timeoutMs));
18988
19050
  }, timeoutMs);
18989
- return () => clearTimeout(timer$3);
19051
+ return () => clearTimeout(timer$4);
18990
19052
  }), signal ? new import_cjs$8.Observable((subscriber) => {
18991
19053
  const abortHandler = () => {
18992
19054
  subscriber.error(new DOMException("The operation was aborted", "AbortError"));
@@ -19386,7 +19448,6 @@ var ClientSessionManager = class extends Destroyable {
19386
19448
  displayDirection: invite.display_direction,
19387
19449
  userVariables: invite.userVariables
19388
19450
  });
19389
- await (0, import_cjs$7.firstValueFrom)(callSession.status$);
19390
19451
  this._calls$.next({
19391
19452
  [`${callSession.id}`]: callSession,
19392
19453
  ...this._calls$.value
@@ -19407,7 +19468,7 @@ var ClientSessionManager = class extends Destroyable {
19407
19468
  logger$8.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
19408
19469
  return;
19409
19470
  }
19410
- const storedOptions = this.attachManager.consumePendingAttachment(callID);
19471
+ const storedOptions = await this.attachManager.consumePendingAttachment(callID);
19411
19472
  logger$8.debug(`[Session] Creating reattached call for callID: ${callID}`);
19412
19473
  const callSession = await this.createCall({
19413
19474
  nodeId: attach.node_id,
@@ -19419,7 +19480,6 @@ var ClientSessionManager = class extends Destroyable {
19419
19480
  reattach: true,
19420
19481
  ...storedOptions
19421
19482
  });
19422
- await (0, import_cjs$7.firstValueFrom)(callSession.status$);
19423
19483
  this._calls$.next({
19424
19484
  [`${callSession.id}`]: callSession,
19425
19485
  ...this._calls$.value
@@ -20142,10 +20202,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
20142
20202
  else this._status$.next("disconnected");
20143
20203
  }
20144
20204
  reconnect() {
20145
- if (this.shouldReconnect) {
20146
- this._status$.next("reconnecting");
20147
- this.scheduleReconnection();
20148
- } else this._status$.next("disconnected");
20205
+ this.shouldReconnect = true;
20206
+ this._status$.next("reconnecting");
20207
+ this.scheduleReconnection();
20149
20208
  }
20150
20209
  send(data) {
20151
20210
  if (this._status$.value === "connected" && this.socket?.readyState === 1) {
@@ -20706,6 +20765,7 @@ var SignalWire = class extends Destroyable {
20706
20765
  * `'reconnecting'`, `'disconnecting'`, or `'disconnected'`.
20707
20766
  */
20708
20767
  async connect() {
20768
+ await this.teardownTransportAndSession();
20709
20769
  try {
20710
20770
  const subscriber = this._subscriber$.value;
20711
20771
  if (!subscriber) throw new UnexpectedError("Subscriber not initialized before connect");
@@ -20937,10 +20997,30 @@ var SignalWire = class extends Destroyable {
20937
20997
  this._refreshTimerId = void 0;
20938
20998
  }
20939
20999
  this._diagnosticsCollector?.record("connection", "disconnected");
20940
- await this._clientSession.disconnect();
20941
- this._clientSession.destroy();
21000
+ await this.teardownTransportAndSession();
20942
21001
  this._isConnected$.next(false);
20943
21002
  }
21003
+ /**
21004
+ * Tear down the current transport / session / attach manager. Safe to call
21005
+ * when nothing has been initialized yet (e.g. first connect()).
21006
+ */
21007
+ async teardownTransportAndSession() {
21008
+ const session = this._clientSession;
21009
+ const transport = this._transport;
21010
+ if (session) {
21011
+ try {
21012
+ await session.disconnect();
21013
+ } catch (error) {
21014
+ logger$1.warn("[SignalWire] Error disconnecting previous session:", error);
21015
+ }
21016
+ session.destroy();
21017
+ }
21018
+ if (transport) transport.destroy();
21019
+ this._clientSession = void 0;
21020
+ this._publicSession = void 0;
21021
+ this._transport = void 0;
21022
+ this._attachManager = void 0;
21023
+ }
20944
21024
  async waitAuthentication() {
20945
21025
  await (0, import_cjs$1.firstValueFrom)(this.ready$.pipe((0, import_cjs$1.filter)((ready$1) => ready$1 === true)));
20946
21026
  }
@@ -20960,22 +21040,27 @@ var SignalWire = class extends Destroyable {
20960
21040
  params: {}
20961
21041
  }));
20962
21042
  this._isRegistered$.next(true);
21043
+ return;
20963
21044
  } catch (error) {
21045
+ if (!this._deps.credential.token) {
21046
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
21047
+ throw error;
21048
+ }
20964
21049
  logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
20965
- if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
21050
+ try {
21051
+ await this._clientSession.reauthenticate(this._deps.credential.token);
20966
21052
  logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
20967
21053
  await this._transport.execute(RPCExecute({
20968
21054
  method: "subscriber.online",
20969
21055
  params: {}
20970
21056
  }));
20971
21057
  this._isRegistered$.next(true);
20972
- }).catch((reauthError) => {
21058
+ } catch (reauthError) {
20973
21059
  logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
20974
21060
  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 }) });
20975
21061
  this._errors$.next(registerError);
20976
- });
20977
- this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
20978
- throw error;
21062
+ throw registerError;
21063
+ }
20979
21064
  }
20980
21065
  }
20981
21066
  /**