@defcomm/meetingsdk 0.2.2 → 0.2.3

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
@@ -280,6 +280,8 @@ interface SFUClientOptions {
280
280
  recording_id: string;
281
281
  stopped_at: string;
282
282
  }) => void;
283
+ /** Authoritative room snapshot received after join/reconnect/resync. */
284
+ onRoomState?: (state: any) => void;
283
285
  onError?: (error: any) => void;
284
286
  autoSubscribe?: boolean;
285
287
  }
@@ -333,6 +335,8 @@ declare class SFUClient {
333
335
  private trackToSource;
334
336
  private localTrackIds;
335
337
  private subscriberStatsTimer;
338
+ private roomSyncTimer;
339
+ private roomSyncInFlight;
336
340
  private onStateChange;
337
341
  private onParticipantJoined;
338
342
  private onParticipantLeft;
@@ -352,12 +356,16 @@ declare class SFUClient {
352
356
  private onTrackStateChanged;
353
357
  private onRecordingStarted;
354
358
  private onRecordingStopped;
359
+ private onRoomState;
355
360
  private onError;
356
361
  private autoSubscribe;
357
362
  constructor(options?: SFUClientOptions);
358
363
  private heartbeatTimer;
359
364
  private startHeartbeat;
360
365
  private stopHeartbeat;
366
+ private startRoomStateSync;
367
+ private stopRoomStateSync;
368
+ private syncRoomState;
361
369
  private createId;
362
370
  private resumeStorageKey;
363
371
  private loadResumeToken;
@@ -392,6 +400,7 @@ declare class SFUClient {
392
400
  private failJoin;
393
401
  private resolveJoin;
394
402
  private handleServerMessage;
403
+ private applyRoomState;
395
404
  private rememberTrack;
396
405
  private handleJoined;
397
406
  private initializePeerConnections;
@@ -469,6 +478,7 @@ type MeetingContextValue = {
469
478
  onEntryRequested: (cb: (req: any) => void) => () => void;
470
479
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
471
480
  onMeetingLeft: (cb: () => void) => () => void;
481
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
472
482
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
473
483
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
474
484
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
@@ -527,6 +537,7 @@ declare const useMeeting: (handlers?: {
527
537
  onEntryRequested: (cb: (req: any) => void) => () => void;
528
538
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
529
539
  onMeetingLeft: (cb: () => void) => () => void;
540
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
530
541
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
531
542
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
532
543
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
package/dist/index.d.ts CHANGED
@@ -280,6 +280,8 @@ interface SFUClientOptions {
280
280
  recording_id: string;
281
281
  stopped_at: string;
282
282
  }) => void;
283
+ /** Authoritative room snapshot received after join/reconnect/resync. */
284
+ onRoomState?: (state: any) => void;
283
285
  onError?: (error: any) => void;
284
286
  autoSubscribe?: boolean;
285
287
  }
@@ -333,6 +335,8 @@ declare class SFUClient {
333
335
  private trackToSource;
334
336
  private localTrackIds;
335
337
  private subscriberStatsTimer;
338
+ private roomSyncTimer;
339
+ private roomSyncInFlight;
336
340
  private onStateChange;
337
341
  private onParticipantJoined;
338
342
  private onParticipantLeft;
@@ -352,12 +356,16 @@ declare class SFUClient {
352
356
  private onTrackStateChanged;
353
357
  private onRecordingStarted;
354
358
  private onRecordingStopped;
359
+ private onRoomState;
355
360
  private onError;
356
361
  private autoSubscribe;
357
362
  constructor(options?: SFUClientOptions);
358
363
  private heartbeatTimer;
359
364
  private startHeartbeat;
360
365
  private stopHeartbeat;
366
+ private startRoomStateSync;
367
+ private stopRoomStateSync;
368
+ private syncRoomState;
361
369
  private createId;
362
370
  private resumeStorageKey;
363
371
  private loadResumeToken;
@@ -392,6 +400,7 @@ declare class SFUClient {
392
400
  private failJoin;
393
401
  private resolveJoin;
394
402
  private handleServerMessage;
403
+ private applyRoomState;
395
404
  private rememberTrack;
396
405
  private handleJoined;
397
406
  private initializePeerConnections;
@@ -469,6 +478,7 @@ type MeetingContextValue = {
469
478
  onEntryRequested: (cb: (req: any) => void) => () => void;
470
479
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
471
480
  onMeetingLeft: (cb: () => void) => () => void;
481
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
472
482
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
473
483
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
474
484
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
@@ -527,6 +537,7 @@ declare const useMeeting: (handlers?: {
527
537
  onEntryRequested: (cb: (req: any) => void) => () => void;
528
538
  onEntryResponded: (cb: (payload: any, decision?: any) => void) => () => void;
529
539
  onMeetingLeft: (cb: () => void) => () => void;
540
+ onRoomUpdate: (cb: (data: any) => void) => () => void;
530
541
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
531
542
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
532
543
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
package/dist/index.js CHANGED
@@ -428,6 +428,8 @@ var SFUClient = class {
428
428
  this.trackToSource = /* @__PURE__ */ new Map();
429
429
  this.localTrackIds = /* @__PURE__ */ new Map();
430
430
  this.subscriberStatsTimer = null;
431
+ this.roomSyncTimer = null;
432
+ this.roomSyncInFlight = false;
431
433
  this.heartbeatTimer = null;
432
434
  this.apiBase = options.apiBase || "https://api.example.com/api";
433
435
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
@@ -470,6 +472,8 @@ var SFUClient = class {
470
472
  });
471
473
  this.onRecordingStopped = options.onRecordingStopped || (() => {
472
474
  });
475
+ this.onRoomState = options.onRoomState || (() => {
476
+ });
473
477
  this.onError = options.onError || console.error;
474
478
  this.autoSubscribe = options.autoSubscribe !== false;
475
479
  }
@@ -491,6 +495,29 @@ var SFUClient = class {
491
495
  this.heartbeatTimer = null;
492
496
  }
493
497
  }
498
+ startRoomStateSync() {
499
+ this.stopRoomStateSync();
500
+ this.roomSyncTimer = setInterval(() => {
501
+ if (this.state !== "connected") return;
502
+ void this.syncRoomState();
503
+ }, 5e3);
504
+ }
505
+ stopRoomStateSync() {
506
+ if (this.roomSyncTimer) {
507
+ clearInterval(this.roomSyncTimer);
508
+ this.roomSyncTimer = null;
509
+ }
510
+ this.roomSyncInFlight = false;
511
+ }
512
+ async syncRoomState() {
513
+ if (this.roomSyncInFlight || !this.isSocketOpen()) return;
514
+ this.roomSyncInFlight = true;
515
+ try {
516
+ this.send({ type: "sync_room_state" });
517
+ } finally {
518
+ this.roomSyncInFlight = false;
519
+ }
520
+ }
494
521
  createId() {
495
522
  return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `id_${Math.random().toString(36).slice(2)}_${Date.now()}`;
496
523
  }
@@ -913,6 +940,10 @@ var SFUClient = class {
913
940
  case "subscription_updated": {
914
941
  break;
915
942
  }
943
+ case "room_state": {
944
+ await this.applyRoomState(payload);
945
+ break;
946
+ }
916
947
  case "recording_started": {
917
948
  this.onRecordingStarted({
918
949
  recording_id: payload.recording_id,
@@ -978,6 +1009,72 @@ var SFUClient = class {
978
1009
  console.debug("[SFU] Ignoring unknown server message", message);
979
1010
  }
980
1011
  }
1012
+ async applyRoomState(payload) {
1013
+ const nextParticipants = /* @__PURE__ */ new Map();
1014
+ for (const participant of payload.participants || []) {
1015
+ const id = participant?.participant_id;
1016
+ if (!id) continue;
1017
+ nextParticipants.set(id, participant);
1018
+ }
1019
+ for (const [id] of this.participants) {
1020
+ if (id === this.currentParticipantId) continue;
1021
+ if (!nextParticipants.has(id)) {
1022
+ this.participants.delete(id);
1023
+ this.removeRemoteParticipant(id);
1024
+ this.onParticipantLeft(id);
1025
+ }
1026
+ }
1027
+ for (const [id, participant] of nextParticipants) {
1028
+ if (id === this.currentParticipantId) continue;
1029
+ const existed = this.participants.has(id);
1030
+ this.participants.set(id, participant);
1031
+ if (!existed) {
1032
+ this.onParticipantJoined(participant);
1033
+ }
1034
+ const sharing = Boolean(participant.screen_sharing);
1035
+ const screenTrackId = participant.screen_track_id ? String(participant.screen_track_id) : void 0;
1036
+ if (sharing) {
1037
+ const track = screenTrackId ? (payload.tracks || []).find(
1038
+ (candidate) => String(candidate.track_id) === screenTrackId
1039
+ ) : void 0;
1040
+ this.onScreenShareStarted(id, track);
1041
+ } else if (this.remoteScreenStreams.has(id)) {
1042
+ this.remoteScreenStreams.get(id)?.getTracks().forEach((track) => track.stop());
1043
+ this.remoteScreenStreams.delete(id);
1044
+ this.onScreenShareStopped(id, screenTrackId);
1045
+ }
1046
+ }
1047
+ const nextTracks = /* @__PURE__ */ new Map();
1048
+ for (const track of payload.tracks || []) {
1049
+ if (track?.track_id) nextTracks.set(String(track.track_id), track);
1050
+ }
1051
+ for (const [trackId, existing] of this.trackMetadata) {
1052
+ if (!nextTracks.has(trackId)) {
1053
+ this.trackMetadata.delete(trackId);
1054
+ this.trackToParticipant.delete(trackId);
1055
+ this.trackToSource.delete(trackId);
1056
+ this.subscribedTracks.delete(trackId);
1057
+ for (const [source, localId] of this.localTrackIds) {
1058
+ if (localId === trackId) this.localTrackIds.delete(source);
1059
+ }
1060
+ if (existing.participant_id !== this.currentParticipantId) {
1061
+ this.onTrackUnpublished(existing);
1062
+ }
1063
+ }
1064
+ }
1065
+ for (const track of nextTracks.values()) {
1066
+ this.rememberTrack(track);
1067
+ }
1068
+ if (this.autoSubscribe) {
1069
+ const missing = [...nextTracks.values()].filter(
1070
+ (track) => track.participant_id !== this.currentParticipantId && !this.subscribedTracks.has(track.track_id)
1071
+ ).map((track) => track.track_id);
1072
+ if (missing.length) {
1073
+ await this.subscribeMultiple(missing);
1074
+ }
1075
+ }
1076
+ this.onRoomState(payload);
1077
+ }
981
1078
  rememberTrack(track) {
982
1079
  this.trackMetadata.set(track.track_id, track);
983
1080
  this.trackToParticipant.set(track.track_id, track.participant_id);
@@ -1007,8 +1104,15 @@ var SFUClient = class {
1007
1104
  for (const track of payload.tracks || []) {
1008
1105
  if (track?.track_id) this.rememberTrack(track);
1009
1106
  }
1107
+ if (payload.recording) {
1108
+ this.onRecordingStarted({
1109
+ recording_id: payload.recording.recording_id,
1110
+ started_at: payload.recording.started_at
1111
+ });
1112
+ }
1010
1113
  await this.initializePeerConnections();
1011
1114
  this.setState("connected");
1115
+ this.startRoomStateSync();
1012
1116
  this.onConnected(payload, resumed);
1013
1117
  for (const participant of this.participants.values()) {
1014
1118
  if (participant.participant_id !== this.currentParticipantId) {
@@ -1563,6 +1667,7 @@ var SFUClient = class {
1563
1667
  }
1564
1668
  async leave() {
1565
1669
  this.stopHeartbeat();
1670
+ this.stopRoomStateSync();
1566
1671
  this.intentionalClose = true;
1567
1672
  this.setState("leaving");
1568
1673
  if (this.reconnectTimer) {
@@ -1950,6 +2055,30 @@ var VideoSDKCore = class {
1950
2055
  name: this.participantName
1951
2056
  });
1952
2057
  },
2058
+ onRoomState: (roomState) => {
2059
+ for (const participant of roomState?.participants || []) {
2060
+ const pid = participant?.participant_id;
2061
+ if (!pid || pid === this.myId) continue;
2062
+ const current = this.state.getParticipant(pid);
2063
+ if (!current) continue;
2064
+ if (participant.screen_sharing) {
2065
+ this.state.updateParticipantMedia(pid, {
2066
+ isScreenSharing: true,
2067
+ screenStream: current.media?.screenStream ?? null
2068
+ });
2069
+ } else if (current.media?.isScreenSharing) {
2070
+ const screen = this.remoteScreenStreams.get(pid);
2071
+ screen?.getTracks().forEach((track) => track.stop());
2072
+ this.remoteScreenStreams.delete(pid);
2073
+ this.state.updateParticipantMedia(pid, {
2074
+ isScreenSharing: false,
2075
+ screenStream: null,
2076
+ screenTrack: void 0
2077
+ });
2078
+ }
2079
+ }
2080
+ this.events.onRoomUpdate?.(roomState);
2081
+ },
1953
2082
  onDisconnected: () => {
1954
2083
  console.log("[SFU] Signaling WebSocket disconnected; reconnecting");
1955
2084
  },
@@ -2509,6 +2638,7 @@ var MeetingProvider = ({
2509
2638
  /* @__PURE__ */ new Set()
2510
2639
  );
2511
2640
  const meetingLeftListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2641
+ const roomUpdateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2512
2642
  const trackStateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2513
2643
  const recordingStartedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2514
2644
  const recordingStoppedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
@@ -2533,6 +2663,9 @@ var MeetingProvider = ({
2533
2663
  onMeetingLeft: () => {
2534
2664
  meetingLeftListeners.current.forEach((fn) => fn());
2535
2665
  },
2666
+ onRoomUpdate: (data) => {
2667
+ roomUpdateListeners.current.forEach((fn) => fn(data));
2668
+ },
2536
2669
  onTrackStateChanged: (track) => {
2537
2670
  trackStateListeners.current.forEach((fn) => fn(track));
2538
2671
  },
@@ -2631,6 +2764,10 @@ var MeetingProvider = ({
2631
2764
  meetingLeftListeners.current.delete(cb);
2632
2765
  };
2633
2766
  },
2767
+ onRoomUpdate: (cb) => {
2768
+ roomUpdateListeners.current.add(cb);
2769
+ return () => roomUpdateListeners.current.delete(cb);
2770
+ },
2634
2771
  onTrackStateChanged: (cb) => {
2635
2772
  trackStateListeners.current.add(cb);
2636
2773
  return () => trackStateListeners.current.delete(cb);