@defcomm/meetingsdk 0.1.1 → 0.1.2

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
@@ -1464,7 +1464,7 @@ var SFUClient = class {
1464
1464
 
1465
1465
  // src/core/VideoCore.ts
1466
1466
  var VideoSDKCore = class {
1467
- constructor(events = {}, apiBase = SDK_CONFIG.apiBase || "https://api.example.com/api", wsBase = SDK_CONFIG.wsBase || "wss://api.example.com/ws") {
1467
+ constructor(events = {}, apiBase = SDK_CONFIG.apiBase, wsBase = SDK_CONFIG.wsBase) {
1468
1468
  this.events = events;
1469
1469
  this.apiBase = apiBase;
1470
1470
  this.wsBase = wsBase;
@@ -1480,6 +1480,8 @@ var VideoSDKCore = class {
1480
1480
  this.intentionalDisconnect = false;
1481
1481
  this.remoteStreams = /* @__PURE__ */ new Map();
1482
1482
  this.remoteScreenStreams = /* @__PURE__ */ new Map();
1483
+ this.joinPromise = null;
1484
+ this.disconnectPromise = null;
1483
1485
  this.state = new MeetingState();
1484
1486
  this.events = events;
1485
1487
  this.activeSpeakerDetector = new ActiveSpeakerDetector({
@@ -1513,7 +1515,7 @@ var VideoSDKCore = class {
1513
1515
  this.events.onError?.(err);
1514
1516
  console.error("[MeetingSDK Error]", err);
1515
1517
  }
1516
- // ============ LOCAL MEDIA ============
1518
+ // LOCAL MEDIA
1517
1519
  async initLocal(video, name) {
1518
1520
  this.participantName = name;
1519
1521
  try {
@@ -1562,7 +1564,7 @@ var VideoSDKCore = class {
1562
1564
  });
1563
1565
  this.state.localStream = this.localStream;
1564
1566
  }
1565
- // ============ CONNECTION / SIGNALLING ============
1567
+ // ROOM MANAGEMENT
1566
1568
  async createRoom(input) {
1567
1569
  const client = this.sfuClient ?? new SFUClient({
1568
1570
  apiBase: this.apiBase,
@@ -1587,6 +1589,7 @@ var VideoSDKCore = class {
1587
1589
  });
1588
1590
  return client.deleteRoom(roomCode);
1589
1591
  }
1592
+ // JOIN
1590
1593
  async joinMeeting(config) {
1591
1594
  const {
1592
1595
  roomId,
@@ -1601,6 +1604,28 @@ var VideoSDKCore = class {
1601
1604
  if (userId) {
1602
1605
  this.myId = userId;
1603
1606
  }
1607
+ if (this.sfuClient && this.room.id === roomId && this.sfuClient.getConnectionState() === "connected") {
1608
+ console.info("[SDK] Already connected; duplicate join ignored", {
1609
+ roomId,
1610
+ userId: this.myId
1611
+ });
1612
+ return;
1613
+ }
1614
+ if (this.joinPromise) {
1615
+ console.info(
1616
+ "[SDK] Join already in progress; waiting for existing join",
1617
+ {
1618
+ roomId,
1619
+ userId: this.myId
1620
+ }
1621
+ );
1622
+ return this.joinPromise;
1623
+ }
1624
+ if (this.sfuClient && this.room.id && this.room.id !== roomId) {
1625
+ throw new Error(
1626
+ `Already connected to room ${this.room.id}. Leave the current meeting before joining another room.`
1627
+ );
1628
+ }
1604
1629
  this.intentionalDisconnect = false;
1605
1630
  this.participantName = name;
1606
1631
  this.room.id = roomId;
@@ -1622,19 +1647,43 @@ var VideoSDKCore = class {
1622
1647
  track.enabled = !videoMuted;
1623
1648
  });
1624
1649
  this.updateLocalMedia(audioMuted, videoMuted);
1625
- return this.connect(roomId, name);
1650
+ const promise = this.connect(roomId, name);
1651
+ this.joinPromise = promise;
1652
+ try {
1653
+ await promise;
1654
+ } finally {
1655
+ if (this.joinPromise === promise) {
1656
+ this.joinPromise = null;
1657
+ }
1658
+ }
1626
1659
  }
1660
+ // SFU CONNECTION
1627
1661
  async connect(roomId, name) {
1628
1662
  if (this.sfuClient) {
1629
- await this.sfuClient.leave();
1630
- this.sfuClient = null;
1663
+ const state = this.sfuClient.getConnectionState();
1664
+ if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
1665
+ console.info(
1666
+ "[SDK] Existing SFU connection is active; connect ignored",
1667
+ {
1668
+ roomId,
1669
+ state
1670
+ }
1671
+ );
1672
+ return;
1673
+ }
1674
+ throw new Error(
1675
+ `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
1676
+ );
1631
1677
  }
1632
1678
  const client = new SFUClient({
1633
1679
  apiBase: this.apiBase,
1634
1680
  wsBase: this.wsBase,
1635
1681
  userId: this.myId,
1636
1682
  autoSubscribe: true,
1637
- onStateChange: (state) => this.handleSFUState(state),
1683
+ // CONNECTION STATE
1684
+ onStateChange: (state) => {
1685
+ this.handleSFUState(state);
1686
+ },
1638
1687
  onConnected: (payload, resumed) => {
1639
1688
  console.log(`[SFU] ${resumed ? "Resumed" : "Joined"} room`, payload);
1640
1689
  this.state.updateLocalParticipant({
@@ -1648,6 +1697,7 @@ var VideoSDKCore = class {
1648
1697
  onReconnecting: (attempt) => {
1649
1698
  console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
1650
1699
  },
1700
+ // ADMISSION CONTROL
1651
1701
  onJoinPending: (info) => {
1652
1702
  console.log("[SFU] Join request pending", info);
1653
1703
  this.events.onEntryRequested?.({
@@ -1660,7 +1710,10 @@ var VideoSDKCore = class {
1660
1710
  console.log("[SFU] Join approved", requestId);
1661
1711
  this.events.onJoinApproved?.(requestId);
1662
1712
  this.events.onEntryResponded?.(
1663
- { participantId: this.myId, decision: "approved" },
1713
+ {
1714
+ participantId: this.myId,
1715
+ decision: "approved"
1716
+ },
1664
1717
  "approved"
1665
1718
  );
1666
1719
  },
@@ -1668,13 +1721,19 @@ var VideoSDKCore = class {
1668
1721
  console.warn("[SFU] Join rejected", requestId, reason);
1669
1722
  this.events.onJoinRejected?.(requestId);
1670
1723
  this.events.onEntryResponded?.(
1671
- { participantId: this.myId, decision: "rejected" },
1724
+ {
1725
+ participantId: this.myId,
1726
+ decision: "rejected"
1727
+ },
1672
1728
  "rejected"
1673
1729
  );
1674
1730
  this.emitError(
1675
1731
  "JOIN_REJECTED",
1676
1732
  reason || "Your request to join was rejected",
1677
- { requestId, reason },
1733
+ {
1734
+ requestId,
1735
+ reason
1736
+ },
1678
1737
  false
1679
1738
  );
1680
1739
  },
@@ -1682,16 +1741,21 @@ var VideoSDKCore = class {
1682
1741
  const requestId = request?.request_id || request?.requestId;
1683
1742
  const userId = request?.user_id || request?.userId;
1684
1743
  const nameValue = request?.user_metadata?.name || request?.display_name || request?.name || "Participant";
1685
- if (!requestId || !userId) return;
1744
+ if (!requestId || !userId) {
1745
+ return;
1746
+ }
1686
1747
  this.events.onEntryRequested?.({
1687
1748
  requestId,
1688
1749
  userId,
1689
1750
  name: nameValue
1690
1751
  });
1691
1752
  },
1753
+ // PARTICIPANTS
1692
1754
  onParticipantJoined: (participant) => {
1693
1755
  const pid = participant.participant_id;
1694
- if (!pid || pid === this.myId) return;
1756
+ if (!pid || pid === this.myId) {
1757
+ return;
1758
+ }
1695
1759
  const pname = participant.user_metadata?.name || participant.display_name || "Participant";
1696
1760
  this.state.addParticipant({
1697
1761
  id: pid,
@@ -1710,9 +1774,13 @@ var VideoSDKCore = class {
1710
1774
  });
1711
1775
  },
1712
1776
  onParticipantPresence: (participantId, presence) => {
1713
- if (participantId === this.myId) return;
1777
+ if (participantId === this.myId) {
1778
+ return;
1779
+ }
1714
1780
  const current = this.state.getParticipant(participantId);
1715
- if (!current) return;
1781
+ if (!current) {
1782
+ return;
1783
+ }
1716
1784
  const connectionState = presence === "reconnecting" ? "reconnecting" : presence === "connected" || presence === "online" ? "connected" : presence === "disconnected" ? "disconnected" : current.connectionState;
1717
1785
  this.state.updateParticipantMedia(participantId, {
1718
1786
  stream: current.media?.stream ?? null
@@ -1728,19 +1796,24 @@ var VideoSDKCore = class {
1728
1796
  },
1729
1797
  onParticipantLeft: (participant) => {
1730
1798
  const pid = typeof participant === "string" ? participant : participant.participant_id;
1731
- if (!pid) return;
1799
+ if (!pid) {
1800
+ return;
1801
+ }
1732
1802
  console.log("[SFU] Participant permanently left", pid);
1733
1803
  this.cleanupRemoteParticipant(pid);
1734
1804
  this.state.removeParticipant(pid);
1735
1805
  this.events.onUserLeft?.(pid);
1736
1806
  },
1807
+ // TRACKS
1737
1808
  onTrackPublished: (track) => {
1738
1809
  console.log("[SFU] Track published", track);
1739
1810
  },
1740
1811
  onTrackUnpublished: (track) => {
1741
1812
  console.log("[SFU] Track unpublished", track);
1742
1813
  const pid = track.participant_id;
1743
- if (!pid) return;
1814
+ if (!pid) {
1815
+ return;
1816
+ }
1744
1817
  const current = this.state.getParticipant(pid);
1745
1818
  if (track.source === "screen") {
1746
1819
  const screen = this.remoteScreenStreams.get(pid);
@@ -1754,7 +1827,9 @@ var VideoSDKCore = class {
1754
1827
  this.events.onScreenShareStopped?.(pid);
1755
1828
  return;
1756
1829
  }
1757
- if (!current) return;
1830
+ if (!current) {
1831
+ return;
1832
+ }
1758
1833
  this.state.updateParticipantMedia(pid, {
1759
1834
  stream: current.media?.stream ?? null,
1760
1835
  micEnabled: track.kind === "audio" ? false : current.media?.micEnabled ?? true,
@@ -1763,9 +1838,13 @@ var VideoSDKCore = class {
1763
1838
  },
1764
1839
  onTrackStateChanged: (track) => {
1765
1840
  const pid = track.participant_id;
1766
- if (!pid || pid === this.myId) return;
1841
+ if (!pid || pid === this.myId) {
1842
+ return;
1843
+ }
1767
1844
  const current = this.state.getParticipant(pid);
1768
- if (!current) return;
1845
+ if (!current) {
1846
+ return;
1847
+ }
1769
1848
  if (track.source === "audio") {
1770
1849
  this.state.updateParticipantMedia(pid, {
1771
1850
  stream: current.media?.stream ?? null,
@@ -1781,17 +1860,22 @@ var VideoSDKCore = class {
1781
1860
  }
1782
1861
  this.events.onTrackStateChanged?.(track);
1783
1862
  },
1863
+ // RECORDING
1784
1864
  onRecordingStarted: (recording) => {
1785
1865
  this.events.onRecordingStarted?.(recording);
1786
1866
  },
1787
1867
  onRecordingStopped: (recording) => {
1788
1868
  this.events.onRecordingStopped?.(recording);
1789
1869
  },
1870
+ // REMOTE MEDIA
1790
1871
  onRemoteTrack: (track, stream, metadata) => {
1791
1872
  this.handleRemoteTrack(track, stream, metadata);
1792
1873
  },
1874
+ // SCREEN SHARE
1793
1875
  onScreenShareStarted: (participantId, track) => {
1794
- if (participantId === this.myId) return;
1876
+ if (participantId === this.myId) {
1877
+ return;
1878
+ }
1795
1879
  console.log("[SFU] Screen share started", participantId, track);
1796
1880
  const current = this.state.getParticipant(participantId);
1797
1881
  if (current) {
@@ -1802,7 +1886,9 @@ var VideoSDKCore = class {
1802
1886
  }
1803
1887
  },
1804
1888
  onScreenShareStopped: (participantId) => {
1805
- if (participantId === this.myId) return;
1889
+ if (participantId === this.myId) {
1890
+ return;
1891
+ }
1806
1892
  const screen = this.remoteScreenStreams.get(participantId);
1807
1893
  screen?.getTracks().forEach((track) => track.stop());
1808
1894
  this.remoteScreenStreams.delete(participantId);
@@ -1813,6 +1899,7 @@ var VideoSDKCore = class {
1813
1899
  });
1814
1900
  this.events.onScreenShareStopped?.(participantId);
1815
1901
  },
1902
+ // ERROR
1816
1903
  onError: (error) => {
1817
1904
  console.error("[SFU] Error:", error);
1818
1905
  this.emitError(
@@ -1825,11 +1912,12 @@ var VideoSDKCore = class {
1825
1912
  });
1826
1913
  this.sfuClient = client;
1827
1914
  client.setLocalStream(this.localStream);
1828
- return client.getToken(roomId, {
1915
+ await client.getToken(roomId, {
1829
1916
  displayName: name,
1830
1917
  userId: this.myId
1831
1918
  });
1832
1919
  }
1920
+ // SFU STATE
1833
1921
  handleSFUState(state) {
1834
1922
  this.events.onConnectionStateChanged?.(state);
1835
1923
  if (state === "reconnecting") {
@@ -1848,9 +1936,12 @@ var VideoSDKCore = class {
1848
1936
  );
1849
1937
  }
1850
1938
  }
1939
+ // REMOTE TRACKS
1851
1940
  handleRemoteTrack(track, stream, metadata) {
1852
1941
  const participantId = metadata?.participant_id;
1853
- if (!participantId || participantId === this.myId) return;
1942
+ if (!participantId || participantId === this.myId) {
1943
+ return;
1944
+ }
1854
1945
  const source = metadata.source;
1855
1946
  if (source === "screen") {
1856
1947
  let screenStream = this.remoteScreenStreams.get(participantId);
@@ -1901,36 +1992,41 @@ var VideoSDKCore = class {
1901
1992
  this.events.onTrack?.(remoteStream, participantId);
1902
1993
  track.onended = () => {
1903
1994
  if (track.kind === "video") {
1904
- const participant2 = this.state.getParticipant(participantId);
1905
1995
  this.state.updateParticipantMedia(participantId, {
1906
1996
  camEnabled: false,
1907
1997
  stream: remoteStream
1908
1998
  });
1909
1999
  return;
1910
2000
  }
1911
- const participant = this.state.getParticipant(participantId);
1912
2001
  this.state.updateParticipantMedia(participantId, {
1913
2002
  micEnabled: false,
1914
2003
  stream: remoteStream
1915
2004
  });
1916
2005
  };
1917
2006
  }
2007
+ // MEETING
1918
2008
  getMeeting() {
1919
2009
  return this.room;
1920
2010
  }
1921
- // ============ ADMISSION CONTROL ============
2011
+ // ADMISSION CONTROL
1922
2012
  async approveJoinRequest(requestId) {
1923
- if (!this.sfuClient) throw new Error("SFU client is not connected");
2013
+ if (!this.sfuClient) {
2014
+ throw new Error("SFU client is not connected");
2015
+ }
1924
2016
  return this.sfuClient.approveJoinRequest(requestId);
1925
2017
  }
1926
2018
  async rejectJoinRequest(requestId, reason) {
1927
- if (!this.sfuClient) throw new Error("SFU client is not connected");
2019
+ if (!this.sfuClient) {
2020
+ throw new Error("SFU client is not connected");
2021
+ }
1928
2022
  return this.sfuClient.rejectJoinRequest(requestId, reason);
1929
2023
  }
1930
- // ============ MEDIA CONTROL ============
2024
+ // MEDIA CONTROL
1931
2025
  toggleMic() {
1932
2026
  const mediaState = this.state.localParticipant?.media;
1933
- if (!mediaState || !this.localStream) return;
2027
+ if (!mediaState || !this.localStream) {
2028
+ return;
2029
+ }
1934
2030
  const nextEnabled = !mediaState.micEnabled;
1935
2031
  this.localStream.getAudioTracks().forEach((track) => {
1936
2032
  track.enabled = nextEnabled;
@@ -1938,14 +2034,18 @@ var VideoSDKCore = class {
1938
2034
  this.state.updateLocalParticipant({
1939
2035
  id: this.myId,
1940
2036
  name: this.participantName,
1941
- media: { micEnabled: nextEnabled }
2037
+ media: {
2038
+ micEnabled: nextEnabled
2039
+ }
1942
2040
  });
1943
2041
  this.sfuClient?.muteAudio(!nextEnabled);
1944
2042
  this.events.onMicToggled?.(this.myId, nextEnabled);
1945
2043
  }
1946
2044
  toggleCam() {
1947
2045
  const mediaState = this.state.localParticipant?.media;
1948
- if (!mediaState || !this.localStream) return;
2046
+ if (!mediaState || !this.localStream) {
2047
+ return;
2048
+ }
1949
2049
  const nextEnabled = !mediaState.camEnabled;
1950
2050
  this.localStream.getVideoTracks().forEach((track) => {
1951
2051
  track.enabled = nextEnabled;
@@ -1953,12 +2053,14 @@ var VideoSDKCore = class {
1953
2053
  this.state.updateLocalParticipant({
1954
2054
  id: this.myId,
1955
2055
  name: this.participantName,
1956
- media: { camEnabled: nextEnabled }
2056
+ media: {
2057
+ camEnabled: nextEnabled
2058
+ }
1957
2059
  });
1958
2060
  this.sfuClient?.muteVideo(!nextEnabled);
1959
2061
  this.events.onCamToggled?.(this.myId, nextEnabled);
1960
2062
  }
1961
- // ============ SCREEN SHARING ============
2063
+ // SCREEN SHARING
1962
2064
  async startScreenShare() {
1963
2065
  try {
1964
2066
  if (!navigator.mediaDevices?.getDisplayMedia) {
@@ -1990,7 +2092,9 @@ var VideoSDKCore = class {
1990
2092
  }
1991
2093
  }
1992
2094
  stopScreenShare() {
1993
- if (!this.screenStream) return;
2095
+ if (!this.screenStream) {
2096
+ return;
2097
+ }
1994
2098
  void this.sfuClient?.stopScreenShare();
1995
2099
  this.screenStream.getTracks().forEach((track) => track.stop());
1996
2100
  this.screenStream = null;
@@ -2003,7 +2107,7 @@ var VideoSDKCore = class {
2003
2107
  }
2004
2108
  });
2005
2109
  }
2006
- // ============ RECORDING ============
2110
+ // RECORDING
2007
2111
  async startRecording() {
2008
2112
  if (!this.sfuClient) {
2009
2113
  throw new Error("SFU client is not connected");
@@ -2022,7 +2126,7 @@ var VideoSDKCore = class {
2022
2126
  }
2023
2127
  return this.sfuClient.getRecordingStatus();
2024
2128
  }
2025
- // ============ CHAT ============
2129
+ // CHAT
2026
2130
  sendChatMessage(payload) {
2027
2131
  if (!this.sfuClient) {
2028
2132
  console.warn("SFU Client not connected");
@@ -2041,7 +2145,7 @@ var VideoSDKCore = class {
2041
2145
  this.state.addChatMessage(msg);
2042
2146
  this.events.onChatMessage?.(msg);
2043
2147
  }
2044
- // ============ CLEANUP ============
2148
+ // CLEANUP
2045
2149
  cleanupRemoteParticipant(participantId) {
2046
2150
  this.activeSpeakerDetector.removeParticipant(participantId);
2047
2151
  const stream = this.remoteStreams.get(participantId);
@@ -2051,16 +2155,34 @@ var VideoSDKCore = class {
2051
2155
  screen?.getTracks().forEach((track) => track.stop());
2052
2156
  this.remoteScreenStreams.delete(participantId);
2053
2157
  }
2158
+ // DISCONNECT
2054
2159
  async disconnect() {
2160
+ if (this.disconnectPromise) {
2161
+ return this.disconnectPromise;
2162
+ }
2163
+ this.disconnectPromise = this.performDisconnect();
2164
+ try {
2165
+ await this.disconnectPromise;
2166
+ } finally {
2167
+ this.disconnectPromise = null;
2168
+ }
2169
+ }
2170
+ async performDisconnect() {
2055
2171
  this.intentionalDisconnect = true;
2056
- await this.sfuClient?.leave();
2172
+ this.joinPromise = null;
2173
+ const client = this.sfuClient;
2057
2174
  this.sfuClient = null;
2175
+ if (client) {
2176
+ await client.leave();
2177
+ }
2058
2178
  if (this.localStream) {
2059
2179
  this.localStream.getTracks().forEach((track) => track.stop());
2060
2180
  this.localStream = null;
2061
2181
  }
2062
- this.screenStream?.getTracks().forEach((track) => track.stop());
2063
- this.screenStream = null;
2182
+ if (this.screenStream) {
2183
+ this.screenStream.getTracks().forEach((track) => track.stop());
2184
+ this.screenStream = null;
2185
+ }
2064
2186
  this.isScreenSharing = false;
2065
2187
  for (const stream of this.remoteStreams.values()) {
2066
2188
  stream.getTracks().forEach((track) => track.stop());