@defcomm/meetingsdk 0.4.4 → 0.4.6

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
@@ -67,6 +67,26 @@ type PubSubMessage<T = unknown> = {
67
67
  timestamp: number;
68
68
  data: T;
69
69
  };
70
+ type PubSubTopicConfig = {
71
+ SECURE_CHAT: {
72
+ publish: ChatInput;
73
+ message: ChatMessage;
74
+ };
75
+ REACTION: {
76
+ publish: {
77
+ emoji: string;
78
+ };
79
+ message: {
80
+ emoji: string;
81
+ };
82
+ };
83
+ };
84
+ type PubSubTopic = keyof PubSubTopicConfig;
85
+ type PubSubHandle<TPublish = unknown, TMessage = TPublish> = {
86
+ messages: PubSubMessage<TMessage>[];
87
+ publish: (data: TPublish) => void;
88
+ };
89
+ type PubSubHandleForTopic<T extends PubSubTopic> = PubSubHandle<PubSubTopicConfig[T]["publish"], PubSubTopicConfig[T]["message"]>;
70
90
  type Participant = {
71
91
  id: string;
72
92
  name?: string;
@@ -103,12 +123,7 @@ type MeetingConfig = {
103
123
  name: string;
104
124
  audioMuted?: boolean;
105
125
  videoMuted?: boolean;
106
- token?: string;
107
- };
108
- type PubSubTopic = "SECURE_CHAT" | "REACTION";
109
- type PubSubHandle<T = unknown> = {
110
- messages: PubSubMessage<T>[];
111
- publish: (data: T) => void;
126
+ token: string;
112
127
  };
113
128
  type RecordingInfo = {
114
129
  recording_id: string;
@@ -145,6 +160,7 @@ interface Recording {
145
160
  }
146
161
  interface VideoSDKCoreOptions {
147
162
  participantId: string;
163
+ token: string;
148
164
  onError?: (error: any) => void;
149
165
  onEntryRequested?: (request: any) => void;
150
166
  onEntryResponded?: (payload: any, decision?: any) => void;
@@ -234,6 +250,7 @@ declare class VideoSDKCore {
234
250
  deleteRoom(roomCode: string): Promise<any>;
235
251
  joinMeeting(config: Omit<MeetingConfig, "participantId">): Promise<void>;
236
252
  private connect;
253
+ private createSFUClient;
237
254
  private handleSFUState;
238
255
  private handleRemoteTrack;
239
256
  getMeeting(): {
@@ -253,6 +270,15 @@ declare class VideoSDKCore {
253
270
  }>;
254
271
  sendChatMessage(payload: ChatInput): void;
255
272
  sendReaction(reaction: string): void;
273
+ /**
274
+ * Generic PubSub publishing entry point.
275
+ *
276
+ * The public SDK exposes one PubSub API for all topics. The current SFU
277
+ * protocol still has dedicated wire messages for chat and reactions, so
278
+ * this method is the compatibility bridge. The application does not need
279
+ * to know about those wire-level message types.
280
+ */
281
+ publishPubSub<T extends PubSubTopic>(topic: T, data: PubSubTopicConfig[T]["publish"]): void;
256
282
  private cleanupRemoteParticipant;
257
283
  disconnect(): Promise<void>;
258
284
  private performDisconnect;
@@ -304,8 +330,8 @@ type MeetingContextValue = {
304
330
  presenterId: string | null;
305
331
  activeSpeakerIds: string[];
306
332
  recording: RecordingInfo | null;
307
- usePubSub: <T = unknown>(topic: PubSubTopic) => PubSubHandle<T>;
308
- onPubSubMessage: <T = unknown>(topic: PubSubTopic, cb: (message: PubSubMessage<T>) => void) => () => void;
333
+ usePubSub: <T extends PubSubTopic>(topic: T) => PubSubHandleForTopic<T>;
334
+ onPubSubMessage: <T extends PubSubTopic>(topic: T, cb: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void) => () => void;
309
335
  approveJoinRequest: (requestId: string) => void;
310
336
  rejectJoinRequest: (requestId: string) => void;
311
337
  startRecording: () => Promise<void>;
@@ -367,8 +393,8 @@ declare const useMeeting: (handlers?: {
367
393
  presenterId: string | null;
368
394
  activeSpeakerIds: string[];
369
395
  recording: RecordingInfo | null;
370
- usePubSub: <T = unknown>(topic: PubSubTopic) => PubSubHandle<T>;
371
- onPubSubMessage: <T = unknown>(topic: PubSubTopic, cb: (message: PubSubMessage<T>) => void) => () => void;
396
+ usePubSub: <T extends PubSubTopic>(topic: T) => PubSubHandleForTopic<T>;
397
+ onPubSubMessage: <T extends PubSubTopic>(topic: T, cb: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void) => () => void;
372
398
  approveJoinRequest: (requestId: string) => void;
373
399
  rejectJoinRequest: (requestId: string) => void;
374
400
  startRecording: () => Promise<void>;
@@ -463,8 +489,8 @@ declare class ActiveSpeakerDetector {
463
489
  destroy(): Promise<void>;
464
490
  }
465
491
 
466
- declare function usePubSub<T = unknown>(topic: PubSubTopic, options?: {
467
- onMessageReceived?: (message: PubSubMessage<T>) => void;
468
- }): PubSubHandle<T>;
492
+ declare function usePubSub<T extends PubSubTopic>(topic: T, options?: {
493
+ onMessageReceived?: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void;
494
+ }): PubSubHandleForTopic<T>;
469
495
 
470
- export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type Reaction, type RecordingInfo, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, usePubSub, useRemoteMedia };
496
+ export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type PubSubHandle, type PubSubHandleForTopic, type PubSubMessage, type PubSubTopic, type PubSubTopicConfig, type Reaction, type RecordingInfo, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, usePubSub, useRemoteMedia };
package/dist/index.d.ts CHANGED
@@ -67,6 +67,26 @@ type PubSubMessage<T = unknown> = {
67
67
  timestamp: number;
68
68
  data: T;
69
69
  };
70
+ type PubSubTopicConfig = {
71
+ SECURE_CHAT: {
72
+ publish: ChatInput;
73
+ message: ChatMessage;
74
+ };
75
+ REACTION: {
76
+ publish: {
77
+ emoji: string;
78
+ };
79
+ message: {
80
+ emoji: string;
81
+ };
82
+ };
83
+ };
84
+ type PubSubTopic = keyof PubSubTopicConfig;
85
+ type PubSubHandle<TPublish = unknown, TMessage = TPublish> = {
86
+ messages: PubSubMessage<TMessage>[];
87
+ publish: (data: TPublish) => void;
88
+ };
89
+ type PubSubHandleForTopic<T extends PubSubTopic> = PubSubHandle<PubSubTopicConfig[T]["publish"], PubSubTopicConfig[T]["message"]>;
70
90
  type Participant = {
71
91
  id: string;
72
92
  name?: string;
@@ -103,12 +123,7 @@ type MeetingConfig = {
103
123
  name: string;
104
124
  audioMuted?: boolean;
105
125
  videoMuted?: boolean;
106
- token?: string;
107
- };
108
- type PubSubTopic = "SECURE_CHAT" | "REACTION";
109
- type PubSubHandle<T = unknown> = {
110
- messages: PubSubMessage<T>[];
111
- publish: (data: T) => void;
126
+ token: string;
112
127
  };
113
128
  type RecordingInfo = {
114
129
  recording_id: string;
@@ -145,6 +160,7 @@ interface Recording {
145
160
  }
146
161
  interface VideoSDKCoreOptions {
147
162
  participantId: string;
163
+ token: string;
148
164
  onError?: (error: any) => void;
149
165
  onEntryRequested?: (request: any) => void;
150
166
  onEntryResponded?: (payload: any, decision?: any) => void;
@@ -234,6 +250,7 @@ declare class VideoSDKCore {
234
250
  deleteRoom(roomCode: string): Promise<any>;
235
251
  joinMeeting(config: Omit<MeetingConfig, "participantId">): Promise<void>;
236
252
  private connect;
253
+ private createSFUClient;
237
254
  private handleSFUState;
238
255
  private handleRemoteTrack;
239
256
  getMeeting(): {
@@ -253,6 +270,15 @@ declare class VideoSDKCore {
253
270
  }>;
254
271
  sendChatMessage(payload: ChatInput): void;
255
272
  sendReaction(reaction: string): void;
273
+ /**
274
+ * Generic PubSub publishing entry point.
275
+ *
276
+ * The public SDK exposes one PubSub API for all topics. The current SFU
277
+ * protocol still has dedicated wire messages for chat and reactions, so
278
+ * this method is the compatibility bridge. The application does not need
279
+ * to know about those wire-level message types.
280
+ */
281
+ publishPubSub<T extends PubSubTopic>(topic: T, data: PubSubTopicConfig[T]["publish"]): void;
256
282
  private cleanupRemoteParticipant;
257
283
  disconnect(): Promise<void>;
258
284
  private performDisconnect;
@@ -304,8 +330,8 @@ type MeetingContextValue = {
304
330
  presenterId: string | null;
305
331
  activeSpeakerIds: string[];
306
332
  recording: RecordingInfo | null;
307
- usePubSub: <T = unknown>(topic: PubSubTopic) => PubSubHandle<T>;
308
- onPubSubMessage: <T = unknown>(topic: PubSubTopic, cb: (message: PubSubMessage<T>) => void) => () => void;
333
+ usePubSub: <T extends PubSubTopic>(topic: T) => PubSubHandleForTopic<T>;
334
+ onPubSubMessage: <T extends PubSubTopic>(topic: T, cb: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void) => () => void;
309
335
  approveJoinRequest: (requestId: string) => void;
310
336
  rejectJoinRequest: (requestId: string) => void;
311
337
  startRecording: () => Promise<void>;
@@ -367,8 +393,8 @@ declare const useMeeting: (handlers?: {
367
393
  presenterId: string | null;
368
394
  activeSpeakerIds: string[];
369
395
  recording: RecordingInfo | null;
370
- usePubSub: <T = unknown>(topic: PubSubTopic) => PubSubHandle<T>;
371
- onPubSubMessage: <T = unknown>(topic: PubSubTopic, cb: (message: PubSubMessage<T>) => void) => () => void;
396
+ usePubSub: <T extends PubSubTopic>(topic: T) => PubSubHandleForTopic<T>;
397
+ onPubSubMessage: <T extends PubSubTopic>(topic: T, cb: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void) => () => void;
372
398
  approveJoinRequest: (requestId: string) => void;
373
399
  rejectJoinRequest: (requestId: string) => void;
374
400
  startRecording: () => Promise<void>;
@@ -463,8 +489,8 @@ declare class ActiveSpeakerDetector {
463
489
  destroy(): Promise<void>;
464
490
  }
465
491
 
466
- declare function usePubSub<T = unknown>(topic: PubSubTopic, options?: {
467
- onMessageReceived?: (message: PubSubMessage<T>) => void;
468
- }): PubSubHandle<T>;
492
+ declare function usePubSub<T extends PubSubTopic>(topic: T, options?: {
493
+ onMessageReceived?: (message: PubSubMessage<PubSubTopicConfig[T]["message"]>) => void;
494
+ }): PubSubHandleForTopic<T>;
469
495
 
470
- export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type Reaction, type RecordingInfo, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, usePubSub, useRemoteMedia };
496
+ export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type PubSubHandle, type PubSubHandleForTopic, type PubSubMessage, type PubSubTopic, type PubSubTopicConfig, type Reaction, type RecordingInfo, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, usePubSub, useRemoteMedia };
package/dist/index.js CHANGED
@@ -404,6 +404,9 @@ var SFUClient = class {
404
404
  this.publisherRevision = 0;
405
405
  this.screenShareGeneration = 0;
406
406
  this.currentParticipantId = null;
407
+ // Provider/application token used for authenticated REST calls.
408
+ this.authToken = null;
409
+ // Room/SFU token returned by /token and used for the WebSocket.
407
410
  this.token = null;
408
411
  this.room = null;
409
412
  this.resumeToken = null;
@@ -444,6 +447,7 @@ var SFUClient = class {
444
447
  this.apiBase = options.apiBase || "https://api.example.com/api";
445
448
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
446
449
  this.participantId = options.participantId || this.createId();
450
+ this.authToken = options.token ?? null;
447
451
  this.onStateChange = options.onStateChange || (() => {
448
452
  });
449
453
  this.onParticipantJoined = options.onParticipantJoined || (() => {
@@ -567,9 +571,14 @@ var SFUClient = class {
567
571
  return (++this.requestId).toString();
568
572
  }
569
573
  async request(url, options = {}) {
574
+ const headers = new Headers(options.headers);
575
+ headers.set("Content-Type", "application/json");
576
+ if (this.authToken) {
577
+ headers.set("Authorization", `Bearer ${this.authToken}`);
578
+ }
570
579
  const res = await fetch(url, {
571
- headers: { "Content-Type": "application/json" },
572
- ...options
580
+ ...options,
581
+ headers
573
582
  });
574
583
  if (!res.ok) {
575
584
  let body = {};
@@ -577,7 +586,9 @@ var SFUClient = class {
577
586
  body = await res.json();
578
587
  } catch {
579
588
  }
580
- throw new Error(body.message || res.statusText);
589
+ throw new Error(
590
+ body.message || `Request failed: ${res.status} ${res.statusText}`
591
+ );
581
592
  }
582
593
  return res.json();
583
594
  }
@@ -1981,6 +1992,7 @@ var VideoSDKCore = class {
1981
1992
  }
1982
1993
  });
1983
1994
  this.myId = options.participantId;
1995
+ this.sfuClient = this.createSFUClient(options.token);
1984
1996
  }
1985
1997
  emitError(code, message, raw, recoverable = true) {
1986
1998
  const err = {
@@ -2045,36 +2057,16 @@ var VideoSDKCore = class {
2045
2057
  }
2046
2058
  // ROOM MANAGEMENT
2047
2059
  async createRoom(input) {
2048
- const client = this.sfuClient ?? new SFUClient({
2049
- apiBase: this.apiBase,
2050
- wsBase: this.wsBase,
2051
- participantId: this.myId
2052
- });
2053
- return client.createRoom(input);
2060
+ return this.sfuClient.createRoom(input);
2054
2061
  }
2055
2062
  async getRoom(roomCode) {
2056
- const client = this.sfuClient ?? new SFUClient({
2057
- apiBase: this.apiBase,
2058
- wsBase: this.wsBase,
2059
- participantId: this.myId
2060
- });
2061
- return client.getRoom(roomCode);
2063
+ return this.sfuClient.getRoom(roomCode);
2062
2064
  }
2063
2065
  async getJoinState(roomCode) {
2064
- const client = this.sfuClient ?? new SFUClient({
2065
- apiBase: this.apiBase,
2066
- wsBase: this.wsBase,
2067
- participantId: this.myId
2068
- });
2069
- return client.getJoinState(roomCode);
2066
+ return this.sfuClient.getJoinState(roomCode);
2070
2067
  }
2071
2068
  async deleteRoom(roomCode) {
2072
- const client = this.sfuClient ?? new SFUClient({
2073
- apiBase: this.apiBase,
2074
- wsBase: this.wsBase,
2075
- participantId: this.myId
2076
- });
2077
- return client.deleteRoom(roomCode);
2069
+ return this.sfuClient.deleteRoom(roomCode);
2078
2070
  }
2079
2071
  // JOIN
2080
2072
  async joinMeeting(config) {
@@ -2140,26 +2132,36 @@ var VideoSDKCore = class {
2140
2132
  }
2141
2133
  // SFU CONNECTION
2142
2134
  async connect(roomId, name) {
2143
- if (this.sfuClient) {
2144
- const state = this.sfuClient.getConnectionState();
2145
- if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
2146
- console.info(
2147
- "[SDK] Existing SFU connection is active; connect ignored",
2148
- {
2149
- roomId,
2150
- state
2151
- }
2152
- );
2153
- return;
2154
- }
2135
+ if (!this.sfuClient) {
2136
+ throw new Error("SFU client is not initialized");
2137
+ }
2138
+ const state = this.sfuClient.getConnectionState();
2139
+ if (this.room.id === roomId && (state === "connecting" || state === "joining" || state === "waiting_approval" || state === "connected" || state === "reconnecting")) {
2140
+ console.info("[SDK] Existing SFU connection is active; connect ignored", {
2141
+ roomId,
2142
+ state
2143
+ });
2144
+ return;
2145
+ }
2146
+ if (state !== "idle" && state !== "disconnected") {
2155
2147
  throw new Error(
2156
- `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
2148
+ `SFU client is already active in state "${state}". Leave the current meeting before joining another room.`
2157
2149
  );
2158
2150
  }
2159
- const client = new SFUClient({
2151
+ this.sfuClient.setLocalStream(this.localStream);
2152
+ await this.sfuClient.getToken(roomId, {
2153
+ displayName: name,
2154
+ participantId: this.myId,
2155
+ audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? true),
2156
+ videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? true)
2157
+ });
2158
+ }
2159
+ createSFUClient(token) {
2160
+ return new SFUClient({
2160
2161
  apiBase: this.apiBase,
2161
2162
  wsBase: this.wsBase,
2162
2163
  participantId: this.myId,
2164
+ token,
2163
2165
  autoSubscribe: true,
2164
2166
  // CONNECTION STATE
2165
2167
  onStateChange: (state) => {
@@ -2498,14 +2500,6 @@ var VideoSDKCore = class {
2498
2500
  );
2499
2501
  }
2500
2502
  });
2501
- this.sfuClient = client;
2502
- client.setLocalStream(this.localStream);
2503
- await client.getToken(roomId, {
2504
- displayName: name,
2505
- participantId: this.myId,
2506
- audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? true),
2507
- videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? true)
2508
- });
2509
2503
  }
2510
2504
  // SFU STATE
2511
2505
  handleSFUState(state) {
@@ -2739,6 +2733,28 @@ var VideoSDKCore = class {
2739
2733
  }
2740
2734
  this.sfuClient.sendReaction(reaction);
2741
2735
  }
2736
+ /**
2737
+ * Generic PubSub publishing entry point.
2738
+ *
2739
+ * The public SDK exposes one PubSub API for all topics. The current SFU
2740
+ * protocol still has dedicated wire messages for chat and reactions, so
2741
+ * this method is the compatibility bridge. The application does not need
2742
+ * to know about those wire-level message types.
2743
+ */
2744
+ publishPubSub(topic, data) {
2745
+ switch (topic) {
2746
+ case "SECURE_CHAT":
2747
+ this.sendChatMessage(data);
2748
+ return;
2749
+ case "REACTION":
2750
+ this.sendReaction(data.emoji);
2751
+ return;
2752
+ default: {
2753
+ const exhaustive = topic;
2754
+ throw new Error(`Unsupported PubSub topic: ${exhaustive}`);
2755
+ }
2756
+ }
2757
+ }
2742
2758
  // CLEANUP
2743
2759
  cleanupRemoteParticipant(participantId) {
2744
2760
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2764,10 +2780,8 @@ var VideoSDKCore = class {
2764
2780
  async performDisconnect() {
2765
2781
  this.intentionalDisconnect = true;
2766
2782
  this.joinPromise = null;
2767
- const client = this.sfuClient;
2768
- this.sfuClient = null;
2769
- if (client) {
2770
- await client.leave();
2783
+ if (this.sfuClient) {
2784
+ await this.sfuClient.leave();
2771
2785
  }
2772
2786
  if (this.localStream) {
2773
2787
  this.localStream.getTracks().forEach((track) => track.stop());
@@ -2829,6 +2843,7 @@ var MeetingProvider = ({
2829
2843
  const pubSubListeners = (0, import_react2.useRef)(
2830
2844
  /* @__PURE__ */ new Map()
2831
2845
  );
2846
+ const [reactionPubSubMessages, setReactionPubSubMessages] = (0, import_react2.useState)([]);
2832
2847
  const activeSpeakerListeners = (0, import_react2.useRef)(
2833
2848
  /* @__PURE__ */ new Set()
2834
2849
  );
@@ -2838,7 +2853,8 @@ var MeetingProvider = ({
2838
2853
  if (!sdkRef.current) {
2839
2854
  sdkRef.current = new VideoSDKCore(
2840
2855
  {
2841
- participantId: config.participantId
2856
+ participantId: config.participantId,
2857
+ token: config.token
2842
2858
  },
2843
2859
  {
2844
2860
  onError: (err) => {
@@ -2862,21 +2878,43 @@ var MeetingProvider = ({
2862
2878
  onRecordingStopped: (recording2) => {
2863
2879
  recordingStoppedListeners.current.forEach((fn) => fn(recording2));
2864
2880
  },
2881
+ onChatMessage: (message) => {
2882
+ const msg = {
2883
+ id: message.id,
2884
+ text: message.text,
2885
+ sender_id: message.sender_id,
2886
+ sender_name: message.sender_name,
2887
+ timestamp: Number(message.timestamp),
2888
+ reply_to: message.reply_to ?? null,
2889
+ target: message.target ?? null
2890
+ };
2891
+ const pubSubMessage = {
2892
+ id: msg.id,
2893
+ topic: "SECURE_CHAT",
2894
+ sender_id: msg.sender_id,
2895
+ sender_name: msg.sender_name,
2896
+ timestamp: msg.timestamp,
2897
+ data: msg
2898
+ };
2899
+ pubSubListeners.current.get("SECURE_CHAT")?.forEach((fn) => fn(pubSubMessage));
2900
+ },
2865
2901
  onReaction: (reaction) => {
2866
2902
  reactionListeners.current.forEach((fn) => fn(reaction));
2867
- const listeners = pubSubListeners.current.get("REACTION");
2868
- if (!listeners) return;
2903
+ const timestamp = typeof reaction.timestamp === "number" ? reaction.timestamp : Date.parse(reaction.timestamp);
2869
2904
  const message = {
2870
2905
  id: reaction.id,
2871
2906
  topic: "REACTION",
2872
2907
  sender_id: reaction.participant_id,
2873
2908
  sender_name: reaction.sender_name,
2874
- timestamp: typeof reaction.timestamp === "number" ? reaction.timestamp : Date.parse(reaction.timestamp),
2909
+ timestamp,
2875
2910
  data: {
2876
2911
  emoji: reaction.emoji
2877
2912
  }
2878
2913
  };
2879
- listeners.forEach((fn) => fn(message));
2914
+ setReactionPubSubMessages(
2915
+ (previous) => [...previous, message].slice(-50)
2916
+ );
2917
+ pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
2880
2918
  },
2881
2919
  onConnectionStateChanged: (state) => {
2882
2920
  connectionStateListeners.current.forEach(
@@ -2939,32 +2977,32 @@ var MeetingProvider = ({
2939
2977
  activeSpeakerIds,
2940
2978
  recording,
2941
2979
  usePubSub: (topic) => {
2980
+ if (topic === "SECURE_CHAT") {
2981
+ const chatMessages = messages.map((message) => ({
2982
+ id: message.id,
2983
+ topic: "SECURE_CHAT",
2984
+ sender_id: message.sender_id,
2985
+ sender_name: message.sender_name,
2986
+ timestamp: message.timestamp,
2987
+ data: message
2988
+ }));
2989
+ return {
2990
+ messages: chatMessages,
2991
+ publish: ((data) => {
2992
+ sdk.publishPubSub("SECURE_CHAT", data);
2993
+ })
2994
+ };
2995
+ }
2942
2996
  if (topic === "REACTION") {
2943
2997
  return {
2944
- messages: [],
2945
- publish: (data) => {
2946
- const reaction = data;
2947
- if (!reaction?.emoji) {
2998
+ messages: reactionPubSubMessages,
2999
+ publish: ((data) => {
3000
+ if (!data?.emoji) {
2948
3001
  console.warn("[PubSub] REACTION requires an emoji");
2949
3002
  return;
2950
3003
  }
2951
- sdk.sendReaction(reaction.emoji);
2952
- }
2953
- };
2954
- }
2955
- if (topic === "SECURE_CHAT") {
2956
- return {
2957
- messages: sdk.state.getChatMessages().map((message) => ({
2958
- id: message.id,
2959
- topic: "SECURE_CHAT",
2960
- sender_id: message.sender_id,
2961
- sender_name: message.sender_name,
2962
- timestamp: message.timestamp,
2963
- data: message
2964
- })),
2965
- publish: (data) => {
2966
- sdk.sendChatMessage(data);
2967
- }
3004
+ sdk.publishPubSub("REACTION", data);
3005
+ })
2968
3006
  };
2969
3007
  }
2970
3008
  throw new Error(`Unsupported PubSub topic: "${topic}"`);
@@ -3049,7 +3087,8 @@ var MeetingProvider = ({
3049
3087
  messages,
3050
3088
  presenterId,
3051
3089
  recording,
3052
- activeSpeakerIds
3090
+ activeSpeakerIds,
3091
+ reactionPubSubMessages
3053
3092
  ]);
3054
3093
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MeetingContext.Provider, { value, children });
3055
3094
  };
@@ -3286,7 +3325,10 @@ function usePubSub(topic, options) {
3286
3325
  const ctx = useMeetingContext();
3287
3326
  (0, import_react8.useEffect)(() => {
3288
3327
  if (!options?.onMessageReceived) return;
3289
- return ctx.onPubSubMessage(topic, options.onMessageReceived);
3328
+ return ctx.onPubSubMessage(
3329
+ topic,
3330
+ options.onMessageReceived
3331
+ );
3290
3332
  }, [ctx, topic, options?.onMessageReceived]);
3291
3333
  return ctx.usePubSub(topic);
3292
3334
  }