@signalwire/js 4.0.0-beta.0 → 4.0.0-beta.10

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/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_operators = require('./operators-DT4UB24-.cjs');
1
+ const require_operators = require('./operators-mm21prWr.cjs');
2
2
  let jwt_decode = require("jwt-decode");
3
3
  let rxjs = require("rxjs");
4
4
  let uuid = require("uuid");
@@ -12,11 +12,55 @@ var Destroyable = class {
12
12
  this._destroyed$ = new rxjs.Subject();
13
13
  }
14
14
  destroy() {
15
+ this._observableCache?.clear();
15
16
  this.subscriptions.forEach((sub) => sub.unsubscribe());
16
17
  this.subjects.forEach((subject) => subject.complete());
17
18
  this._destroyed$.next();
18
19
  this._destroyed$.complete();
19
20
  }
21
+ cachedObservable(key, factory) {
22
+ this._observableCache ??= /* @__PURE__ */ new Map();
23
+ let cached = this._observableCache.get(key);
24
+ if (!cached) {
25
+ cached = factory();
26
+ this._observableCache.set(key, cached);
27
+ }
28
+ return cached;
29
+ }
30
+ /**
31
+ * Like `cachedObservable`, but defers emissions to the microtask queue
32
+ * via `observeOn(asapScheduler)`.
33
+ *
34
+ * Use ONLY for public-facing observable getters that external consumers
35
+ * subscribe to. Prevents a class of bugs where `BehaviorSubject` or
36
+ * `ReplaySubject` replays synchronously during `subscribe()`, before
37
+ * the subscription variable is assigned in the caller's scope.
38
+ *
39
+ * Do NOT use for observables consumed internally by the SDK — internal
40
+ * code using `subscribeTo()`, `firstValueFrom()`, or `withLatestFrom()`
41
+ * depends on synchronous emission delivery.
42
+ */
43
+ publicCachedObservable(key, factory) {
44
+ const publicKey = `public:${key}`;
45
+ this._observableCache ??= /* @__PURE__ */ new Map();
46
+ let cached = this._observableCache.get(publicKey);
47
+ if (!cached) {
48
+ cached = factory().pipe((0, rxjs.observeOn)(rxjs.asapScheduler));
49
+ this._observableCache.set(publicKey, cached);
50
+ }
51
+ return cached;
52
+ }
53
+ /**
54
+ * Wraps an observable so emissions are deferred to the microtask queue.
55
+ *
56
+ * Use ONLY for public-facing getters that expose a subject via
57
+ * `.asObservable()` without going through `cachedObservable`.
58
+ *
59
+ * Do NOT use for observables consumed internally by the SDK.
60
+ */
61
+ deferEmission(observable) {
62
+ return observable.pipe((0, rxjs.observeOn)(rxjs.asapScheduler));
63
+ }
20
64
  subscribeTo(observable, observerOrNext) {
21
65
  const subscription = observable.subscribe(observerOrNext);
22
66
  this.subscriptions.push(subscription);
@@ -37,7 +81,7 @@ var Destroyable = class {
37
81
  return subject;
38
82
  }
39
83
  get $() {
40
- return (0, rxjs.merge)(...this.subjects.map((s) => s instanceof rxjs.BehaviorSubject ? s.pipe((0, rxjs.skip)(1)) : s)).pipe((0, rxjs.map)((_) => this));
84
+ return this.cachedObservable("$", () => (0, rxjs.merge)(...this.subjects.map((s) => s instanceof rxjs.BehaviorSubject ? s.pipe((0, rxjs.skip)(1)) : s)).pipe((0, rxjs.map)((_) => this)));
41
85
  }
42
86
  /**
43
87
  * Observable that emits when the instance is destroyed
@@ -285,7 +329,7 @@ var PreferencesContainer = class PreferencesContainer {
285
329
  skipDeviceMonitoring: false,
286
330
  savePreferences: false
287
331
  };
288
- this.receiveVideo = true;
332
+ this.receiveVideo = false;
289
333
  this.receiveAudio = true;
290
334
  this.preferredAudioInput = null;
291
335
  this.preferredAudioOutput = null;
@@ -588,15 +632,16 @@ const selectDevice = (devices = [], selected, preferred) => {
588
632
  return selected;
589
633
  };
590
634
  var NavigatorDeviceController = class extends Destroyable {
591
- constructor() {
635
+ constructor(webRTCApiProvider) {
592
636
  super();
637
+ this.webRTCApiProvider = webRTCApiProvider;
593
638
  this.deviceChangeHandler = () => {
594
639
  logger$18.debug("[DeviceController] Device change detected");
595
640
  this.enumerateDevices();
596
641
  };
597
642
  this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
598
643
  this._selectedDevicesState$ = this.createBehaviorSubject(initialSelectedDevicesState);
599
- this._errors$ = this.createSubject();
644
+ this._errors$ = this.createReplaySubject(1);
600
645
  this.init();
601
646
  }
602
647
  get selectedAudioInputDeviceConstraints() {
@@ -613,25 +658,25 @@ var NavigatorDeviceController = class extends Destroyable {
613
658
  return {};
614
659
  }
615
660
  get errors$() {
616
- return this._errors$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
661
+ return this.cachedObservable("errors$", () => this._errors$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
617
662
  }
618
663
  get audioInputDevices$() {
619
- return this._devicesState$.pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$));
664
+ return this.cachedObservable("audioInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
620
665
  }
621
666
  get audioOutputDevices$() {
622
- return this._devicesState$.pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$));
667
+ return this.cachedObservable("audioOutputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
623
668
  }
624
669
  get videoInputDevices$() {
625
- return this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$));
670
+ return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
626
671
  }
627
672
  get selectedAudioInputDevice$() {
628
- return this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected audio input device changed:", info)));
673
+ return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected audio input device changed:", info))));
629
674
  }
630
675
  get selectedAudioOutputDevice$() {
631
- return this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected audio output device changed:", info)));
676
+ return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected audio output device changed:", info))));
632
677
  }
633
678
  get selectedVideoInputDevice$() {
634
- return this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected video input device changed:", info)));
679
+ return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$18.debug("[DeviceController] Selected video input device changed:", info))));
635
680
  }
636
681
  get selectedAudioInputDevice() {
637
682
  return this._selectedDevicesState$.value.audioinput;
@@ -671,24 +716,22 @@ var NavigatorDeviceController = class extends Destroyable {
671
716
  });
672
717
  }
673
718
  init() {
674
- if (navigator.mediaDevices) {
675
- this.subscribeTo(this._devicesState$.pipe((0, rxjs.debounceTime)(PreferencesContainer.instance.deviceDebounceTime)), (devicesState) => {
676
- const currentSelected = this._selectedDevicesState$.value;
677
- const newAudioInput = selectDevice(devicesState.audioinput, currentSelected.audioinput, PreferencesContainer.instance.preferredAudioInput);
678
- const newAudioOutput = selectDevice(devicesState.audiooutput, currentSelected.audiooutput, PreferencesContainer.instance.preferredAudioOutput);
679
- const newVideoInput = selectDevice(devicesState.videoinput, currentSelected.videoinput, PreferencesContainer.instance.preferredVideoInput);
680
- if (newAudioInput !== currentSelected.audioinput || newAudioOutput !== currentSelected.audiooutput || newVideoInput !== currentSelected.videoinput) this._selectedDevicesState$.next({
681
- audioinput: newAudioInput,
682
- audiooutput: newAudioOutput,
683
- videoinput: newVideoInput
684
- });
719
+ this.subscribeTo(this._devicesState$.pipe((0, rxjs.debounceTime)(PreferencesContainer.instance.deviceDebounceTime)), (devicesState) => {
720
+ const currentSelected = this._selectedDevicesState$.value;
721
+ const newAudioInput = selectDevice(devicesState.audioinput, currentSelected.audioinput, PreferencesContainer.instance.preferredAudioInput);
722
+ const newAudioOutput = selectDevice(devicesState.audiooutput, currentSelected.audiooutput, PreferencesContainer.instance.preferredAudioOutput);
723
+ const newVideoInput = selectDevice(devicesState.videoinput, currentSelected.videoinput, PreferencesContainer.instance.preferredVideoInput);
724
+ if (newAudioInput !== currentSelected.audioinput || newAudioOutput !== currentSelected.audiooutput || newVideoInput !== currentSelected.videoinput) this._selectedDevicesState$.next({
725
+ audioinput: newAudioInput,
726
+ audiooutput: newAudioOutput,
727
+ videoinput: newVideoInput
685
728
  });
686
- this.enumerateDevices();
687
- }
729
+ });
730
+ this.enumerateDevices();
688
731
  }
689
732
  enableDeviceMonitoring() {
690
733
  this.disableDeviceMonitoring();
691
- navigator.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
734
+ this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
692
735
  if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
693
736
  logger$18.debug("[DeviceController] Polling devices due to interval");
694
737
  this.enumerateDevices();
@@ -696,7 +739,7 @@ var NavigatorDeviceController = class extends Destroyable {
696
739
  this.enumerateDevices();
697
740
  }
698
741
  disableDeviceMonitoring() {
699
- navigator.mediaDevices.removeEventListener("devicechange", this.deviceChangeHandler);
742
+ this.webRTCApiProvider.mediaDevices.removeEventListener("devicechange", this.deviceChangeHandler);
700
743
  if (this._devicesPoolingSubscription) {
701
744
  this._devicesPoolingSubscription.unsubscribe();
702
745
  this._devicesPoolingSubscription = void 0;
@@ -704,7 +747,7 @@ var NavigatorDeviceController = class extends Destroyable {
704
747
  }
705
748
  async enumerateDevices() {
706
749
  try {
707
- const devicesByKind = (await navigator.mediaDevices.enumerateDevices()).reduce((acc, device) => {
750
+ const devicesByKind = (await this.webRTCApiProvider.mediaDevices.enumerateDevices()).reduce((acc, device) => {
708
751
  acc[device.kind].push(device);
709
752
  return acc;
710
753
  }, {
@@ -727,7 +770,7 @@ var NavigatorDeviceController = class extends Destroyable {
727
770
  if (deviceInfo.kind === "audiooutput") return null;
728
771
  try {
729
772
  const constraints = this.deviceInfoToConstraints(deviceInfo);
730
- const stream = await navigator.mediaDevices.getUserMedia({
773
+ const stream = await this.webRTCApiProvider.mediaDevices.getUserMedia({
731
774
  audio: deviceInfo.kind === "audioinput" ? constraints : false,
732
775
  video: deviceInfo.kind === "videoinput" ? constraints : false
733
776
  });
@@ -899,9 +942,23 @@ var DependencyContainer = class {
899
942
  this._webSocketConstructor = WebSocketConstructor;
900
943
  }
901
944
  get deviceController() {
902
- this._deviceController ??= new NavigatorDeviceController();
945
+ this._deviceController ??= new NavigatorDeviceController(this.webRTCApiProvider);
903
946
  return this._deviceController;
904
947
  }
948
+ get webRTCApiProvider() {
949
+ if (!this._webRTCApiProvider) {
950
+ if (typeof RTCPeerConnection === "undefined" || typeof navigator === "undefined") throw new require_operators.DependencyError("WebRTCApiProvider: RTCPeerConnection or navigator.mediaDevices is not available. Please provide a custom webRTCApiProvider in SignalWireOptions.");
951
+ this._webRTCApiProvider = {
952
+ RTCPeerConnection,
953
+ mediaDevices: navigator.mediaDevices
954
+ };
955
+ }
956
+ return this._webRTCApiProvider;
957
+ }
958
+ set webRTCApiProvider(webRTCApiProvider) {
959
+ this._webRTCApiProvider = webRTCApiProvider;
960
+ this._deviceController = void 0;
961
+ }
905
962
  get authorizationStateKey() {
906
963
  return `sw:${this.subscriberId}:as`;
907
964
  }
@@ -1035,6 +1092,15 @@ const RPCConnect = (params) => {
1035
1092
  });
1036
1093
  };
1037
1094
 
1095
+ //#endregion
1096
+ //#region src/core/RPCMessages/RPCReauthenticate.ts
1097
+ const RPCReauthenticate = (authentication) => {
1098
+ return buildRPCRequest({
1099
+ method: "signalwire.reauthenticate",
1100
+ params: { authentication }
1101
+ });
1102
+ };
1103
+
1038
1104
  //#endregion
1039
1105
  //#region src/core/RPCMessages/RPCPing.ts
1040
1106
  const RPCPingResponse = (id, timestamp) => {
@@ -1055,7 +1121,7 @@ const RPCExecute = ({ method, params }) => {
1055
1121
 
1056
1122
  //#endregion
1057
1123
  //#region src/core/RPCMessages/VertoMessages.ts
1058
- const tmpMap = {
1124
+ const SDK_TO_VERTO_FIELD_MAP = {
1059
1125
  id: "callID",
1060
1126
  destinationNumber: "destination_number",
1061
1127
  remoteCallerName: "remote_caller_id_name",
@@ -1064,19 +1130,31 @@ const tmpMap = {
1064
1130
  callerNumber: "caller_id_number",
1065
1131
  fromCallAddressId: "from_fabric_address_id"
1066
1132
  };
1133
+ const EXCLUDED_DIALOG_PARAMS = new Set([
1134
+ "remoteSdp",
1135
+ "localStream",
1136
+ "remoteStream"
1137
+ ]);
1067
1138
  /**
1068
- * Translate SDK fields into verto variables
1139
+ * Translate SDK fields into verto variables.
1140
+ * Returns a new object — the input is never mutated.
1069
1141
  */
1142
+ /** @internal Exported for testing only. */
1070
1143
  const filterVertoParams = (params) => {
1071
- if (Object.prototype.hasOwnProperty.call(params, "dialogParams")) {
1072
- const { remoteSdp, localStream, remoteStream, ...dialogParams } = params.dialogParams;
1073
- for (const key in tmpMap) if (key && Object.prototype.hasOwnProperty.call(dialogParams, key)) {
1074
- dialogParams[tmpMap[key]] = dialogParams[key];
1075
- delete dialogParams[key];
1076
- }
1077
- params.dialogParams = dialogParams;
1078
- }
1079
- return params;
1144
+ if (!Object.prototype.hasOwnProperty.call(params, "dialogParams")) return params;
1145
+ const sourceDialogParams = params.dialogParams;
1146
+ const filteredDialogParams = Object.entries(sourceDialogParams).reduce((acc, [key, value]) => {
1147
+ if (EXCLUDED_DIALOG_PARAMS.has(key)) return acc;
1148
+ const mappedKey = SDK_TO_VERTO_FIELD_MAP[key] ?? key;
1149
+ return {
1150
+ ...acc,
1151
+ [mappedKey]: value
1152
+ };
1153
+ }, {});
1154
+ return {
1155
+ ...params,
1156
+ dialogParams: filteredDialogParams
1157
+ };
1080
1158
  };
1081
1159
  const buildVertoRPCMessage = (method) => {
1082
1160
  return (params = {}) => {
@@ -1187,17 +1265,21 @@ var AttachManager = class {
1187
1265
  buildCallOptions(attachment) {
1188
1266
  const { audio: audioDirection, video: videoDirection } = attachment.mediaDirections;
1189
1267
  const { audioInputDevice, videoInputDevice } = attachment;
1268
+ const receiveAudio = audioDirection.includes("recv");
1269
+ const receiveVideo = videoDirection.includes("recv");
1270
+ const sendAudio = audioDirection.includes("send");
1271
+ const sendVideo = videoDirection.includes("send");
1190
1272
  return {
1191
- receiveAudio: audioDirection.includes("recv"),
1192
- receiveVideo: videoDirection.includes("recv"),
1193
- inputAudioDeviceConstraints: {
1194
- audio: audioDirection.includes("send"),
1273
+ receiveAudio,
1274
+ receiveVideo,
1275
+ inputAudioDeviceConstraints: sendAudio ? {
1276
+ audio: true,
1195
1277
  ...this.deviceController.deviceInfoToConstraints(audioInputDevice)
1196
- },
1197
- inputVideoDeviceConstraints: {
1198
- video: videoDirection.includes("send"),
1278
+ } : void 0,
1279
+ inputVideoDeviceConstraints: sendVideo ? {
1280
+ video: true,
1199
1281
  ...this.deviceController.deviceInfoToConstraints(videoInputDevice)
1200
- },
1282
+ } : void 0,
1201
1283
  reattach: true
1202
1284
  };
1203
1285
  }
@@ -1375,7 +1457,7 @@ var SelfCapabilities = class extends Destroyable {
1375
1457
  }
1376
1458
  /** Observable for self member capabilities */
1377
1459
  get self$() {
1378
- return this._state$.pipe((0, rxjs.map)((state) => state.self), (0, rxjs.distinctUntilChanged)());
1460
+ return this.cachedObservable("self$", () => this._state$.pipe((0, rxjs.map)((state) => state.self), (0, rxjs.distinctUntilChanged)()));
1379
1461
  }
1380
1462
  /** Current self member capabilities */
1381
1463
  get self() {
@@ -1383,7 +1465,7 @@ var SelfCapabilities = class extends Destroyable {
1383
1465
  }
1384
1466
  /** Observable for other member capabilities */
1385
1467
  get member$() {
1386
- return this._state$.pipe((0, rxjs.map)((state) => state.member), (0, rxjs.distinctUntilChanged)());
1468
+ return this.cachedObservable("member$", () => this._state$.pipe((0, rxjs.map)((state) => state.member), (0, rxjs.distinctUntilChanged)()));
1387
1469
  }
1388
1470
  /** Current other member capabilities */
1389
1471
  get member() {
@@ -1391,7 +1473,7 @@ var SelfCapabilities = class extends Destroyable {
1391
1473
  }
1392
1474
  /** Observable for end call capability */
1393
1475
  get end$() {
1394
- return this._state$.pipe((0, rxjs.map)((state) => state.end), (0, rxjs.distinctUntilChanged)());
1476
+ return this.cachedObservable("end$", () => this._state$.pipe((0, rxjs.map)((state) => state.end), (0, rxjs.distinctUntilChanged)()));
1395
1477
  }
1396
1478
  /** Current end call capability */
1397
1479
  get end() {
@@ -1399,7 +1481,7 @@ var SelfCapabilities = class extends Destroyable {
1399
1481
  }
1400
1482
  /** Observable for set layout capability */
1401
1483
  get setLayout$() {
1402
- return this._state$.pipe((0, rxjs.map)((state) => state.setLayout), (0, rxjs.distinctUntilChanged)());
1484
+ return this.cachedObservable("setLayout$", () => this._state$.pipe((0, rxjs.map)((state) => state.setLayout), (0, rxjs.distinctUntilChanged)()));
1403
1485
  }
1404
1486
  /** Current set layout capability */
1405
1487
  get setLayout() {
@@ -1407,7 +1489,7 @@ var SelfCapabilities = class extends Destroyable {
1407
1489
  }
1408
1490
  /** Observable for send digit capability */
1409
1491
  get sendDigit$() {
1410
- return this._state$.pipe((0, rxjs.map)((state) => state.sendDigit), (0, rxjs.distinctUntilChanged)());
1492
+ return this.cachedObservable("sendDigit$", () => this._state$.pipe((0, rxjs.map)((state) => state.sendDigit), (0, rxjs.distinctUntilChanged)()));
1411
1493
  }
1412
1494
  /** Current send digit capability */
1413
1495
  get sendDigit() {
@@ -1415,7 +1497,7 @@ var SelfCapabilities = class extends Destroyable {
1415
1497
  }
1416
1498
  /** Observable for vmuted hide capability */
1417
1499
  get vmutedHide$() {
1418
- return this._state$.pipe((0, rxjs.map)((state) => state.vmutedHide), (0, rxjs.distinctUntilChanged)());
1500
+ return this.cachedObservable("vmutedHide$", () => this._state$.pipe((0, rxjs.map)((state) => state.vmutedHide), (0, rxjs.distinctUntilChanged)()));
1419
1501
  }
1420
1502
  /** Current vmuted hide capability */
1421
1503
  get vmutedHide() {
@@ -1423,7 +1505,7 @@ var SelfCapabilities = class extends Destroyable {
1423
1505
  }
1424
1506
  /** Observable for lock capability */
1425
1507
  get lock$() {
1426
- return this._state$.pipe((0, rxjs.map)((state) => state.lock), (0, rxjs.distinctUntilChanged)());
1508
+ return this.cachedObservable("lock$", () => this._state$.pipe((0, rxjs.map)((state) => state.lock), (0, rxjs.distinctUntilChanged)()));
1427
1509
  }
1428
1510
  /** Current lock capability */
1429
1511
  get lock() {
@@ -1431,7 +1513,7 @@ var SelfCapabilities = class extends Destroyable {
1431
1513
  }
1432
1514
  /** Observable for device capability */
1433
1515
  get device$() {
1434
- return this._state$.pipe((0, rxjs.map)((state) => state.device), (0, rxjs.distinctUntilChanged)());
1516
+ return this.cachedObservable("device$", () => this._state$.pipe((0, rxjs.map)((state) => state.device), (0, rxjs.distinctUntilChanged)()));
1435
1517
  }
1436
1518
  /** Current device capability */
1437
1519
  get device() {
@@ -1439,7 +1521,7 @@ var SelfCapabilities = class extends Destroyable {
1439
1521
  }
1440
1522
  /** Observable for screenshare capability */
1441
1523
  get screenshare$() {
1442
- return this._state$.pipe((0, rxjs.map)((state) => state.screenshare), (0, rxjs.distinctUntilChanged)());
1524
+ return this.cachedObservable("screenshare$", () => this._state$.pipe((0, rxjs.map)((state) => state.screenshare), (0, rxjs.distinctUntilChanged)()));
1443
1525
  }
1444
1526
  /** Current screenshare capability */
1445
1527
  get screenshare() {
@@ -1492,83 +1574,83 @@ var Participant = class extends Destroyable {
1492
1574
  }
1493
1575
  /** Observable of the participant's display name. */
1494
1576
  get name$() {
1495
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.name), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1577
+ return this.cachedObservable("name$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.name), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1496
1578
  }
1497
1579
  /** Observable of the participant type (e.g. `'member'`, `'screen'`). */
1498
1580
  get type$() {
1499
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.type), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1581
+ return this.cachedObservable("type$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.type), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1500
1582
  }
1501
1583
  /** Observable indicating whether the participant has raised their hand. */
1502
1584
  get handraised$() {
1503
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.handraised), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1585
+ return this.cachedObservable("handraised$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.handraised), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1504
1586
  }
1505
1587
  /** Observable indicating whether the participant is visible in the layout. */
1506
1588
  get visible$() {
1507
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.visible), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1589
+ return this.cachedObservable("visible$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.visible), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1508
1590
  }
1509
1591
  /** Observable indicating whether the participant's audio is muted. */
1510
1592
  get audioMuted$() {
1511
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.audio_muted), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1593
+ return this.cachedObservable("audioMuted$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.audio_muted), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1512
1594
  }
1513
1595
  /** Observable indicating whether the participant's video is muted. */
1514
1596
  get videoMuted$() {
1515
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.video_muted), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1597
+ return this.cachedObservable("videoMuted$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.video_muted), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1516
1598
  }
1517
1599
  /** Observable indicating whether the participant is deafened. */
1518
1600
  get deaf$() {
1519
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.deaf), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1601
+ return this.cachedObservable("deaf$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.deaf), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1520
1602
  }
1521
1603
  /** Observable of the participant's microphone input volume. */
1522
1604
  get inputVolume$() {
1523
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.input_volume), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1605
+ return this.cachedObservable("inputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_volume), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1524
1606
  }
1525
1607
  /** Observable of the participant's speaker output volume. */
1526
1608
  get outputVolume$() {
1527
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.output_volume), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1609
+ return this.cachedObservable("outputVolume$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.output_volume), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1528
1610
  }
1529
1611
  /** Observable of the microphone input sensitivity level. */
1530
1612
  get inputSensitivity$() {
1531
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.input_sensitivity), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1613
+ return this.cachedObservable("inputSensitivity$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.input_sensitivity), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1532
1614
  }
1533
1615
  /** Observable indicating whether echo cancellation is enabled. */
1534
1616
  get echoCancellation$() {
1535
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.echo_cancellation), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1617
+ return this.cachedObservable("echoCancellation$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.echo_cancellation), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1536
1618
  }
1537
1619
  /** Observable indicating whether auto-gain control is enabled. */
1538
1620
  get autoGain$() {
1539
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.auto_gain), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1621
+ return this.cachedObservable("autoGain$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.auto_gain), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1540
1622
  }
1541
1623
  /** Observable indicating whether noise suppression is enabled. */
1542
1624
  get noiseSuppression$() {
1543
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.noise_suppression), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1625
+ return this.cachedObservable("noiseSuppression$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.noise_suppression), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1544
1626
  }
1545
1627
  /** Observable indicating whether low-bitrate mode is active. */
1546
1628
  get lowbitrate$() {
1547
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.lowbitrate), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1629
+ return this.cachedObservable("lowbitrate$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.lowbitrate), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1548
1630
  }
1549
1631
  /** Observable indicating whether noise reduction is active. */
1550
1632
  get denoise$() {
1551
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.denoise), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1633
+ return this.cachedObservable("denoise$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.denoise), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1552
1634
  }
1553
1635
  /** Observable of custom metadata for this participant. */
1554
1636
  get meta$() {
1555
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.meta), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1637
+ return this.cachedObservable("meta$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.meta), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1556
1638
  }
1557
1639
  /** Observable of the participant's subscriber ID. */
1558
1640
  get subscriberId$() {
1559
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.subscriber_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1641
+ return this.cachedObservable("subscriberId$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.subscriber_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1560
1642
  }
1561
1643
  /** Observable of the participant's address ID. */
1562
1644
  get addressId$() {
1563
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.address_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1645
+ return this.cachedObservable("addressId$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.address_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1564
1646
  }
1565
1647
  /** Observable of the server node ID for this participant. */
1566
1648
  get nodeId$() {
1567
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.node_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1649
+ return this.cachedObservable("nodeId$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.node_id), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1568
1650
  }
1569
1651
  /** Observable indicating whether the participant is currently speaking. */
1570
1652
  get isTalking$() {
1571
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.talking), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1653
+ return this.cachedObservable("isTalking$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.talking), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1572
1654
  }
1573
1655
  /** Whether the participant is currently speaking. */
1574
1656
  get isTalking() {
@@ -1576,7 +1658,7 @@ var Participant = class extends Destroyable {
1576
1658
  }
1577
1659
  /** Observable of the participant's layout position. */
1578
1660
  get position$() {
1579
- return this._state$.pipe((0, rxjs_operators.map)((state) => state.position), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull());
1661
+ return this.cachedObservable("position$", () => this._state$.pipe((0, rxjs_operators.map)((state) => state.position), (0, rxjs_operators.distinctUntilChanged)(), require_operators.filterNull()));
1580
1662
  }
1581
1663
  /** Current layout position. */
1582
1664
  get position() {
@@ -1727,7 +1809,13 @@ var Participant = class extends Destroyable {
1727
1809
  }
1728
1810
  /** Removes this participant from the call. */
1729
1811
  async remove() {
1730
- await this.executeMethod(this.id, "call.member.remove", {});
1812
+ const state = this._state$.value;
1813
+ const target = {
1814
+ member_id: this.id,
1815
+ call_id: state.call_id ?? "",
1816
+ node_id: state.node_id ?? ""
1817
+ };
1818
+ await this.executeMethod(target, "call.member.remove", {});
1731
1819
  }
1732
1820
  /** Ends the call for this participant. */
1733
1821
  async end() {
@@ -1895,7 +1983,10 @@ function isJSONRPCRequest(value) {
1895
1983
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && hasProperty(value, "method") && typeof value.method === "string";
1896
1984
  }
1897
1985
  function isJSONRPCResponse(value) {
1898
- return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && hasProperty(value, "result");
1986
+ return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && (hasProperty(value, "result") || hasProperty(value, "error"));
1987
+ }
1988
+ function isJSONRPCErrorResponse(value) {
1989
+ 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"));
1899
1990
  }
1900
1991
 
1901
1992
  //#endregion
@@ -1969,23 +2060,19 @@ var CallEventsManager = class extends Destroyable {
1969
2060
  this.options = options;
1970
2061
  this.callIds = /* @__PURE__ */ new Set();
1971
2062
  this.roomSessionIds = /* @__PURE__ */ new Set();
1972
- this._status$ = this.createBehaviorSubject("trying");
1973
2063
  this._participants$ = this.createBehaviorSubject({});
1974
2064
  this._self$ = this.createBehaviorSubject(null);
1975
2065
  this._sessionState$ = this.createBehaviorSubject(initialSessionState);
1976
2066
  this.initSubscriptions();
1977
2067
  }
1978
2068
  get participants$() {
1979
- return this._participants$.asObservable().pipe((0, rxjs.map)((participantsRecord) => Object.values(participantsRecord)));
1980
- }
1981
- get self$() {
1982
- return this._self$.asObservable().pipe(require_operators.filterNull());
2069
+ return this.cachedObservable("participants$", () => this._participants$.asObservable().pipe((0, rxjs.map)((participantsRecord) => Object.values(participantsRecord))));
1983
2070
  }
1984
- get status$() {
1985
- return this._status$.asObservable();
2071
+ get participants() {
2072
+ return Object.values(this._participants$.value);
1986
2073
  }
1987
- get status() {
1988
- return this._status$.value;
2074
+ get self$() {
2075
+ return this.cachedObservable("self$", () => this._self$.asObservable().pipe(require_operators.filterNull()));
1989
2076
  }
1990
2077
  isRoomSessionIdValid(roomSessionId) {
1991
2078
  return this.roomSessionIds.has(roomSessionId);
@@ -1997,40 +2084,40 @@ var CallEventsManager = class extends Destroyable {
1997
2084
  return this.callIds.has(callId);
1998
2085
  }
1999
2086
  get recording$() {
2000
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.recording), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2087
+ return this.cachedObservable("recording$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.recording), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2001
2088
  }
2002
2089
  get recordings$() {
2003
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.recordings), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2090
+ return this.cachedObservable("recordings$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.recordings), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2004
2091
  }
2005
2092
  get streaming$() {
2006
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.streaming), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2093
+ return this.cachedObservable("streaming$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.streaming), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2007
2094
  }
2008
2095
  get streams$() {
2009
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.streams), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2096
+ return this.cachedObservable("streams$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.streams), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2010
2097
  }
2011
2098
  get playbacks$() {
2012
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.playbacks), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2099
+ return this.cachedObservable("playbacks$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.playbacks), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2013
2100
  }
2014
2101
  get raiseHandPriority$() {
2015
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.prioritize_handraise), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2102
+ return this.cachedObservable("raiseHandPriority$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.prioritize_handraise), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2016
2103
  }
2017
2104
  get locked$() {
2018
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.locked), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2105
+ return this.cachedObservable("locked$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.locked), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2019
2106
  }
2020
2107
  get meta$() {
2021
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.meta), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2108
+ return this.cachedObservable("meta$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.meta), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2022
2109
  }
2023
2110
  get capabilities$() {
2024
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.capabilities), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2111
+ return this.cachedObservable("capabilities$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.capabilities), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2025
2112
  }
2026
2113
  get layout$() {
2027
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.layout_name), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2114
+ return this.cachedObservable("layout$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.layout_name), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2028
2115
  }
2029
2116
  get layouts$() {
2030
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.layouts), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2117
+ return this.cachedObservable("layouts$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.layouts), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2031
2118
  }
2032
2119
  get layoutLayers$() {
2033
- return this._sessionState$.pipe((0, rxjs.map)((state) => state.layout_layers), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull());
2120
+ return this.cachedObservable("layoutLayers$", () => this._sessionState$.pipe((0, rxjs.map)((state) => state.layout_layers), (0, rxjs.distinctUntilChanged)(), require_operators.filterNull()));
2034
2121
  }
2035
2122
  get self() {
2036
2123
  return this._self$.value;
@@ -2071,7 +2158,6 @@ var CallEventsManager = class extends Destroyable {
2071
2158
  callId: callJoinedEvent.call_id,
2072
2159
  roomSessionId: callJoinedEvent.room_session_id
2073
2160
  });
2074
- this._status$.next("connected");
2075
2161
  const sessionState = callJoinedEvent.room_session;
2076
2162
  const { capabilities } = callJoinedEvent;
2077
2163
  this.selfId = this.selfId ?? callJoinedEvent.member_id;
@@ -2092,7 +2178,7 @@ var CallEventsManager = class extends Destroyable {
2092
2178
  });
2093
2179
  this.updateParticipants(sessionState.members);
2094
2180
  this._self$.value?.capabilities.updateFromRaw(capabilities);
2095
- this.updateLayouts();
2181
+ if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
2096
2182
  });
2097
2183
  this.subscribeTo(this.memberUpdates$, (member) => {
2098
2184
  logger$15.debug("[CallEventsManager] Handling member update event for member ID:", member);
@@ -2166,19 +2252,19 @@ var CallEventsManager = class extends Destroyable {
2166
2252
  this._participants$.next(this._participants$.value);
2167
2253
  }
2168
2254
  get callJoinedEvent$() {
2169
- return this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
2255
+ return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
2170
2256
  logger$15.debug("[CallEventsManager] Call joined event:", event);
2171
- }));
2257
+ })));
2172
2258
  }
2173
2259
  get layoutChangedEvent$() {
2174
- return this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
2260
+ return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
2175
2261
  logger$15.debug("[CallEventsManager] Layout changed event:", event);
2176
- }));
2262
+ })));
2177
2263
  }
2178
2264
  get memberUpdates$() {
2179
- return (0, rxjs.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, rxjs.map)((event) => event.member), (0, rxjs.tap)((event) => {
2265
+ return this.cachedObservable("memberUpdates$", () => (0, rxjs.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, rxjs.map)((event) => event.member), (0, rxjs.tap)((event) => {
2180
2266
  logger$15.debug("[CallEventsManager] Member update event:", event);
2181
- }));
2267
+ })));
2182
2268
  }
2183
2269
  destroy() {
2184
2270
  Object.values(this._participants$.value).forEach((participant) => {
@@ -2198,12 +2284,53 @@ var CallEventsManager = class extends Destroyable {
2198
2284
 
2199
2285
  //#endregion
2200
2286
  //#region src/helpers/SDPHelper.ts
2287
+ /** Valid SDP direction attribute values. */
2288
+ const SDP_DIRECTIONS = new Set([
2289
+ "sendrecv",
2290
+ "sendonly",
2291
+ "recvonly",
2292
+ "inactive"
2293
+ ]);
2201
2294
  /**
2202
- * SDPHelper - Utility functions for SDP (Session Description Protocol) parsing and validation.
2295
+ * Extracts the media directions (audio/video) from an SDP string.
2296
+ *
2297
+ * Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
2298
+ * attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
2299
+ * If no explicit direction attribute is found for a media section, defaults to `sendrecv`
2300
+ * per RFC 4566.
2301
+ *
2302
+ * @param sdp - The SDP string to parse
2303
+ * @returns The extracted audio and video directions
2203
2304
  *
2204
- * This module provides helper functions to analyze and validate SDP content,
2205
- * particularly for ICE candidate validation in WebRTC connections.
2305
+ * @example
2306
+ * ```typescript
2307
+ * 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`;
2308
+ * extractMediaDirectionsFromSDP(sdp);
2309
+ * // { audio: 'sendrecv', video: 'recvonly' }
2310
+ * ```
2206
2311
  */
2312
+ function extractMediaDirectionsFromSDP(sdp) {
2313
+ const result = {
2314
+ audio: "inactive",
2315
+ video: "inactive"
2316
+ };
2317
+ if (!sdp) return result;
2318
+ const lines = sdp.split(/\r?\n/);
2319
+ let currentMediaKind = null;
2320
+ let currentDirection = null;
2321
+ for (const line of lines) if (line.startsWith("m=")) {
2322
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
2323
+ if (line.startsWith("m=audio")) currentMediaKind = "audio";
2324
+ else if (line.startsWith("m=video")) currentMediaKind = "video";
2325
+ else currentMediaKind = null;
2326
+ currentDirection = null;
2327
+ } else if (currentMediaKind && line.startsWith("a=")) {
2328
+ const attr = line.substring(2).trim();
2329
+ if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
2330
+ }
2331
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
2332
+ return result;
2333
+ }
2207
2334
  /**
2208
2335
  * Validates that an SDP string has at least one non-host ICE candidate
2209
2336
  * for each media section (m= line).
@@ -2504,6 +2631,15 @@ var LocalStreamController = class extends Destroyable {
2504
2631
  track.addEventListener("ended", this.mediaTrackEndedHandler);
2505
2632
  }
2506
2633
  /**
2634
+ * Update the controller options (e.g., when media overrides are applied).
2635
+ */
2636
+ updateOptions(options) {
2637
+ this.options = {
2638
+ ...this.options,
2639
+ ...options
2640
+ };
2641
+ }
2642
+ /**
2507
2643
  * Stop all local tracks and clean up.
2508
2644
  */
2509
2645
  stopAllTracks() {
@@ -2817,7 +2953,7 @@ var RTCPeerConnectionController = class extends Destroyable {
2817
2953
  logger$11.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
2818
2954
  return;
2819
2955
  }
2820
- const streamTrack = (await navigator.mediaDevices.getUserMedia({ [kind]: {
2956
+ const streamTrack = (await this.getUserMedia({ [kind]: {
2821
2957
  ...track.getConstraints(),
2822
2958
  ...this.deviceController.deviceInfoToConstraints(deviceInfo)
2823
2959
  } })).getTracks().find((t) => t.kind === kind);
@@ -2841,11 +2977,12 @@ var RTCPeerConnectionController = class extends Destroyable {
2841
2977
  this._connectionState$ = this.createReplaySubject(1);
2842
2978
  this._signalingState$ = this.createReplaySubject(1);
2843
2979
  this._iceGatheringState$ = this.createReplaySubject(1);
2844
- this._errors$ = this.createSubject();
2980
+ this._errors$ = this.createReplaySubject(1);
2845
2981
  this._iceCandidates$ = this.createReplaySubject(1);
2846
2982
  this._initialized$ = this.createReplaySubject(1);
2847
2983
  this._remoteDescription$ = this.createReplaySubject(1);
2848
2984
  this._remoteStream$ = this.createBehaviorSubject(null);
2985
+ this._remoteOfferMediaDirections = null;
2849
2986
  this.deviceController = deviceController ?? {};
2850
2987
  this.id = options.callId ?? (0, uuid.v4)();
2851
2988
  this._type = remoteSessionDescription ? "answer" : "offer";
@@ -2853,10 +2990,19 @@ var RTCPeerConnectionController = class extends Destroyable {
2853
2990
  type: "offer",
2854
2991
  sdp: remoteSessionDescription
2855
2992
  } : void 0;
2993
+ this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
2994
+ const offerDefaults = this._remoteOfferMediaDirections ? {
2995
+ audio: this._remoteOfferMediaDirections.audio.includes("recv"),
2996
+ video: this._remoteOfferMediaDirections.video.includes("recv"),
2997
+ receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
2998
+ receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
2999
+ } : {};
2856
3000
  this.options = {
2857
- receiveAudio: options.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
2858
- receiveVideo: options.receiveVideo ?? PreferencesContainer.instance.receiveVideo,
2859
- ...options
3001
+ ...options,
3002
+ audio: options.audio ?? offerDefaults.audio,
3003
+ video: options.video ?? offerDefaults.video,
3004
+ receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
3005
+ receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
2860
3006
  };
2861
3007
  this.localStreamController = new LocalStreamController({
2862
3008
  propose: this.propose,
@@ -2906,43 +3052,43 @@ var RTCPeerConnectionController = class extends Destroyable {
2906
3052
  };
2907
3053
  }
2908
3054
  get iceGatheringState$() {
2909
- return this._iceGatheringState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3055
+ return this.cachedObservable("iceGatheringState$", () => this._iceGatheringState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2910
3056
  }
2911
3057
  get mediaTrackEnded$() {
2912
- return this.localStreamController.mediaTrackEnded$.pipe((0, rxjs.takeUntil)(this.destroyed$));
3058
+ return this.cachedObservable("mediaTrackEnded$", () => this.localStreamController.mediaTrackEnded$.pipe((0, rxjs.takeUntil)(this.destroyed$)));
2913
3059
  }
2914
3060
  get errors$() {
2915
- return this._errors$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3061
+ return this.cachedObservable("errors$", () => this._errors$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2916
3062
  }
2917
3063
  get iceCandidates$() {
2918
- return this._iceCandidates$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3064
+ return this.cachedObservable("iceCandidates$", () => this._iceCandidates$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2919
3065
  }
2920
3066
  get initialized$() {
2921
- return this._initialized$.asObservable().pipe((0, rxjs.filter)((initialized) => initialized), (0, rxjs.takeUntil)(this.destroyed$));
3067
+ return this.cachedObservable("initialized$", () => this._initialized$.asObservable().pipe((0, rxjs.filter)((initialized) => initialized), (0, rxjs.takeUntil)(this.destroyed$)));
2922
3068
  }
2923
3069
  get remoteDescription$() {
2924
- return this._remoteDescription$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3070
+ return this.cachedObservable("remoteDescription$", () => this._remoteDescription$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2925
3071
  }
2926
3072
  get localStream$() {
2927
- return this.localStreamController.localStream$.pipe((0, rxjs.takeUntil)(this.destroyed$));
3073
+ return this.cachedObservable("localStream$", () => this.localStreamController.localStream$.pipe((0, rxjs.takeUntil)(this.destroyed$)));
2928
3074
  }
2929
3075
  get remoteStream$() {
2930
- return this._remoteStream$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3076
+ return this.cachedObservable("remoteStream$", () => this._remoteStream$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2931
3077
  }
2932
3078
  get localAudioTracks$() {
2933
- return this.localStreamController.localAudioTracks$.pipe((0, rxjs.takeUntil)(this.destroyed$));
3079
+ return this.cachedObservable("localAudioTracks$", () => this.localStreamController.localAudioTracks$.pipe((0, rxjs.takeUntil)(this.destroyed$)));
2934
3080
  }
2935
3081
  get localVideoTracks$() {
2936
- return this.localStreamController.localVideoTracks$.pipe((0, rxjs.takeUntil)(this.destroyed$));
3082
+ return this.cachedObservable("localVideoTracks$", () => this.localStreamController.localVideoTracks$.pipe((0, rxjs.takeUntil)(this.destroyed$)));
2937
3083
  }
2938
3084
  get iceConnectionState$() {
2939
- return this._iceConnectionState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3085
+ return this.cachedObservable("iceConnectionState$", () => this._iceConnectionState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2940
3086
  }
2941
3087
  get connectionState$() {
2942
- return this._connectionState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3088
+ return this.cachedObservable("connectionState$", () => this._connectionState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2943
3089
  }
2944
3090
  get signalingState$() {
2945
- return this._signalingState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
3091
+ return this.cachedObservable("signalingState$", () => this._signalingState$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
2946
3092
  }
2947
3093
  get type() {
2948
3094
  return this._type;
@@ -3001,14 +3147,14 @@ var RTCPeerConnectionController = class extends Destroyable {
3001
3147
  };
3002
3148
  }
3003
3149
  get inputVideoDeviceConstraints() {
3004
- if (this.options.video === false && !this.options.inputVideoDeviceConstraints) return false;
3150
+ if (!this.options.video && !this.options.inputVideoDeviceConstraints) return false;
3005
3151
  return {
3006
3152
  ...this.options.inputVideoDeviceConstraints,
3007
3153
  ...this.deviceController.selectedVideoInputDeviceConstraints
3008
3154
  };
3009
3155
  }
3010
3156
  get WebRTCPeerConnectionConstructor() {
3011
- return this.options.WebRTCPeerConnectionConstructor ?? RTCPeerConnection;
3157
+ return this.options.webRTCApiProvider?.RTCPeerConnection ?? RTCPeerConnection;
3012
3158
  }
3013
3159
  get offerOptions() {
3014
3160
  const options = { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
@@ -3023,12 +3169,12 @@ var RTCPeerConnectionController = class extends Destroyable {
3023
3169
  default: return {
3024
3170
  ...options,
3025
3171
  offerToReceiveAudio: true,
3026
- offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
3172
+ offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
3027
3173
  };
3028
3174
  }
3029
3175
  }
3030
3176
  get answerOptions() {
3031
- return {};
3177
+ return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
3032
3178
  }
3033
3179
  /**
3034
3180
  * Initialize the RTCPeerConnection and setup event listeners.
@@ -3063,11 +3209,15 @@ var RTCPeerConnectionController = class extends Destroyable {
3063
3209
  });
3064
3210
  await this.updateSelectedInputDevice(kind, deviceInfo);
3065
3211
  });
3066
- await this.setupTrackHandling();
3067
- this._initialized$.next(true);
3068
3212
  if (this.type === "answer" && this.sdpInit) {
3213
+ await this.setupRemoteTracks();
3214
+ this._initialized$.next(true);
3069
3215
  this.setupEventListeners();
3070
- await this.handleOfferReceived();
3216
+ this._isNegotiating$.next(true);
3217
+ await this._setRemoteDescription(this.sdpInit);
3218
+ } else {
3219
+ await this.setupTrackHandling();
3220
+ this._initialized$.next(true);
3071
3221
  }
3072
3222
  } catch (error) {
3073
3223
  logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
@@ -3165,6 +3315,35 @@ var RTCPeerConnectionController = class extends Destroyable {
3165
3315
  default:
3166
3316
  }
3167
3317
  }
3318
+ /**
3319
+ * Accept an inbound call by creating the SDP answer.
3320
+ * Optionally override media options before the answer is generated.
3321
+ * Must be called after initialization for inbound (answer-type) connections.
3322
+ */
3323
+ async acceptInbound(mediaOverrides) {
3324
+ if (mediaOverrides) {
3325
+ const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
3326
+ this.options = {
3327
+ ...this.options,
3328
+ ...audio !== void 0 ? { audio } : {},
3329
+ ...video !== void 0 ? { video } : {},
3330
+ ...receiveAudio !== void 0 ? { receiveAudio } : {},
3331
+ ...receiveVideo !== void 0 ? { receiveVideo } : {}
3332
+ };
3333
+ this.transceiverController?.updateOptions({
3334
+ receiveAudio: this.receiveAudio,
3335
+ receiveVideo: this.receiveVideo
3336
+ });
3337
+ this.localStreamController.updateOptions({
3338
+ inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
3339
+ inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
3340
+ });
3341
+ }
3342
+ await this.setupLocalTracks();
3343
+ const { answerOptions } = this;
3344
+ logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
3345
+ await this.createAnswer(answerOptions);
3346
+ }
3168
3347
  async handleOfferReceived() {
3169
3348
  if (!this.sdpInit) throw new require_operators.DependencyError("SDP initialization parameters are not set");
3170
3349
  this._isNegotiating$.next(true);
@@ -3239,52 +3418,51 @@ var RTCPeerConnectionController = class extends Destroyable {
3239
3418
  }
3240
3419
  async setupLocalTracks() {
3241
3420
  logger$11.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
3242
- let { localStream } = this;
3243
- if (!localStream) try {
3244
- localStream = await this.localStreamController.buildLocalStream();
3245
- } catch (error) {
3246
- logger$11.error("[RTCPeerConnectionController] Error building local stream:", error);
3247
- this._errors$.next(error);
3248
- }
3249
- if (localStream) {
3250
- if (this.transceiverController?.useAddStream ?? false) {
3251
- logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
3252
- this.peerConnection?.addStream(localStream);
3253
- if (!this.isNegotiating) {
3254
- logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
3255
- this.negotiationNeeded$.next();
3256
- }
3257
- return;
3421
+ const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
3422
+ if (this.transceiverController?.useAddStream ?? false) {
3423
+ logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
3424
+ this.peerConnection?.addStream(localStream);
3425
+ if (!this.isNegotiating) {
3426
+ logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
3427
+ this.negotiationNeeded$.next();
3258
3428
  }
3259
- for (const kind of ["audio", "video"]) {
3260
- const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
3261
- index,
3262
- track
3263
- }));
3264
- for (const { index, track } of tracks) {
3265
- this.localStreamController.addTrackEndedListener(track);
3266
- if (this.transceiverController?.useAddTransceivers ?? false) {
3267
- const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
3268
- await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
3269
- } else {
3270
- logger$11.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
3271
- this.peerConnection?.addTrack(track, localStream);
3272
- }
3429
+ return;
3430
+ }
3431
+ for (const kind of ["audio", "video"]) {
3432
+ const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
3433
+ index,
3434
+ track
3435
+ }));
3436
+ for (const { index, track } of tracks) {
3437
+ this.localStreamController.addTrackEndedListener(track);
3438
+ if (this.transceiverController?.useAddTransceivers ?? false) {
3439
+ const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
3440
+ await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
3441
+ } else {
3442
+ logger$11.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
3443
+ this.peerConnection?.addTrack(track, localStream);
3273
3444
  }
3274
3445
  }
3275
3446
  }
3276
3447
  }
3277
3448
  async getUserMedia(constraints) {
3278
- return this.options.getUserMedia?.(constraints) ?? navigator.mediaDevices.getUserMedia(constraints);
3449
+ return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
3279
3450
  }
3280
3451
  async getDisplayMedia(options) {
3281
- return this.options.getDisplayMedia?.(options) ?? navigator.mediaDevices.getDisplayMedia(options);
3452
+ const mediaDevices = this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices;
3453
+ if (!mediaDevices.getDisplayMedia) throw new require_operators.DependencyError("getDisplayMedia is not supported by the current WebRTC provider");
3454
+ return mediaDevices.getDisplayMedia(options);
3282
3455
  }
3283
3456
  async setupRemoteTracks() {
3284
3457
  if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
3285
3458
  this.peerConnection.ontrack = (event) => {
3286
3459
  logger$11.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
3287
- this._remoteStream$.next(event.streams[0]);
3460
+ if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
3461
+ else {
3462
+ const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
3463
+ const newStream = new MediaStream([...existingTracks, event.track]);
3464
+ this._remoteStream$.next(newStream);
3465
+ }
3288
3466
  };
3289
3467
  await this.transceiverController?.setupRemoteTransceivers(this.type);
3290
3468
  }
@@ -3383,7 +3561,7 @@ var RTCPeerConnectionController = class extends Destroyable {
3383
3561
  });
3384
3562
  }
3385
3563
  get mediaDirections() {
3386
- return this.transceiverController?.getMediaDirections() ?? {
3564
+ return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
3387
3565
  audio: "inactive",
3388
3566
  video: "inactive"
3389
3567
  };
@@ -3419,7 +3597,10 @@ function isVertoAttachMessage(value) {
3419
3597
  return value.method === "verto.attach";
3420
3598
  }
3421
3599
  function isVertoAnswerInnerParams(value) {
3422
- return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.answer" && isObject(value.params) && hasProperty(value.params, "callID") && hasProperty(value.params, "sdp");
3600
+ return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.answer" && isObject(value.params) && hasProperty(value.params, "callID");
3601
+ }
3602
+ function isVertoMediaInnerParams(value) {
3603
+ return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.media" && isObject(value.params) && hasProperty(value.params, "callID") && hasProperty(value.params, "sdp");
3423
3604
  }
3424
3605
  function isVertoMediaParamsInnerParams(value) {
3425
3606
  return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.mediaParams" && isObject(value.params) && hasProperty(value.params, "mediaParams");
@@ -3442,11 +3623,12 @@ var VertoManager = class extends Destroyable {
3442
3623
  }
3443
3624
  };
3444
3625
  var WebRTCVertoManager = class extends VertoManager {
3445
- constructor(webRtcCallSession, attachManager, deviceController, options = {}) {
3626
+ constructor(webRtcCallSession, attachManager, deviceController, webRTCApiProvider, options = {}) {
3446
3627
  super(webRtcCallSession);
3447
3628
  this.webRtcCallSession = webRtcCallSession;
3448
3629
  this.attachManager = attachManager;
3449
3630
  this.deviceController = deviceController;
3631
+ this.webRTCApiProvider = webRTCApiProvider;
3450
3632
  this._rtcPeerConnections$ = this.createBehaviorSubject([]);
3451
3633
  this._selfId$ = this.createBehaviorSubject(null);
3452
3634
  this._signalingStatus$ = this.createBehaviorSubject(null);
@@ -3520,17 +3702,32 @@ var WebRTCVertoManager = class extends VertoManager {
3520
3702
  return rtcPeerConnection;
3521
3703
  }
3522
3704
  get signalingStatus$() {
3523
- return (0, rxjs.merge)(this._signalingStatus$.pipe(require_operators.filterNull()), this.mainPeerConnection.connectionState$.pipe((0, rxjs.filter)((connectionState) => [
3705
+ return this.cachedObservable("signalingStatus$", () => (0, rxjs.merge)(this._signalingStatus$.pipe(require_operators.filterNull()), this.mainPeerConnection.connectionState$.pipe((0, rxjs.filter)((connectionState) => [
3524
3706
  "connected",
3525
3707
  "disconnected",
3526
3708
  "failed"
3527
- ].includes(connectionState))));
3709
+ ].includes(connectionState)))));
3528
3710
  }
3529
3711
  initSubscriptions() {
3712
+ this.subscribeTo(this.callJoinedEvent$, (event) => {
3713
+ const memberNodeId = event.room_session.members.find((m) => m.call_id === event.call_id)?.node_id;
3714
+ if (memberNodeId) this.setNodeIdIfNull(memberNodeId);
3715
+ if (event.member_id) this.setSelfIdIfNull(event.member_id);
3716
+ });
3717
+ this.subscribeTo(this.vertoMedia$, (event) => {
3718
+ logger$10.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
3719
+ this._signalingStatus$.next("ringing");
3720
+ const { sdp, callID } = event;
3721
+ this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
3722
+ status: "received",
3723
+ sdp
3724
+ });
3725
+ });
3530
3726
  this.subscribeTo(this.vertoAnswer$, (event) => {
3531
3727
  logger$10.debug("[WebRTCManager] Received Verto answer event:", event);
3532
- const { sdp } = event;
3533
- this._rtcPeerConnectionsMap.get(event.callID)?.updateAnswerStatus({
3728
+ this._signalingStatus$.next("connecting");
3729
+ const { sdp, callID } = event;
3730
+ this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
3534
3731
  status: "received",
3535
3732
  sdp
3536
3733
  });
@@ -3548,6 +3745,28 @@ var WebRTCVertoManager = class extends VertoManager {
3548
3745
  this.sendVertoPong(vertoPing);
3549
3746
  });
3550
3747
  }
3748
+ /**
3749
+ * Set node_id/selfId only when the current value is null.
3750
+ *
3751
+ * During reattach, `call.joined` and `verto.answer` events can deliver
3752
+ * these identifiers before the `verto.invite` RPC response (`CALL CREATED`)
3753
+ * arrives. These methods let early events populate them eagerly so that
3754
+ * downstream RPC calls (e.g. `call.layout.list`) don't fail with empty
3755
+ * identifiers. `processInviteResponse()` remains the authoritative source
3756
+ * and always overwrites unconditionally.
3757
+ */
3758
+ setNodeIdIfNull(nodeId) {
3759
+ if (!this._nodeId$.value && nodeId) {
3760
+ logger$10.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
3761
+ this._nodeId$.next(nodeId);
3762
+ }
3763
+ }
3764
+ setSelfIdIfNull(selfId) {
3765
+ if (!this._selfId$.value && selfId) {
3766
+ logger$10.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
3767
+ this._selfId$.next(selfId);
3768
+ }
3769
+ }
3551
3770
  async sendVertoPong(vertoPing) {
3552
3771
  try {
3553
3772
  const vertoPongMessage = VertoPong({ ...vertoPing });
@@ -3571,20 +3790,26 @@ var WebRTCVertoManager = class extends VertoManager {
3571
3790
  get selfId() {
3572
3791
  return this._selfId$.value;
3573
3792
  }
3793
+ get callJoinedEvent$() {
3794
+ return this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.takeUntil)(this.destroyed$));
3795
+ }
3796
+ get vertoMedia$() {
3797
+ return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3798
+ }
3574
3799
  get vertoAnswer$() {
3575
- return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAnswerInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3800
+ return this.cachedObservable("vertoAnswer$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAnswerInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
3576
3801
  }
3577
3802
  get vertoMediaParams$() {
3578
- return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaParamsInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3803
+ return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaParamsInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
3579
3804
  }
3580
3805
  get vertoBye$() {
3581
- return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3806
+ return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
3582
3807
  }
3583
3808
  get vertoAttach$() {
3584
- return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAttachMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3809
+ return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAttachMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
3585
3810
  }
3586
3811
  get vertoPing$() {
3587
- return this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoPingInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$));
3812
+ return this.cachedObservable("vertoPing$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoPingInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
3588
3813
  }
3589
3814
  async executeVerto(message, optionals = {}) {
3590
3815
  const webrtcVertoMessage = WebrtcVerto({
@@ -3597,19 +3822,19 @@ var WebRTCVertoManager = class extends VertoManager {
3597
3822
  if (response.error) {
3598
3823
  const error = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data);
3599
3824
  this.onError?.(error);
3600
- throw error;
3825
+ return response;
3601
3826
  }
3602
3827
  const innerResult = require_operators.getValueFrom(response, "result.result");
3603
3828
  if (innerResult?.error) {
3604
3829
  const error = new require_operators.JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
3605
3830
  this.onError?.(error);
3606
- throw error;
3831
+ return response;
3607
3832
  }
3608
3833
  return response;
3609
3834
  }
3610
3835
  async sendLocalDescription(message, rtcPeerConnController) {
3611
3836
  const vertoMethod = message.method;
3612
- const optionalsParams = this.getSendLocalSDPOptionalParams(rtcPeerConnController);
3837
+ const optionalsParams = this.getSendLocalSDPOptionalParams(rtcPeerConnController, vertoMethod);
3613
3838
  try {
3614
3839
  const response = await this.executeVerto(message, optionalsParams);
3615
3840
  switch (vertoMethod) {
@@ -3623,7 +3848,7 @@ var WebRTCVertoManager = class extends VertoManager {
3623
3848
  }
3624
3849
  } catch (error) {
3625
3850
  logger$10.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
3626
- throw error;
3851
+ this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
3627
3852
  }
3628
3853
  }
3629
3854
  async processModifyResponse(response, rtcPeerConnController) {
@@ -3637,12 +3862,14 @@ var WebRTCVertoManager = class extends VertoManager {
3637
3862
  });
3638
3863
  } catch (error) {
3639
3864
  logger$10.warn("[WebRTCManager] Error processing modify response:", error);
3640
- this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
3865
+ const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
3866
+ this.onError?.(modifyError);
3641
3867
  }
3642
3868
  }
3643
3869
  }
3644
3870
  processInviteResponse(response, rtcPeerConnController) {
3645
3871
  if (!response.error && require_operators.getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
3872
+ this._signalingStatus$.next("trying");
3646
3873
  this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
3647
3874
  const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
3648
3875
  const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
@@ -3654,7 +3881,6 @@ var WebRTCVertoManager = class extends VertoManager {
3654
3881
  this._selfId$.next(memberId);
3655
3882
  rtcPeerConnController.setMemberId(memberId);
3656
3883
  if (callId) this.webRtcCallSession.addCallId(callId);
3657
- this._signalingStatus$.next("ringing");
3658
3884
  this.attachManager.attach(this.webRtcCallSession);
3659
3885
  logger$10.info("[WebRTCManager] Verto invite successful");
3660
3886
  logger$10.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
@@ -3686,6 +3912,7 @@ var WebRTCVertoManager = class extends VertoManager {
3686
3912
  inputVideoStream: options.inputVideoStream,
3687
3913
  receiveAudio: options.receiveAudio,
3688
3914
  receiveVideo: options.receiveVideo,
3915
+ webRTCApiProvider: this.webRTCApiProvider,
3689
3916
  ...this.RTCPeerConnectionConfig
3690
3917
  }, options.initOffer, this.deviceController);
3691
3918
  this.setupLocalDescriptionHandler(rtcPeerConnController);
@@ -3697,6 +3924,36 @@ var WebRTCVertoManager = class extends VertoManager {
3697
3924
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
3698
3925
  this.onError?.(error);
3699
3926
  });
3927
+ if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
3928
+ }
3929
+ async handleInboundAnswer(rtcPeerConnController) {
3930
+ logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
3931
+ const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
3932
+ if (vertoByeOrAccepted === null) {
3933
+ logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
3934
+ return;
3935
+ }
3936
+ if (isVertoByeMessage(vertoByeOrAccepted)) {
3937
+ logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
3938
+ this.callSession?.destroy();
3939
+ } else if (!vertoByeOrAccepted) {
3940
+ logger$10.info("[WebRTCManager] Inbound call rejected by user.");
3941
+ try {
3942
+ await this.bye("USER_BUSY");
3943
+ } finally {
3944
+ this._signalingStatus$.next("disconnected");
3945
+ this.callSession?.destroy();
3946
+ }
3947
+ } else {
3948
+ logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
3949
+ const answerOptions = this.webRtcCallSession.answerMediaOptions;
3950
+ try {
3951
+ await rtcPeerConnController.acceptInbound(answerOptions);
3952
+ } catch (error) {
3953
+ logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
3954
+ this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
3955
+ }
3956
+ }
3700
3957
  }
3701
3958
  setupVertoAttachHandler() {
3702
3959
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
@@ -3714,7 +3971,7 @@ var WebRTCVertoManager = class extends VertoManager {
3714
3971
  });
3715
3972
  }
3716
3973
  initObservables(rtcPeerConnController) {
3717
- this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.map)(() => rtcPeerConnController.mediaDirections));
3974
+ this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.map)(() => rtcPeerConnController.mediaDirections), (0, rxjs.startWith)(rtcPeerConnController.mediaDirections), (0, rxjs.takeUntil)(this.destroyed$));
3718
3975
  this.localStream$ = rtcPeerConnController.localStream$.pipe(require_operators.filterNull(), (0, rxjs.takeUntil)(this.destroyed$));
3719
3976
  this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(require_operators.filterNull(), (0, rxjs.takeUntil)(this.destroyed$));
3720
3977
  }
@@ -3730,7 +3987,6 @@ var WebRTCVertoManager = class extends VertoManager {
3730
3987
  });
3731
3988
  this.sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnController);
3732
3989
  } else if (initial) {
3733
- this._signalingStatus$.next("trying");
3734
3990
  const vertoMessageRequest = VertoInvite({
3735
3991
  dialogParams,
3736
3992
  sdp
@@ -3748,22 +4004,23 @@ var WebRTCVertoManager = class extends VertoManager {
3748
4004
  }
3749
4005
  setupVertoByeHandler() {
3750
4006
  this.subscribeTo(this.vertoBye$, () => {
4007
+ this._signalingStatus$.next("disconnected");
3751
4008
  this.attachManager.detach(this.webRtcCallSession);
3752
4009
  this.callSession?.destroy();
3753
4010
  });
3754
4011
  }
3755
- getSendLocalSDPOptionalParams(rtcPeerConnController) {
4012
+ getSendLocalSDPOptionalParams(rtcPeerConnController, vertoMethod) {
3756
4013
  let subscribe = void 0;
3757
- const initial = !rtcPeerConnController.firstSDPExchangeCompleted;
3758
- if (initial) {
4014
+ if (!rtcPeerConnController.firstSDPExchangeCompleted) {
3759
4015
  subscribe = [];
3760
4016
  if (rtcPeerConnController.isMainDevice) subscribe.push(...PreferencesContainer.instance.inviteSubscribeMainDevice);
3761
4017
  else if (rtcPeerConnController.isAdditionalDevice) subscribe.push(...PreferencesContainer.instance.inviteSubscribeAdditionalDevice);
3762
4018
  else if (rtcPeerConnController.isScreenShare) subscribe.push(...PreferencesContainer.instance.inviteSubscribeScreenshare);
3763
4019
  }
4020
+ const isInvite = vertoMethod === "verto.invite";
3764
4021
  return {
3765
4022
  callID: rtcPeerConnController.id,
3766
- node_id: initial ? "" : this._nodeId$.value ?? "",
4023
+ node_id: isInvite ? "" : this._nodeId$.value ?? "",
3767
4024
  subscribe
3768
4025
  };
3769
4026
  }
@@ -3775,7 +4032,12 @@ var WebRTCVertoManager = class extends VertoManager {
3775
4032
  this.callSession?.destroy();
3776
4033
  } else if (!vertoByeOrAccepted) {
3777
4034
  logger$10.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
3778
- await this.bye("USER_BUSY");
4035
+ try {
4036
+ await this.bye("USER_BUSY");
4037
+ } finally {
4038
+ this._signalingStatus$.next("disconnected");
4039
+ this.callSession?.destroy();
4040
+ }
3779
4041
  } else {
3780
4042
  logger$10.debug("[WebRTCManager] Call accepted, sending answer");
3781
4043
  try {
@@ -3805,8 +4067,7 @@ var WebRTCVertoManager = class extends VertoManager {
3805
4067
  userVariables: {
3806
4068
  memberCallId: this.webRtcCallSession.id,
3807
4069
  memberId,
3808
- ...this.webRtcCallSession.options.userVariables,
3809
- ...PreferencesContainer.instance.userVariables
4070
+ ...this.webRtcCallSession.userVariables
3810
4071
  },
3811
4072
  screenShare: rtcPeerConnectionController.isScreenShare,
3812
4073
  additionalDevice: rtcPeerConnectionController.isAdditionalDevice,
@@ -3864,7 +4125,8 @@ var WebRTCVertoManager = class extends VertoManager {
3864
4125
  rtcPeerConnController = new RTCPeerConnectionController({
3865
4126
  ...options,
3866
4127
  ...this.RTCPeerConnectionConfig,
3867
- propose
4128
+ propose,
4129
+ webRTCApiProvider: this.webRTCApiProvider
3868
4130
  }, void 0, this.deviceController);
3869
4131
  this.setupLocalDescriptionHandler(rtcPeerConnController);
3870
4132
  if (propose === "screenshare") this._screenShareId = rtcPeerConnController.id;
@@ -3921,7 +4183,7 @@ var WebRTCVertoManager = class extends VertoManager {
3921
4183
  try {
3922
4184
  const causeParams = cause ? {
3923
4185
  cause,
3924
- cause_code: VertoByeCauseCodes[cause]
4186
+ causeCode: VertoByeCauseCodes[cause]
3925
4187
  } : {};
3926
4188
  await this.executeVerto(VertoBye({
3927
4189
  ...causeParams,
@@ -4003,6 +4265,20 @@ var ParticipantFactory = class {
4003
4265
  //#endregion
4004
4266
  //#region src/core/entities/Call.ts
4005
4267
  const logger$9 = require_operators.getLogger();
4268
+ const fromDestinationParams = (destination) => {
4269
+ if (!destination) return {};
4270
+ try {
4271
+ const url = new URL(`destination:${destination}`);
4272
+ const params = {};
4273
+ url.searchParams.forEach((value, key) => {
4274
+ params[key] = value;
4275
+ });
4276
+ return params;
4277
+ } catch (error) {
4278
+ logger$9.warn(`Failed to parse destination URI: ${destination}`, error);
4279
+ return {};
4280
+ }
4281
+ };
4006
4282
  /**
4007
4283
  * Concrete WebRTC call implementation.
4008
4284
  *
@@ -4016,27 +4292,50 @@ var WebRTCCall = class extends Destroyable {
4016
4292
  this.clientSession = clientSession;
4017
4293
  this.options = options;
4018
4294
  this.address = address;
4019
- this.participantsMap = /* @__PURE__ */ new Map();
4020
- this._errors$ = this.createSubject();
4295
+ this._errors$ = this.createReplaySubject(1);
4296
+ this._lastMergedStatus = "new";
4021
4297
  this._answered$ = this.createReplaySubject();
4022
4298
  this._holdState = false;
4299
+ this._userVariables$ = this.createBehaviorSubject({ ...PreferencesContainer.instance.userVariables });
4023
4300
  this.id = options.callId ?? (0, uuid.v4)();
4024
4301
  this.to = options.to;
4302
+ this._userVariables$.next({
4303
+ ...this._userVariables$.value,
4304
+ ...fromDestinationParams(options.to),
4305
+ ...options.userVariables
4306
+ });
4307
+ this.subscribeTo(this.webrtcMessages$, (message) => {
4308
+ const userVars = require_operators.getValueFrom(message, "params.userVariables");
4309
+ if (userVars) this._userVariables$.next({
4310
+ ...this._userVariables$.value,
4311
+ ...userVars
4312
+ });
4313
+ });
4025
4314
  const managers = initialization.initializeManagers(this);
4026
4315
  this.vertoManager = managers.vertoManager;
4027
4316
  this.callEventsManager = managers.callEventsManager;
4028
- if (options.initOffer) this._status$ = this.createBehaviorSubject("ringing");
4029
- else this._status$ = this.createBehaviorSubject("new");
4317
+ if (options.initOffer) {
4318
+ this._status$ = this.createBehaviorSubject("ringing");
4319
+ this._lastMergedStatus = "ringing";
4320
+ } else this._status$ = this.createBehaviorSubject("new");
4030
4321
  const { deviceController } = initialization;
4031
4322
  this.participantFactory = new ParticipantFactory(this.executeMethod.bind(this), this.vertoManager, deviceController);
4032
4323
  }
4033
4324
  /** Observable stream of errors from media, signaling, and peer connection layers. */
4034
4325
  get errors$() {
4035
- return this._errors$.asObservable();
4326
+ return this.deferEmission(this._errors$.asObservable());
4036
4327
  }
4037
- /** @internal Push an error to the call's error stream. */
4038
- emitError(error) {
4039
- this._errors$.next(error);
4328
+ /**
4329
+ * @internal Push an error to the call's error stream.
4330
+ * Fatal errors automatically transition the call to `'failed'` and destroy it.
4331
+ */
4332
+ emitError(callError) {
4333
+ if (this._status$.value === "destroyed" || this._status$.value === "failed") return;
4334
+ this._errors$.next(callError);
4335
+ if (callError.fatal) {
4336
+ this._status$.next("failed");
4337
+ this.destroy();
4338
+ }
4040
4339
  }
4041
4340
  /** Whether this call is `'inbound'` or `'outbound'`. */
4042
4341
  get direction() {
@@ -4044,7 +4343,7 @@ var WebRTCCall = class extends Destroyable {
4044
4343
  }
4045
4344
  /** Observable of the address associated with this call. */
4046
4345
  get address$() {
4047
- return (0, rxjs.from)([this.address]);
4346
+ return this.deferEmission((0, rxjs.from)([this.address])).pipe((0, rxjs.takeUntil)(this._destroyed$));
4048
4347
  }
4049
4348
  /** Display name of the caller. */
4050
4349
  get fromName() {
@@ -4076,7 +4375,7 @@ var WebRTCCall = class extends Destroyable {
4076
4375
  }
4077
4376
  /** Current snapshot of all participants in the call. */
4078
4377
  get participants() {
4079
- return Array.from(this.participantsMap.values());
4378
+ return this.callEventsManager.participants;
4080
4379
  }
4081
4380
  /** The local participant, or `null` if not yet joined. */
4082
4381
  get self() {
@@ -4085,7 +4384,6 @@ var WebRTCCall = class extends Destroyable {
4085
4384
  async toggleLock() {
4086
4385
  const method = this.locked ? "call.unlock" : "call.lock";
4087
4386
  await this.executeMethod(this.selfId ?? "", method, {});
4088
- throw new require_operators.UnimplementedError();
4089
4387
  }
4090
4388
  async toggleHold() {
4091
4389
  if (this._holdState) await this.vertoManager.unhold();
@@ -4106,7 +4404,7 @@ var WebRTCCall = class extends Destroyable {
4106
4404
  }
4107
4405
  /** Observable of layout layer positions for all participants. */
4108
4406
  get layoutLayers$() {
4109
- return this.callEventsManager.layoutLayers$;
4407
+ return this.deferEmission(this.callEventsManager.layoutLayers$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4110
4408
  }
4111
4409
  /** Current snapshot of layout layers. */
4112
4410
  get layoutLayers() {
@@ -4120,72 +4418,80 @@ var WebRTCCall = class extends Destroyable {
4120
4418
  params
4121
4419
  });
4122
4420
  try {
4123
- return await this.clientSession.execute(request);
4421
+ const response = await this.clientSession.execute(request);
4422
+ if (isJSONRPCErrorResponse(response)) throw new require_operators.JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
4423
+ return response;
4124
4424
  } catch (error) {
4125
4425
  logger$9.error(`[Call] Error executing method ${method} with params`, params, error);
4126
4426
  throw error;
4127
4427
  }
4128
4428
  }
4129
4429
  buildMethodParams(target, args) {
4130
- const reference = {
4131
- node_id: this.nodeId,
4132
- call_id: this.id
4430
+ const self = {
4431
+ node_id: this.nodeId ?? "",
4432
+ call_id: this.id,
4433
+ member_id: this.vertoManager.selfId ?? ""
4434
+ };
4435
+ if (typeof target === "object") return {
4436
+ ...args,
4437
+ self,
4438
+ targets: [target]
4133
4439
  };
4134
4440
  return {
4135
4441
  ...args,
4136
- self: {
4137
- ...reference,
4138
- member_id: this.vertoManager.selfId
4139
- },
4442
+ self,
4140
4443
  target: {
4141
- ...reference,
4444
+ node_id: this.nodeId ?? "",
4445
+ call_id: this.id,
4142
4446
  member_id: target
4143
4447
  }
4144
4448
  };
4145
4449
  }
4146
4450
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
4147
4451
  get status$() {
4148
- return (0, rxjs.merge)(this._status$.asObservable(), this.vertoManager.signalingStatus$);
4452
+ return this.publicCachedObservable("status$", () => (0, rxjs.merge)(this._status$.asObservable(), this.vertoManager.signalingStatus$).pipe((0, rxjs.distinctUntilChanged)(), (0, rxjs.tap)((status) => {
4453
+ this._lastMergedStatus = status;
4454
+ })));
4149
4455
  }
4150
4456
  /** Observable of the participants list, emits on join/leave/update. */
4151
4457
  get participants$() {
4152
- return this.callEventsManager.participants$;
4458
+ return this.deferEmission(this.callEventsManager.participants$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4153
4459
  }
4154
4460
  /** Observable of the local (self) participant. */
4155
4461
  get self$() {
4156
- return this.callEventsManager.self$;
4462
+ return this.deferEmission(this.callEventsManager.self$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4157
4463
  }
4158
4464
  /** Observable indicating whether the call is being recorded. */
4159
4465
  get recording$() {
4160
- return this.callEventsManager.recording$;
4466
+ return this.deferEmission(this.callEventsManager.recording$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4161
4467
  }
4162
4468
  /** Observable indicating whether the call is being streamed. */
4163
4469
  get streaming$() {
4164
- return this.callEventsManager.streaming$;
4470
+ return this.deferEmission(this.callEventsManager.streaming$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4165
4471
  }
4166
4472
  /** Observable indicating whether raise-hand priority is active. */
4167
4473
  get raiseHandPriority$() {
4168
- return this.callEventsManager.raiseHandPriority$;
4474
+ return this.deferEmission(this.callEventsManager.raiseHandPriority$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4169
4475
  }
4170
4476
  /** Observable indicating whether the call room is locked. */
4171
4477
  get locked$() {
4172
- return this.callEventsManager.locked$;
4478
+ return this.deferEmission(this.callEventsManager.locked$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4173
4479
  }
4174
4480
  /** Observable of custom metadata associated with the call. */
4175
4481
  get meta$() {
4176
- return this.callEventsManager.meta$;
4482
+ return this.deferEmission(this.callEventsManager.meta$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4177
4483
  }
4178
4484
  /** Observable of the call's capability flags. */
4179
4485
  get capabilities$() {
4180
- return this.callEventsManager.capabilities$;
4486
+ return this.deferEmission(this.callEventsManager.capabilities$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4181
4487
  }
4182
4488
  /** Observable of the current layout name. */
4183
4489
  get layout$() {
4184
- return this.callEventsManager.layout$;
4490
+ return this.deferEmission(this.callEventsManager.layout$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4185
4491
  }
4186
4492
  /** Current call status. */
4187
4493
  get status() {
4188
- return this._status$.value;
4494
+ return this._lastMergedStatus;
4189
4495
  }
4190
4496
  /** Whether the call is currently being recorded. */
4191
4497
  get recording() {
@@ -4213,7 +4519,7 @@ var WebRTCCall = class extends Destroyable {
4213
4519
  }
4214
4520
  /** Observable of available layout names. */
4215
4521
  get layouts$() {
4216
- return this.callEventsManager.layouts$;
4522
+ return this.deferEmission(this.callEventsManager.layouts$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4217
4523
  }
4218
4524
  /** Current snapshot of available layout names. */
4219
4525
  get layouts() {
@@ -4221,7 +4527,7 @@ var WebRTCCall = class extends Destroyable {
4221
4527
  }
4222
4528
  /** Observable of the local media stream (camera/microphone). */
4223
4529
  get localStream$() {
4224
- return this.vertoManager.localStream$;
4530
+ return this.deferEmission(this.vertoManager.localStream$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4225
4531
  }
4226
4532
  /** Current local media stream, or `null` if not available. */
4227
4533
  get localStream() {
@@ -4229,12 +4535,27 @@ var WebRTCCall = class extends Destroyable {
4229
4535
  }
4230
4536
  /** Observable of the remote media stream from the far end. */
4231
4537
  get remoteStream$() {
4232
- return this.vertoManager.remoteStream$;
4538
+ return this.deferEmission(this.vertoManager.remoteStream$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4233
4539
  }
4234
4540
  /** Current remote media stream, or `null` if not available. */
4235
4541
  get remoteStream() {
4236
4542
  return this.vertoManager.remoteStream;
4237
4543
  }
4544
+ /** Observable of custom user variables associated with the call. */
4545
+ get userVariables$() {
4546
+ return this.deferEmission(this._userVariables$.asObservable());
4547
+ }
4548
+ /** a copy of the current custom user variables of the call. */
4549
+ get userVariables() {
4550
+ return { ...this._userVariables$.value };
4551
+ }
4552
+ /** Merge current custom user variables of the call. */
4553
+ set userVariables(variables) {
4554
+ this._userVariables$.next({
4555
+ ...this._userVariables$.value,
4556
+ ...variables
4557
+ });
4558
+ }
4238
4559
  /** @internal */
4239
4560
  createParticipant(memberId, selfId) {
4240
4561
  if (memberId === (selfId ?? this.vertoManager.selfId)) return this.participantFactory.createSelfParticipant(memberId);
@@ -4242,14 +4563,14 @@ var WebRTCCall = class extends Destroyable {
4242
4563
  }
4243
4564
  /** Observable of the current audio/video send/receive directions. */
4244
4565
  get mediaDirections$() {
4245
- return this.vertoManager.mediaDirections$;
4566
+ return this.deferEmission(this.vertoManager.mediaDirections$).pipe((0, rxjs.takeUntil)(this._destroyed$));
4246
4567
  }
4247
4568
  /** Current audio/video send/receive directions. */
4248
4569
  get mediaDirections() {
4249
4570
  return this.vertoManager.mediaDirections;
4250
4571
  }
4251
4572
  get participantsId$() {
4252
- return this.participants$.pipe((0, rxjs.map)((participants) => participants.map((participant) => participant.id)));
4573
+ return this.cachedObservable("participantsId$", () => this.participants$.pipe((0, rxjs.map)((participants) => participants.map((participant) => participant.id))));
4253
4574
  }
4254
4575
  /** Executes a raw JSON-RPC request on the client session. */
4255
4576
  async execute(request, options) {
@@ -4284,35 +4605,35 @@ var WebRTCCall = class extends Destroyable {
4284
4605
  }
4285
4606
  }
4286
4607
  get callSessionEvents$() {
4287
- return this.clientSession.signalingEvent$.pipe((0, rxjs.filter)((event) => this.isCallSessionEvent(event)), (0, rxjs.tap)((event) => logger$9.debug("[Call] Received call session event:", event)), (0, rxjs.takeUntil)(this.destroyed$));
4608
+ return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, rxjs.filter)((event) => this.isCallSessionEvent(event)), (0, rxjs.tap)((event) => logger$9.debug("[Call] Received call session event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
4288
4609
  }
4289
4610
  /** Observable of call-updated events. */
4290
4611
  get callUpdated$() {
4291
- return this.callSessionEvents$.pipe(require_operators.filterAs(isCallUpdatedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4612
+ return this.publicCachedObservable("callUpdated$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isCallUpdatedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4292
4613
  }
4293
4614
  /** Observable of member-joined events. */
4294
4615
  get memberJoined$() {
4295
- return this.callSessionEvents$.pipe(require_operators.filterAs(isMemberJoinedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4616
+ return this.publicCachedObservable("memberJoined$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isMemberJoinedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4296
4617
  }
4297
4618
  /** Observable of member-left events. */
4298
4619
  get memberLeft$() {
4299
- return this.callSessionEvents$.pipe(require_operators.filterAs(isMemberLeftMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4620
+ return this.publicCachedObservable("memberLeft$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isMemberLeftMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4300
4621
  }
4301
4622
  /** Observable of member-updated events (mute, volume, etc.). */
4302
4623
  get memberUpdated$() {
4303
- return this.callSessionEvents$.pipe(require_operators.filterAs(isMemberUpdatedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4624
+ return this.publicCachedObservable("memberUpdated$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isMemberUpdatedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4304
4625
  }
4305
4626
  /** Observable of member-talking events (speech start/stop). */
4306
4627
  get memberTalking$() {
4307
- return this.callSessionEvents$.pipe(require_operators.filterAs(isMemberTalkingMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4628
+ return this.publicCachedObservable("memberTalking$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isMemberTalkingMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4308
4629
  }
4309
4630
  /** Observable of call state-change events. */
4310
4631
  get callStates$() {
4311
- return this.callSessionEvents$.pipe(require_operators.filterAs(isCallStateMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4632
+ return this.publicCachedObservable("callStates$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isCallStateMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4312
4633
  }
4313
4634
  /** Observable of layout-changed events. */
4314
4635
  get layoutUpdates$() {
4315
- return this.callSessionEvents$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$));
4636
+ return this.publicCachedObservable("layoutUpdates$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
4316
4637
  }
4317
4638
  /** Underlying `RTCPeerConnection`, for advanced use cases. */
4318
4639
  get rtcPeerConnection() {
@@ -4320,19 +4641,19 @@ var WebRTCCall = class extends Destroyable {
4320
4641
  }
4321
4642
  /** Observable of raw signaling events as plain objects. */
4322
4643
  get signalingEvent$() {
4323
- return this.callEvent$.pipe((0, rxjs.map)((event) => JSON.parse(JSON.stringify(event))));
4644
+ return this.publicCachedObservable("signalingEvent$", () => this.callEvent$.pipe((0, rxjs.map)((event) => JSON.parse(JSON.stringify(event)))));
4324
4645
  }
4325
4646
  /** Observable of WebRTC-specific signaling messages. */
4326
4647
  get webrtcMessages$() {
4327
- return this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$9.debug("[Call] Event is a WebRTC message event:", event)), (0, rxjs.takeUntil)(this.destroyed$));
4648
+ return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$9.debug("[Call] Event is a WebRTC message event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
4328
4649
  }
4329
4650
  /** Observable of call-level signaling events. */
4330
4651
  get callEvent$() {
4331
- return this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$9.debug("[Call] Event is a call event:", event)), (0, rxjs.takeUntil)(this.destroyed$));
4652
+ return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$9.debug("[Call] Event is a call event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
4332
4653
  }
4333
4654
  /** Observable of layout-changed signaling events. */
4334
4655
  get layoutEvent$() {
4335
- return this.callEvent$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params"));
4656
+ return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params")));
4336
4657
  }
4337
4658
  /** Hangs up the call and releases all resources. */
4338
4659
  async hangup() {
@@ -4340,7 +4661,6 @@ var WebRTCCall = class extends Destroyable {
4340
4661
  try {
4341
4662
  await this.vertoManager.bye();
4342
4663
  } finally {
4343
- this._status$.next("destroyed");
4344
4664
  this.destroy();
4345
4665
  }
4346
4666
  }
@@ -4348,17 +4668,22 @@ var WebRTCCall = class extends Destroyable {
4348
4668
  async sendDigits(dtmf) {
4349
4669
  return this.vertoManager.sendDigits(dtmf);
4350
4670
  }
4351
- /** Accepts an inbound call. */
4352
- answer() {
4671
+ /** Accepts an inbound call, optionally overriding media options for the answer. */
4672
+ answer(options) {
4673
+ this._answerMediaOptions = options;
4353
4674
  this._answered$.next(true);
4354
4675
  }
4676
+ /** Media options provided when answering. Used internally by the VertoManager. */
4677
+ get answerMediaOptions() {
4678
+ return this._answerMediaOptions;
4679
+ }
4355
4680
  /** Rejects an inbound call. */
4356
4681
  reject() {
4357
4682
  this._answered$.next(false);
4358
4683
  }
4359
4684
  /** Observable that emits `true` when answered, `false` when rejected. */
4360
4685
  get answered$() {
4361
- return this._answered$.asObservable();
4686
+ return this.deferEmission(this._answered$.asObservable());
4362
4687
  }
4363
4688
  /**
4364
4689
  * Sets the call layout and participant positions.
@@ -4379,9 +4704,10 @@ var WebRTCCall = class extends Destroyable {
4379
4704
  }
4380
4705
  /** Destroys the call, releasing all resources and subscriptions. */
4381
4706
  destroy() {
4707
+ if (this._status$.value === "destroyed") return;
4708
+ this._status$.next("destroyed");
4382
4709
  this.vertoManager.destroy();
4383
4710
  this.callEventsManager.destroy();
4384
- this.participantsMap.clear();
4385
4711
  super.destroy();
4386
4712
  }
4387
4713
  };
@@ -4389,14 +4715,32 @@ var WebRTCCall = class extends Destroyable {
4389
4715
  //#endregion
4390
4716
  //#region src/managers/CallFactory.ts
4391
4717
  /**
4718
+ * Infers the semantic error category from a raw Error thrown by VertoManager
4719
+ * or an RTCPeerConnection layer.
4720
+ */
4721
+ function inferCallErrorKind(error) {
4722
+ if (error instanceof require_operators.RPCTimeoutError) return "timeout";
4723
+ if (error instanceof require_operators.JSONRPCError) return "signaling";
4724
+ if (error instanceof require_operators.MediaTrackError) return "media";
4725
+ if (error instanceof require_operators.WebSocketConnectionError || error instanceof require_operators.TransportConnectionError) return "network";
4726
+ return "internal";
4727
+ }
4728
+ /** Determines whether an error should be fatal (destroy the call). */
4729
+ function isFatalError(error) {
4730
+ if (error instanceof require_operators.VertoPongError) return false;
4731
+ if (error instanceof require_operators.MediaTrackError) return false;
4732
+ return true;
4733
+ }
4734
+ /**
4392
4735
  * Factory for creating WebRTCCall instances with proper manager wiring.
4393
4736
  * Eliminates circular dependencies by centralizing Call and Manager creation.
4394
4737
  */
4395
4738
  var CallFactory = class {
4396
- constructor(sessionManager, deviceController, attachManager) {
4739
+ constructor(sessionManager, deviceController, attachManager, webRTCApiProvider) {
4397
4740
  this.sessionManager = sessionManager;
4398
4741
  this.deviceController = deviceController;
4399
4742
  this.attachManager = attachManager;
4743
+ this.webRTCApiProvider = webRTCApiProvider;
4400
4744
  }
4401
4745
  /**
4402
4746
  * Create a new WebRTCCall with properly initialized managers
@@ -4405,10 +4749,16 @@ var CallFactory = class {
4405
4749
  return new WebRTCCall(this.sessionManager, options, {
4406
4750
  initializeManagers: (callInstance) => {
4407
4751
  return {
4408
- vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, {
4752
+ vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
4409
4753
  nodeId: options.nodeId,
4410
4754
  onError: (error) => {
4411
- callInstance.emitError(error);
4755
+ const callError = {
4756
+ kind: inferCallErrorKind(error),
4757
+ fatal: isFatalError(error),
4758
+ error,
4759
+ callId: callInstance.id
4760
+ };
4761
+ callInstance.emitError(callError);
4412
4762
  }
4413
4763
  }),
4414
4764
  callEventsManager: new CallEventsManager(callInstance)
@@ -4431,7 +4781,10 @@ var Fetcher = class {
4431
4781
  this.nextUrl = `${this.endpoint}?${params}`;
4432
4782
  }
4433
4783
  async next() {
4434
- if (!this.nextUrl) return [];
4784
+ if (!this.nextUrl) {
4785
+ this.hasMore = false;
4786
+ return [];
4787
+ }
4435
4788
  const response = await this.http.request({
4436
4789
  ...GET_PARAMS,
4437
4790
  url: this.nextUrl
@@ -4439,6 +4792,7 @@ var Fetcher = class {
4439
4792
  if (response.ok && !!response.body) {
4440
4793
  const result = JSON.parse(response.body);
4441
4794
  this.nextUrl = result.links.next;
4795
+ this.hasMore = !!this.nextUrl;
4442
4796
  return result.data.filter(this.filter).map(this.mapper);
4443
4797
  }
4444
4798
  logger$8.error("Failed to fetch entity");
@@ -4472,10 +4826,11 @@ var EntityCollection = class extends Destroyable {
4472
4826
  this.observablesRegistry.get(data.id)?.next(updated);
4473
4827
  this.values$.next(Array.from(this.collectionData.values()));
4474
4828
  };
4829
+ this._hasMore$ = this.createBehaviorSubject(true);
4475
4830
  this._destroy$ = new rxjs.Subject();
4476
4831
  this.updateSubscription = this.update$.subscribe(this.upsertData);
4477
4832
  this.loading$.next(false);
4478
- this.hasMore$ = (0, rxjs.defer)(() => (0, rxjs.from)(this.init())).pipe((0, rxjs.shareReplay)(1), (0, rxjs.takeUntil)(this._destroy$));
4833
+ this.hasMore$ = (0, rxjs.defer)(() => (0, rxjs.from)(this.init())).pipe((0, rxjs.switchMap)(() => this._hasMore$), (0, rxjs.distinctUntilChanged)(), (0, rxjs.shareReplay)(1), (0, rxjs.takeUntil)(this._destroy$));
4479
4834
  }
4480
4835
  get loading() {
4481
4836
  return this.loading$.value;
@@ -4484,23 +4839,27 @@ var EntityCollection = class extends Destroyable {
4484
4839
  return this.fetchController.hasMore ?? true;
4485
4840
  }
4486
4841
  get updated$() {
4487
- return this.loading$.pipe((0, rxjs.distinctUntilChanged)(), (0, rxjs.skip)(1), (0, rxjs.filter)((loading) => !loading), (0, rxjs.map)(() => void 0), (0, rxjs.takeUntil)(this._destroy$));
4842
+ return this.cachedObservable("updated$", () => this.loading$.pipe((0, rxjs.distinctUntilChanged)(), (0, rxjs.skip)(1), (0, rxjs.filter)((loading) => !loading), (0, rxjs.map)(() => void 0), (0, rxjs.takeUntil)(this._destroy$)));
4488
4843
  }
4489
4844
  get values() {
4490
4845
  return Array.from(this.collectionData.values());
4491
4846
  }
4492
4847
  async init() {
4493
- if (this.fetchController.hasMore === false) return Promise.resolve(false);
4848
+ if (this.fetchController.hasMore === false) {
4849
+ this._hasMore$.next(false);
4850
+ return;
4851
+ }
4494
4852
  await this.fetchMore();
4495
- return this.fetchController.hasMore ?? true;
4496
4853
  }
4497
4854
  async fetchMore() {
4498
4855
  try {
4499
4856
  this.loading$.next(true);
4500
4857
  (await this.fetchController.next()).forEach(this.upsertData);
4858
+ this._hasMore$.next(this.fetchController.hasMore ?? false);
4501
4859
  this.loading$.next(false);
4502
4860
  } catch (error) {
4503
4861
  logger$8.error(`Failed to fetch initial collection data`, error);
4862
+ this._hasMore$.next(this.fetchController.hasMore ?? false);
4504
4863
  this.loading$.next(false);
4505
4864
  this.onError?.(new require_operators.CollectionFetchError("fetchMore", error));
4506
4865
  }
@@ -4564,7 +4923,7 @@ var EntityCollectionTransformed = class {
4564
4923
  return this.originalCollection.values.filter(this.filter).map(this.mapper);
4565
4924
  }
4566
4925
  get values$() {
4567
- return this.originalCollection.values$.pipe((0, rxjs.map)((values) => values.filter(this.filter).map(this.mapper)));
4926
+ return this._values$ ??= this.originalCollection.values$.pipe((0, rxjs.map)((values) => values.filter(this.filter).map(this.mapper)));
4568
4927
  }
4569
4928
  get$(id) {
4570
4929
  const original$ = this.originalCollection.get$(id);
@@ -4637,7 +4996,7 @@ var Address = class extends Destroyable {
4637
4996
  }
4638
4997
  /** Observable of the human-readable display name. */
4639
4998
  get displayName$() {
4640
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.map)((state) => state.display_name), (0, rxjs.takeUntil)(this.destroyed$));
4999
+ return this.cachedObservable("displayName$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.map)((state) => state.display_name), (0, rxjs.takeUntil)(this.destroyed$)));
4641
5000
  }
4642
5001
  /** Human-readable display name. */
4643
5002
  get displayName() {
@@ -4646,7 +5005,7 @@ var Address = class extends Destroyable {
4646
5005
  }
4647
5006
  /** Observable of the preview image URL. */
4648
5007
  get previewUrl$() {
4649
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.map)((state) => state.preview_url), (0, rxjs.takeUntil)(this.destroyed$));
5008
+ return this.cachedObservable("previewUrl$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.map)((state) => state.preview_url), (0, rxjs.takeUntil)(this.destroyed$)));
4650
5009
  }
4651
5010
  /** Preview image URL. */
4652
5011
  get previewUrl() {
@@ -4655,7 +5014,7 @@ var Address = class extends Destroyable {
4655
5014
  }
4656
5015
  /** Observable of the cover image URL. */
4657
5016
  get coverUrl$() {
4658
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.cover_url), (0, rxjs.takeUntil)(this.destroyed$));
5017
+ return this.cachedObservable("coverUrl$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.cover_url), (0, rxjs.takeUntil)(this.destroyed$)));
4659
5018
  }
4660
5019
  /** Cover image URL. */
4661
5020
  get coverUrl() {
@@ -4664,7 +5023,7 @@ var Address = class extends Destroyable {
4664
5023
  }
4665
5024
  /** Observable of the underlying resource ID. */
4666
5025
  get resourceId$() {
4667
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.resource_id), (0, rxjs.takeUntil)(this.destroyed$));
5026
+ return this.cachedObservable("resourceId$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.resource_id), (0, rxjs.takeUntil)(this.destroyed$)));
4668
5027
  }
4669
5028
  /** Underlying resource ID. */
4670
5029
  get resourceId() {
@@ -4673,7 +5032,7 @@ var Address = class extends Destroyable {
4673
5032
  }
4674
5033
  /** Observable of the resource type (e.g. `'room'`, `'subscriber'`). */
4675
5034
  get type$() {
4676
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.type), (0, rxjs.takeUntil)(this.destroyed$));
5035
+ return this.cachedObservable("type$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.type), (0, rxjs.takeUntil)(this.destroyed$)));
4677
5036
  }
4678
5037
  /** Resource type (e.g. `'room'`, `'subscriber'`). */
4679
5038
  get type() {
@@ -4682,7 +5041,7 @@ var Address = class extends Destroyable {
4682
5041
  }
4683
5042
  /** Observable of available communication channels (audio, video, messaging). */
4684
5043
  get channels$() {
4685
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.channels), (0, rxjs.takeUntil)(this.destroyed$));
5044
+ return this.cachedObservable("channels$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.channels), (0, rxjs.takeUntil)(this.destroyed$)));
4686
5045
  }
4687
5046
  /** Available communication channels. */
4688
5047
  get channels() {
@@ -4696,7 +5055,7 @@ var Address = class extends Destroyable {
4696
5055
  }
4697
5056
  /** Observable indicating whether the address (room) is locked. */
4698
5057
  get locked$() {
4699
- return this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.locked), (0, rxjs.takeUntil)(this.destroyed$));
5058
+ return this.cachedObservable("locked$", () => this._state$.pipe(require_operators.filterNull(), (0, rxjs.shareReplay)(1), (0, rxjs.map)((state) => state.locked), (0, rxjs.takeUntil)(this.destroyed$)));
4700
5059
  }
4701
5060
  /** Sends a text message to this address. */
4702
5061
  async sendText(text) {
@@ -4776,9 +5135,15 @@ var PendingRPC = class PendingRPC {
4776
5135
  return () => signal.removeEventListener("abort", abortHandler);
4777
5136
  }) : rxjs.NEVER).subscribe({
4778
5137
  next: (response) => {
4779
- logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
4780
5138
  isSettled = true;
4781
- resolve(response);
5139
+ if (response.error) {
5140
+ const rpcError = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
5141
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
5142
+ reject(rpcError);
5143
+ } else {
5144
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
5145
+ resolve(response);
5146
+ }
4782
5147
  subscription.unsubscribe();
4783
5148
  },
4784
5149
  error: (error) => {
@@ -4817,7 +5182,7 @@ const getAddressSearchURI = (options) => {
4817
5182
  return name;
4818
5183
  };
4819
5184
  var ClientSessionManager = class extends Destroyable {
4820
- constructor(credential, transport, storage, authorizationStateKey, deviceController, attachManager) {
5185
+ constructor(credential, transport, storage, authorizationStateKey, deviceController, attachManager, webRTCApiProvider) {
4821
5186
  super();
4822
5187
  this.credential = credential;
4823
5188
  this.transport = transport;
@@ -4827,24 +5192,24 @@ var ClientSessionManager = class extends Destroyable {
4827
5192
  this.callCreateTimeout = 6e3;
4828
5193
  this.agent = `signalwire-typescript-sdk/1.0.0`;
4829
5194
  this.eventAcks = true;
4830
- this.authorization$ = this.createSubject();
4831
5195
  this.authorizationState$ = this.createReplaySubject(1);
4832
5196
  this.connectVersion = {
4833
5197
  major: 4,
4834
5198
  minor: 0,
4835
5199
  revision: 0
4836
5200
  };
4837
- this._errors$ = this.createSubject();
5201
+ this._authorization$ = this.createBehaviorSubject(void 0);
5202
+ this._errors$ = this.createReplaySubject(1);
4838
5203
  this._authenticated$ = this.createBehaviorSubject(false);
4839
5204
  this._subscriberInfo$ = this.createBehaviorSubject(null);
4840
5205
  this._calls$ = this.createBehaviorSubject({});
4841
5206
  this._iceServers$ = this.createBehaviorSubject([]);
4842
5207
  attachManager.setSession(this);
4843
- this.callFactory = new CallFactory(this, deviceController, attachManager);
5208
+ this.callFactory = new CallFactory(this, deviceController, attachManager, webRTCApiProvider);
4844
5209
  this.initialized$ = (0, rxjs.defer)(() => (0, rxjs.from)(this.init())).pipe((0, rxjs.shareReplay)(1), (0, rxjs.takeUntil)(this.destroyed$));
4845
5210
  }
4846
5211
  get incomingCalls$() {
4847
- return this.calls$.pipe((0, rxjs.map)((calls) => calls.filter((call) => call.direction === "inbound")));
5212
+ return this.cachedObservable("incomingCalls$", () => this.calls$.pipe((0, rxjs.map)((calls) => calls.filter((call) => call.direction === "inbound"))));
4848
5213
  }
4849
5214
  get incomingCalls() {
4850
5215
  return Object.values(this._calls$.value).filter((call) => call.direction === "inbound");
@@ -4856,7 +5221,7 @@ var ClientSessionManager = class extends Destroyable {
4856
5221
  return this._subscriberInfo$.value;
4857
5222
  }
4858
5223
  get calls$() {
4859
- return this._calls$.pipe((0, rxjs.map)((calls) => Object.values(calls)));
5224
+ return this.cachedObservable("calls$", () => this._calls$.pipe((0, rxjs.map)((calls) => Object.values(calls))));
4860
5225
  }
4861
5226
  get calls() {
4862
5227
  return Object.values(this._calls$.value);
@@ -4864,6 +5229,12 @@ var ClientSessionManager = class extends Destroyable {
4864
5229
  get iceServers() {
4865
5230
  return this._iceServers$.value;
4866
5231
  }
5232
+ get authorization$() {
5233
+ return this._authorization$.asObservable();
5234
+ }
5235
+ get authorization() {
5236
+ return this._authorization$.value;
5237
+ }
4867
5238
  get errors$() {
4868
5239
  return this._errors$.asObservable();
4869
5240
  }
@@ -4959,20 +5330,20 @@ var ClientSessionManager = class extends Destroyable {
4959
5330
  }
4960
5331
  }
4961
5332
  get authStateEvent$() {
4962
- return this.signalingEvent$.pipe((0, rxjs.tap)((msg) => {
5333
+ return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, rxjs.tap)((msg) => {
4963
5334
  logger$6.debug("[Session] Received incoming message:", msg);
4964
5335
  }), require_operators.filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, rxjs.tap)((event) => {
4965
5336
  logger$6.debug("[Session] Authorization state event received:", event.authorization_state);
4966
- }));
5337
+ })));
4967
5338
  }
4968
5339
  get signalingEvent$() {
4969
- return this.transport.incomingEvent$.pipe(require_operators.filterAs(isSignalwireRequest, "params"));
5340
+ return this.cachedObservable("signalingEvent$", () => this.transport.incomingEvent$.pipe(require_operators.filterAs(isSignalwireRequest, "params"), (0, rxjs.share)()));
4970
5341
  }
4971
5342
  get vertoInvite$() {
4972
- return this.signalingEvent$.pipe((0, rxjs.filter)(isWebrtcMessageMetadata), (0, rxjs.filter)((event) => isVertoInviteMessage(event.params)), (0, rxjs.map)((event) => ({
5343
+ return this.cachedObservable("vertoInvite$", () => this.signalingEvent$.pipe((0, rxjs.filter)(isWebrtcMessageMetadata), (0, rxjs.filter)((event) => isVertoInviteMessage(event.params)), (0, rxjs.map)((event) => ({
4973
5344
  node_id: event.node_id,
4974
5345
  ...event.params.params
4975
- })));
5346
+ }))));
4976
5347
  }
4977
5348
  get contexts() {
4978
5349
  return [];
@@ -5015,6 +5386,24 @@ var ClientSessionManager = class extends Destroyable {
5015
5386
  this._errors$.next(new require_operators.AuthStateHandlerError(error));
5016
5387
  }
5017
5388
  }
5389
+ async reauthenticate(token) {
5390
+ logger$6.debug("[Session] Re-authenticating session");
5391
+ try {
5392
+ const request = RPCReauthenticate({
5393
+ project: this._authorization$.value?.project_id ?? "",
5394
+ jwt_token: token
5395
+ });
5396
+ await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(request)).pipe(require_operators.throwOnRPCError(), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
5397
+ logger$6.error("[Session] Re-authentication RPC failed:", err);
5398
+ throw err;
5399
+ })));
5400
+ logger$6.debug("[Session] Re-authentication successful, updating stored auth state");
5401
+ } catch (error) {
5402
+ logger$6.error("[Session] Re-authentication failed:", error);
5403
+ this._errors$.next(new require_operators.AuthStateHandlerError(error));
5404
+ throw error;
5405
+ }
5406
+ }
5018
5407
  async authenticate() {
5019
5408
  logger$6.debug("[Session] Starting authentication process");
5020
5409
  const params = {
@@ -5038,6 +5427,7 @@ var ClientSessionManager = class extends Destroyable {
5038
5427
  ...params,
5039
5428
  ...persistedParams
5040
5429
  });
5430
+ this.transport.resetSessionEpoch();
5041
5431
  const response = await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(rpcConnectRequest)).pipe(require_operators.throwOnRPCError(), (0, rxjs.map)((res) => res.result), (0, rxjs.filter)(isRPCConnectResult), (0, rxjs.tap)(() => {
5042
5432
  logger$6.debug("[Session] Response passed filter, processing authentication result");
5043
5433
  }), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
@@ -5050,7 +5440,7 @@ var ClientSessionManager = class extends Destroyable {
5050
5440
  hasIceServers: !!response.ice_servers
5051
5441
  });
5052
5442
  if (response.protocol) await this.transport.setProtocol(response.protocol);
5053
- this.authorization$.next(response.authorization);
5443
+ this._authorization$.next(response.authorization);
5054
5444
  this._iceServers$.next(response.ice_servers ?? []);
5055
5445
  this._authenticated$.next(true);
5056
5446
  logger$6.debug("[Session] Authentication completed successfully");
@@ -5069,7 +5459,8 @@ var ClientSessionManager = class extends Destroyable {
5069
5459
  to: invite.callee_id_number,
5070
5460
  fromName: invite.caller_id_name,
5071
5461
  from: invite.caller_id_number,
5072
- displayDirection: invite.display_direction
5462
+ displayDirection: invite.display_direction,
5463
+ userVariables: invite.userVariables
5073
5464
  });
5074
5465
  await (0, rxjs.firstValueFrom)(callSession.status$);
5075
5466
  this._calls$.next({
@@ -5079,12 +5470,13 @@ var ClientSessionManager = class extends Destroyable {
5079
5470
  }
5080
5471
  async createOutboundCall(destination, options = {}) {
5081
5472
  const destinationURI = destination instanceof Address ? destination.defaultChannel : destination;
5473
+ let callSession;
5082
5474
  try {
5083
- const callSession = await this.createCall({
5475
+ callSession = await this.createCall({
5084
5476
  to: destinationURI,
5085
5477
  ...options
5086
5478
  });
5087
- await (0, rxjs.firstValueFrom)(callSession.selfId$.pipe((0, rxjs.filter)((id) => Boolean(id)), (0, rxjs.take)(1), (0, rxjs.timeout)(this.callCreateTimeout)));
5479
+ await (0, rxjs.firstValueFrom)((0, rxjs.race)(callSession.selfId$.pipe((0, rxjs.filter)((id) => Boolean(id)), (0, rxjs.take)(1), (0, rxjs.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, rxjs.take)(1), (0, rxjs.switchMap)((callError) => (0, rxjs.throwError)(() => callError.error)))));
5088
5480
  this._calls$.next({
5089
5481
  [`${callSession.id}`]: callSession,
5090
5482
  ...this._calls$.value
@@ -5092,19 +5484,25 @@ var ClientSessionManager = class extends Destroyable {
5092
5484
  return callSession;
5093
5485
  } catch (error) {
5094
5486
  logger$6.error("[Session] Error creating outbound call:", error);
5095
- const callError = new require_operators.CallCreateError("Call create timeout", error);
5487
+ callSession?.destroy();
5488
+ const callError = new require_operators.CallCreateError(error instanceof rxjs.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
5096
5489
  this._errors$.next(callError);
5097
5490
  throw callError;
5098
5491
  }
5099
5492
  }
5100
5493
  async createCall(options = {}) {
5101
5494
  try {
5102
- if (!this._directory) throw new require_operators.DependencyError("Directory not initialized");
5103
5495
  const addressURI = getAddressSearchURI(options);
5104
- const addressId = await this._directory.findAddressIdByURI(addressURI);
5105
- if (!addressId) throw new require_operators.DependencyError(`Address name: ${addressURI} not found`);
5106
- const address = this._directory.get(addressId);
5107
- if (!address) throw new require_operators.DependencyError(`Address ID: ${addressId} not found`);
5496
+ let address;
5497
+ try {
5498
+ if (!this._directory) throw new require_operators.DependencyError("Directory not initialized");
5499
+ const addressId = await this._directory.findAddressIdByURI(addressURI);
5500
+ if (!addressId) throw new require_operators.DependencyError(`Address name: ${addressURI} not found`);
5501
+ address = this._directory.get(addressId);
5502
+ if (!address) throw new require_operators.DependencyError(`Address ID: ${addressId} not found`);
5503
+ } catch (error) {
5504
+ logger$6.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
5505
+ }
5108
5506
  const callSession = this.callFactory.createCall(address, { ...options });
5109
5507
  callSession.status$.pipe((0, rxjs.filter)((status) => status === "destroyed"), (0, rxjs.take)(1)).subscribe(() => {
5110
5508
  const { [`${callSession.id}`]: _, ...remainingCalls } = this._calls$.value;
@@ -5113,7 +5511,7 @@ var ClientSessionManager = class extends Destroyable {
5113
5511
  return callSession;
5114
5512
  } catch (error) {
5115
5513
  logger$6.error("[Session] Error creating call session:", error);
5116
- throw new require_operators.CallCreateError("Call create error", error);
5514
+ throw new require_operators.CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
5117
5515
  }
5118
5516
  }
5119
5517
  destroy() {
@@ -5134,6 +5532,9 @@ var ClientSessionWrapper = class {
5134
5532
  get signalingEvent$() {
5135
5533
  return this.clientSessionManager.signalingEvent$;
5136
5534
  }
5535
+ get iceServers() {
5536
+ return this.clientSessionManager.iceServers;
5537
+ }
5137
5538
  async execute(request, options) {
5138
5539
  return this.clientSessionManager.execute(request, options);
5139
5540
  }
@@ -5356,7 +5757,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
5356
5757
  this.shouldReconnect = false;
5357
5758
  this._status$ = this.createBehaviorSubject("disconnected");
5358
5759
  this._incomingMessages$ = this.createSubject();
5359
- this._errors$ = this.createSubject();
5760
+ this._errors$ = this.createReplaySubject(1);
5360
5761
  this.reconnectDelayMin = options.reconnectDelayMin ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MIN_MS;
5361
5762
  this.reconnectDelayMax = options.reconnectDelayMax ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MAX_MS;
5362
5763
  this.connectionTimeout = options.connectionTimeout ?? WebSocketController.DEFAULT_CONNECTION_TIMEOUT_MS;
@@ -5506,7 +5907,30 @@ function isSignalwirePingRequest(value) {
5506
5907
  //#endregion
5507
5908
  //#region src/managers/TransportManager.ts
5508
5909
  const logger$2 = require_operators.getLogger();
5509
- var TransportManager = class extends Destroyable {
5910
+ var TransportManager = class TransportManager extends Destroyable {
5911
+ /**
5912
+ * Normalise a server event timestamp to epoch seconds.
5913
+ *
5914
+ * The server uses two formats:
5915
+ * - `webrtc.message`: float epoch seconds (e.g. 1774372099.022817)
5916
+ * - all other events: int epoch microseconds (e.g. 1774372099925857)
5917
+ *
5918
+ * Values above 1e12 are treated as microseconds and divided by 1e6.
5919
+ */
5920
+ static toEpochSeconds(ts) {
5921
+ const n = typeof ts === "string" ? Number(ts) : ts;
5922
+ return n > 0xe8d4a51000 ? n / 1e6 : n;
5923
+ }
5924
+ /**
5925
+ * Extract the event timestamp from a signalwire.event message.
5926
+ * Returns `null` for messages that have no timestamp
5927
+ * (e.g. signalwire.authorization.state, RPC responses).
5928
+ */
5929
+ static extractEventTimestamp(message) {
5930
+ if (!isSignalwireRequest(message)) return null;
5931
+ if (message.params.event_type === "signalwire.authorization.state") return null;
5932
+ return TransportManager.toEpochSeconds(message.params.timestamp);
5933
+ }
5510
5934
  constructor(storage, protocolKey, webSocketConstructor, relayHost, onError) {
5511
5935
  super();
5512
5936
  this.storage = storage;
@@ -5539,6 +5963,23 @@ var TransportManager = class extends Destroyable {
5539
5963
  return true;
5540
5964
  });
5541
5965
  };
5966
+ this.discardStaleEvents = () => {
5967
+ return (0, rxjs.filter)((message) => {
5968
+ const ts = TransportManager.extractEventTimestamp(message);
5969
+ if (ts === null) return true;
5970
+ if (this._sessionEpoch === null) {
5971
+ this._sessionEpoch = ts;
5972
+ return true;
5973
+ }
5974
+ if (ts < this._sessionEpoch) {
5975
+ const eventType = isSignalwireRequest(message) ? message.params.event_type : "unknown";
5976
+ logger$2.warn(`[Transport] Discarding stale event: ${eventType} (timestamp=${ts.toFixed(3)}, sessionEpoch=${this._sessionEpoch.toFixed(3)}, delta=${(this._sessionEpoch - ts).toFixed(3)}s)`);
5977
+ return false;
5978
+ }
5979
+ return true;
5980
+ });
5981
+ };
5982
+ this._sessionEpoch = null;
5542
5983
  this._outgoingMessages$ = this.createSubject();
5543
5984
  this._webSocketConnections = new WebSocketController(webSocketConstructor, relayHost, this._outgoingMessages$.asObservable(), {
5544
5985
  connectionTimeout: PreferencesContainer.instance.connectionTimeout,
@@ -5563,7 +6004,14 @@ var TransportManager = class extends Destroyable {
5563
6004
  return rxjs.EMPTY;
5564
6005
  }), (0, rxjs.share)(), (0, rxjs.takeUntil)(this.destroyed$));
5565
6006
  this._jsonRPCResponse$ = this._jsonRPCMessage$.pipe((0, rxjs.filter)(isJSONRPCResponse));
5566
- this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), (0, rxjs.filter)((message) => !isJSONRPCResponse(message)), (0, rxjs.share)(), (0, rxjs.takeUntil)(this.destroyed$));
6007
+ this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), (0, rxjs.filter)((message) => !isJSONRPCResponse(message)), this.discardStaleEvents(), (0, rxjs.share)(), (0, rxjs.takeUntil)(this.destroyed$));
6008
+ }
6009
+ /**
6010
+ * Reset the session epoch. Call this before each signalwire.connect
6011
+ * so that the first event after authentication establishes the new baseline.
6012
+ */
6013
+ resetSessionEpoch() {
6014
+ this._sessionEpoch = null;
5567
6015
  }
5568
6016
  async setProtocol(protocol) {
5569
6017
  this.protocol$.next(protocol);
@@ -5681,7 +6129,8 @@ const buildOptionsFromDestination = (destination) => {
5681
6129
  const channel = new URLSearchParams(queryString).get("channel");
5682
6130
  if (channel === "video") return {
5683
6131
  audio: true,
5684
- video: true
6132
+ video: true,
6133
+ receiveVideo: true
5685
6134
  };
5686
6135
  else if (channel === "audio") return {
5687
6136
  audio: true,
@@ -5711,7 +6160,7 @@ var SignalWire = class extends Destroyable {
5711
6160
  this._directory$ = this.createBehaviorSubject(void 0);
5712
6161
  this._isConnected$ = this.createBehaviorSubject(false);
5713
6162
  this._isRegistered$ = this.createBehaviorSubject(false);
5714
- this._errors$ = this.createSubject();
6163
+ this._errors$ = this.createReplaySubject(1);
5715
6164
  this._options = {};
5716
6165
  this._deps = new DependencyContainer();
5717
6166
  this._options = {
@@ -5721,6 +6170,7 @@ var SignalWire = class extends Destroyable {
5721
6170
  if (this._options.storageImplementation) this._deps.storageImpl = this._options.storageImplementation;
5722
6171
  if (this._options.webSocketConstructor) this._deps.WebSocket = this._options.webSocketConstructor;
5723
6172
  if (this._options.savePreferences) this.preferences.enableSavePreferences(this._deps.storage);
6173
+ if (this._options.webRTCApiProvider) this._deps.webRTCApiProvider = this._options.webRTCApiProvider;
5724
6174
  this._deviceController = this._deps.deviceController;
5725
6175
  if (!this._options.skipDeviceMonitoring) this._deviceController.enableDeviceMonitoring();
5726
6176
  this.subscribeTo(this._deviceController.errors$, (error) => {
@@ -5754,7 +6204,10 @@ var SignalWire = class extends Destroyable {
5754
6204
  throw new require_operators.InvalidCredentialsError("Provided credentials have expired.");
5755
6205
  }
5756
6206
  if (_credentials.expiry_at && credentialProvider.refresh) {
5757
- const refreshFn = credentialProvider.refresh;
6207
+ const refreshFn = async () => {
6208
+ if (!credentialProvider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
6209
+ return credentialProvider.refresh();
6210
+ };
5758
6211
  const refreshInterval = Math.max(_credentials.expiry_at - Date.now() - 5e3, 1e3);
5759
6212
  this._refreshTimerId = setTimeout(async () => {
5760
6213
  try {
@@ -5772,12 +6225,24 @@ var SignalWire = class extends Destroyable {
5772
6225
  }, refreshInterval);
5773
6226
  }
5774
6227
  this._deps.credential = _credentials;
6228
+ if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
6229
+ await this._clientSession.reauthenticate(_credentials.token);
6230
+ logger$1.info("[SignalWire] Session refreshed with new credentials.");
6231
+ } catch (error) {
6232
+ logger$1.error("[SignalWire] Failed to refresh session with new credentials:", error);
6233
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
6234
+ }
5775
6235
  }
5776
6236
  async init() {
5777
6237
  this._subscriber$.next(new Subscriber(this._deps.http));
5778
6238
  if (!this._options.skipConnection) await this.connect();
5779
- if (this._options.skipReconnect && this._attachManager) await this._attachManager.flush();
5780
- if (!this._options.skipRegister) this.register();
6239
+ if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
6240
+ if (!this._options.skipRegister) try {
6241
+ await this.register();
6242
+ } catch (error) {
6243
+ logger$1.error("[SignalWire] Registration failed:", error);
6244
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
6245
+ }
5781
6246
  this.handleAttachments();
5782
6247
  }
5783
6248
  async handleAttachments() {
@@ -5845,7 +6310,7 @@ var SignalWire = class extends Destroyable {
5845
6310
  };
5846
6311
  this._transport = new TransportManager(this._deps.storage, this._deps.protocolKey, this._deps.WebSocket, PreferencesContainer.instance.relayHost ?? this._deps.relayHost, errorHandler);
5847
6312
  this._attachManager = new AttachManager(this._deps.storage, this._deps.deviceController, PreferencesContainer.instance.reconnectCallsTimeout, this._deps.attachedCallsKey);
5848
- this._clientSession = new ClientSessionManager(this._deps.credential, this._transport, this._deps.storage, this._deps.authorizationStateKey, this._deps.deviceController, this._attachManager);
6313
+ this._clientSession = new ClientSessionManager(this._deps.credential, this._transport, this._deps.storage, this._deps.authorizationStateKey, this._deps.deviceController, this._attachManager, this._deps.webRTCApiProvider);
5849
6314
  this._publicSession = new ClientSessionWrapper(this._clientSession);
5850
6315
  this.subscribeTo(this._clientSession.errors$, (error) => {
5851
6316
  this._errors$.next(error);
@@ -5869,7 +6334,7 @@ var SignalWire = class extends Destroyable {
5869
6334
  * ```
5870
6335
  */
5871
6336
  get subscriber$() {
5872
- return this._subscriber$.asObservable();
6337
+ return this.deferEmission(this._subscriber$.asObservable());
5873
6338
  }
5874
6339
  /** Current subscriber snapshot, or `undefined` if not yet authenticated. */
5875
6340
  get subscriber() {
@@ -5888,7 +6353,7 @@ var SignalWire = class extends Destroyable {
5888
6353
  * ```
5889
6354
  */
5890
6355
  get directory$() {
5891
- return this._directory$.asObservable();
6356
+ return this.deferEmission(this._directory$.asObservable());
5892
6357
  }
5893
6358
  /**
5894
6359
  * Current directory snapshot, or `undefined` if the client is not yet connected.
@@ -5899,7 +6364,7 @@ var SignalWire = class extends Destroyable {
5899
6364
  }
5900
6365
  /** Observable that emits when the subscriber registration state changes. */
5901
6366
  get isRegistered$() {
5902
- return this._isRegistered$.asObservable();
6367
+ return this.deferEmission(this._isRegistered$.asObservable());
5903
6368
  }
5904
6369
  /** Whether the subscriber is currently registered. */
5905
6370
  get isRegistered() {
@@ -5911,15 +6376,15 @@ var SignalWire = class extends Destroyable {
5911
6376
  }
5912
6377
  /** Observable that emits when the connection state changes. */
5913
6378
  get isConnected$() {
5914
- return this._isConnected$.asObservable();
6379
+ return this.deferEmission(this._isConnected$.asObservable());
5915
6380
  }
5916
6381
  /** Observable that emits `true` when the client is both connected and authenticated. */
5917
6382
  get ready$() {
5918
- return this._isConnected$.pipe((0, rxjs.switchMap)((connected) => connected ? this._clientSession.authenticated$ : (0, rxjs.of)(false)));
6383
+ return this.publicCachedObservable("ready$", () => this._isConnected$.pipe((0, rxjs.switchMap)((connected) => connected ? this._clientSession.authenticated$ : (0, rxjs.of)(false))));
5919
6384
  }
5920
6385
  /** Observable stream of errors from transport, authentication, and devices. */
5921
6386
  get errors$() {
5922
- return this._errors$.asObservable();
6387
+ return this.deferEmission(this._errors$.asObservable());
5923
6388
  }
5924
6389
  /** Disconnects the WebSocket and tears down the session. */
5925
6390
  async disconnect() {
@@ -5940,8 +6405,22 @@ var SignalWire = class extends Destroyable {
5940
6405
  }));
5941
6406
  this._isRegistered$.next(true);
5942
6407
  } catch (error) {
5943
- logger$1.error("[SignalWire] Failed to register subscriber:", error);
6408
+ logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
6409
+ if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
6410
+ logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
6411
+ await this._transport.execute(RPCExecute({
6412
+ method: "subscriber.online",
6413
+ params: {}
6414
+ }));
6415
+ this._isRegistered$.next(true);
6416
+ }).catch((reauthError) => {
6417
+ logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
6418
+ const registerError = new require_operators.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 }) });
6419
+ this._errors$.next(registerError);
6420
+ throw registerError;
6421
+ });
5944
6422
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
6423
+ throw error;
5945
6424
  }
5946
6425
  }
5947
6426
  /** Unregisters the subscriber, going offline for inbound calls. */
@@ -5980,7 +6459,7 @@ var SignalWire = class extends Destroyable {
5980
6459
  }
5981
6460
  /** Observable list of available audio input (microphone) devices. */
5982
6461
  get audioInputDevices$() {
5983
- return this._deviceController.audioInputDevices$;
6462
+ return this.deferEmission(this._deviceController.audioInputDevices$);
5984
6463
  }
5985
6464
  /** Current snapshot of available audio input devices. */
5986
6465
  get audioInputDevices() {
@@ -5988,7 +6467,7 @@ var SignalWire = class extends Destroyable {
5988
6467
  }
5989
6468
  /** Observable list of available audio output (speaker) devices. */
5990
6469
  get audioOutputDevices$() {
5991
- return this._deviceController.audioOutputDevices$;
6470
+ return this.deferEmission(this._deviceController.audioOutputDevices$);
5992
6471
  }
5993
6472
  /** Current snapshot of available audio output devices. */
5994
6473
  get audioOutputDevices() {
@@ -5996,7 +6475,7 @@ var SignalWire = class extends Destroyable {
5996
6475
  }
5997
6476
  /** Observable list of available video input (camera) devices. */
5998
6477
  get videoInputDevices$() {
5999
- return this._deviceController.videoInputDevices$;
6478
+ return this.deferEmission(this._deviceController.videoInputDevices$);
6000
6479
  }
6001
6480
  /** Current snapshot of available video input devices. */
6002
6481
  get videoInputDevices() {
@@ -6004,15 +6483,15 @@ var SignalWire = class extends Destroyable {
6004
6483
  }
6005
6484
  /** Observable of the currently selected audio input device. */
6006
6485
  get selectedAudioInputDevice$() {
6007
- return this._deviceController.selectedAudioInputDevice$;
6486
+ return this.deferEmission(this._deviceController.selectedAudioInputDevice$);
6008
6487
  }
6009
6488
  /** Observable of the currently selected audio output device. */
6010
6489
  get selectedAudioOutputDevice$() {
6011
- return this._deviceController.selectedAudioOutputDevice$;
6490
+ return this.deferEmission(this._deviceController.selectedAudioOutputDevice$);
6012
6491
  }
6013
6492
  /** Observable of the currently selected video input device. */
6014
6493
  get selectedVideoInputDevice$() {
6015
- return this._deviceController.selectedVideoInputDevice$;
6494
+ return this.deferEmission(this._deviceController.selectedVideoInputDevice$);
6016
6495
  }
6017
6496
  /** Currently selected audio input device, or `null` if none. */
6018
6497
  get selectedAudioInputDevice() {
@@ -6194,13 +6673,16 @@ exports.Address = Address;
6194
6673
  exports.CallCreateError = require_operators.CallCreateError;
6195
6674
  exports.ClientPreferences = ClientPreferences;
6196
6675
  exports.CollectionFetchError = require_operators.CollectionFetchError;
6676
+ exports.InvalidCredentialsError = require_operators.InvalidCredentialsError;
6197
6677
  exports.MediaTrackError = require_operators.MediaTrackError;
6198
6678
  exports.MessageParseError = require_operators.MessageParseError;
6199
6679
  exports.Participant = Participant;
6680
+ exports.SelfCapabilities = SelfCapabilities;
6200
6681
  exports.SelfParticipant = SelfParticipant;
6201
6682
  exports.SignalWire = SignalWire;
6202
6683
  exports.StaticCredentialProvider = StaticCredentialProvider;
6203
6684
  exports.Subscriber = Subscriber;
6685
+ exports.UnexpectedError = require_operators.UnexpectedError;
6204
6686
  exports.VertoPongError = require_operators.VertoPongError;
6205
6687
  exports.WebRTCCall = WebRTCCall;
6206
6688
  exports.embeddableCall = embeddableCall;