@defcomm/meetingsdk 0.1.8 → 0.2.0

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.d.mts CHANGED
@@ -316,6 +316,8 @@ declare class SFUClient {
316
316
  private maxReconnectAttempts;
317
317
  private reconnectBaseMs;
318
318
  private joinWaiter;
319
+ private transportRecoveryInProgress;
320
+ private transportRecoveryTimer;
319
321
  private pendingIce;
320
322
  private subscriberNegotiating;
321
323
  private offerQueue;
@@ -353,6 +355,9 @@ declare class SFUClient {
353
355
  private onError;
354
356
  private autoSubscribe;
355
357
  constructor(options?: SFUClientOptions);
358
+ private heartbeatTimer;
359
+ private startHeartbeat;
360
+ private stopHeartbeat;
356
361
  private createId;
357
362
  private resumeStorageKey;
358
363
  private loadResumeToken;
@@ -421,6 +426,7 @@ declare class SFUClient {
421
426
  private closePeerConnectionsOnly;
422
427
  private cleanupAll;
423
428
  private logSubscriberVideoStats;
429
+ private recoverWebRTC;
424
430
  }
425
431
 
426
432
  type PubSubHandle = {
package/dist/index.d.ts CHANGED
@@ -316,6 +316,8 @@ declare class SFUClient {
316
316
  private maxReconnectAttempts;
317
317
  private reconnectBaseMs;
318
318
  private joinWaiter;
319
+ private transportRecoveryInProgress;
320
+ private transportRecoveryTimer;
319
321
  private pendingIce;
320
322
  private subscriberNegotiating;
321
323
  private offerQueue;
@@ -353,6 +355,9 @@ declare class SFUClient {
353
355
  private onError;
354
356
  private autoSubscribe;
355
357
  constructor(options?: SFUClientOptions);
358
+ private heartbeatTimer;
359
+ private startHeartbeat;
360
+ private stopHeartbeat;
356
361
  private createId;
357
362
  private resumeStorageKey;
358
363
  private loadResumeToken;
@@ -421,6 +426,7 @@ declare class SFUClient {
421
426
  private closePeerConnectionsOnly;
422
427
  private cleanupAll;
423
428
  private logSubscriberVideoStats;
429
+ private recoverWebRTC;
424
430
  }
425
431
 
426
432
  type PubSubHandle = {
package/dist/index.js CHANGED
@@ -408,6 +408,8 @@ var SFUClient = class {
408
408
  this.maxReconnectAttempts = 8;
409
409
  this.reconnectBaseMs = 750;
410
410
  this.joinWaiter = null;
411
+ this.transportRecoveryInProgress = false;
412
+ this.transportRecoveryTimer = null;
411
413
  this.pendingIce = {
412
414
  publisher: [],
413
415
  subscriber: []
@@ -426,6 +428,7 @@ var SFUClient = class {
426
428
  this.trackToSource = /* @__PURE__ */ new Map();
427
429
  this.localTrackIds = /* @__PURE__ */ new Map();
428
430
  this.subscriberStatsTimer = null;
431
+ this.heartbeatTimer = null;
429
432
  this.apiBase = options.apiBase || "https://api.example.com/api";
430
433
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
431
434
  this.participantId = options.participantId || this.createId();
@@ -470,6 +473,24 @@ var SFUClient = class {
470
473
  this.onError = options.onError || console.error;
471
474
  this.autoSubscribe = options.autoSubscribe !== false;
472
475
  }
476
+ startHeartbeat() {
477
+ this.stopHeartbeat();
478
+ this.heartbeatTimer = setInterval(() => {
479
+ if (this.ws?.readyState !== WebSocket.OPEN) return;
480
+ this.send({
481
+ type: "ping",
482
+ payload: {
483
+ nonce: crypto.randomUUID()
484
+ }
485
+ });
486
+ }, 1e4);
487
+ }
488
+ stopHeartbeat() {
489
+ if (this.heartbeatTimer) {
490
+ clearInterval(this.heartbeatTimer);
491
+ this.heartbeatTimer = null;
492
+ }
493
+ }
473
494
  createId() {
474
495
  return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `id_${Math.random().toString(36).slice(2)}_${Date.now()}`;
475
496
  }
@@ -630,6 +651,7 @@ var SFUClient = class {
630
651
  );
631
652
  this.ws = ws;
632
653
  ws.onopen = () => {
654
+ this.startHeartbeat();
633
655
  this.setState("joining");
634
656
  this.joiningWithResume = Boolean(this.resumeToken);
635
657
  const joinPayload = {
@@ -653,6 +675,7 @@ var SFUClient = class {
653
675
  this.onError(event);
654
676
  };
655
677
  ws.onclose = () => {
678
+ this.stopHeartbeat();
656
679
  if (this.ws !== ws) return;
657
680
  this.ws = null;
658
681
  if (this.intentionalClose || this.state === "leaving") {
@@ -934,11 +957,21 @@ var SFUClient = class {
934
957
  break;
935
958
  }
936
959
  case "error": {
937
- const error = new Error(payload.message || "SFU signaling error");
938
- this.onError(error);
960
+ const message2 = payload.message || "SFU signaling error";
961
+ if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
962
+ console.debug(
963
+ "[SFU] Ignoring stale-session error during recovery",
964
+ message2
965
+ );
966
+ return;
967
+ }
968
+ const error = new Error(message2);
939
969
  if (this.state === "joining" || this.state === "waiting_approval") {
970
+ this.onError(error);
940
971
  this.failJoin(error);
972
+ return;
941
973
  }
974
+ this.onError(error);
942
975
  break;
943
976
  }
944
977
  default:
@@ -1016,17 +1049,49 @@ var SFUClient = class {
1016
1049
  if (event.candidate) this.sendCandidate("subscriber", event.candidate);
1017
1050
  };
1018
1051
  this.publisher.onconnectionstatechange = () => {
1019
- const state = this.publisher?.connectionState;
1052
+ const publisher = this.publisher;
1053
+ if (!publisher) return;
1054
+ const state = publisher.connectionState;
1055
+ console.debug("[SFU] Publisher connection state", {
1056
+ connectionState: publisher.connectionState,
1057
+ iceConnectionState: publisher.iceConnectionState,
1058
+ iceGatheringState: publisher.iceGatheringState,
1059
+ signalingState: publisher.signalingState
1060
+ });
1020
1061
  if (state === "failed") {
1021
- this.onError(new Error("Publisher PeerConnection failed"));
1062
+ this.recoverWebRTC("publisher_failed");
1022
1063
  }
1023
1064
  };
1024
1065
  this.subscriber.onconnectionstatechange = () => {
1025
- const state = this.subscriber?.connectionState;
1066
+ const subscriber = this.subscriber;
1067
+ if (!subscriber) return;
1068
+ const state = subscriber.connectionState;
1069
+ console.debug("[SFU] Subscriber connection state", {
1070
+ connectionState: subscriber.connectionState,
1071
+ iceConnectionState: subscriber.iceConnectionState,
1072
+ iceGatheringState: subscriber.iceGatheringState,
1073
+ signalingState: subscriber.signalingState
1074
+ });
1026
1075
  if (state === "failed") {
1027
- this.onError(new Error("Subscriber PeerConnection failed"));
1076
+ this.recoverWebRTC("subscriber_failed");
1028
1077
  }
1029
1078
  };
1079
+ this.publisher.oniceconnectionstatechange = () => {
1080
+ const publisher = this.publisher;
1081
+ if (!publisher) return;
1082
+ console.debug("[SFU] Publisher ICE state", {
1083
+ iceConnectionState: publisher.iceConnectionState,
1084
+ connectionState: publisher.connectionState
1085
+ });
1086
+ };
1087
+ this.subscriber.oniceconnectionstatechange = () => {
1088
+ const subscriber = this.subscriber;
1089
+ if (!subscriber) return;
1090
+ console.debug("[SFU] Subscriber ICE state", {
1091
+ iceConnectionState: subscriber.iceConnectionState,
1092
+ connectionState: subscriber.connectionState
1093
+ });
1094
+ };
1030
1095
  this.subscriber.ontrack = (event) => {
1031
1096
  void this.handleRemoteTrack(event);
1032
1097
  };
@@ -1497,6 +1562,7 @@ var SFUClient = class {
1497
1562
  }
1498
1563
  }
1499
1564
  async leave() {
1565
+ this.stopHeartbeat();
1500
1566
  this.intentionalClose = true;
1501
1567
  this.setState("leaving");
1502
1568
  if (this.reconnectTimer) {
@@ -1632,6 +1698,44 @@ var SFUClient = class {
1632
1698
  } catch {
1633
1699
  }
1634
1700
  }
1701
+ recoverWebRTC(reason) {
1702
+ if (this.intentionalClose) return;
1703
+ if (this.transportRecoveryInProgress) {
1704
+ console.debug("[SFU] WebRTC recovery already in progress", {
1705
+ reason
1706
+ });
1707
+ return;
1708
+ }
1709
+ this.transportRecoveryInProgress = true;
1710
+ console.warn("[SFU] WebRTC transport failed; starting recovery", {
1711
+ reason,
1712
+ publisherState: this.publisher?.connectionState,
1713
+ publisherIceState: this.publisher?.iceConnectionState,
1714
+ subscriberState: this.subscriber?.connectionState,
1715
+ subscriberIceState: this.subscriber?.iceConnectionState
1716
+ });
1717
+ this.setState("reconnecting");
1718
+ this.transportRecoveryTimer = setTimeout(() => {
1719
+ this.transportRecoveryTimer = null;
1720
+ if (this.intentionalClose) {
1721
+ this.transportRecoveryInProgress = false;
1722
+ return;
1723
+ }
1724
+ const ws = this.ws;
1725
+ if (ws && ws.readyState === WebSocket.OPEN) {
1726
+ try {
1727
+ ws.close(4e3, "webrtc_recovery");
1728
+ } catch (error) {
1729
+ console.warn("[SFU] Failed to close WebSocket for recovery", error);
1730
+ this.transportRecoveryInProgress = false;
1731
+ this.scheduleReconnect();
1732
+ }
1733
+ return;
1734
+ }
1735
+ this.transportRecoveryInProgress = false;
1736
+ this.scheduleReconnect();
1737
+ }, 250);
1738
+ }
1635
1739
  };
1636
1740
 
1637
1741
  // src/core/VideoCore.ts