@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.mjs CHANGED
@@ -372,6 +372,8 @@ var SFUClient = class {
372
372
  this.maxReconnectAttempts = 8;
373
373
  this.reconnectBaseMs = 750;
374
374
  this.joinWaiter = null;
375
+ this.transportRecoveryInProgress = false;
376
+ this.transportRecoveryTimer = null;
375
377
  this.pendingIce = {
376
378
  publisher: [],
377
379
  subscriber: []
@@ -390,6 +392,7 @@ var SFUClient = class {
390
392
  this.trackToSource = /* @__PURE__ */ new Map();
391
393
  this.localTrackIds = /* @__PURE__ */ new Map();
392
394
  this.subscriberStatsTimer = null;
395
+ this.heartbeatTimer = null;
393
396
  this.apiBase = options.apiBase || "https://api.example.com/api";
394
397
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
395
398
  this.participantId = options.participantId || this.createId();
@@ -434,6 +437,24 @@ var SFUClient = class {
434
437
  this.onError = options.onError || console.error;
435
438
  this.autoSubscribe = options.autoSubscribe !== false;
436
439
  }
440
+ startHeartbeat() {
441
+ this.stopHeartbeat();
442
+ this.heartbeatTimer = setInterval(() => {
443
+ if (this.ws?.readyState !== WebSocket.OPEN) return;
444
+ this.send({
445
+ type: "ping",
446
+ payload: {
447
+ nonce: crypto.randomUUID()
448
+ }
449
+ });
450
+ }, 1e4);
451
+ }
452
+ stopHeartbeat() {
453
+ if (this.heartbeatTimer) {
454
+ clearInterval(this.heartbeatTimer);
455
+ this.heartbeatTimer = null;
456
+ }
457
+ }
437
458
  createId() {
438
459
  return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `id_${Math.random().toString(36).slice(2)}_${Date.now()}`;
439
460
  }
@@ -594,6 +615,7 @@ var SFUClient = class {
594
615
  );
595
616
  this.ws = ws;
596
617
  ws.onopen = () => {
618
+ this.startHeartbeat();
597
619
  this.setState("joining");
598
620
  this.joiningWithResume = Boolean(this.resumeToken);
599
621
  const joinPayload = {
@@ -617,6 +639,7 @@ var SFUClient = class {
617
639
  this.onError(event);
618
640
  };
619
641
  ws.onclose = () => {
642
+ this.stopHeartbeat();
620
643
  if (this.ws !== ws) return;
621
644
  this.ws = null;
622
645
  if (this.intentionalClose || this.state === "leaving") {
@@ -898,11 +921,21 @@ var SFUClient = class {
898
921
  break;
899
922
  }
900
923
  case "error": {
901
- const error = new Error(payload.message || "SFU signaling error");
902
- this.onError(error);
924
+ const message2 = payload.message || "SFU signaling error";
925
+ if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
926
+ console.debug(
927
+ "[SFU] Ignoring stale-session error during recovery",
928
+ message2
929
+ );
930
+ return;
931
+ }
932
+ const error = new Error(message2);
903
933
  if (this.state === "joining" || this.state === "waiting_approval") {
934
+ this.onError(error);
904
935
  this.failJoin(error);
936
+ return;
905
937
  }
938
+ this.onError(error);
906
939
  break;
907
940
  }
908
941
  default:
@@ -980,17 +1013,49 @@ var SFUClient = class {
980
1013
  if (event.candidate) this.sendCandidate("subscriber", event.candidate);
981
1014
  };
982
1015
  this.publisher.onconnectionstatechange = () => {
983
- const state = this.publisher?.connectionState;
1016
+ const publisher = this.publisher;
1017
+ if (!publisher) return;
1018
+ const state = publisher.connectionState;
1019
+ console.debug("[SFU] Publisher connection state", {
1020
+ connectionState: publisher.connectionState,
1021
+ iceConnectionState: publisher.iceConnectionState,
1022
+ iceGatheringState: publisher.iceGatheringState,
1023
+ signalingState: publisher.signalingState
1024
+ });
984
1025
  if (state === "failed") {
985
- this.onError(new Error("Publisher PeerConnection failed"));
1026
+ this.recoverWebRTC("publisher_failed");
986
1027
  }
987
1028
  };
988
1029
  this.subscriber.onconnectionstatechange = () => {
989
- const state = this.subscriber?.connectionState;
1030
+ const subscriber = this.subscriber;
1031
+ if (!subscriber) return;
1032
+ const state = subscriber.connectionState;
1033
+ console.debug("[SFU] Subscriber connection state", {
1034
+ connectionState: subscriber.connectionState,
1035
+ iceConnectionState: subscriber.iceConnectionState,
1036
+ iceGatheringState: subscriber.iceGatheringState,
1037
+ signalingState: subscriber.signalingState
1038
+ });
990
1039
  if (state === "failed") {
991
- this.onError(new Error("Subscriber PeerConnection failed"));
1040
+ this.recoverWebRTC("subscriber_failed");
992
1041
  }
993
1042
  };
1043
+ this.publisher.oniceconnectionstatechange = () => {
1044
+ const publisher = this.publisher;
1045
+ if (!publisher) return;
1046
+ console.debug("[SFU] Publisher ICE state", {
1047
+ iceConnectionState: publisher.iceConnectionState,
1048
+ connectionState: publisher.connectionState
1049
+ });
1050
+ };
1051
+ this.subscriber.oniceconnectionstatechange = () => {
1052
+ const subscriber = this.subscriber;
1053
+ if (!subscriber) return;
1054
+ console.debug("[SFU] Subscriber ICE state", {
1055
+ iceConnectionState: subscriber.iceConnectionState,
1056
+ connectionState: subscriber.connectionState
1057
+ });
1058
+ };
994
1059
  this.subscriber.ontrack = (event) => {
995
1060
  void this.handleRemoteTrack(event);
996
1061
  };
@@ -1461,6 +1526,7 @@ var SFUClient = class {
1461
1526
  }
1462
1527
  }
1463
1528
  async leave() {
1529
+ this.stopHeartbeat();
1464
1530
  this.intentionalClose = true;
1465
1531
  this.setState("leaving");
1466
1532
  if (this.reconnectTimer) {
@@ -1596,6 +1662,44 @@ var SFUClient = class {
1596
1662
  } catch {
1597
1663
  }
1598
1664
  }
1665
+ recoverWebRTC(reason) {
1666
+ if (this.intentionalClose) return;
1667
+ if (this.transportRecoveryInProgress) {
1668
+ console.debug("[SFU] WebRTC recovery already in progress", {
1669
+ reason
1670
+ });
1671
+ return;
1672
+ }
1673
+ this.transportRecoveryInProgress = true;
1674
+ console.warn("[SFU] WebRTC transport failed; starting recovery", {
1675
+ reason,
1676
+ publisherState: this.publisher?.connectionState,
1677
+ publisherIceState: this.publisher?.iceConnectionState,
1678
+ subscriberState: this.subscriber?.connectionState,
1679
+ subscriberIceState: this.subscriber?.iceConnectionState
1680
+ });
1681
+ this.setState("reconnecting");
1682
+ this.transportRecoveryTimer = setTimeout(() => {
1683
+ this.transportRecoveryTimer = null;
1684
+ if (this.intentionalClose) {
1685
+ this.transportRecoveryInProgress = false;
1686
+ return;
1687
+ }
1688
+ const ws = this.ws;
1689
+ if (ws && ws.readyState === WebSocket.OPEN) {
1690
+ try {
1691
+ ws.close(4e3, "webrtc_recovery");
1692
+ } catch (error) {
1693
+ console.warn("[SFU] Failed to close WebSocket for recovery", error);
1694
+ this.transportRecoveryInProgress = false;
1695
+ this.scheduleReconnect();
1696
+ }
1697
+ return;
1698
+ }
1699
+ this.transportRecoveryInProgress = false;
1700
+ this.scheduleReconnect();
1701
+ }, 250);
1702
+ }
1599
1703
  };
1600
1704
 
1601
1705
  // src/core/VideoCore.ts