@defcomm/meetingsdk 0.2.7 → 0.2.8

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
@@ -22,6 +22,7 @@ type Events = {
22
22
  onUserLeft?: (id: string) => void;
23
23
  onMeetingLeft?: () => void;
24
24
  onChatMessage?: (msg: ChatMessage) => void;
25
+ onReaction?: (reaction: Reaction) => void;
25
26
  onScreenShareStarted?: (peerId: string, stream: MediaStream) => void;
26
27
  onScreenShareStopped?: (peerId: string) => void;
27
28
  onMuteStateChanged?: (peerId: string, kind: "audio" | "video", muted: boolean) => void;
@@ -39,6 +40,13 @@ type Events = {
39
40
  stopped_at: string;
40
41
  }) => void;
41
42
  };
43
+ type Reaction = {
44
+ id: string;
45
+ participant_id: string;
46
+ sender_name?: string | null;
47
+ emoji: string;
48
+ timestamp: string | number;
49
+ };
42
50
  type ChatMessage = {
43
51
  id: string;
44
52
  text: string;
@@ -219,6 +227,7 @@ declare class VideoSDKCore {
219
227
  recording: RecordingInfo | null;
220
228
  }>;
221
229
  sendChatMessage(payload: ChatInput): void;
230
+ sendReaction(reaction: string): void;
222
231
  private cleanupRemoteParticipant;
223
232
  disconnect(): Promise<void>;
224
233
  private performDisconnect;
@@ -285,6 +294,7 @@ interface SFUClientOptions {
285
294
  }) => void;
286
295
  onError?: (error: any) => void;
287
296
  onChatMessage?: (message: any) => void;
297
+ onReaction?: (reaction: any) => void;
288
298
  onRoomUpdate?: (data: any) => void;
289
299
  onParticipantMediaState?: (participantId: string, audioMuted: boolean, videoMuted: boolean) => void;
290
300
  autoSubscribe?: boolean;
