@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.d.mts CHANGED
@@ -172,6 +172,8 @@ declare class VideoSDKCore {
172
172
  private remoteStreams;
173
173
  private remoteScreenStreams;
174
174
  private activeSpeakerDetector;
175
+ private joinPromise;
176
+ private disconnectPromise;
175
177
  private emitError;
176
178
  constructor(events?: Events, apiBase?: string, wsBase?: string);
177
179
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
@@ -206,6 +208,7 @@ declare class VideoSDKCore {
206
208
  sendChatMessage(payload: ChatInput): void;
207
209
  private cleanupRemoteParticipant;
208
210
  disconnect(): Promise<void>;
211
+ private performDisconnect;
209
212
  }
210
213
 
211
214
  /**
package/dist/index.d.ts CHANGED
@@ -172,6 +172,8 @@ declare class VideoSDKCore {
172
172
  private remoteStreams;
173
173
  private remoteScreenStreams;
174
174
  private activeSpeakerDetector;
175
+ private joinPromise;
176
+ private disconnectPromise;
175
177
  private emitError;
176
178
  constructor(events?: Events, apiBase?: string, wsBase?: string);
177
179
  initLocal(video: HTMLVideoElement, name: string): Promise<void>;
@@ -206,6 +208,7 @@ declare class VideoSDKCore {
206
208
  sendChatMessage(payload: ChatInput): void;
207
209
  private cleanupRemoteParticipant;
208
210
  disconnect(): Promise<void>;
211
+ private performDisconnect;
209
212
  }
210
213
 
211
214
  /**
package/dist/index.js CHANGED
@@ -1500,7 +1500,7 @@ var SFUClient = class {
1500
1500
 
1501
1501
  // src/core/VideoCore.ts
1502
1502
  var VideoSDKCore = class {
1503
- constructor(events = {}, apiBase = SDK_CONFIG.apiBase || "https://api.example.com/api", wsBase = SDK_CONFIG.wsBase || "wss://api.example.com/ws") {
1503
+ constructor(events = {}, apiBase = SDK_CONFIG.apiBase, wsBase = SDK_CONFIG.wsBase) {
1504
1504
  this.events = events;
1505
1505
  this.apiBase = apiBase;
1506
1506
  this.wsBase = wsBase;
@@ -1516,6 +1516,8 @@ var VideoSDKCore = class {
1516
1516
  this.intentionalDisconnect = false;
1517
1517
  this.remoteStreams = /* @__PURE__ */ new Map();
1518
1518
  this.remoteScreenStreams = /* @__PURE__ */ new Map();
1519
+ this.joinPromise = null;
1520
+ this.disconnectPromise = null;
1519
1521
  this.state = new MeetingState();
1520
1522
  this.events = events;
1521
1523
  this.activeSpeakerDetector = new ActiveSpeakerDetector({
@@ -1549,7 +1551,7 @@ var VideoSDKCore = class {
1549
1551
  this.events.onError?.(err);
1550
1552
  console.error("[MeetingSDK Error]", err);
1551
1553
  }
1552
- // ============ LOCAL MEDIA ============
1554
+ // LOCAL MEDIA
1553
1555
  async initLocal(video, name) {
1554
1556
  this.participantName = name;
1555
1557
  try {
@@ -1598,7 +1600,7 @@ var VideoSDKCore = class {
1598
1600
  });
1599
1601
  this.state.localStream = this.localStream;
1600
1602
  }
1601
- // ============ CONNECTION / SIGNALLING ============
1603
+ // ROOM MANAGEMENT
1602
1604
  async createRoom(input) {
1603
1605
  const client = this.sfuClient ?? new SFUClient({
1604
1606
  apiBase: this.apiBase,
@@ -1623,6 +1625,7 @@ var VideoSDKCore = class {
1623
1625
  });
1624
1626
  return client.deleteRoom(roomCode);
1625
1627
  }
1628
+ // JOIN
1626
1629
  async joinMeeting(config) {
1627
1630
  const {
1628
1631
  roomId,
@@ -1637,6 +1640,28 @@ var VideoSDKCore = class {
1637
1640
  if (userId) {
1638
1641
  this.myId = userId;
1639
1642
  }
1643
+ if (this.sfuClient && this.room.id === roomId && this.sfuClient.getConnectionState() === "connected") {
1644
+ console.info("[SDK] Already connected; duplicate join ignored", {
1645
+ roomId,
1646
+ userId: this.myId
1647
+ });
1648
+ return;
1649
+ }
1650
+ if (this.joinPromise) {
1651
+ console.info(
1652
+ "[SDK] Join already in progress; waiting for existing join",
1653
+ {
1654
+ roomId,
1655
+ userId: this.myId
1656
+ }
1657
+ );
1658
+ return this.joinPromise;
1659
+ }
1660
+ if (this.sfuClient && this.room.id && this.room.id !== roomId) {
1661
+ throw new Error(
1662
+ `Already connected to room ${this.room.id}. Leave the current meeting before joining another room.`
1663
+ );
1664
+ }
1640
1665
  this.intentionalDisconnect = false;
1641
1666
  this.participantName = name;
1642
1667
  this.room.id = roomId;
@@ -1658,19 +1683,43 @@ var VideoSDKCore = class {
1658
1683
  track.enabled = !videoMuted;
1659
1684
  });
1660
1685
  this.updateLocalMedia(audioMuted, videoMuted);
1661
- return this.connect(roomId, name);
1686
+ const promise = this.connect(roomId, name);
1687
+ this.joinPromise = promise;
1688
+ try {
1689
+ await promise;
1690
+ } finally {
1691
+ if (this.joinPromise === promise) {
1692
+ this.joinPromise = null;
1693
+ }
1694
+ }
1662
1695
  }
1696
+ // SFU CONNECTION
1663
1697
  async connect(roomId, name) {
1664
1698
  if (this.sfuClient) {
1665
- await this.sfuClient.leave();
1666
- this.sfuClient = null;
1699
+ const state = this.sfuClient.getConnectionState();
1700
+ if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
1701
+ console.info(
1702
+ "[SDK] Existing SFU connection is active; connect ignored",
1703
+ {
1704
+ roomId,
1705
+ state
1706
+ }
1707
+ );
1708
+ return;
1709
+ }
1710
+ throw new Error(
1711
+ `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
1712
+ );
1667
1713
  }
1668
1714
  const client = new SFUClient({
1669
1715
  apiBase: this.apiBase,
1670
1716
  wsBase: this.wsBase,
1671
1717
  userId: this.myId,
1672
1718
  autoSubscribe: true,
1673
- onStateChange: (state) => this.handleSFUState(state),
1719
+ // CONNECTION STATE
1720
+ onStateChange: (state) => {
1721
+ this.handleSFUState(state);
1722
+ },
1674
1723
  onConnected: (payload, resumed) => {
1675
1724
  console.log(`[SFU] ${resumed ? "Resumed" : "Joined"} room`, payload);
1676
1725
  this.state.updateLocalParticipant({
@@ -1684,6 +1733,7 @@ var VideoSDKCore = class {
1684
1733
  onReconnecting: (attempt) => {
1685
1734
  console.warn(`[SFU] Reconnecting, attempt ${attempt}`);
1686
1735
  },
1736
+ // ADMISSION CONTROL
1687
1737
  onJoinPending: (info) => {
1688
1738
  console.log("[SFU] Join request pending", info);
1689
1739
  this.events.onEntryRequested?.({
@@ -1696,7 +1746,10 @@ var VideoSDKCore = class {
1696
1746
  console.log("[SFU] Join approved", requestId);
1697
1747
  this.events.onJoinApproved?.(requestId);
1698
1748
  this.events.onEntryResponded?.(
1699
- { participantId: this.myId, decision: "approved" },
1749
+ {
1750
+ participantId: this.myId,
1751
+ decision: "approved"
1752
+ },
1700
1753
  "approved"
1701
1754
  );
1702
1755
  },
@@ -1704,13 +1757,19 @@ var VideoSDKCore = class {
1704
1757
  console.warn("[SFU] Join rejected", requestId, reason);
1705
1758
  this.events.onJoinRejected?.(requestId);
1706
1759
  this.events.onEntryResponded?.(
1707
- { participantId: this.myId, decision: "rejected" },
1760
+ {
1761
+ participantId: this.myId,
1762
+ decision: "rejected"
1763
+ },
1708
1764
  "rejected"
1709
1765
  );
1710
1766
  this.emitError(
1711
1767
  "JOIN_REJECTED",
1712
1768
  reason || "Your request to join was rejected",
1713
- { requestId, reason },
1769
+ {
1770
+ requestId,
1771
+ reason
1772
+ },
1714
1773
  false
1715
1774
  );
1716
1775
  },
@@ -1718,16 +1777,21 @@ var VideoSDKCore = class {
1718
1777
  const requestId = request?.request_id || request?.requestId;
1719
1778
  const userId = request?.user_id || request?.userId;
1720
1779
  const nameValue = request?.user_metadata?.name || request?.display_name || request?.name || "Participant";
1721
- if (!requestId || !userId) return;
1780
+ if (!requestId || !userId) {
1781
+ return;
1782
+ }
1722
1783
  this.events.onEntryRequested?.({
1723
1784
  requestId,
1724
1785
  userId,
1725
1786
  name: nameValue
1726
1787
  });
1727
1788
  },
1789
+ // PARTICIPANTS
1728
1790
  onParticipantJoined: (participant) => {
1729
1791
  const pid = participant.participant_id;
1730
- if (!pid || pid === this.myId) return;
1792
+ if (!pid || pid === this.myId) {
1793
+ return;
1794
+ }
1731
1795
  const pname = participant.user_metadata?.name || participant.display_name || "Participant";
1732
1796
  this.state.addParticipant({
1733
1797
  id: pid,
@@ -1746,9 +1810,13 @@ var VideoSDKCore = class {
1746
1810
  });
1747
1811
  },
1748
1812
  onParticipantPresence: (participantId, presence) => {
1749
- if (participantId === this.myId) return;
1813
+ if (participantId === this.myId) {
1814
+ return;
1815
+ }
1750
1816
  const current = this.state.getParticipant(participantId);
1751
- if (!current) return;
1817
+ if (!current) {
1818
+ return;
1819
+ }
1752
1820
  const connectionState = presence === "reconnecting" ? "reconnecting" : presence === "connected" || presence === "online" ? "connected" : presence === "disconnected" ? "disconnected" : current.connectionState;
1753
1821
  this.state.updateParticipantMedia(participantId, {
1754
1822
  stream: current.media?.stream ?? null
@@ -1764,19 +1832,24 @@ var VideoSDKCore = class {
1764
1832
  },
1765
1833
  onParticipantLeft: (participant) => {
1766
1834
  const pid = typeof participant === "string" ? participant : participant.participant_id;
1767
- if (!pid) return;
1835
+ if (!pid) {
1836
+ return;
1837
+ }
1768
1838
  console.log("[SFU] Participant permanently left", pid);
1769
1839
  this.cleanupRemoteParticipant(pid);
1770
1840
  this.state.removeParticipant(pid);
1771
1841
  this.events.onUserLeft?.(pid);
1772
1842
  },
1843
+ // TRACKS
1773
1844
  onTrackPublished: (track) => {
1774
1845
  console.log("[SFU] Track published", track);
1775
1846
  },
1776
1847
  onTrackUnpublished: (track) => {
1777
1848
  console.log("[SFU] Track unpublished", track);
1778
1849
  const pid = track.participant_id;
1779
- if (!pid) return;
1850
+ if (!pid) {
1851
+ return;
1852
+ }
1780
1853
  const current = this.state.getParticipant(pid);
1781
1854
  if (track.source === "screen") {
1782
1855
  const screen = this.remoteScreenStreams.get(pid);
@@ -1790,7 +1863,9 @@ var VideoSDKCore = class {
1790
1863
  this.events.onScreenShareStopped?.(pid);
1791
1864
  return;
1792
1865
  }
1793
- if (!current) return;
1866
+ if (!current) {
1867
+ return;
1868
+ }
1794
1869
  this.state.updateParticipantMedia(pid, {
1795
1870
  stream: current.media?.stream ?? null,
1796
1871
  micEnabled: track.kind === "audio" ? false : current.media?.micEnabled ?? true,
@@ -1799,9 +1874,13 @@ var VideoSDKCore = class {
1799
1874
  },
1800
1875
  onTrackStateChanged: (track) => {
1801
1876
  const pid = track.participant_id;
1802
- if (!pid || pid === this.myId) return;
1877
+ if (!pid || pid === this.myId) {
1878
+ return;
1879
+ }
1803
1880
  const current = this.state.getParticipant(pid);
1804
- if (!current) return;
1881
+ if (!current) {
1882
+ return;
1883
+ }
1805
1884
  if (track.source === "audio") {
1806
1885
  this.state.updateParticipantMedia(pid, {
1807
1886
  stream: current.media?.stream ?? null,
@@ -1817,17 +1896,22 @@ var VideoSDKCore = class {
1817
1896
  }
1818
1897
  this.events.onTrackStateChanged?.(track);
1819
1898
  },
1899
+ // RECORDING
1820
1900
  onRecordingStarted: (recording) => {
1821
1901
  this.events.onRecordingStarted?.(recording);
1822
1902
  },
1823
1903
  onRecordingStopped: (recording) => {
1824
1904
  this.events.onRecordingStopped?.(recording);
1825
1905
  },
1906
+ // REMOTE MEDIA
1826
1907
  onRemoteTrack: (track, stream, metadata) => {
1827
1908
  this.handleRemoteTrack(track, stream, metadata);
1828
1909
  },
1910
+ // SCREEN SHARE
1829
1911
  onScreenShareStarted: (participantId, track) => {
1830
- if (participantId === this.myId) return;
1912
+ if (participantId === this.myId) {
1913
+ return;
1914
+ }
1831
1915
  console.log("[SFU] Screen share started", participantId, track);
1832
1916
  const current = this.state.getParticipant(participantId);
1833
1917
  if (current) {
@@ -1838,7 +1922,9 @@ var VideoSDKCore = class {
1838
1922
  }
1839
1923
  },
1840
1924
  onScreenShareStopped: (participantId) => {
1841
- if (participantId === this.myId) return;
1925
+ if (participantId === this.myId) {
1926
+ return;
1927
+ }
1842
1928
  const screen = this.remoteScreenStreams.get(participantId);
1843
1929
  screen?.getTracks().forEach((track) => track.stop());
1844
1930
  this.remoteScreenStreams.delete(participantId);
@@ -1849,6 +1935,7 @@ var VideoSDKCore = class {
1849
1935
  });
1850
1936
  this.events.onScreenShareStopped?.(participantId);
1851
1937
  },
1938
+ // ERROR
1852
1939
  onError: (error) => {
1853
1940
  console.error("[SFU] Error:", error);
1854
1941
  this.emitError(
@@ -1861,11 +1948,12 @@ var VideoSDKCore = class {
1861
1948
  });
1862
1949
  this.sfuClient = client;
1863
1950
  client.setLocalStream(this.localStream);
1864
- return client.getToken(roomId, {
1951
+ await client.getToken(roomId, {
1865
1952
  displayName: name,
1866
1953
  userId: this.myId
1867
1954
  });
1868
1955
  }
1956
+ // SFU STATE
1869
1957
  handleSFUState(state) {
1870
1958
  this.events.onConnectionStateChanged?.(state);
1871
1959
  if (state === "reconnecting") {
@@ -1884,9 +1972,12 @@ var VideoSDKCore = class {
1884
1972
  );
1885
1973
  }
1886
1974
  }
1975
+ // REMOTE TRACKS
1887
1976
  handleRemoteTrack(track, stream, metadata) {
1888
1977
  const participantId = metadata?.participant_id;
1889
- if (!participantId || participantId === this.myId) return;
1978
+ if (!participantId || participantId === this.myId) {
1979
+ return;
1980
+ }
1890
1981
  const source = metadata.source;
1891
1982
  if (source === "screen") {
1892
1983
  let screenStream = this.remoteScreenStreams.get(participantId);
@@ -1937,36 +2028,41 @@ var VideoSDKCore = class {
1937
2028
  this.events.onTrack?.(remoteStream, participantId);
1938
2029
  track.onended = () => {
1939
2030
  if (track.kind === "video") {
1940
- const participant2 = this.state.getParticipant(participantId);
1941
2031
  this.state.updateParticipantMedia(participantId, {
1942
2032
  camEnabled: false,
1943
2033
  stream: remoteStream
1944
2034
  });
1945
2035
  return;
1946
2036
  }
1947
- const participant = this.state.getParticipant(participantId);
1948
2037
  this.state.updateParticipantMedia(participantId, {
1949
2038
  micEnabled: false,
1950
2039
  stream: remoteStream
1951
2040
  });
1952
2041
  };
1953
2042
  }
2043
+ // MEETING
1954
2044
  getMeeting() {
1955
2045
  return this.room;
1956
2046
  }
1957
- // ============ ADMISSION CONTROL ============
2047
+ // ADMISSION CONTROL
1958
2048
  async approveJoinRequest(requestId) {
1959
- if (!this.sfuClient) throw new Error("SFU client is not connected");
2049
+ if (!this.sfuClient) {
2050
+ throw new Error("SFU client is not connected");
2051
+ }
1960
2052
  return this.sfuClient.approveJoinRequest(requestId);
1961
2053
  }
1962
2054
  async rejectJoinRequest(requestId, reason) {
1963
- if (!this.sfuClient) throw new Error("SFU client is not connected");
2055
+ if (!this.sfuClient) {
2056
+ throw new Error("SFU client is not connected");
2057
+ }
1964
2058
  return this.sfuClient.rejectJoinRequest(requestId, reason);
1965
2059
  }
1966
- // ============ MEDIA CONTROL ============
2060
+ // MEDIA CONTROL
1967
2061
  toggleMic() {
1968
2062
  const mediaState = this.state.localParticipant?.media;
1969
- if (!mediaState || !this.localStream) return;
2063
+ if (!mediaState || !this.localStream) {
2064
+ return;
2065
+ }
1970
2066
  const nextEnabled = !mediaState.micEnabled;
1971
2067
  this.localStream.getAudioTracks().forEach((track) => {
1972
2068
  track.enabled = nextEnabled;
@@ -1974,14 +2070,18 @@ var VideoSDKCore = class {
1974
2070
  this.state.updateLocalParticipant({
1975
2071
  id: this.myId,
1976
2072
  name: this.participantName,
1977
- media: { micEnabled: nextEnabled }
2073
+ media: {
2074
+ micEnabled: nextEnabled
2075
+ }
1978
2076
  });
1979
2077
  this.sfuClient?.muteAudio(!nextEnabled);
1980
2078
  this.events.onMicToggled?.(this.myId, nextEnabled);
1981
2079
  }
1982
2080
  toggleCam() {
1983
2081
  const mediaState = this.state.localParticipant?.media;
1984
- if (!mediaState || !this.localStream) return;
2082
+ if (!mediaState || !this.localStream) {
2083
+ return;
2084
+ }
1985
2085
  const nextEnabled = !mediaState.camEnabled;
1986
2086
  this.localStream.getVideoTracks().forEach((track) => {
1987
2087
  track.enabled = nextEnabled;
@@ -1989,12 +2089,14 @@ var VideoSDKCore = class {
1989
2089
  this.state.updateLocalParticipant({
1990
2090
  id: this.myId,
1991
2091
  name: this.participantName,
1992
- media: { camEnabled: nextEnabled }
2092
+ media: {
2093
+ camEnabled: nextEnabled
2094
+ }
1993
2095
  });
1994
2096
  this.sfuClient?.muteVideo(!nextEnabled);
1995
2097
  this.events.onCamToggled?.(this.myId, nextEnabled);
1996
2098
  }
1997
- // ============ SCREEN SHARING ============
2099
+ // SCREEN SHARING
1998
2100
  async startScreenShare() {
1999
2101
  try {
2000
2102
  if (!navigator.mediaDevices?.getDisplayMedia) {
@@ -2026,7 +2128,9 @@ var VideoSDKCore = class {
2026
2128
  }
2027
2129
  }
2028
2130
  stopScreenShare() {
2029
- if (!this.screenStream) return;
2131
+ if (!this.screenStream) {
2132
+ return;
2133
+ }
2030
2134
  void this.sfuClient?.stopScreenShare();
2031
2135
  this.screenStream.getTracks().forEach((track) => track.stop());
2032
2136
  this.screenStream = null;
@@ -2039,7 +2143,7 @@ var VideoSDKCore = class {
2039
2143
  }
2040
2144
  });
2041
2145
  }
2042
- // ============ RECORDING ============
2146
+ // RECORDING
2043
2147
  async startRecording() {
2044
2148
  if (!this.sfuClient) {
2045
2149
  throw new Error("SFU client is not connected");
@@ -2058,7 +2162,7 @@ var VideoSDKCore = class {
2058
2162
  }
2059
2163
  return this.sfuClient.getRecordingStatus();
2060
2164
  }
2061
- // ============ CHAT ============
2165
+ // CHAT
2062
2166
  sendChatMessage(payload) {
2063
2167
  if (!this.sfuClient) {
2064
2168
  console.warn("SFU Client not connected");
@@ -2077,7 +2181,7 @@ var VideoSDKCore = class {
2077
2181
  this.state.addChatMessage(msg);
2078
2182
  this.events.onChatMessage?.(msg);
2079
2183
  }
2080
- // ============ CLEANUP ============
2184
+ // CLEANUP
2081
2185
  cleanupRemoteParticipant(participantId) {
2082
2186
  this.activeSpeakerDetector.removeParticipant(participantId);
2083
2187
  const stream = this.remoteStreams.get(participantId);
@@ -2087,16 +2191,34 @@ var VideoSDKCore = class {
2087
2191
  screen?.getTracks().forEach((track) => track.stop());
2088
2192
  this.remoteScreenStreams.delete(participantId);
2089
2193
  }
2194
+ // DISCONNECT
2090
2195
  async disconnect() {
2196
+ if (this.disconnectPromise) {
2197
+ return this.disconnectPromise;
2198
+ }
2199
+ this.disconnectPromise = this.performDisconnect();
2200
+ try {
2201
+ await this.disconnectPromise;
2202
+ } finally {
2203
+ this.disconnectPromise = null;
2204
+ }
2205
+ }
2206
+ async performDisconnect() {
2091
2207
  this.intentionalDisconnect = true;
2092
- await this.sfuClient?.leave();
2208
+ this.joinPromise = null;
2209
+ const client = this.sfuClient;
2093
2210
  this.sfuClient = null;
2211
+ if (client) {
2212
+ await client.leave();
2213
+ }
2094
2214
  if (this.localStream) {
2095
2215
  this.localStream.getTracks().forEach((track) => track.stop());
2096
2216
  this.localStream = null;
2097
2217
  }
2098
- this.screenStream?.getTracks().forEach((track) => track.stop());
2099
- this.screenStream = null;
2218
+ if (this.screenStream) {
2219
+ this.screenStream.getTracks().forEach((track) => track.stop());
2220
+ this.screenStream = null;
2221
+ }
2100
2222
  this.isScreenSharing = false;
2101
2223
  for (const stream of this.remoteStreams.values()) {
2102
2224
  stream.getTracks().forEach((track) => track.stop());