@signalwire/js 4.0.0-beta.7 → 4.0.0-beta.9

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
@@ -2879,7 +2879,7 @@ var require_observeOn = /* @__PURE__ */ __commonJSMin(((exports) => {
2879
2879
  var executeSchedule_1$7 = require_executeSchedule();
2880
2880
  var lift_1$67 = require_lift();
2881
2881
  var OperatorSubscriber_1$56 = require_OperatorSubscriber();
2882
- function observeOn(scheduler, delay$1) {
2882
+ function observeOn$1(scheduler, delay$1) {
2883
2883
  if (delay$1 === void 0) delay$1 = 0;
2884
2884
  return lift_1$67.operate(function(source, subscriber) {
2885
2885
  source.subscribe(OperatorSubscriber_1$56.createOperatorSubscriber(subscriber, function(value) {
@@ -2897,7 +2897,7 @@ var require_observeOn = /* @__PURE__ */ __commonJSMin(((exports) => {
2897
2897
  }));
2898
2898
  });
2899
2899
  }
2900
- exports.observeOn = observeOn;
2900
+ exports.observeOn = observeOn$1;
2901
2901
  }));
2902
2902
 
2903
2903
  //#endregion
@@ -3107,7 +3107,7 @@ var require_throwError = /* @__PURE__ */ __commonJSMin(((exports) => {
3107
3107
  exports.throwError = void 0;
3108
3108
  var Observable_1$18 = require_Observable();
3109
3109
  var isFunction_1$14 = require_isFunction();
3110
- function throwError(errorOrErrorFactory, scheduler) {
3110
+ function throwError$1(errorOrErrorFactory, scheduler) {
3111
3111
  var errorFactory = isFunction_1$14.isFunction(errorOrErrorFactory) ? errorOrErrorFactory : function() {
3112
3112
  return errorOrErrorFactory;
3113
3113
  };
@@ -3118,7 +3118,7 @@ var require_throwError = /* @__PURE__ */ __commonJSMin(((exports) => {
3118
3118
  return scheduler.schedule(init, 0, subscriber);
3119
3119
  } : init);
3120
3120
  }
3121
- exports.throwError = throwError;
3121
+ exports.throwError = throwError$1;
3122
3122
  }));
3123
3123
 
3124
3124
  //#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$3() {
4330
+ function race$4() {
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$3;
4336
+ exports.race = race$4;
4337
4337
  function raceInit(sources) {
4338
4338
  return function(subscriber) {
4339
4339
  var subscriptions = [];
@@ -5470,7 +5470,7 @@ var require_distinctUntilChanged = /* @__PURE__ */ __commonJSMin(((exports) => {
5470
5470
  var identity_1$10 = require_identity();
5471
5471
  var lift_1$42 = require_lift();
5472
5472
  var OperatorSubscriber_1$31 = require_OperatorSubscriber();
5473
- function distinctUntilChanged$5(comparator, keySelector) {
5473
+ function distinctUntilChanged$6(comparator, keySelector) {
5474
5474
  if (keySelector === void 0) keySelector = identity_1$10.identity;
5475
5475
  comparator = comparator !== null && comparator !== void 0 ? comparator : defaultCompare;
5476
5476
  return lift_1$42.operate(function(source, subscriber) {
@@ -5486,7 +5486,7 @@ var require_distinctUntilChanged = /* @__PURE__ */ __commonJSMin(((exports) => {
5486
5486
  }));
5487
5487
  });
5488
5488
  }
5489
- exports.distinctUntilChanged = distinctUntilChanged$5;
5489
+ exports.distinctUntilChanged = distinctUntilChanged$6;
5490
5490
  function defaultCompare(a, b) {
5491
5491
  return a === b;
5492
5492
  }
@@ -6915,7 +6915,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
6915
6915
  var concat_1$2 = require_concat$1();
6916
6916
  var args_1$2 = require_args();
6917
6917
  var lift_1$14 = require_lift();
6918
- function startWith() {
6918
+ function startWith$1() {
6919
6919
  var values = [];
6920
6920
  for (var _i = 0; _i < arguments.length; _i++) values[_i] = arguments[_i];
6921
6921
  var scheduler = args_1$2.popScheduler(values);
@@ -6923,7 +6923,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
6923
6923
  (scheduler ? concat_1$2.concat(values, source, scheduler) : concat_1$2.concat(values, source)).subscribe(subscriber);
6924
6924
  });
6925
6925
  }
6926
- exports.startWith = startWith;
6926
+ exports.startWith = startWith$1;
6927
6927
  }));
6928
6928
 
6929
6929
  //#endregion
@@ -6934,7 +6934,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6934
6934
  var innerFrom_1$6 = require_innerFrom();
6935
6935
  var lift_1$13 = require_lift();
6936
6936
  var OperatorSubscriber_1$11 = require_OperatorSubscriber();
6937
- function switchMap$3(project, resultSelector) {
6937
+ function switchMap$4(project, resultSelector) {
6938
6938
  return lift_1$13.operate(function(source, subscriber) {
6939
6939
  var innerSubscriber = null;
6940
6940
  var index = 0;
@@ -6958,7 +6958,7 @@ var require_switchMap = /* @__PURE__ */ __commonJSMin(((exports) => {
6958
6958
  }));
6959
6959
  });
6960
6960
  }
6961
- exports.switchMap = switchMap$3;
6961
+ exports.switchMap = switchMap$4;
6962
6962
  }));
6963
6963
 
6964
6964
  //#endregion
@@ -8956,6 +8956,40 @@ var Destroyable = class {
8956
8956
  }
8957
8957
  return cached;
8958
8958
  }
8959
+ /**
8960
+ * Like `cachedObservable`, but defers emissions to the microtask queue
8961
+ * via `observeOn(asapScheduler)`.
8962
+ *
8963
+ * Use ONLY for public-facing observable getters that external consumers
8964
+ * subscribe to. Prevents a class of bugs where `BehaviorSubject` or
8965
+ * `ReplaySubject` replays synchronously during `subscribe()`, before
8966
+ * the subscription variable is assigned in the caller's scope.
8967
+ *
8968
+ * Do NOT use for observables consumed internally by the SDK — internal
8969
+ * code using `subscribeTo()`, `firstValueFrom()`, or `withLatestFrom()`
8970
+ * depends on synchronous emission delivery.
8971
+ */
8972
+ publicCachedObservable(key, factory) {
8973
+ const publicKey = `public:${key}`;
8974
+ this._observableCache ??= /* @__PURE__ */ new Map();
8975
+ let cached = this._observableCache.get(publicKey);
8976
+ if (!cached) {
8977
+ cached = factory().pipe((0, import_cjs$22.observeOn)(import_cjs$22.asapScheduler));
8978
+ this._observableCache.set(publicKey, cached);
8979
+ }
8980
+ return cached;
8981
+ }
8982
+ /**
8983
+ * Wraps an observable so emissions are deferred to the microtask queue.
8984
+ *
8985
+ * Use ONLY for public-facing getters that expose a subject via
8986
+ * `.asObservable()` without going through `cachedObservable`.
8987
+ *
8988
+ * Do NOT use for observables consumed internally by the SDK.
8989
+ */
8990
+ deferEmission(observable) {
8991
+ return observable.pipe((0, import_cjs$22.observeOn)(import_cjs$22.asapScheduler));
8992
+ }
8959
8993
  subscribeTo(observable, observerOrNext) {
8960
8994
  const subscription = observable.subscribe(observerOrNext);
8961
8995
  this.subscriptions.push(subscription);
@@ -9104,21 +9138,23 @@ var DependencyError = class extends Error {
9104
9138
  }
9105
9139
  };
9106
9140
  var CallCreateError = class extends Error {
9107
- constructor(message, error = null, options) {
9141
+ constructor(message, error = null, direction = "outbound", options) {
9108
9142
  super(message, {
9109
9143
  ...options,
9110
9144
  cause: options?.cause ?? (error instanceof Error ? error : void 0)
9111
9145
  });
9112
9146
  this.message = message;
9113
9147
  this.error = error;
9148
+ this.direction = direction;
9114
9149
  this.name = "CallCreateError";
9115
9150
  }
9116
9151
  };
9117
9152
  var JSONRPCError = class extends Error {
9118
- constructor(code, message, data, options) {
9153
+ constructor(code, message, data, options, requestId) {
9119
9154
  super(message, options);
9120
9155
  this.code = code;
9121
9156
  this.data = data;
9157
+ this.requestId = requestId;
9122
9158
  this.name = "JSONRPCError";
9123
9159
  }
9124
9160
  };
@@ -9660,7 +9696,7 @@ var PreferencesContainer = class PreferencesContainer {
9660
9696
  skipDeviceMonitoring: false,
9661
9697
  savePreferences: false
9662
9698
  };
9663
- this.receiveVideo = true;
9699
+ this.receiveVideo = false;
9664
9700
  this.receiveAudio = true;
9665
9701
  this.preferredAudioInput = null;
9666
9702
  this.preferredAudioOutput = null;
@@ -9973,7 +10009,7 @@ var NavigatorDeviceController = class extends Destroyable {
9973
10009
  };
9974
10010
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
9975
10011
  this._selectedDevicesState$ = this.createBehaviorSubject(initialSelectedDevicesState);
9976
- this._errors$ = this.createSubject();
10012
+ this._errors$ = this.createReplaySubject(1);
9977
10013
  this.init();
9978
10014
  }
9979
10015
  get selectedAudioInputDeviceConstraints() {
@@ -10500,7 +10536,7 @@ const RPCExecute = ({ method, params }) => {
10500
10536
 
10501
10537
  //#endregion
10502
10538
  //#region src/core/RPCMessages/VertoMessages.ts
10503
- const tmpMap = {
10539
+ const SDK_TO_VERTO_FIELD_MAP = {
10504
10540
  id: "callID",
10505
10541
  destinationNumber: "destination_number",
10506
10542
  remoteCallerName: "remote_caller_id_name",
@@ -10509,19 +10545,31 @@ const tmpMap = {
10509
10545
  callerNumber: "caller_id_number",
10510
10546
  fromCallAddressId: "from_fabric_address_id"
10511
10547
  };
10548
+ const EXCLUDED_DIALOG_PARAMS = new Set([
10549
+ "remoteSdp",
10550
+ "localStream",
10551
+ "remoteStream"
10552
+ ]);
10512
10553
  /**
10513
- * Translate SDK fields into verto variables
10554
+ * Translate SDK fields into verto variables.
10555
+ * Returns a new object — the input is never mutated.
10514
10556
  */
10557
+ /** @internal Exported for testing only. */
10515
10558
  const filterVertoParams = (params) => {
10516
- if (Object.prototype.hasOwnProperty.call(params, "dialogParams")) {
10517
- const { remoteSdp, localStream, remoteStream, ...dialogParams } = params.dialogParams;
10518
- for (const key in tmpMap) if (key && Object.prototype.hasOwnProperty.call(dialogParams, key)) {
10519
- dialogParams[tmpMap[key]] = dialogParams[key];
10520
- delete dialogParams[key];
10521
- }
10522
- params.dialogParams = dialogParams;
10523
- }
10524
- return params;
10559
+ if (!Object.prototype.hasOwnProperty.call(params, "dialogParams")) return params;
10560
+ const sourceDialogParams = params.dialogParams;
10561
+ const filteredDialogParams = Object.entries(sourceDialogParams).reduce((acc, [key, value]) => {
10562
+ if (EXCLUDED_DIALOG_PARAMS.has(key)) return acc;
10563
+ const mappedKey = SDK_TO_VERTO_FIELD_MAP[key] ?? key;
10564
+ return {
10565
+ ...acc,
10566
+ [mappedKey]: value
10567
+ };
10568
+ }, {});
10569
+ return {
10570
+ ...params,
10571
+ dialogParams: filteredDialogParams
10572
+ };
10525
10573
  };
10526
10574
  const buildVertoRPCMessage = (method) => {
10527
10575
  return (params = {}) => {
@@ -10632,17 +10680,21 @@ var AttachManager = class {
10632
10680
  buildCallOptions(attachment) {
10633
10681
  const { audio: audioDirection, video: videoDirection } = attachment.mediaDirections;
10634
10682
  const { audioInputDevice, videoInputDevice } = attachment;
10683
+ const receiveAudio = audioDirection.includes("recv");
10684
+ const receiveVideo = videoDirection.includes("recv");
10685
+ const sendAudio = audioDirection.includes("send");
10686
+ const sendVideo = videoDirection.includes("send");
10635
10687
  return {
10636
- receiveAudio: audioDirection.includes("recv"),
10637
- receiveVideo: videoDirection.includes("recv"),
10638
- inputAudioDeviceConstraints: {
10639
- audio: audioDirection.includes("send"),
10688
+ receiveAudio,
10689
+ receiveVideo,
10690
+ inputAudioDeviceConstraints: sendAudio ? {
10691
+ audio: true,
10640
10692
  ...this.deviceController.deviceInfoToConstraints(audioInputDevice)
10641
- },
10642
- inputVideoDeviceConstraints: {
10643
- video: videoDirection.includes("send"),
10693
+ } : void 0,
10694
+ inputVideoDeviceConstraints: sendVideo ? {
10695
+ video: true,
10644
10696
  ...this.deviceController.deviceInfoToConstraints(videoInputDevice)
10645
- },
10697
+ } : void 0,
10646
10698
  reattach: true
10647
10699
  };
10648
10700
  }
@@ -10703,12 +10755,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
10703
10755
  exports.race = void 0;
10704
10756
  var argsOrArgArray_1 = require_argsOrArgArray();
10705
10757
  var raceWith_1$1 = require_raceWith();
10706
- function race$2() {
10758
+ function race$3() {
10707
10759
  var args = [];
10708
10760
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
10709
10761
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
10710
10762
  }
10711
- exports.race = race$2;
10763
+ exports.race = race$3;
10712
10764
  }));
10713
10765
 
10714
10766
  //#endregion
@@ -12042,7 +12094,13 @@ var Participant = class extends Destroyable {
12042
12094
  }
12043
12095
  /** Removes this participant from the call. */
12044
12096
  async remove() {
12045
- await this.executeMethod(this.id, "call.member.remove", {});
12097
+ const state = this._state$.value;
12098
+ const target = {
12099
+ member_id: this.id,
12100
+ call_id: state.call_id ?? "",
12101
+ node_id: state.node_id ?? ""
12102
+ };
12103
+ await this.executeMethod(target, "call.member.remove", {});
12046
12104
  }
12047
12105
  /** Ends the call for this participant. */
12048
12106
  async end() {
@@ -12212,6 +12270,9 @@ function isJSONRPCRequest(value) {
12212
12270
  function isJSONRPCResponse(value) {
12213
12271
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && (hasProperty(value, "result") || hasProperty(value, "error"));
12214
12272
  }
12273
+ function isJSONRPCErrorResponse(value) {
12274
+ return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && (hasProperty(value, "error") && isObject(value.error) && hasProperty(value.error, "code") && hasProperty(value.error, "message") || hasProperty(value, "result") && isObject(value.result) && hasProperty(value.result, "code") && value.result.code !== "200" && hasProperty(value.result, "message"));
12275
+ }
12215
12276
 
12216
12277
  //#endregion
12217
12278
  //#region src/core/RPCMessages/guards/events.guards.ts
@@ -12401,7 +12462,6 @@ var CallEventsManager = class extends Destroyable {
12401
12462
  this.options = options;
12402
12463
  this.callIds = /* @__PURE__ */ new Set();
12403
12464
  this.roomSessionIds = /* @__PURE__ */ new Set();
12404
- this._status$ = this.createBehaviorSubject("trying");
12405
12465
  this._participants$ = this.createBehaviorSubject({});
12406
12466
  this._self$ = this.createBehaviorSubject(null);
12407
12467
  this._sessionState$ = this.createBehaviorSubject(initialSessionState);
@@ -12410,15 +12470,12 @@ var CallEventsManager = class extends Destroyable {
12410
12470
  get participants$() {
12411
12471
  return this.cachedObservable("participants$", () => this._participants$.asObservable().pipe((0, import_cjs$14.map)((participantsRecord) => Object.values(participantsRecord))));
12412
12472
  }
12473
+ get participants() {
12474
+ return Object.values(this._participants$.value);
12475
+ }
12413
12476
  get self$() {
12414
12477
  return this.cachedObservable("self$", () => this._self$.asObservable().pipe(filterNull()));
12415
12478
  }
12416
- get status$() {
12417
- return this._status$.asObservable();
12418
- }
12419
- get status() {
12420
- return this._status$.value;
12421
- }
12422
12479
  isRoomSessionIdValid(roomSessionId) {
12423
12480
  return this.roomSessionIds.has(roomSessionId);
12424
12481
  }
@@ -12503,7 +12560,6 @@ var CallEventsManager = class extends Destroyable {
12503
12560
  callId: callJoinedEvent.call_id,
12504
12561
  roomSessionId: callJoinedEvent.room_session_id
12505
12562
  });
12506
- this._status$.next("connected");
12507
12563
  const sessionState = callJoinedEvent.room_session;
12508
12564
  const { capabilities } = callJoinedEvent;
12509
12565
  this.selfId = this.selfId ?? callJoinedEvent.member_id;
@@ -12630,12 +12686,53 @@ var CallEventsManager = class extends Destroyable {
12630
12686
 
12631
12687
  //#endregion
12632
12688
  //#region src/helpers/SDPHelper.ts
12689
+ /** Valid SDP direction attribute values. */
12690
+ const SDP_DIRECTIONS = new Set([
12691
+ "sendrecv",
12692
+ "sendonly",
12693
+ "recvonly",
12694
+ "inactive"
12695
+ ]);
12633
12696
  /**
12634
- * SDPHelper - Utility functions for SDP (Session Description Protocol) parsing and validation.
12697
+ * Extracts the media directions (audio/video) from an SDP string.
12698
+ *
12699
+ * Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
12700
+ * attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
12701
+ * If no explicit direction attribute is found for a media section, defaults to `sendrecv`
12702
+ * per RFC 4566.
12635
12703
  *
12636
- * This module provides helper functions to analyze and validate SDP content,
12637
- * particularly for ICE candidate validation in WebRTC connections.
12704
+ * @param sdp - The SDP string to parse
12705
+ * @returns The extracted audio and video directions
12706
+ *
12707
+ * @example
12708
+ * ```typescript
12709
+ * const sdp = `v=0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=sendrecv\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=recvonly`;
12710
+ * extractMediaDirectionsFromSDP(sdp);
12711
+ * // { audio: 'sendrecv', video: 'recvonly' }
12712
+ * ```
12638
12713
  */
12714
+ function extractMediaDirectionsFromSDP(sdp) {
12715
+ const result = {
12716
+ audio: "inactive",
12717
+ video: "inactive"
12718
+ };
12719
+ if (!sdp) return result;
12720
+ const lines = sdp.split(/\r?\n/);
12721
+ let currentMediaKind = null;
12722
+ let currentDirection = null;
12723
+ for (const line of lines) if (line.startsWith("m=")) {
12724
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
12725
+ if (line.startsWith("m=audio")) currentMediaKind = "audio";
12726
+ else if (line.startsWith("m=video")) currentMediaKind = "video";
12727
+ else currentMediaKind = null;
12728
+ currentDirection = null;
12729
+ } else if (currentMediaKind && line.startsWith("a=")) {
12730
+ const attr = line.substring(2).trim();
12731
+ if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
12732
+ }
12733
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
12734
+ return result;
12735
+ }
12639
12736
  /**
12640
12737
  * Validates that an SDP string has at least one non-host ICE candidate
12641
12738
  * for each media section (m= line).
@@ -12938,6 +13035,15 @@ var LocalStreamController = class extends Destroyable {
12938
13035
  track.addEventListener("ended", this.mediaTrackEndedHandler);
12939
13036
  }
12940
13037
  /**
13038
+ * Update the controller options (e.g., when media overrides are applied).
13039
+ */
13040
+ updateOptions(options) {
13041
+ this.options = {
13042
+ ...this.options,
13043
+ ...options
13044
+ };
13045
+ }
13046
+ /**
12941
13047
  * Stop all local tracks and clean up.
12942
13048
  */
12943
13049
  stopAllTracks() {
@@ -13276,11 +13382,12 @@ var RTCPeerConnectionController = class extends Destroyable {
13276
13382
  this._connectionState$ = this.createReplaySubject(1);
13277
13383
  this._signalingState$ = this.createReplaySubject(1);
13278
13384
  this._iceGatheringState$ = this.createReplaySubject(1);
13279
- this._errors$ = this.createSubject();
13385
+ this._errors$ = this.createReplaySubject(1);
13280
13386
  this._iceCandidates$ = this.createReplaySubject(1);
13281
13387
  this._initialized$ = this.createReplaySubject(1);
13282
13388
  this._remoteDescription$ = this.createReplaySubject(1);
13283
13389
  this._remoteStream$ = this.createBehaviorSubject(null);
13390
+ this._remoteOfferMediaDirections = null;
13284
13391
  this.deviceController = deviceController ?? {};
13285
13392
  this.id = options.callId ?? v4_default();
13286
13393
  this._type = remoteSessionDescription ? "answer" : "offer";
@@ -13288,10 +13395,19 @@ var RTCPeerConnectionController = class extends Destroyable {
13288
13395
  type: "offer",
13289
13396
  sdp: remoteSessionDescription
13290
13397
  } : void 0;
13398
+ this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
13399
+ const offerDefaults = this._remoteOfferMediaDirections ? {
13400
+ audio: this._remoteOfferMediaDirections.audio.includes("recv"),
13401
+ video: this._remoteOfferMediaDirections.video.includes("recv"),
13402
+ receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
13403
+ receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
13404
+ } : {};
13291
13405
  this.options = {
13292
- receiveAudio: options.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
13293
- receiveVideo: options.receiveVideo ?? PreferencesContainer.instance.receiveVideo,
13294
- ...options
13406
+ ...options,
13407
+ audio: options.audio ?? offerDefaults.audio,
13408
+ video: options.video ?? offerDefaults.video,
13409
+ receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
13410
+ receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
13295
13411
  };
13296
13412
  this.localStreamController = new LocalStreamController({
13297
13413
  propose: this.propose,
@@ -13436,7 +13552,7 @@ var RTCPeerConnectionController = class extends Destroyable {
13436
13552
  };
13437
13553
  }
13438
13554
  get inputVideoDeviceConstraints() {
13439
- if (this.options.video === false && !this.options.inputVideoDeviceConstraints) return false;
13555
+ if (!this.options.video && !this.options.inputVideoDeviceConstraints) return false;
13440
13556
  return {
13441
13557
  ...this.options.inputVideoDeviceConstraints,
13442
13558
  ...this.deviceController.selectedVideoInputDeviceConstraints
@@ -13458,12 +13574,12 @@ var RTCPeerConnectionController = class extends Destroyable {
13458
13574
  default: return {
13459
13575
  ...options,
13460
13576
  offerToReceiveAudio: true,
13461
- offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
13577
+ offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
13462
13578
  };
13463
13579
  }
13464
13580
  }
13465
13581
  get answerOptions() {
13466
- return {};
13582
+ return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
13467
13583
  }
13468
13584
  /**
13469
13585
  * Initialize the RTCPeerConnection and setup event listeners.
@@ -13498,11 +13614,15 @@ var RTCPeerConnectionController = class extends Destroyable {
13498
13614
  });
13499
13615
  await this.updateSelectedInputDevice(kind, deviceInfo);
13500
13616
  });
13501
- await this.setupTrackHandling();
13502
- this._initialized$.next(true);
13503
13617
  if (this.type === "answer" && this.sdpInit) {
13618
+ await this.setupRemoteTracks();
13619
+ this._initialized$.next(true);
13504
13620
  this.setupEventListeners();
13505
- await this.handleOfferReceived();
13621
+ this._isNegotiating$.next(true);
13622
+ await this._setRemoteDescription(this.sdpInit);
13623
+ } else {
13624
+ await this.setupTrackHandling();
13625
+ this._initialized$.next(true);
13506
13626
  }
13507
13627
  } catch (error) {
13508
13628
  logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
@@ -13600,6 +13720,35 @@ var RTCPeerConnectionController = class extends Destroyable {
13600
13720
  default:
13601
13721
  }
13602
13722
  }
13723
+ /**
13724
+ * Accept an inbound call by creating the SDP answer.
13725
+ * Optionally override media options before the answer is generated.
13726
+ * Must be called after initialization for inbound (answer-type) connections.
13727
+ */
13728
+ async acceptInbound(mediaOverrides) {
13729
+ if (mediaOverrides) {
13730
+ const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
13731
+ this.options = {
13732
+ ...this.options,
13733
+ ...audio !== void 0 ? { audio } : {},
13734
+ ...video !== void 0 ? { video } : {},
13735
+ ...receiveAudio !== void 0 ? { receiveAudio } : {},
13736
+ ...receiveVideo !== void 0 ? { receiveVideo } : {}
13737
+ };
13738
+ this.transceiverController?.updateOptions({
13739
+ receiveAudio: this.receiveAudio,
13740
+ receiveVideo: this.receiveVideo
13741
+ });
13742
+ this.localStreamController.updateOptions({
13743
+ inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
13744
+ inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
13745
+ });
13746
+ }
13747
+ await this.setupLocalTracks();
13748
+ const { answerOptions } = this;
13749
+ logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
13750
+ await this.createAnswer(answerOptions);
13751
+ }
13603
13752
  async handleOfferReceived() {
13604
13753
  if (!this.sdpInit) throw new DependencyError("SDP initialization parameters are not set");
13605
13754
  this._isNegotiating$.next(true);
@@ -13674,37 +13823,29 @@ var RTCPeerConnectionController = class extends Destroyable {
13674
13823
  }
13675
13824
  async setupLocalTracks() {
13676
13825
  logger$11.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
13677
- let { localStream } = this;
13678
- if (!localStream) try {
13679
- localStream = await this.localStreamController.buildLocalStream();
13680
- } catch (error) {
13681
- logger$11.error("[RTCPeerConnectionController] Error building local stream:", error);
13682
- this._errors$.next(error);
13683
- }
13684
- if (localStream) {
13685
- if (this.transceiverController?.useAddStream ?? false) {
13686
- logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
13687
- this.peerConnection?.addStream(localStream);
13688
- if (!this.isNegotiating) {
13689
- logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
13690
- this.negotiationNeeded$.next();
13691
- }
13692
- return;
13826
+ const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
13827
+ if (this.transceiverController?.useAddStream ?? false) {
13828
+ logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
13829
+ this.peerConnection?.addStream(localStream);
13830
+ if (!this.isNegotiating) {
13831
+ logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
13832
+ this.negotiationNeeded$.next();
13693
13833
  }
13694
- for (const kind of ["audio", "video"]) {
13695
- const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
13696
- index,
13697
- track
13698
- }));
13699
- for (const { index, track } of tracks) {
13700
- this.localStreamController.addTrackEndedListener(track);
13701
- if (this.transceiverController?.useAddTransceivers ?? false) {
13702
- const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
13703
- await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
13704
- } else {
13705
- logger$11.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
13706
- this.peerConnection?.addTrack(track, localStream);
13707
- }
13834
+ return;
13835
+ }
13836
+ for (const kind of ["audio", "video"]) {
13837
+ const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
13838
+ index,
13839
+ track
13840
+ }));
13841
+ for (const { index, track } of tracks) {
13842
+ this.localStreamController.addTrackEndedListener(track);
13843
+ if (this.transceiverController?.useAddTransceivers ?? false) {
13844
+ const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
13845
+ await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
13846
+ } else {
13847
+ logger$11.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
13848
+ this.peerConnection?.addTrack(track, localStream);
13708
13849
  }
13709
13850
  }
13710
13851
  }
@@ -13721,7 +13862,12 @@ var RTCPeerConnectionController = class extends Destroyable {
13721
13862
  if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
13722
13863
  this.peerConnection.ontrack = (event) => {
13723
13864
  logger$11.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
13724
- this._remoteStream$.next(event.streams[0]);
13865
+ if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
13866
+ else {
13867
+ const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
13868
+ const newStream = new MediaStream([...existingTracks, event.track]);
13869
+ this._remoteStream$.next(newStream);
13870
+ }
13725
13871
  };
13726
13872
  await this.transceiverController?.setupRemoteTransceivers(this.type);
13727
13873
  }
@@ -13820,7 +13966,7 @@ var RTCPeerConnectionController = class extends Destroyable {
13820
13966
  });
13821
13967
  }
13822
13968
  get mediaDirections() {
13823
- return this.transceiverController?.getMediaDirections() ?? {
13969
+ return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
13824
13970
  audio: "inactive",
13825
13971
  video: "inactive"
13826
13972
  };
@@ -13969,6 +14115,11 @@ var WebRTCVertoManager = class extends VertoManager {
13969
14115
  ].includes(connectionState)))));
13970
14116
  }
13971
14117
  initSubscriptions() {
14118
+ this.subscribeTo(this.callJoinedEvent$, (event) => {
14119
+ const memberNodeId = event.room_session.members.find((m) => m.call_id === event.call_id)?.node_id;
14120
+ if (memberNodeId) this.setNodeIdIfNull(memberNodeId);
14121
+ if (event.member_id) this.setSelfIdIfNull(event.member_id);
14122
+ });
13972
14123
  this.subscribeTo(this.vertoMedia$, (event) => {
13973
14124
  logger$10.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
13974
14125
  this._signalingStatus$.next("ringing");
@@ -13980,6 +14131,7 @@ var WebRTCVertoManager = class extends VertoManager {
13980
14131
  });
13981
14132
  this.subscribeTo(this.vertoAnswer$, (event) => {
13982
14133
  logger$10.debug("[WebRTCManager] Received Verto answer event:", event);
14134
+ this._signalingStatus$.next("connecting");
13983
14135
  const { sdp, callID } = event;
13984
14136
  this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
13985
14137
  status: "received",
@@ -13999,6 +14151,28 @@ var WebRTCVertoManager = class extends VertoManager {
13999
14151
  this.sendVertoPong(vertoPing);
14000
14152
  });
14001
14153
  }
14154
+ /**
14155
+ * Set node_id/selfId only when the current value is null.
14156
+ *
14157
+ * During reattach, `call.joined` and `verto.answer` events can deliver
14158
+ * these identifiers before the `verto.invite` RPC response (`CALL CREATED`)
14159
+ * arrives. These methods let early events populate them eagerly so that
14160
+ * downstream RPC calls (e.g. `call.layout.list`) don't fail with empty
14161
+ * identifiers. `processInviteResponse()` remains the authoritative source
14162
+ * and always overwrites unconditionally.
14163
+ */
14164
+ setNodeIdIfNull(nodeId) {
14165
+ if (!this._nodeId$.value && nodeId) {
14166
+ logger$10.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
14167
+ this._nodeId$.next(nodeId);
14168
+ }
14169
+ }
14170
+ setSelfIdIfNull(selfId) {
14171
+ if (!this._selfId$.value && selfId) {
14172
+ logger$10.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
14173
+ this._selfId$.next(selfId);
14174
+ }
14175
+ }
14002
14176
  async sendVertoPong(vertoPing) {
14003
14177
  try {
14004
14178
  const vertoPongMessage = VertoPong({ ...vertoPing });
@@ -14022,6 +14196,9 @@ var WebRTCVertoManager = class extends VertoManager {
14022
14196
  get selfId() {
14023
14197
  return this._selfId$.value;
14024
14198
  }
14199
+ get callJoinedEvent$() {
14200
+ return this.webRtcCallSession.callEvent$.pipe((0, import_cjs$10.filter)(isCallJoinedPayload), (0, import_cjs$10.takeUntil)(this.destroyed$));
14201
+ }
14025
14202
  get vertoMedia$() {
14026
14203
  return this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaInnerParams, "params"), (0, import_cjs$10.takeUntil)(this.destroyed$));
14027
14204
  }
@@ -14051,13 +14228,13 @@ var WebRTCVertoManager = class extends VertoManager {
14051
14228
  if (response.error) {
14052
14229
  const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
14053
14230
  this.onError?.(error);
14054
- throw error;
14231
+ return response;
14055
14232
  }
14056
14233
  const innerResult = getValueFrom(response, "result.result");
14057
14234
  if (innerResult?.error) {
14058
14235
  const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
14059
14236
  this.onError?.(error);
14060
- throw error;
14237
+ return response;
14061
14238
  }
14062
14239
  return response;
14063
14240
  }
@@ -14077,7 +14254,7 @@ var WebRTCVertoManager = class extends VertoManager {
14077
14254
  }
14078
14255
  } catch (error) {
14079
14256
  logger$10.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
14080
- throw error;
14257
+ this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
14081
14258
  }
14082
14259
  }
14083
14260
  async processModifyResponse(response, rtcPeerConnController) {
@@ -14091,12 +14268,14 @@ var WebRTCVertoManager = class extends VertoManager {
14091
14268
  });
14092
14269
  } catch (error) {
14093
14270
  logger$10.warn("[WebRTCManager] Error processing modify response:", error);
14094
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
14271
+ const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
14272
+ this.onError?.(modifyError);
14095
14273
  }
14096
14274
  }
14097
14275
  }
14098
14276
  processInviteResponse(response, rtcPeerConnController) {
14099
14277
  if (!response.error && getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
14278
+ this._signalingStatus$.next("trying");
14100
14279
  this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
14101
14280
  const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
14102
14281
  const callId = getValueFrom(response, "result.result.result.callID") ?? null;
@@ -14151,6 +14330,36 @@ var WebRTCVertoManager = class extends VertoManager {
14151
14330
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
14152
14331
  this.onError?.(error);
14153
14332
  });
14333
+ if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
14334
+ }
14335
+ async handleInboundAnswer(rtcPeerConnController) {
14336
+ logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
14337
+ const vertoByeOrAccepted = await (0, import_cjs$10.firstValueFrom)((0, import_cjs$10.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$10.takeUntil)(this.destroyed$))).catch(() => null);
14338
+ if (vertoByeOrAccepted === null) {
14339
+ logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
14340
+ return;
14341
+ }
14342
+ if (isVertoByeMessage(vertoByeOrAccepted)) {
14343
+ logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
14344
+ this.callSession?.destroy();
14345
+ } else if (!vertoByeOrAccepted) {
14346
+ logger$10.info("[WebRTCManager] Inbound call rejected by user.");
14347
+ try {
14348
+ await this.bye("USER_BUSY");
14349
+ } finally {
14350
+ this._signalingStatus$.next("disconnected");
14351
+ this.callSession?.destroy();
14352
+ }
14353
+ } else {
14354
+ logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
14355
+ const answerOptions = this.webRtcCallSession.answerMediaOptions;
14356
+ try {
14357
+ await rtcPeerConnController.acceptInbound(answerOptions);
14358
+ } catch (error) {
14359
+ logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
14360
+ this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
14361
+ }
14362
+ }
14154
14363
  }
14155
14364
  setupVertoAttachHandler() {
14156
14365
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
@@ -14168,7 +14377,7 @@ var WebRTCVertoManager = class extends VertoManager {
14168
14377
  });
14169
14378
  }
14170
14379
  initObservables(rtcPeerConnController) {
14171
- this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.takeUntil)(this.destroyed$), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections));
14380
+ this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections), (0, import_cjs$10.startWith)(rtcPeerConnController.mediaDirections), (0, import_cjs$10.takeUntil)(this.destroyed$));
14172
14381
  this.localStream$ = rtcPeerConnController.localStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
14173
14382
  this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
14174
14383
  }
@@ -14184,7 +14393,6 @@ var WebRTCVertoManager = class extends VertoManager {
14184
14393
  });
14185
14394
  this.sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnController);
14186
14395
  } else if (initial) {
14187
- this._signalingStatus$.next("trying");
14188
14396
  const vertoMessageRequest = VertoInvite({
14189
14397
  dialogParams,
14190
14398
  sdp
@@ -14491,8 +14699,8 @@ var WebRTCCall = class extends Destroyable {
14491
14699
  this.clientSession = clientSession;
14492
14700
  this.options = options;
14493
14701
  this.address = address;
14494
- this.participantsMap = /* @__PURE__ */ new Map();
14495
- this._errors$ = this.createSubject();
14702
+ this._errors$ = this.createReplaySubject(1);
14703
+ this._lastMergedStatus = "new";
14496
14704
  this._answered$ = this.createReplaySubject();
14497
14705
  this._holdState = false;
14498
14706
  this._userVariables$ = this.createBehaviorSubject({ ...PreferencesContainer.instance.userVariables });
@@ -14513,18 +14721,28 @@ var WebRTCCall = class extends Destroyable {
14513
14721
  const managers = initialization.initializeManagers(this);
14514
14722
  this.vertoManager = managers.vertoManager;
14515
14723
  this.callEventsManager = managers.callEventsManager;
14516
- if (options.initOffer) this._status$ = this.createBehaviorSubject("ringing");
14517
- else this._status$ = this.createBehaviorSubject("new");
14724
+ if (options.initOffer) {
14725
+ this._status$ = this.createBehaviorSubject("ringing");
14726
+ this._lastMergedStatus = "ringing";
14727
+ } else this._status$ = this.createBehaviorSubject("new");
14518
14728
  const { deviceController } = initialization;
14519
14729
  this.participantFactory = new ParticipantFactory(this.executeMethod.bind(this), this.vertoManager, deviceController);
14520
14730
  }
14521
14731
  /** Observable stream of errors from media, signaling, and peer connection layers. */
14522
14732
  get errors$() {
14523
- return this._errors$.asObservable();
14733
+ return this.deferEmission(this._errors$.asObservable());
14524
14734
  }
14525
- /** @internal Push an error to the call's error stream. */
14526
- emitError(error) {
14527
- this._errors$.next(error);
14735
+ /**
14736
+ * @internal Push an error to the call's error stream.
14737
+ * Fatal errors automatically transition the call to `'failed'` and destroy it.
14738
+ */
14739
+ emitError(callError) {
14740
+ if (this._status$.value === "destroyed" || this._status$.value === "failed") return;
14741
+ this._errors$.next(callError);
14742
+ if (callError.fatal) {
14743
+ this._status$.next("failed");
14744
+ this.destroy();
14745
+ }
14528
14746
  }
14529
14747
  /** Whether this call is `'inbound'` or `'outbound'`. */
14530
14748
  get direction() {
@@ -14532,7 +14750,7 @@ var WebRTCCall = class extends Destroyable {
14532
14750
  }
14533
14751
  /** Observable of the address associated with this call. */
14534
14752
  get address$() {
14535
- return (0, import_cjs$9.from)([this.address]);
14753
+ return this.deferEmission((0, import_cjs$9.from)([this.address])).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14536
14754
  }
14537
14755
  /** Display name of the caller. */
14538
14756
  get fromName() {
@@ -14564,7 +14782,7 @@ var WebRTCCall = class extends Destroyable {
14564
14782
  }
14565
14783
  /** Current snapshot of all participants in the call. */
14566
14784
  get participants() {
14567
- return Array.from(this.participantsMap.values());
14785
+ return this.callEventsManager.participants;
14568
14786
  }
14569
14787
  /** The local participant, or `null` if not yet joined. */
14570
14788
  get self() {
@@ -14573,7 +14791,6 @@ var WebRTCCall = class extends Destroyable {
14573
14791
  async toggleLock() {
14574
14792
  const method = this.locked ? "call.unlock" : "call.lock";
14575
14793
  await this.executeMethod(this.selfId ?? "", method, {});
14576
- throw new UnimplementedError();
14577
14794
  }
14578
14795
  async toggleHold() {
14579
14796
  if (this._holdState) await this.vertoManager.unhold();
@@ -14594,7 +14811,7 @@ var WebRTCCall = class extends Destroyable {
14594
14811
  }
14595
14812
  /** Observable of layout layer positions for all participants. */
14596
14813
  get layoutLayers$() {
14597
- return this.callEventsManager.layoutLayers$;
14814
+ return this.deferEmission(this.callEventsManager.layoutLayers$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14598
14815
  }
14599
14816
  /** Current snapshot of layout layers. */
14600
14817
  get layoutLayers() {
@@ -14608,72 +14825,80 @@ var WebRTCCall = class extends Destroyable {
14608
14825
  params
14609
14826
  });
14610
14827
  try {
14611
- return await this.clientSession.execute(request);
14828
+ const response = await this.clientSession.execute(request);
14829
+ if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
14830
+ return response;
14612
14831
  } catch (error) {
14613
14832
  logger$9.error(`[Call] Error executing method ${method} with params`, params, error);
14614
14833
  throw error;
14615
14834
  }
14616
14835
  }
14617
14836
  buildMethodParams(target, args) {
14618
- const reference = {
14619
- node_id: this.nodeId,
14620
- call_id: this.id
14837
+ const self = {
14838
+ node_id: this.nodeId ?? "",
14839
+ call_id: this.id,
14840
+ member_id: this.vertoManager.selfId ?? ""
14841
+ };
14842
+ if (typeof target === "object") return {
14843
+ ...args,
14844
+ self,
14845
+ targets: [target]
14621
14846
  };
14622
14847
  return {
14623
14848
  ...args,
14624
- self: {
14625
- ...reference,
14626
- member_id: this.vertoManager.selfId
14627
- },
14849
+ self,
14628
14850
  target: {
14629
- ...reference,
14851
+ node_id: this.nodeId ?? "",
14852
+ call_id: this.id,
14630
14853
  member_id: target
14631
14854
  }
14632
14855
  };
14633
14856
  }
14634
14857
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
14635
14858
  get status$() {
14636
- return this.cachedObservable("status$", () => (0, import_cjs$9.merge)(this._status$.asObservable(), this.vertoManager.signalingStatus$));
14859
+ return this.publicCachedObservable("status$", () => (0, import_cjs$9.merge)(this._status$.asObservable(), this.vertoManager.signalingStatus$).pipe((0, import_cjs$9.distinctUntilChanged)(), (0, import_cjs$9.tap)((status) => {
14860
+ this._lastMergedStatus = status;
14861
+ })));
14637
14862
  }
14638
14863
  /** Observable of the participants list, emits on join/leave/update. */
14639
14864
  get participants$() {
14640
- return this.callEventsManager.participants$;
14865
+ return this.deferEmission(this.callEventsManager.participants$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14641
14866
  }
14642
14867
  /** Observable of the local (self) participant. */
14643
14868
  get self$() {
14644
- return this.callEventsManager.self$;
14869
+ return this.deferEmission(this.callEventsManager.self$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14645
14870
  }
14646
14871
  /** Observable indicating whether the call is being recorded. */
14647
14872
  get recording$() {
14648
- return this.callEventsManager.recording$;
14873
+ return this.deferEmission(this.callEventsManager.recording$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14649
14874
  }
14650
14875
  /** Observable indicating whether the call is being streamed. */
14651
14876
  get streaming$() {
14652
- return this.callEventsManager.streaming$;
14877
+ return this.deferEmission(this.callEventsManager.streaming$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14653
14878
  }
14654
14879
  /** Observable indicating whether raise-hand priority is active. */
14655
14880
  get raiseHandPriority$() {
14656
- return this.callEventsManager.raiseHandPriority$;
14881
+ return this.deferEmission(this.callEventsManager.raiseHandPriority$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14657
14882
  }
14658
14883
  /** Observable indicating whether the call room is locked. */
14659
14884
  get locked$() {
14660
- return this.callEventsManager.locked$;
14885
+ return this.deferEmission(this.callEventsManager.locked$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14661
14886
  }
14662
14887
  /** Observable of custom metadata associated with the call. */
14663
14888
  get meta$() {
14664
- return this.callEventsManager.meta$;
14889
+ return this.deferEmission(this.callEventsManager.meta$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14665
14890
  }
14666
14891
  /** Observable of the call's capability flags. */
14667
14892
  get capabilities$() {
14668
- return this.callEventsManager.capabilities$;
14893
+ return this.deferEmission(this.callEventsManager.capabilities$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14669
14894
  }
14670
14895
  /** Observable of the current layout name. */
14671
14896
  get layout$() {
14672
- return this.callEventsManager.layout$;
14897
+ return this.deferEmission(this.callEventsManager.layout$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14673
14898
  }
14674
14899
  /** Current call status. */
14675
14900
  get status() {
14676
- return this._status$.value;
14901
+ return this._lastMergedStatus;
14677
14902
  }
14678
14903
  /** Whether the call is currently being recorded. */
14679
14904
  get recording() {
@@ -14701,7 +14926,7 @@ var WebRTCCall = class extends Destroyable {
14701
14926
  }
14702
14927
  /** Observable of available layout names. */
14703
14928
  get layouts$() {
14704
- return this.callEventsManager.layouts$;
14929
+ return this.deferEmission(this.callEventsManager.layouts$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14705
14930
  }
14706
14931
  /** Current snapshot of available layout names. */
14707
14932
  get layouts() {
@@ -14709,7 +14934,7 @@ var WebRTCCall = class extends Destroyable {
14709
14934
  }
14710
14935
  /** Observable of the local media stream (camera/microphone). */
14711
14936
  get localStream$() {
14712
- return this.vertoManager.localStream$;
14937
+ return this.deferEmission(this.vertoManager.localStream$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14713
14938
  }
14714
14939
  /** Current local media stream, or `null` if not available. */
14715
14940
  get localStream() {
@@ -14717,7 +14942,7 @@ var WebRTCCall = class extends Destroyable {
14717
14942
  }
14718
14943
  /** Observable of the remote media stream from the far end. */
14719
14944
  get remoteStream$() {
14720
- return this.vertoManager.remoteStream$;
14945
+ return this.deferEmission(this.vertoManager.remoteStream$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14721
14946
  }
14722
14947
  /** Current remote media stream, or `null` if not available. */
14723
14948
  get remoteStream() {
@@ -14725,7 +14950,7 @@ var WebRTCCall = class extends Destroyable {
14725
14950
  }
14726
14951
  /** Observable of custom user variables associated with the call. */
14727
14952
  get userVariables$() {
14728
- return this._userVariables$.asObservable();
14953
+ return this.deferEmission(this._userVariables$.asObservable());
14729
14954
  }
14730
14955
  /** a copy of the current custom user variables of the call. */
14731
14956
  get userVariables() {
@@ -14745,7 +14970,7 @@ var WebRTCCall = class extends Destroyable {
14745
14970
  }
14746
14971
  /** Observable of the current audio/video send/receive directions. */
14747
14972
  get mediaDirections$() {
14748
- return this.vertoManager.mediaDirections$;
14973
+ return this.deferEmission(this.vertoManager.mediaDirections$).pipe((0, import_cjs$9.takeUntil)(this._destroyed$));
14749
14974
  }
14750
14975
  /** Current audio/video send/receive directions. */
14751
14976
  get mediaDirections() {
@@ -14791,31 +15016,31 @@ var WebRTCCall = class extends Destroyable {
14791
15016
  }
14792
15017
  /** Observable of call-updated events. */
14793
15018
  get callUpdated$() {
14794
- return this.cachedObservable("callUpdated$", () => this.callSessionEvents$.pipe(filterAs(isCallUpdatedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15019
+ return this.publicCachedObservable("callUpdated$", () => this.callSessionEvents$.pipe(filterAs(isCallUpdatedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14795
15020
  }
14796
15021
  /** Observable of member-joined events. */
14797
15022
  get memberJoined$() {
14798
- return this.cachedObservable("memberJoined$", () => this.callSessionEvents$.pipe(filterAs(isMemberJoinedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15023
+ return this.publicCachedObservable("memberJoined$", () => this.callSessionEvents$.pipe(filterAs(isMemberJoinedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14799
15024
  }
14800
15025
  /** Observable of member-left events. */
14801
15026
  get memberLeft$() {
14802
- return this.cachedObservable("memberLeft$", () => this.callSessionEvents$.pipe(filterAs(isMemberLeftMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15027
+ return this.publicCachedObservable("memberLeft$", () => this.callSessionEvents$.pipe(filterAs(isMemberLeftMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14803
15028
  }
14804
15029
  /** Observable of member-updated events (mute, volume, etc.). */
14805
15030
  get memberUpdated$() {
14806
- return this.cachedObservable("memberUpdated$", () => this.callSessionEvents$.pipe(filterAs(isMemberUpdatedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15031
+ return this.publicCachedObservable("memberUpdated$", () => this.callSessionEvents$.pipe(filterAs(isMemberUpdatedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14807
15032
  }
14808
15033
  /** Observable of member-talking events (speech start/stop). */
14809
15034
  get memberTalking$() {
14810
- return this.cachedObservable("memberTalking$", () => this.callSessionEvents$.pipe(filterAs(isMemberTalkingMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15035
+ return this.publicCachedObservable("memberTalking$", () => this.callSessionEvents$.pipe(filterAs(isMemberTalkingMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14811
15036
  }
14812
15037
  /** Observable of call state-change events. */
14813
15038
  get callStates$() {
14814
- return this.cachedObservable("callStates$", () => this.callSessionEvents$.pipe(filterAs(isCallStateMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15039
+ return this.publicCachedObservable("callStates$", () => this.callSessionEvents$.pipe(filterAs(isCallStateMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14815
15040
  }
14816
15041
  /** Observable of layout-changed events. */
14817
15042
  get layoutUpdates$() {
14818
- return this.cachedObservable("layoutUpdates$", () => this.callSessionEvents$.pipe(filterAs(isLayoutChangedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
15043
+ return this.publicCachedObservable("layoutUpdates$", () => this.callSessionEvents$.pipe(filterAs(isLayoutChangedMetadata, "params"), (0, import_cjs$9.takeUntil)(this.destroyed$)));
14819
15044
  }
14820
15045
  /** Underlying `RTCPeerConnection`, for advanced use cases. */
14821
15046
  get rtcPeerConnection() {
@@ -14823,7 +15048,7 @@ var WebRTCCall = class extends Destroyable {
14823
15048
  }
14824
15049
  /** Observable of raw signaling events as plain objects. */
14825
15050
  get signalingEvent$() {
14826
- return this.cachedObservable("signalingEvent$", () => this.callEvent$.pipe((0, import_cjs$9.map)((event) => JSON.parse(JSON.stringify(event)))));
15051
+ return this.publicCachedObservable("signalingEvent$", () => this.callEvent$.pipe((0, import_cjs$9.map)((event) => JSON.parse(JSON.stringify(event)))));
14827
15052
  }
14828
15053
  /** Observable of WebRTC-specific signaling messages. */
14829
15054
  get webrtcMessages$() {
@@ -14850,17 +15075,22 @@ var WebRTCCall = class extends Destroyable {
14850
15075
  async sendDigits(dtmf) {
14851
15076
  return this.vertoManager.sendDigits(dtmf);
14852
15077
  }
14853
- /** Accepts an inbound call. */
14854
- answer() {
15078
+ /** Accepts an inbound call, optionally overriding media options for the answer. */
15079
+ answer(options) {
15080
+ this._answerMediaOptions = options;
14855
15081
  this._answered$.next(true);
14856
15082
  }
15083
+ /** Media options provided when answering. Used internally by the VertoManager. */
15084
+ get answerMediaOptions() {
15085
+ return this._answerMediaOptions;
15086
+ }
14857
15087
  /** Rejects an inbound call. */
14858
15088
  reject() {
14859
15089
  this._answered$.next(false);
14860
15090
  }
14861
15091
  /** Observable that emits `true` when answered, `false` when rejected. */
14862
15092
  get answered$() {
14863
- return this._answered$.asObservable();
15093
+ return this.deferEmission(this._answered$.asObservable());
14864
15094
  }
14865
15095
  /**
14866
15096
  * Sets the call layout and participant positions.
@@ -14881,10 +15111,10 @@ var WebRTCCall = class extends Destroyable {
14881
15111
  }
14882
15112
  /** Destroys the call, releasing all resources and subscriptions. */
14883
15113
  destroy() {
15114
+ if (this._status$.value === "destroyed") return;
14884
15115
  this._status$.next("destroyed");
14885
15116
  this.vertoManager.destroy();
14886
15117
  this.callEventsManager.destroy();
14887
- this.participantsMap.clear();
14888
15118
  super.destroy();
14889
15119
  }
14890
15120
  };
@@ -14892,6 +15122,23 @@ var WebRTCCall = class extends Destroyable {
14892
15122
  //#endregion
14893
15123
  //#region src/managers/CallFactory.ts
14894
15124
  /**
15125
+ * Infers the semantic error category from a raw Error thrown by VertoManager
15126
+ * or an RTCPeerConnection layer.
15127
+ */
15128
+ function inferCallErrorKind(error) {
15129
+ if (error instanceof RPCTimeoutError) return "timeout";
15130
+ if (error instanceof JSONRPCError) return "signaling";
15131
+ if (error instanceof MediaTrackError) return "media";
15132
+ if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
15133
+ return "internal";
15134
+ }
15135
+ /** Determines whether an error should be fatal (destroy the call). */
15136
+ function isFatalError(error) {
15137
+ if (error instanceof VertoPongError) return false;
15138
+ if (error instanceof MediaTrackError) return false;
15139
+ return true;
15140
+ }
15141
+ /**
14895
15142
  * Factory for creating WebRTCCall instances with proper manager wiring.
14896
15143
  * Eliminates circular dependencies by centralizing Call and Manager creation.
14897
15144
  */
@@ -14912,7 +15159,13 @@ var CallFactory = class {
14912
15159
  vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
14913
15160
  nodeId: options.nodeId,
14914
15161
  onError: (error) => {
14915
- callInstance.emitError(error);
15162
+ const callError = {
15163
+ kind: inferCallErrorKind(error),
15164
+ fatal: isFatalError(error),
15165
+ error,
15166
+ callId: callInstance.id
15167
+ };
15168
+ callInstance.emitError(callError);
14916
15169
  }
14917
15170
  }),
14918
15171
  callEventsManager: new CallEventsManager(callInstance)
@@ -15292,9 +15545,15 @@ var PendingRPC = class PendingRPC {
15292
15545
  return () => signal.removeEventListener("abort", abortHandler);
15293
15546
  }) : import_cjs$6.NEVER).subscribe({
15294
15547
  next: (response) => {
15295
- logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
15296
15548
  isSettled = true;
15297
- resolve(response);
15549
+ if (response.error) {
15550
+ const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
15551
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
15552
+ reject(rpcError);
15553
+ } else {
15554
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
15555
+ resolve(response);
15556
+ }
15298
15557
  subscription.unsubscribe();
15299
15558
  },
15300
15559
  error: (error) => {
@@ -15351,7 +15610,7 @@ var ClientSessionManager = class extends Destroyable {
15351
15610
  revision: 0
15352
15611
  };
15353
15612
  this._authorization$ = this.createBehaviorSubject(void 0);
15354
- this._errors$ = this.createSubject();
15613
+ this._errors$ = this.createReplaySubject(1);
15355
15614
  this._authenticated$ = this.createBehaviorSubject(false);
15356
15615
  this._subscriberInfo$ = this.createBehaviorSubject(null);
15357
15616
  this._calls$ = this.createBehaviorSubject({});
@@ -15579,6 +15838,7 @@ var ClientSessionManager = class extends Destroyable {
15579
15838
  ...params,
15580
15839
  ...persistedParams
15581
15840
  });
15841
+ this.transport.resetSessionEpoch();
15582
15842
  const response = await (0, import_cjs$5.lastValueFrom)((0, import_cjs$5.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$5.map)((res) => res.result), (0, import_cjs$5.filter)(isRPCConnectResult), (0, import_cjs$5.tap)(() => {
15583
15843
  logger$6.debug("[Session] Response passed filter, processing authentication result");
15584
15844
  }), (0, import_cjs$5.take)(1), (0, import_cjs$5.catchError)((err) => {
@@ -15621,12 +15881,13 @@ var ClientSessionManager = class extends Destroyable {
15621
15881
  }
15622
15882
  async createOutboundCall(destination, options = {}) {
15623
15883
  const destinationURI = destination instanceof Address ? destination.defaultChannel : destination;
15884
+ let callSession;
15624
15885
  try {
15625
- const callSession = await this.createCall({
15886
+ callSession = await this.createCall({
15626
15887
  to: destinationURI,
15627
15888
  ...options
15628
15889
  });
15629
- await (0, import_cjs$5.firstValueFrom)(callSession.selfId$.pipe((0, import_cjs$5.filter)((id) => Boolean(id)), (0, import_cjs$5.take)(1), (0, import_cjs$5.timeout)(this.callCreateTimeout)));
15890
+ await (0, import_cjs$5.firstValueFrom)((0, import_cjs$5.race)(callSession.selfId$.pipe((0, import_cjs$5.filter)((id) => Boolean(id)), (0, import_cjs$5.take)(1), (0, import_cjs$5.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$5.take)(1), (0, import_cjs$5.switchMap)((callError) => (0, import_cjs$5.throwError)(() => callError.error)))));
15630
15891
  this._calls$.next({
15631
15892
  [`${callSession.id}`]: callSession,
15632
15893
  ...this._calls$.value
@@ -15634,7 +15895,8 @@ var ClientSessionManager = class extends Destroyable {
15634
15895
  return callSession;
15635
15896
  } catch (error) {
15636
15897
  logger$6.error("[Session] Error creating outbound call:", error);
15637
- const callError = new CallCreateError("Call create timeout", error);
15898
+ callSession?.destroy();
15899
+ const callError = new CallCreateError(error instanceof import_cjs$5.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
15638
15900
  this._errors$.next(callError);
15639
15901
  throw callError;
15640
15902
  }
@@ -15660,7 +15922,7 @@ var ClientSessionManager = class extends Destroyable {
15660
15922
  return callSession;
15661
15923
  } catch (error) {
15662
15924
  logger$6.error("[Session] Error creating call session:", error);
15663
- throw new CallCreateError("Call create error", error);
15925
+ throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
15664
15926
  }
15665
15927
  }
15666
15928
  destroy() {
@@ -15909,7 +16171,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
15909
16171
  this.shouldReconnect = false;
15910
16172
  this._status$ = this.createBehaviorSubject("disconnected");
15911
16173
  this._incomingMessages$ = this.createSubject();
15912
- this._errors$ = this.createSubject();
16174
+ this._errors$ = this.createReplaySubject(1);
15913
16175
  this.reconnectDelayMin = options.reconnectDelayMin ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MIN_MS;
15914
16176
  this.reconnectDelayMax = options.reconnectDelayMax ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MAX_MS;
15915
16177
  this.connectionTimeout = options.connectionTimeout ?? WebSocketController.DEFAULT_CONNECTION_TIMEOUT_MS;
@@ -16060,7 +16322,30 @@ function isSignalwirePingRequest(value) {
16060
16322
  //#region src/managers/TransportManager.ts
16061
16323
  var import_cjs$1 = require_cjs();
16062
16324
  const logger$2 = getLogger();
16063
- var TransportManager = class extends Destroyable {
16325
+ var TransportManager = class TransportManager extends Destroyable {
16326
+ /**
16327
+ * Normalise a server event timestamp to epoch seconds.
16328
+ *
16329
+ * The server uses two formats:
16330
+ * - `webrtc.message`: float epoch seconds (e.g. 1774372099.022817)
16331
+ * - all other events: int epoch microseconds (e.g. 1774372099925857)
16332
+ *
16333
+ * Values above 1e12 are treated as microseconds and divided by 1e6.
16334
+ */
16335
+ static toEpochSeconds(ts) {
16336
+ const n = typeof ts === "string" ? Number(ts) : ts;
16337
+ return n > 0xe8d4a51000 ? n / 1e6 : n;
16338
+ }
16339
+ /**
16340
+ * Extract the event timestamp from a signalwire.event message.
16341
+ * Returns `null` for messages that have no timestamp
16342
+ * (e.g. signalwire.authorization.state, RPC responses).
16343
+ */
16344
+ static extractEventTimestamp(message) {
16345
+ if (!isSignalwireRequest(message)) return null;
16346
+ if (message.params.event_type === "signalwire.authorization.state") return null;
16347
+ return TransportManager.toEpochSeconds(message.params.timestamp);
16348
+ }
16064
16349
  constructor(storage, protocolKey, webSocketConstructor, relayHost, onError) {
16065
16350
  super();
16066
16351
  this.storage = storage;
@@ -16093,6 +16378,23 @@ var TransportManager = class extends Destroyable {
16093
16378
  return true;
16094
16379
  });
16095
16380
  };
16381
+ this.discardStaleEvents = () => {
16382
+ return (0, import_cjs$1.filter)((message) => {
16383
+ const ts = TransportManager.extractEventTimestamp(message);
16384
+ if (ts === null) return true;
16385
+ if (this._sessionEpoch === null) {
16386
+ this._sessionEpoch = ts;
16387
+ return true;
16388
+ }
16389
+ if (ts < this._sessionEpoch) {
16390
+ const eventType = isSignalwireRequest(message) ? message.params.event_type : "unknown";
16391
+ logger$2.warn(`[Transport] Discarding stale event: ${eventType} (timestamp=${ts.toFixed(3)}, sessionEpoch=${this._sessionEpoch.toFixed(3)}, delta=${(this._sessionEpoch - ts).toFixed(3)}s)`);
16392
+ return false;
16393
+ }
16394
+ return true;
16395
+ });
16396
+ };
16397
+ this._sessionEpoch = null;
16096
16398
  this._outgoingMessages$ = this.createSubject();
16097
16399
  this._webSocketConnections = new WebSocketController(webSocketConstructor, relayHost, this._outgoingMessages$.asObservable(), {
16098
16400
  connectionTimeout: PreferencesContainer.instance.connectionTimeout,
@@ -16117,7 +16419,14 @@ var TransportManager = class extends Destroyable {
16117
16419
  return import_cjs$1.EMPTY;
16118
16420
  }), (0, import_cjs$1.share)(), (0, import_cjs$1.takeUntil)(this.destroyed$));
16119
16421
  this._jsonRPCResponse$ = this._jsonRPCMessage$.pipe((0, import_cjs$1.filter)(isJSONRPCResponse));
16120
- this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), (0, import_cjs$1.filter)((message) => !isJSONRPCResponse(message)), (0, import_cjs$1.share)(), (0, import_cjs$1.takeUntil)(this.destroyed$));
16422
+ this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), (0, import_cjs$1.filter)((message) => !isJSONRPCResponse(message)), this.discardStaleEvents(), (0, import_cjs$1.share)(), (0, import_cjs$1.takeUntil)(this.destroyed$));
16423
+ }
16424
+ /**
16425
+ * Reset the session epoch. Call this before each signalwire.connect
16426
+ * so that the first event after authentication establishes the new baseline.
16427
+ */
16428
+ resetSessionEpoch() {
16429
+ this._sessionEpoch = null;
16121
16430
  }
16122
16431
  async setProtocol(protocol) {
16123
16432
  this.protocol$.next(protocol);
@@ -16236,7 +16545,8 @@ const buildOptionsFromDestination = (destination) => {
16236
16545
  const channel = new URLSearchParams(queryString).get("channel");
16237
16546
  if (channel === "video") return {
16238
16547
  audio: true,
16239
- video: true
16548
+ video: true,
16549
+ receiveVideo: true
16240
16550
  };
16241
16551
  else if (channel === "audio") return {
16242
16552
  audio: true,
@@ -16266,7 +16576,7 @@ var SignalWire = class extends Destroyable {
16266
16576
  this._directory$ = this.createBehaviorSubject(void 0);
16267
16577
  this._isConnected$ = this.createBehaviorSubject(false);
16268
16578
  this._isRegistered$ = this.createBehaviorSubject(false);
16269
- this._errors$ = this.createSubject();
16579
+ this._errors$ = this.createReplaySubject(1);
16270
16580
  this._options = {};
16271
16581
  this._deps = new DependencyContainer();
16272
16582
  this._options = {
@@ -16343,7 +16653,12 @@ var SignalWire = class extends Destroyable {
16343
16653
  this._subscriber$.next(new Subscriber(this._deps.http));
16344
16654
  if (!this._options.skipConnection) await this.connect();
16345
16655
  if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
16346
- if (!this._options.skipRegister) this.register();
16656
+ if (!this._options.skipRegister) try {
16657
+ await this.register();
16658
+ } catch (error) {
16659
+ logger$1.error("[SignalWire] Registration failed:", error);
16660
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
16661
+ }
16347
16662
  this.handleAttachments();
16348
16663
  }
16349
16664
  async handleAttachments() {
@@ -16435,7 +16750,7 @@ var SignalWire = class extends Destroyable {
16435
16750
  * ```
16436
16751
  */
16437
16752
  get subscriber$() {
16438
- return this._subscriber$.asObservable();
16753
+ return this.deferEmission(this._subscriber$.asObservable());
16439
16754
  }
16440
16755
  /** Current subscriber snapshot, or `undefined` if not yet authenticated. */
16441
16756
  get subscriber() {
@@ -16454,7 +16769,7 @@ var SignalWire = class extends Destroyable {
16454
16769
  * ```
16455
16770
  */
16456
16771
  get directory$() {
16457
- return this._directory$.asObservable();
16772
+ return this.deferEmission(this._directory$.asObservable());
16458
16773
  }
16459
16774
  /**
16460
16775
  * Current directory snapshot, or `undefined` if the client is not yet connected.
@@ -16465,7 +16780,7 @@ var SignalWire = class extends Destroyable {
16465
16780
  }
16466
16781
  /** Observable that emits when the subscriber registration state changes. */
16467
16782
  get isRegistered$() {
16468
- return this._isRegistered$.asObservable();
16783
+ return this.deferEmission(this._isRegistered$.asObservable());
16469
16784
  }
16470
16785
  /** Whether the subscriber is currently registered. */
16471
16786
  get isRegistered() {
@@ -16477,15 +16792,15 @@ var SignalWire = class extends Destroyable {
16477
16792
  }
16478
16793
  /** Observable that emits when the connection state changes. */
16479
16794
  get isConnected$() {
16480
- return this._isConnected$.asObservable();
16795
+ return this.deferEmission(this._isConnected$.asObservable());
16481
16796
  }
16482
16797
  /** Observable that emits `true` when the client is both connected and authenticated. */
16483
16798
  get ready$() {
16484
- return this.cachedObservable("ready$", () => this._isConnected$.pipe((0, import_cjs.switchMap)((connected) => connected ? this._clientSession.authenticated$ : (0, import_cjs.of)(false))));
16799
+ return this.publicCachedObservable("ready$", () => this._isConnected$.pipe((0, import_cjs.switchMap)((connected) => connected ? this._clientSession.authenticated$ : (0, import_cjs.of)(false))));
16485
16800
  }
16486
16801
  /** Observable stream of errors from transport, authentication, and devices. */
16487
16802
  get errors$() {
16488
- return this._errors$.asObservable();
16803
+ return this.deferEmission(this._errors$.asObservable());
16489
16804
  }
16490
16805
  /** Disconnects the WebSocket and tears down the session. */
16491
16806
  async disconnect() {
@@ -16506,8 +16821,22 @@ var SignalWire = class extends Destroyable {
16506
16821
  }));
16507
16822
  this._isRegistered$.next(true);
16508
16823
  } catch (error) {
16509
- logger$1.error("[SignalWire] Failed to register subscriber:", error);
16824
+ logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
16825
+ if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
16826
+ logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
16827
+ await this._transport.execute(RPCExecute({
16828
+ method: "subscriber.online",
16829
+ params: {}
16830
+ }));
16831
+ this._isRegistered$.next(true);
16832
+ }).catch((reauthError) => {
16833
+ logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
16834
+ 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 }) });
16835
+ this._errors$.next(registerError);
16836
+ throw registerError;
16837
+ });
16510
16838
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
16839
+ throw error;
16511
16840
  }
16512
16841
  }
16513
16842
  /** Unregisters the subscriber, going offline for inbound calls. */
@@ -16546,7 +16875,7 @@ var SignalWire = class extends Destroyable {
16546
16875
  }
16547
16876
  /** Observable list of available audio input (microphone) devices. */
16548
16877
  get audioInputDevices$() {
16549
- return this._deviceController.audioInputDevices$;
16878
+ return this.deferEmission(this._deviceController.audioInputDevices$);
16550
16879
  }
16551
16880
  /** Current snapshot of available audio input devices. */
16552
16881
  get audioInputDevices() {
@@ -16554,7 +16883,7 @@ var SignalWire = class extends Destroyable {
16554
16883
  }
16555
16884
  /** Observable list of available audio output (speaker) devices. */
16556
16885
  get audioOutputDevices$() {
16557
- return this._deviceController.audioOutputDevices$;
16886
+ return this.deferEmission(this._deviceController.audioOutputDevices$);
16558
16887
  }
16559
16888
  /** Current snapshot of available audio output devices. */
16560
16889
  get audioOutputDevices() {
@@ -16562,7 +16891,7 @@ var SignalWire = class extends Destroyable {
16562
16891
  }
16563
16892
  /** Observable list of available video input (camera) devices. */
16564
16893
  get videoInputDevices$() {
16565
- return this._deviceController.videoInputDevices$;
16894
+ return this.deferEmission(this._deviceController.videoInputDevices$);
16566
16895
  }
16567
16896
  /** Current snapshot of available video input devices. */
16568
16897
  get videoInputDevices() {
@@ -16570,15 +16899,15 @@ var SignalWire = class extends Destroyable {
16570
16899
  }
16571
16900
  /** Observable of the currently selected audio input device. */
16572
16901
  get selectedAudioInputDevice$() {
16573
- return this._deviceController.selectedAudioInputDevice$;
16902
+ return this.deferEmission(this._deviceController.selectedAudioInputDevice$);
16574
16903
  }
16575
16904
  /** Observable of the currently selected audio output device. */
16576
16905
  get selectedAudioOutputDevice$() {
16577
- return this._deviceController.selectedAudioOutputDevice$;
16906
+ return this.deferEmission(this._deviceController.selectedAudioOutputDevice$);
16578
16907
  }
16579
16908
  /** Observable of the currently selected video input device. */
16580
16909
  get selectedVideoInputDevice$() {
16581
- return this._deviceController.selectedVideoInputDevice$;
16910
+ return this.deferEmission(this._deviceController.selectedVideoInputDevice$);
16582
16911
  }
16583
16912
  /** Currently selected audio input device, or `null` if none. */
16584
16913
  get selectedAudioInputDevice() {