@@ -307,6 +317,7 @@ declare class SFUClient {
307
317
  private iceServers;
308
318
  private requestId;
309
319
  private publisherRevision;
320
+ private screenShareGeneration;
310
321
  private currentParticipantId;
311
322
  private token;
312
323
  private room;
@@ -362,6 +373,7 @@ declare class SFUClient {
362
373
  private onRecordingStopped;
363
374
  private onError;
364
375
  private onChatMessage;
376
+ private onReaction;
365
377
  private onRoomUpdate;
366
378
  private onParticipantMediaState;
367
379
  private autoSubscribe;
@@ -440,6 +452,7 @@ declare class SFUClient {
440
452
  rejectJoinRequest(requestId: string, reason?: string): Promise<void>;
441
453
  private isSocketOpen;
442
454
  private send;
455
+ sendReaction(reaction: string): void;
443
456
  private removeRemoteParticipant;
444
457
  leave(): Promise<void>;
445
458
  private closePeerConnectionsOnly;
@@ -469,6 +482,7 @@ type MeetingContextValue = {
469
482
  startScreenShare: () => Promise<MediaStream>;
470
483
  stopScreenShare: () => void;
471
484
  sendMessage: (input: ChatInput) => void;
485
+ sendReaction: (emoji: string) => void;
472
486
  room: {
473
487
  id: string | null;
474
488
  name: string | null;
@@ -491,6 +505,7 @@ type MeetingContextValue = {
491
505
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
492
506
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
493
507
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
508
+ onReaction: (cb: (reaction: Reaction) => void) => () => void;
494
509
  onActiveSpeakersChanged: (cb: (participantIds: string[]) => void) => () => void;
495
510
  };
496
511
  declare const MeetingProvider: ({ config, children, }: {
@@ -507,6 +522,7 @@ declare const useMeeting: (handlers?: {
507
522
  onTrackStateChanged?: (track: any) => void;
508
523
  onRecordingStarted?: (recording: any) => void;
509
524
  onRecordingStopped?: (recording: any) => void;
525
+ onReaction?: (reaction: any) => void;
510
526
  onActiveSpeakersChanged?: (participantIds: string[]) => void;
511
527
  }) => {
512
528
  join: (config?: Omit<MeetingConfig, "participantId">) => Promise<void>;
@@ -524,6 +540,7 @@ declare const useMeeting: (handlers?: {
524
540
  startScreenShare: () => Promise<MediaStream>;
525
541
  stopScreenShare: () => void;
526
542
  sendMessage: (input: ChatInput) => void;
543
+ sendReaction: (emoji: string) => void;
527
544
  room: {
528
545
  id: string | null;
529
546
  name: string | null;
@@ -549,6 +566,7 @@ declare const useMeeting: (handlers?: {
549
566
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
550
567
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
551
568
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
569
+ onReaction: (cb: (reaction: Reaction) => void) => () => void;
552
570
  onActiveSpeakersChanged: (cb: (participantIds: string[]) => void) => () => void;
553
571
  };
554
572
 
@@ -629,4 +647,4 @@ declare class ActiveSpeakerDetector {
629
647
  destroy(): Promise<void>;
630
648
  }
631
649
 
632
- export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type RecordingInfo, SFUClient, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, useRemoteMedia };
650
+ export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type Reaction, type RecordingInfo, SFUClient, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, useRemoteMedia };
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ type Events = {
22
22
  onUserLeft?: (id: string) => void;
23
23
  onMeetingLeft?: () => void;
24
24
  onChatMessage?: (msg: ChatMessage) => void;
25
+ onReaction?: (reaction: Reaction) => void;
25
26
  onScreenShareStarted?: (peerId: string, stream: MediaStream) => void;
26
27
  onScreenShareStopped?: (peerId: string) => void;
27
28
  onMuteStateChanged?: (peerId: string, kind: "audio" | "video", muted: boolean) => void;
@@ -39,6 +40,13 @@ type Events = {
39
40
  stopped_at: string;
40
41
  }) => void;
41
42
  };
43
+ type Reaction = {
44
+ id: string;
45
+ participant_id: string;
46
+ sender_name?: string | null;
47
+ emoji: string;
48
+ timestamp: string | number;
49
+ };
42
50
  type ChatMessage = {
43
51
  id: string;
44
52
  text: string;
@@ -219,6 +227,7 @@ declare class VideoSDKCore {
219
227
  recording: RecordingInfo | null;
220
228
  }>;
221
229
  sendChatMessage(payload: ChatInput): void;
230
+ sendReaction(reaction: string): void;
222
231
  private cleanupRemoteParticipant;
223
232
  disconnect(): Promise<void>;
224
233
  private performDisconnect;
@@ -285,6 +294,7 @@ interface SFUClientOptions {
285
294
  }) => void;
286
295
  onError?: (error: any) => void;
287
296
  onChatMessage?: (message: any) => void;
297
+ onReaction?: (reaction: any) => void;
288
298
  onRoomUpdate?: (data: any) => void;
289
299
  onParticipantMediaState?: (participantId: string, audioMuted: boolean, videoMuted: boolean) => void;
290
300
  autoSubscribe?: boolean;
@@ -307,6 +317,7 @@ declare class SFUClient {
307
317
  private iceServers;
308
318
  private requestId;
309
319
  private publisherRevision;
320
+ private screenShareGeneration;
310
321
  private currentParticipantId;
311
322
  private token;
312
323
  private room;
@@ -362,6 +373,7 @@ declare class SFUClient {
362
373
  private onRecordingStopped;
363
374
  private onError;
364
375
  private onChatMessage;
376
+ private onReaction;
365
377
  private onRoomUpdate;
366
378
  private onParticipantMediaState;
367
379
  private autoSubscribe;
@@ -440,6 +452,7 @@ declare class SFUClient {
440
452
  rejectJoinRequest(requestId: string, reason?: string): Promise<void>;
441
453
  private isSocketOpen;
442
454
  private send;
455
+ sendReaction(reaction: string): void;
443
456
  private removeRemoteParticipant;
444
457
  leave(): Promise<void>;
445
458
  private closePeerConnectionsOnly;
@@ -469,6 +482,7 @@ type MeetingContextValue = {
469
482
  startScreenShare: () => Promise<MediaStream>;
470
483
  stopScreenShare: () => void;
471
484
  sendMessage: (input: ChatInput) => void;
485
+ sendReaction: (emoji: string) => void;
472
486
  room: {
473
487
  id: string | null;
474
488
  name: string | null;
@@ -491,6 +505,7 @@ type MeetingContextValue = {
491
505
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
492
506
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
493
507
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
508
+ onReaction: (cb: (reaction: Reaction) => void) => () => void;
494
509
  onActiveSpeakersChanged: (cb: (participantIds: string[]) => void) => () => void;
495
510
  };
496
511
  declare const MeetingProvider: ({ config, children, }: {
@@ -507,6 +522,7 @@ declare const useMeeting: (handlers?: {
507
522
  onTrackStateChanged?: (track: any) => void;
508
523
  onRecordingStarted?: (recording: any) => void;
509
524
  onRecordingStopped?: (recording: any) => void;
525
+ onReaction?: (reaction: any) => void;
510
526
  onActiveSpeakersChanged?: (participantIds: string[]) => void;
511
527
  }) => {
512
528
  join: (config?: Omit<MeetingConfig, "participantId">) => Promise<void>;
@@ -524,6 +540,7 @@ declare const useMeeting: (handlers?: {
524
540
  startScreenShare: () => Promise<MediaStream>;
525
541
  stopScreenShare: () => void;
526
542
  sendMessage: (input: ChatInput) => void;
543
+ sendReaction: (emoji: string) => void;
527
544
  room: {
528
545
  id: string | null;
529
546
  name: string | null;
@@ -549,6 +566,7 @@ declare const useMeeting: (handlers?: {
549
566
  onTrackStateChanged: (cb: (track: any) => void) => () => void;
550
567
  onRecordingStarted: (cb: (recording: Recording) => void) => () => void;
551
568
  onRecordingStopped: (cb: (recording: any) => void) => () => void;
569
+ onReaction: (cb: (reaction: Reaction) => void) => () => void;
552
570
  onActiveSpeakersChanged: (cb: (participantIds: string[]) => void) => () => void;
553
571
  };
554
572
 
@@ -629,4 +647,4 @@ declare class ActiveSpeakerDetector {
629
647
  destroy(): Promise<void>;
630
648
  }
631
649
 
632
- export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type RecordingInfo, SFUClient, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, useRemoteMedia };
650
+ export { ActiveSpeakerDetector, type ChatInput, type MeetingConfig, MeetingProvider, MeetingState, type Participant, type ParticipantInfo, type Reaction, type RecordingInfo, SFUClient, type SFUConnectionState, type TrackInfo, VideoSDKCore, useLocalParticipant, useMeeting, useMeetingContext, useMeetingPreview, useParticipants, useRemoteMedia };
package/dist/index.js CHANGED
@@ -393,6 +393,7 @@ var SFUClient = class {
393
393
  this.iceServers = [];
394
394
  this.requestId = 0;
395
395
  this.publisherRevision = 0;
396
+ this.screenShareGeneration = 0;
396
397
  this.currentParticipantId = null;
397
398
  this.token = null;
398
399
  this.room = null;
@@ -475,6 +476,8 @@ var SFUClient = class {
475
476
  this.onError = options.onError || console.error;
476
477
  this.onChatMessage = options.onChatMessage || (() => {
477
478
  });
479
+ this.onReaction = options.onReaction || (() => {
480
+ });
478
481
  this.onRoomUpdate = options.onRoomUpdate || (() => {
479
482
  });
480
483
  this.onParticipantMediaState = options.onParticipantMediaState || (() => {
@@ -601,8 +604,10 @@ var SFUClient = class {
601
604
  avatarUrl: options.avatarUrl
602
605
  };
603
606
  if (options.participantId) this.participantId = options.participantId;
604
- if (typeof options.audioMuted === "boolean") this.desiredAudioMuted = options.audioMuted;
605
- if (typeof options.videoMuted === "boolean") this.desiredVideoMuted = options.videoMuted;
607
+ if (typeof options.audioMuted === "boolean")
608
+ this.desiredAudioMuted = options.audioMuted;
609
+ if (typeof options.videoMuted === "boolean")
610
+ this.desiredVideoMuted = options.videoMuted;
606
611
  if (!this.resumeToken) {
607
612
  this.resumeToken = this.loadResumeToken(this.room);
608
613
  }
@@ -987,8 +992,15 @@ var SFUClient = class {
987
992
  if (!pid) continue;
988
993
  if (pid !== this.currentParticipantId) {
989
994
  this.participants.set(pid, participant);
990
- this.onParticipantPresence(pid, String(participant.presence || "connected"));
991
- this.onParticipantMediaState(pid, Boolean(participant.audio_muted), Boolean(participant.video_muted));
995
+ this.onParticipantPresence(
996
+ pid,
997
+ String(participant.presence || "connected")
998
+ );
999
+ this.onParticipantMediaState(
1000
+ pid,
1001
+ Boolean(participant.audio_muted),
1002
+ Boolean(participant.video_muted)
1003
+ );
992
1004
  }
993
1005
  }
994
1006
  for (const [pid] of this.participants) {
@@ -1000,7 +1012,8 @@ var SFUClient = class {
1000
1012
  }
1001
1013
  for (const track of payload.tracks || []) this.rememberTrack(track);
1002
1014
  if (Array.isArray(payload.chat_history)) {
1003
- for (const message2 of payload.chat_history) this.onChatMessage(message2);
1015
+ for (const message2 of payload.chat_history)
1016
+ this.onChatMessage(message2);
1004
1017
  }
1005
1018
  break;
1006
1019
  }
@@ -1009,6 +1022,11 @@ var SFUClient = class {
1009
1022
  if (msg?.id) this.onChatMessage(msg);
1010
1023
  break;
1011
1024
  }
1025
+ case "reaction": {
1026
+ const reaction = payload.reaction || payload;
1027
+ if (reaction?.id && reaction?.emoji) this.onReaction(reaction);
1028
+ break;
1029
+ }
1012
1030
  case "error": {
1013
1031
  const message2 = payload.message || "SFU signaling error";
1014
1032
  if (this.state === "reconnecting" && message2.includes("participant session is stale")) {
@@ -1033,7 +1051,9 @@ var SFUClient = class {
1033
1051
  }
1034
1052
  onRoomStateReconciled(payload) {
1035
1053
  const participants = payload.participants || [];
1036
- const local = participants.find((p) => p?.participant_id === this.currentParticipantId);
1054
+ const local = participants.find(
1055
+ (p) => p?.participant_id === this.currentParticipantId
1056
+ );
1037
1057
  if (local) {
1038
1058
  this.desiredAudioMuted = Boolean(local.audio_muted);
1039
1059
  this.desiredVideoMuted = Boolean(local.video_muted);
@@ -1447,9 +1467,27 @@ var SFUClient = class {
1447
1467
  if (!this.screenTransceiver || !this.screenSender) {
1448
1468
  throw new Error("Screen transceiver is not initialized");
1449
1469
  }
1450
- if (this.screenStream) {
1470
+ const existingTrack = this.screenSender.track;
1471
+ if (this.screenStream && existingTrack && existingTrack.readyState === "live") {
1451
1472
  return this.screenStream;
1452
1473
  }
1474
+ const generation = ++this.screenShareGeneration;
1475
+ try {
1476
+ await this.screenSender.replaceTrack(null);
1477
+ } catch (err) {
1478
+ console.warn("[SFU] Failed to clear previous screen track", err);
1479
+ }
1480
+ if (this.screenStream) {
1481
+ for (const track of this.screenStream.getTracks()) {
1482
+ try {
1483
+ track.onended = null;
1484
+ track.stop();
1485
+ } catch {
1486
+ }
1487
+ }
1488
+ }
1489
+ this.screenStream = null;
1490
+ this.screenTransceiver.direction = "inactive";
1453
1491
  const screenStream = await navigator.mediaDevices.getDisplayMedia({
1454
1492
  video: true,
1455
1493
  audio: false
@@ -1463,74 +1501,87 @@ var SFUClient = class {
1463
1501
  try {
1464
1502
  await this.screenSender.replaceTrack(screenTrack);
1465
1503
  this.screenTransceiver.direction = "sendonly";
1504
+ this.send({
1505
+ type: "start_screen_share"
1506
+ });
1507
+ await new Promise((resolve) => {
1508
+ queueMicrotask(resolve);
1509
+ });
1466
1510
  const offer = await this.publisher.createOffer();
1467
1511
  await this.publisher.setLocalDescription(offer);
1512
+ const localDescription = this.publisher.localDescription;
1513
+ if (!localDescription?.sdp) {
1514
+ throw new Error("Publisher did not produce a valid screen-share SDP");
1515
+ }
1516
+ const revision = ++this.publisherRevision;
1468
1517
  this.send({
1469
1518
  type: "publish_offer",
1470
1519
  payload: {
1471
- revision: ++this.publisherRevision,
1472
- sdp: offer.sdp
1520
+ revision,
1521
+ sdp: localDescription.sdp
1473
1522
  }
1474
1523
  });
1475
- this.send({ type: "start_screen_share" });
1476
1524
  screenTrack.onended = () => {
1525
+ if (generation !== this.screenShareGeneration) {
1526
+ return;
1527
+ }
1477
1528
  void this.stopScreenShare();
1478
1529
  };
1479
- console.info("[publisher] Screen track attached", {
1480
- screenTrackId: screenTrack.id,
1481
- cameraTrackId: this.videoSender?.track?.id,
1482
- screenMid: this.screenTransceiver.mid,
1483
- cameraMid: this.cameraTransceiver?.mid
1484
- });
1485
- return this.screenStream;
1530
+ return screenStream;
1486
1531
  } catch (error) {
1487
- try {
1488
- await this.screenSender.replaceTrack(null);
1489
- } catch {
1490
- }
1491
- this.screenTransceiver.direction = "inactive";
1492
- screenStream.getTracks().forEach((track) => track.stop());
1493
- this.screenStream = null;
1494
- try {
1495
- this.send({ type: "stop_screen_share" });
1496
- } catch {
1532
+ if (generation === this.screenShareGeneration) {
1533
+ try {
1534
+ await this.screenSender.replaceTrack(null);
1535
+ } catch {
1536
+ }
1537
+ this.screenTransceiver.direction = "inactive";
1538
+ screenTrack.onended = null;
1539
+ screenStream.getTracks().forEach((track) => {
1540
+ try {
1541
+ track.stop();
1542
+ } catch {
1543
+ }
1544
+ });
1545
+ this.screenStream = null;
1546
+ try {
1547
+ this.send({
1548
+ type: "stop_screen_share"
1549
+ });
1550
+ } catch {
1551
+ }
1497
1552
  }
1498
1553
  throw error;
1499
1554
  }
1500
1555
  }
1501
1556
  async stopScreenShare() {
1502
- if (!this.screenStream && !this.screenSender?.track) return;
1503
- const screenTrack = this.screenSender?.track || null;
1504
- if (screenTrack) screenTrack.onended = null;
1557
+ const stream = this.screenStream;
1558
+ const senderTrack = this.screenSender?.track || null;
1559
+ if (!stream && !senderTrack) return;
1560
+ ++this.screenShareGeneration;
1561
+ if (senderTrack) senderTrack.onended = null;
1505
1562
  try {
1506
1563
  await this.screenSender?.replaceTrack(null);
1507
1564
  } catch (error) {
1508
1565
  this.onError(error);
1509
1566
  }
1510
- if (this.screenTransceiver) {
1511
- this.screenTransceiver.direction = "inactive";
1512
- }
1513
- this.send({ type: "stop_screen_share" });
1567
+ if (this.screenTransceiver) this.screenTransceiver.direction = "inactive";
1514
1568
  try {
1515
1569
  const offer = await this.publisher?.createOffer();
1516
1570
  if (offer && this.publisher) {
1517
1571
  await this.publisher.setLocalDescription(offer);
1518
1572
  this.send({
1519
1573
  type: "publish_offer",
1520
- payload: {
1521
- revision: ++this.publisherRevision,
1522
- sdp: offer.sdp
1523
- }
1574
+ payload: { revision: ++this.publisherRevision, sdp: offer.sdp }
1524
1575
  });
1525
1576
  }
1526
1577
  } catch (error) {
1527
1578
  this.onError(error);
1528
1579
  }
1529
- this.screenStream?.getTracks().forEach((track) => track.stop());
1580
+ this.send({ type: "stop_screen_share" });
1581
+ stream?.getTracks().forEach((track) => track.stop());
1530
1582
  this.screenStream = null;
1531
- if (this.currentParticipantId) {
1583
+ if (this.currentParticipantId)
1532
1584
  this.onScreenShareStopped(this.currentParticipantId);
1533
- }
1534
1585
  }
1535
1586
  muteAudio(muted) {
1536
1587
  this.desiredAudioMuted = muted;
@@ -1633,6 +1684,12 @@ var SFUClient = class {
1633
1684
  );
1634
1685
  return true;
1635
1686
  }
1687
+ sendReaction(reaction) {
1688
+ this.send({
1689
+ type: "send_reaction",
1690
+ reaction
1691
+ });
1692
+ }
1636
1693
  removeRemoteParticipant(participantId) {
1637
1694
  const stream = this.remoteStreams.get(participantId);
1638
1695
  if (stream) {
@@ -2051,7 +2108,9 @@ var VideoSDKCore = class {
2051
2108
  // ADMISSION CONTROL
2052
2109
  onRoomUpdate: (payload) => {
2053
2110
  const participants = payload?.participants || [];
2054
- const local = participants.find((p) => p?.participant_id === this.myId);
2111
+ const local = participants.find(
2112
+ (p) => p?.participant_id === this.myId
2113
+ );
2055
2114
  if (local) {
2056
2115
  const media = this.state.localParticipant?.media;
2057
2116
  this.state.updateLocalParticipant({
@@ -2074,7 +2133,8 @@ var VideoSDKCore = class {
2074
2133
  });
2075
2134
  }
2076
2135
  if (local.screen_sharing) this.state.setPresenterId(this.myId);
2077
- else if (this.state.presenterId === this.myId) this.state.setPresenterId(null);
2136
+ else if (this.state.presenterId === this.myId)
2137
+ this.state.setPresenterId(null);
2078
2138
  }
2079
2139
  const presenter = participants.find((p) => p?.screen_sharing);
2080
2140
  if (presenter) this.state.setPresenterId(presenter.participant_id);
@@ -2505,7 +2565,7 @@ var VideoSDKCore = class {
2505
2565
  media: {
2506
2566
  isScreenSharing: true,
2507
2567
  screenStream: this.screenStream,
2508
- screenTrack: this.screenStream.getVideoTracks()[0]
2568
+ screenTrack: this.screenStream?.getVideoTracks()[0]
2509
2569
  }
2510
2570
  });
2511
2571
  return this.screenStream;
@@ -2570,6 +2630,13 @@ var VideoSDKCore = class {
2570
2630
  target: payload.target ?? null
2571
2631
  });
2572
2632
  }
2633
+ sendReaction(reaction) {
2634
+ if (!this.sfuClient) {
2635
+ console.warn("SFU Client not connected");
2636
+ return;
2637
+ }
2638
+ this.sfuClient.sendReaction(reaction);
2639
+ }
2573
2640
  // CLEANUP
2574
2641
  cleanupRemoteParticipant(participantId) {
2575
2642
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2656,6 +2723,7 @@ var MeetingProvider = ({
2656
2723
  const trackStateListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2657
2724
  const recordingStartedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2658
2725
  const recordingStoppedListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2726
+ const reactionListeners = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
2659
2727
  const activeSpeakerListeners = (0, import_react2.useRef)(
2660
2728
  /* @__PURE__ */ new Set()
2661
2729
  );
@@ -2685,6 +2753,9 @@ var MeetingProvider = ({
2685
2753
  },
2686
2754
  onRecordingStopped: (recording) => {
2687
2755
  recordingStoppedListeners.current.forEach((fn) => fn(recording));
2756
+ },
2757
+ onReaction: (reaction) => {
2758
+ reactionListeners.current.forEach((fn) => fn(reaction));
2688
2759
  }
2689
2760
  }
2690
2761
  );
@@ -2731,6 +2802,7 @@ var MeetingProvider = ({
2731
2802
  startScreenShare: sdk.startScreenShare.bind(sdk),
2732
2803
  stopScreenShare: sdk.stopScreenShare.bind(sdk),
2733
2804
  sendMessage: sdk.sendChatMessage.bind(sdk),
2805
+ sendReaction: sdk.sendReaction.bind(sdk),
2734
2806
  room: sdk.getMeeting(),
2735
2807
  localParticipant,
2736
2808
  participants,
@@ -2787,6 +2859,10 @@ var MeetingProvider = ({
2787
2859
  recordingStoppedListeners.current.add(cb);
2788
2860
  return () => recordingStoppedListeners.current.delete(cb);
2789
2861
  },
2862
+ onReaction: (cb) => {
2863
+ reactionListeners.current.add(cb);
2864
+ return () => reactionListeners.current.delete(cb);
2865
+ },
2790
2866
  onActiveSpeakersChanged: (cb) => {
2791
2867
  activeSpeakerListeners.current.add(cb);
2792
2868
  return () => {
@@ -2883,6 +2959,10 @@ var useMeeting = (handlers) => {
2883
2959
  if (!handlers?.onRecordingStopped) return;
2884
2960
  return ctx.onRecordingStopped(handlers.onRecordingStopped);
2885
2961
  }, [handlers?.onRecordingStopped]);
2962
+ (0, import_react4.useEffect)(() => {
2963
+ if (!handlers?.onReaction) return;
2964
+ return ctx.onReaction(handlers.onReaction);
2965
+ }, [handlers?.onReaction]);
2886
2966
  const { sdk: _, ...publicApi } = ctx;
2887
2967
  return publicApi;
2888
2968
  };