@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.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/react/useLocalParticipant.tsx
2
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
2
+ import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
3
3
 
4
4
  // src/react/MeetingProvider.tsx
5
- import { createContext, useContext, useMemo, useRef } from "react";
5
+ import { createContext, useContext, useMemo, useRef, useState as useState2 } from "react";
6
6
 
7
7
  // src/config/ws.ts
8
8
  var SDK_CONFIG = {
@@ -368,6 +368,9 @@ var SFUClient = class {
368
368
  this.publisherRevision = 0;
369
369
  this.screenShareGeneration = 0;
370
370
  this.currentParticipantId = null;
371
+ // Provider/application token used for authenticated REST calls.
372
+ this.authToken = null;
373
+ // Room/SFU token returned by /token and used for the WebSocket.
371
374
  this.token = null;
372
375
  this.room = null;
373
376
  this.resumeToken = null;
@@ -408,6 +411,7 @@ var SFUClient = class {
408
411
  this.apiBase = options.apiBase || "https://api.example.com/api";
409
412
  this.wsBase = options.wsBase || "wss://api.example.com/ws";
410
413
  this.participantId = options.participantId || this.createId();
414
+ this.authToken = options.token ?? null;
411
415
  this.onStateChange = options.onStateChange || (() => {
412
416
  });
413
417
  this.onParticipantJoined = options.onParticipantJoined || (() => {
@@ -531,9 +535,14 @@ var SFUClient = class {
531
535
  return (++this.requestId).toString();
532
536
  }
533
537
  async request(url, options = {}) {
538
+ const headers = new Headers(options.headers);
539
+ headers.set("Content-Type", "application/json");
540
+ if (this.authToken) {
541
+ headers.set("Authorization", `Bearer ${this.authToken}`);
542
+ }
534
543
  const res = await fetch(url, {
535
- headers: { "Content-Type": "application/json" },
536
- ...options
544
+ ...options,
545
+ headers
537
546
  });
538
547
  if (!res.ok) {
539
548
  let body = {};
@@ -541,7 +550,9 @@ var SFUClient = class {
541
550
  body = await res.json();
542
551
  } catch {
543
552
  }
544
- throw new Error(body.message || res.statusText);
553
+ throw new Error(
554
+ body.message || `Request failed: ${res.status} ${res.statusText}`
555
+ );
545
556
  }
546
557
  return res.json();
547
558
  }
@@ -1945,6 +1956,7 @@ var VideoSDKCore = class {
1945
1956
  }
1946
1957
  });
1947
1958
  this.myId = options.participantId;
1959
+ this.sfuClient = this.createSFUClient(options.token);
1948
1960
  }
1949
1961
  emitError(code, message, raw, recoverable = true) {
1950
1962
  const err = {
@@ -2009,36 +2021,16 @@ var VideoSDKCore = class {
2009
2021
  }
2010
2022
  // ROOM MANAGEMENT
2011
2023
  async createRoom(input) {
2012
- const client = this.sfuClient ?? new SFUClient({
2013
- apiBase: this.apiBase,
2014
- wsBase: this.wsBase,
2015
- participantId: this.myId
2016
- });
2017
- return client.createRoom(input);
2024
+ return this.sfuClient.createRoom(input);
2018
2025
  }
2019
2026
  async getRoom(roomCode) {
2020
- const client = this.sfuClient ?? new SFUClient({
2021
- apiBase: this.apiBase,
2022
- wsBase: this.wsBase,
2023
- participantId: this.myId
2024
- });
2025
- return client.getRoom(roomCode);
2027
+ return this.sfuClient.getRoom(roomCode);
2026
2028
  }
2027
2029
  async getJoinState(roomCode) {
2028
- const client = this.sfuClient ?? new SFUClient({
2029
- apiBase: this.apiBase,
2030
- wsBase: this.wsBase,
2031
- participantId: this.myId
2032
- });
2033
- return client.getJoinState(roomCode);
2030
+ return this.sfuClient.getJoinState(roomCode);
2034
2031
  }
2035
2032
  async deleteRoom(roomCode) {
2036
- const client = this.sfuClient ?? new SFUClient({
2037
- apiBase: this.apiBase,
2038
- wsBase: this.wsBase,
2039
- participantId: this.myId
2040
- });
2041
- return client.deleteRoom(roomCode);
2033
+ return this.sfuClient.deleteRoom(roomCode);
2042
2034
  }
2043
2035
  // JOIN
2044
2036
  async joinMeeting(config) {
@@ -2104,26 +2096,36 @@ var VideoSDKCore = class {
2104
2096
  }
2105
2097
  // SFU CONNECTION
2106
2098
  async connect(roomId, name) {
2107
- if (this.sfuClient) {
2108
- const state = this.sfuClient.getConnectionState();
2109
- if (this.room.id === roomId && (state === "connecting" || state === "connected" || state === "reconnecting")) {
2110
- console.info(
2111
- "[SDK] Existing SFU connection is active; connect ignored",
2112
- {
2113
- roomId,
2114
- state
2115
- }
2116
- );
2117
- return;
2118
- }
2099
+ if (!this.sfuClient) {
2100
+ throw new Error("SFU client is not initialized");
2101
+ }
2102
+ const state = this.sfuClient.getConnectionState();
2103
+ if (this.room.id === roomId && (state === "connecting" || state === "joining" || state === "waiting_approval" || state === "connected" || state === "reconnecting")) {
2104
+ console.info("[SDK] Existing SFU connection is active; connect ignored", {
2105
+ roomId,
2106
+ state
2107
+ });
2108
+ return;
2109
+ }
2110
+ if (state !== "idle" && state !== "disconnected") {
2119
2111
  throw new Error(
2120
- `SFU client already exists in state "${state}". Disconnect explicitly before creating another connection.`
2112
+ `SFU client is already active in state "${state}". Leave the current meeting before joining another room.`
2121
2113
  );
2122
2114
  }
2123
- const client = new SFUClient({
2115
+ this.sfuClient.setLocalStream(this.localStream);
2116
+ await this.sfuClient.getToken(roomId, {
2117
+ displayName: name,
2118
+ participantId: this.myId,
2119
+ audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? true),
2120
+ videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? true)
2121
+ });
2122
+ }
2123
+ createSFUClient(token) {
2124
+ return new SFUClient({
2124
2125
  apiBase: this.apiBase,
2125
2126
  wsBase: this.wsBase,
2126
2127
  participantId: this.myId,
2128
+ token,
2127
2129
  autoSubscribe: true,
2128
2130
  // CONNECTION STATE
2129
2131
  onStateChange: (state) => {
@@ -2462,14 +2464,6 @@ var VideoSDKCore = class {
2462
2464
  );
2463
2465
  }
2464
2466
  });
2465
- this.sfuClient = client;
2466
- client.setLocalStream(this.localStream);
2467
- await client.getToken(roomId, {
2468
- displayName: name,
2469
- participantId: this.myId,
2470
- audioMuted: !(this.localStream?.getAudioTracks()[0]?.enabled ?? true),
2471
- videoMuted: !(this.localStream?.getVideoTracks()[0]?.enabled ?? true)
2472
- });
2473
2467
  }
2474
2468
  // SFU STATE
2475
2469
  handleSFUState(state) {
@@ -2703,6 +2697,28 @@ var VideoSDKCore = class {
2703
2697
  }
2704
2698
  this.sfuClient.sendReaction(reaction);
2705
2699
  }
2700
+ /**
2701
+ * Generic PubSub publishing entry point.
2702
+ *
2703
+ * The public SDK exposes one PubSub API for all topics. The current SFU
2704
+ * protocol still has dedicated wire messages for chat and reactions, so
2705
+ * this method is the compatibility bridge. The application does not need
2706
+ * to know about those wire-level message types.
2707
+ */
2708
+ publishPubSub(topic, data) {
2709
+ switch (topic) {
2710
+ case "SECURE_CHAT":
2711
+ this.sendChatMessage(data);
2712
+ return;
2713
+ case "REACTION":
2714
+ this.sendReaction(data.emoji);
2715
+ return;
2716
+ default: {
2717
+ const exhaustive = topic;
2718
+ throw new Error(`Unsupported PubSub topic: ${exhaustive}`);
2719
+ }
2720
+ }
2721
+ }
2706
2722
  // CLEANUP
2707
2723
  cleanupRemoteParticipant(participantId) {
2708
2724
  this.activeSpeakerDetector.removeParticipant(participantId);
@@ -2728,10 +2744,8 @@ var VideoSDKCore = class {
2728
2744
  async performDisconnect() {
2729
2745
  this.intentionalDisconnect = true;
2730
2746
  this.joinPromise = null;
2731
- const client = this.sfuClient;
2732
- this.sfuClient = null;
2733
- if (client) {
2734
- await client.leave();
2747
+ if (this.sfuClient) {
2748
+ await this.sfuClient.leave();
2735
2749
  }
2736
2750
  if (this.localStream) {
2737
2751
  this.localStream.getTracks().forEach((track) => track.stop());
@@ -2793,6 +2807,7 @@ var MeetingProvider = ({
2793
2807
  const pubSubListeners = useRef(
2794
2808
  /* @__PURE__ */ new Map()
2795
2809
  );
2810
+ const [reactionPubSubMessages, setReactionPubSubMessages] = useState2([]);
2796
2811
  const activeSpeakerListeners = useRef(
2797
2812
  /* @__PURE__ */ new Set()
2798
2813
  );
@@ -2802,7 +2817,8 @@ var MeetingProvider = ({
2802
2817
  if (!sdkRef.current) {
2803
2818
  sdkRef.current = new VideoSDKCore(
2804
2819
  {
2805
- participantId: config.participantId
2820
+ participantId: config.participantId,
2821
+ token: config.token
2806
2822
  },
2807
2823
  {
2808
2824
  onError: (err) => {
@@ -2826,21 +2842,43 @@ var MeetingProvider = ({
2826
2842
  onRecordingStopped: (recording2) => {
2827
2843
  recordingStoppedListeners.current.forEach((fn) => fn(recording2));
2828
2844
  },
2845
+ onChatMessage: (message) => {
2846
+ const msg = {
2847
+ id: message.id,
2848
+ text: message.text,
2849
+ sender_id: message.sender_id,
2850
+ sender_name: message.sender_name,
2851
+ timestamp: Number(message.timestamp),
2852
+ reply_to: message.reply_to ?? null,
2853
+ target: message.target ?? null
2854
+ };
2855
+ const pubSubMessage = {
2856
+ id: msg.id,
2857
+ topic: "SECURE_CHAT",
2858
+ sender_id: msg.sender_id,
2859
+ sender_name: msg.sender_name,
2860
+ timestamp: msg.timestamp,
2861
+ data: msg
2862
+ };
2863
+ pubSubListeners.current.get("SECURE_CHAT")?.forEach((fn) => fn(pubSubMessage));
2864
+ },
2829
2865
  onReaction: (reaction) => {
2830
2866
  reactionListeners.current.forEach((fn) => fn(reaction));
2831
- const listeners = pubSubListeners.current.get("REACTION");
2832
- if (!listeners) return;
2867
+ const timestamp = typeof reaction.timestamp === "number" ? reaction.timestamp : Date.parse(reaction.timestamp);
2833
2868
  const message = {
2834
2869
  id: reaction.id,
2835
2870
  topic: "REACTION",
2836
2871
  sender_id: reaction.participant_id,
2837
2872
  sender_name: reaction.sender_name,
2838
- timestamp: typeof reaction.timestamp === "number" ? reaction.timestamp : Date.parse(reaction.timestamp),
2873
+ timestamp,
2839
2874
  data: {
2840
2875
  emoji: reaction.emoji
2841
2876
  }
2842
2877
  };
2843
- listeners.forEach((fn) => fn(message));
2878
+ setReactionPubSubMessages(
2879
+ (previous) => [...previous, message].slice(-50)
2880
+ );
2881
+ pubSubListeners.current.get("REACTION")?.forEach((fn) => fn(message));
2844
2882
  },
2845
2883
  onConnectionStateChanged: (state) => {
2846
2884
  connectionStateListeners.current.forEach(
@@ -2903,32 +2941,32 @@ var MeetingProvider = ({
2903
2941
  activeSpeakerIds,
2904
2942
  recording,
2905
2943
  usePubSub: (topic) => {
2944
+ if (topic === "SECURE_CHAT") {
2945
+ const chatMessages = messages.map((message) => ({
2946
+ id: message.id,
2947
+ topic: "SECURE_CHAT",
2948
+ sender_id: message.sender_id,
2949
+ sender_name: message.sender_name,
2950
+ timestamp: message.timestamp,
2951
+ data: message
2952
+ }));
2953
+ return {
2954
+ messages: chatMessages,
2955
+ publish: ((data) => {
2956
+ sdk.publishPubSub("SECURE_CHAT", data);
2957
+ })
2958
+ };
2959
+ }
2906
2960
  if (topic === "REACTION") {
2907
2961
  return {
2908
- messages: [],
2909
- publish: (data) => {
2910
- const reaction = data;
2911
- if (!reaction?.emoji) {
2962
+ messages: reactionPubSubMessages,
2963
+ publish: ((data) => {
2964
+ if (!data?.emoji) {
2912
2965
  console.warn("[PubSub] REACTION requires an emoji");
2913
2966
  return;
2914
2967
  }
2915
- sdk.sendReaction(reaction.emoji);
2916
- }
2917
- };
2918
- }
2919
- if (topic === "SECURE_CHAT") {
2920
- return {
2921
- messages: sdk.state.getChatMessages().map((message) => ({
2922
- id: message.id,
2923
- topic: "SECURE_CHAT",
2924
- sender_id: message.sender_id,
2925
- sender_name: message.sender_name,
2926
- timestamp: message.timestamp,
2927
- data: message
2928
- })),
2929
- publish: (data) => {
2930
- sdk.sendChatMessage(data);
2931
- }
2968
+ sdk.publishPubSub("REACTION", data);
2969
+ })
2932
2970
  };
2933
2971
  }
2934
2972
  throw new Error(`Unsupported PubSub topic: "${topic}"`);
@@ -3013,7 +3051,8 @@ var MeetingProvider = ({
3013
3051
  messages,
3014
3052
  presenterId,
3015
3053
  recording,
3016
- activeSpeakerIds
3054
+ activeSpeakerIds,
3055
+ reactionPubSubMessages
3017
3056
  ]);
3018
3057
  return /* @__PURE__ */ jsx(MeetingContext.Provider, { value, children });
3019
3058
  };
@@ -3027,7 +3066,7 @@ var useMeetingContext = () => {
3027
3066
  // src/react/useLocalParticipant.tsx
3028
3067
  var useLocalParticipant = () => {
3029
3068
  const { sdk } = useMeetingContext();
3030
- const [localParticipant, setLocalParticipant] = useState2(
3069
+ const [localParticipant, setLocalParticipant] = useState3(
3031
3070
  () => {
3032
3071
  const current = sdk.state.localParticipant;
3033
3072
  return current && current.id ? current : null;
@@ -3116,10 +3155,10 @@ var useMeeting = (handlers) => {
3116
3155
  };
3117
3156
 
3118
3157
  // src/react/useParticipants.ts
3119
- import { useEffect as useEffect4, useState as useState3 } from "react";
3158
+ import { useEffect as useEffect4, useState as useState4 } from "react";
3120
3159
  var useParticipants = () => {
3121
3160
  const { sdk } = useMeetingContext();
3122
- const [participants, setParticipants] = useState3(
3161
+ const [participants, setParticipants] = useState4(
3123
3162
  () => sdk.state.getParticipants()
3124
3163
  );
3125
3164
  useEffect4(() => {
@@ -3134,12 +3173,12 @@ var useParticipants = () => {
3134
3173
  };
3135
3174
 
3136
3175
  // src/react/useRemoteMedia.ts
3137
- import { useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
3176
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
3138
3177
  var useRemoteMedia = (participantId) => {
3139
3178
  const { sdk } = useMeetingContext();
3140
3179
  const videoRef = useRef3(null);
3141
3180
  const audioRef = useRef3(null);
3142
- const [participant, setParticipant] = useState4(
3181
+ const [participant, setParticipant] = useState5(
3143
3182
  () => sdk.state.getParticipant(participantId) || null
3144
3183
  );
3145
3184
  useEffect5(() => {
@@ -3175,12 +3214,12 @@ var useRemoteMedia = (participantId) => {
3175
3214
  };
3176
3215
 
3177
3216
  // src/react/useMeetingPreview.ts
3178
- import { useEffect as useEffect6, useState as useState5 } from "react";
3217
+ import { useEffect as useEffect6, useState as useState6 } from "react";
3179
3218
  function useMeetingPreview(roomId, participantId) {
3180
- const [room, setRoom] = useState5(null);
3181
- const [error, setError] = useState5(null);
3182
- const [isConnected, setIsConnected] = useState5(false);
3183
- const [isLoading, setIsLoading] = useState5(true);
3219
+ const [room, setRoom] = useState6(null);
3220
+ const [error, setError] = useState6(null);
3221
+ const [isConnected, setIsConnected] = useState6(false);
3222
+ const [isLoading, setIsLoading] = useState6(true);
3184
3223
  useEffect6(() => {
3185
3224
  if (!roomId) {
3186
3225
  setIsLoading(false);
@@ -3250,7 +3289,10 @@ function usePubSub(topic, options) {
3250
3289
  const ctx = useMeetingContext();
3251
3290
  useEffect7(() => {
3252
3291
  if (!options?.onMessageReceived) return;
3253
- return ctx.onPubSubMessage(topic, options.onMessageReceived);
3292
+ return ctx.onPubSubMessage(
3293
+ topic,
3294
+ options.onMessageReceived
3295
+ );
3254
3296
  }, [ctx, topic, options?.onMessageReceived]);
3255
3297
  return ctx.usePubSub(topic);
3256
3298
  }