@defcomm/meetingsdk 0.6.1 → 0.6.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/README.md CHANGED
@@ -1,1347 +1,1190 @@
1
- # DEFCOMM Meet SDK
1
+ # @defcomm/meetingsdk
2
2
 
3
- `@afosecure/meetingsdk` is the React/TypeScript client SDK used by **DEFCOMM Meet**.
3
+ A React and TypeScript SDK for building real-time audio/video meetings with DefComm Meet infrastructure.
4
4
 
5
- It provides the application layer for:
5
+ The SDK provides the meeting connection, participant state, media controls, chat, reactions, admission control, moderation, screen sharing, recording, transcript/summary access, network-quality state, and participant lifecycle events needed to build a complete meeting experience without implementing the SFU/WebSocket protocol yourself.
6
6
 
7
- - joining and leaving meetings
8
- - microphone and camera control
9
- - remote participant media
10
- - screen sharing
11
- - active-speaker detection
12
- - participant presence and reconnection state
13
- - private-room admission
14
- - server-side recording
15
- - room management
16
- - chat/PubSub integration
17
- - WebRTC and SFU signaling orchestration
7
+ ## Live sample
18
8
 
19
- The SDK is designed so that a DEFCOMM Meet consumer should **not need to manage WebRTC peer connections, SDP, ICE candidates, RTP tracks, reconnect logic, or browser media plumbing directly**.
9
+ A complete example application is available at:
20
10
 
21
- ---
11
+ **https://meet.defcomm.ng**
22
12
 
23
- ## Table of contents
24
-
25
- 1. [What the SDK does](#what-the-sdk-does)
26
- 2. [Architecture](#architecture)
27
- 3. [Installation](#installation)
28
- 4. [SDK configuration](#sdk-configuration)
29
- 5. [Creating a meeting](#creating-a-meeting)
30
- 6. [Joining a meeting](#joining-a-meeting)
31
- 7. [MeetingProvider](#meetingprovider)
32
- 8. [The `useMeeting` hook](#the-usemeeting-hook)
33
- 9. [Participants](#participants)
34
- 10. [Local participant](#local-participant)
35
- 11. [Remote media](#remote-media)
36
- 12. [Microphone and camera](#microphone-and-camera)
37
- 13. [Active speaker detection](#active-speaker-detection)
38
- 14. [Screen sharing](#screen-sharing)
39
- 15. [Recording](#recording)
40
- 16. [Private rooms and join approval](#private-rooms-and-join-approval)
41
- 17. [Reconnection and participant presence](#reconnection-and-participant-presence)
42
- 18. [Leaving a meeting](#leaving-a-meeting)
43
- 19. [Room management](#room-management)
44
- 20. [Meeting preview](#meeting-preview)
45
- 21. [Chat](#chat)
46
- 22. [Error handling](#error-handling)
47
- 23. [Recommended DEFCOMM Meet structure](#recommended-defcomm-meet-structure)
48
- 24. [Important implementation rules](#important-implementation-rules)
49
- 25. [Build and publish](#build-and-publish)
13
+ Use the sample as a reference for a full meeting UI, including pre-join device selection, waiting-room/admission behavior, participant lists, moderation, chat, reactions, screen sharing, and meeting controls.
50
14
 
51
15
  ---
52
16
 
53
- # What the SDK does
17
+ ## Installation
54
18
 
55
- DEFCOMM Meet is built around an **SFU architecture**.
19
+ Install the package from npm:
56
20
 
57
- The browser does not establish a separate peer connection with every participant.
21
+ ```bash
22
+ pnpm add @defcomm/meetingsdk
23
+ ```
58
24
 
59
- Instead:
25
+ or:
60
26
 
61
- ```text
62
- Participant A ──┐
63
- Participant B ──┼──► DEFCOMM Rust SFU ──► Participants
64
- Participant C ──┘
27
+ ```bash
28
+ npm install @defcomm/meetingsdk
29
+ ```
30
+
31
+ The SDK is intended for browser/client-side React applications because it uses browser media APIs such as `navigator.mediaDevices`, `MediaStream`, and WebRTC.
32
+
33
+ ### React / Next.js
34
+
35
+ For Next.js App Router, components that use the SDK should run on the client:
36
+
37
+ ```tsx
38
+ "use client";
65
39
  ```
66
40
 
67
- The SDK sits between the React application and the SFU:
41
+ ---
42
+
43
+ ## What the SDK does
44
+
45
+ At a high level, your application supplies a participant identity and an access token, creates a `MeetingProvider`, and uses the exported hooks to control the meeting.
68
46
 
69
47
  ```text
70
- ┌───────────────────────────────┐
71
- │ DEFCOMM Meet App │
72
- │ │
73
- │ Meeting UI / Participant UI │
74
- └───────────────┬───────────────┘
75
- │
76
- ▼
77
- ┌───────────────────────────────┐
78
- │ @afosecure/meetingsdk │
79
- │ │
80
- │ React hooks │
81
- │ Meeting state │
82
- │ WebRTC │
83
- │ WebSocket signaling │
84
- │ Reconnection │
85
- │ Active speaker detection │
86
- │ Media attachment │
87
- │ Recording controls │
88
- └───────────────┬───────────────┘
89
- │
90
- ▼
91
- ┌───────────────────────────────┐
92
- │ DEFCOMM Rust SFU │
93
- │ │
94
- │ Rooms │
95
- │ Participants │
96
- │ Admission │
97
- │ SDP / ICE │
98
- │ RTP forwarding │
99
- │ Track state │
100
- │ Recording │
101
- └───────────────────────────────┘
102
- ```
103
-
104
- ### The responsibility split
105
-
106
- **Your application owns:**
107
-
108
- - layout
109
- - buttons
110
- - participant tiles
111
- - navbar
112
- - chat UI
113
- - recording indicator
114
- - active-speaker styling
115
- - loading/error screens
116
-
117
- **The SDK owns:**
118
-
119
- - browser media access
120
- - publisher PeerConnection
121
- - subscriber PeerConnection
122
- - WebSocket signaling
123
- - media track mapping
124
- - reconnect/resume behavior
125
- - active-speaker detection
126
- - SDK meeting state
127
-
128
- **The SFU owns:**
129
-
130
- - room state
131
- - participant membership
132
- - admission/approval
133
- - signaling authority
134
- - media forwarding
135
- - track publication/subscription
136
- - server-side recording
48
+ Your application
49
+ │
50
+ ▼
51
+ MeetingProvider
52
+ │
53
+ ├── useMeeting()
54
+ ├── useParticipants()
55
+ ├── useLocalParticipant()
56
+ ├── useMeetingPreview()
57
+ ├── useRemoteMedia()
58
+ ├── useNetworkQuality()
59
+ └── usePubSub()
60
+ │
61
+ ▼
62
+ DefComm Meeting SDK
63
+ │
64
+ ├── REST API
65
+ └── SFU / WebSocket
66
+ ```
67
+
68
+ The SDK owns the media/session state and translates SFU events into React-friendly state and callbacks.
137
69
 
138
70
  ---
139
71
 
140
- # Installation
72
+ # 1. Authentication and meeting tokens
141
73
 
142
- ```bash
143
- npm install @afosecure/meetingsdk
74
+ The meeting client needs a token that authorizes the participant to connect to a room.
75
+
76
+ The recommended architecture is:
77
+
78
+ ```text
79
+ Your backend
80
+ │
81
+ ├── authenticates your application user
82
+ ├── decides which room the user may access
83
+ └── obtains/creates a DefComm meeting token
84
+ │
85
+ ▼
86
+ Your React app
87
+ │
88
+ ▼
89
+ <MeetingProvider />
144
90
  ```
145
91
 
146
- The SDK is intended for browser-based React + TypeScript applications.
92
+ Do **not** put a server-only API secret in browser code. Your backend should handle server credentials and issue the short-lived/access-scoped token that the browser uses for the meeting.
93
+
94
+ Your application is responsible for deciding who the participant is and for obtaining the token securely.
147
95
 
148
96
  ---
149
97
 
150
- # Creating a meeting
98
+ # 2. Basic integration
99
+
100
+ The smallest useful integration looks like this.
151
101
 
152
- The SDK exposes room management through `VideoSDKCore` and `useMeeting()`.
102
+ ## Step 1: Create the provider
153
103
 
154
104
  ```tsx
155
- const { createRoom } = useMeeting();
105
+ "use client";
156
106
 
157
- const room = await createRoom({
158
- name: "Engineering Standup",
159
- description: "Daily engineering meeting",
160
- capacity: 50,
161
- is_private: true,
162
- });
107
+ import { MeetingProvider } from "@defcomm/meetingsdk";
108
+
109
+ export default function MeetingShell() {
110
+ return (
111
+ <MeetingProvider
112
+ config={{
113
+ roomId: "ROOM_ID",
114
+ participantId: "USER_ID",
115
+ name: "Cornel",
116
+ token: "MEETING_ACCESS_TOKEN",
117
+ }}
118
+ >
119
+ <Meeting />
120
+ </MeetingProvider>
121
+ );
122
+ }
163
123
  ```
164
124
 
165
- A room can also be retrieved:
125
+ `MeetingProvider` supplies the meeting SDK to all child components.
166
126
 
167
- ```tsx
168
- const { getRoom } = useMeeting();
127
+ ### `config`
169
128
 
170
- const room = await getRoom(roomCode);
171
- ```
129
+ | Property | Required | Description |
130
+ |---|---:|---|
131
+ | `roomId` | Yes | DefComm room ID/code used for the meeting. |
132
+ | `participantId` | Yes | Your application's stable participant/user identifier. |
133
+ | `name` | Yes | Display name shown to other participants. |
134
+ | `token` | Yes | Access token generated/issued for the participant. |
135
+ | `audioMuted` | No | Start with microphone disabled. |
136
+ | `videoMuted` | No | Start with camera disabled. |
137
+ | `cameraDeviceId` | No | Camera to use when creating the initial local media stream. |
138
+ | `microphoneDeviceId` | No | Microphone to use when creating the initial local media stream. |
139
+
140
+ ---
172
141
 
173
- And deleted when appropriate:
142
+ ## Step 2: Join the meeting
174
143
 
175
144
  ```tsx
176
- const { deleteRoom } = useMeeting();
145
+ "use client";
146
+
147
+ import { useMeeting } from "@defcomm/meetingsdk";
177
148
 
178
- await deleteRoom(roomCode);
149
+ export function JoinButton() {
150
+ const { join } = useMeeting();
151
+
152
+ const handleJoin = async () => {
153
+ await join();
154
+ };
155
+
156
+ return <button onClick={handleJoin}>Join meeting</button>;
157
+ }
179
158
  ```
180
159
 
181
- Room creation and deletion should normally be restricted to the appropriate application role.
160
+ The provider's original `config` supplies the participant ID and token. `join()` can also receive per-join overrides for supported meeting configuration fields.
161
+
162
+ ```tsx
163
+ await join({
164
+ roomId: "ROOM_ID",
165
+ name: "Cornel",
166
+ cameraDeviceId: selectedCameraId,
167
+ microphoneDeviceId: selectedMicrophoneId,
168
+ audioMuted: false,
169
+ videoMuted: false,
170
+ });
171
+ ```
182
172
 
183
173
  ---
184
174
 
185
- # Joining a meeting
175
+ # 3. Participant lifecycle events
186
176
 
187
- The normal React integration starts with `MeetingProvider`.
177
+ Participant lifecycle events are first-class SDK events. Developers do not need to compare the participant list before and after every update.
188
178
 
189
- ```tsx
190
- import { MeetingProvider, useMeeting } from "@afosecure/meetingsdk";
179
+ The most useful events are:
191
180
 
192
- function MeetingRoom() {
193
- const { join, leave } = useMeeting();
181
+ - `onParticipantJoined`
182
+ - `onParticipantLeft`
183
+ - `onChatMessage`
194
184
 
195
- return (
196
- <div>
197
- <button onClick={() => join()}>Join meeting</button>
185
+ Example:
198
186
 
199
- <button onClick={() => leave()}>Leave</button>
200
- </div>
201
- );
202
- }
187
+ ```tsx
188
+ useMeeting({
189
+ onParticipantJoined: (participant) => {
190
+ console.log("Participant joined:", participant);
191
+ },
203
192
 
204
- export function MeetingPage() {
205
- return (
206
- <MeetingProvider
207
- config={{
208
- roomId: "room-code",
209
- name: "Afolabi",
210
- audioMuted: false,
211
- videoMuted: false,
212
- }}
213
- >
214
- <MeetingRoom />
215
- </MeetingProvider>
216
- );
217
- }
193
+ onParticipantLeft: (participant) => {
194
+ console.log("Participant left:", participant);
195
+ },
196
+
197
+ onChatMessage: (message) => {
198
+ console.log("Chat message:", message);
199
+ },
200
+ });
218
201
  ```
219
202
 
220
- `MeetingProvider` accepts:
203
+ These callbacks are especially useful for application-level notifications, sounds, analytics, toasts, logging, and custom UI.
221
204
 
222
- ```ts
223
- type MeetingConfig = {
224
- roomId: string;
225
- name: string;
226
- audioMuted?: boolean;
227
- videoMuted?: boolean;
228
- token?: string;
205
+ ## Join sound example
206
+
207
+ ```tsx
208
+ const playSound = (src: string) => {
209
+ const audio = new Audio(src);
210
+ audio.play().catch(() => {
211
+ // Browsers can block autoplay until the user interacts with the page.
212
+ });
229
213
  };
214
+
215
+ useMeeting({
216
+ onParticipantJoined: (participant) => {
217
+ console.log(`${participant.name ?? "A participant"} joined`);
218
+ playSound("/audio/join.mp3");
219
+ },
220
+ });
230
221
  ```
231
222
 
232
- ### Recommended join flow
223
+ ## Message notification example
233
224
 
234
- ```text
235
- Meeting page
236
- ↓
237
- MeetingProvider
238
- ↓
239
- User clicks Join
240
- ↓
241
- join()
242
- ↓
243
- Browser requests camera/microphone
244
- ↓
245
- SDK connects to SFU
246
- ↓
247
- Publisher PeerConnection
248
- ↓
249
- Subscriber PeerConnection
250
- ↓
251
- Remote tracks become available
225
+ ```tsx
226
+ useMeeting({
227
+ onChatMessage: (message) => {
228
+ playSound("/audio/message.mp3");
229
+ },
230
+ });
252
231
  ```
253
232
 
254
- Call `join()` from a user action whenever possible. This gives the browser a normal user-initiated context for requesting media permissions.
233
+ Because the callback represents an incoming SDK chat event, the application can decide how it wants to present the notification instead of the SDK forcing a particular sound or UI.
255
234
 
256
235
  ---
257
236
 
258
- # The `useMeeting` hook
237
+ # 4. React hooks
238
+
239
+ ## `useMeeting()`
259
240
 
260
- `useMeeting()` is the main application API.
241
+ The primary hook for meeting actions, state, and events.
261
242
 
262
243
  ```tsx
263
244
  const {
264
245
  join,
265
246
  leave,
247
+ room,
248
+ localParticipant,
249
+ participants,
250
+ messages,
251
+ presenterId,
252
+ activeSpeakerIds,
253
+ networkQuality,
254
+ recording,
266
255
 
267
256
  toggleMic,
268
257
  toggleCam,
269
-
270
258
  startScreenShare,
271
259
  stopScreenShare,
272
260
 
273
- startRecording,
274
- stopRecording,
275
- getRecordingStatus,
261
+ sendMessage,
262
+ sendReaction,
276
263
 
277
- participants,
278
- localParticipant,
279
- activeSpeakerIds,
280
- presenterId,
281
- room,
264
+ getMediaDevices,
265
+ selectCamera,
266
+ selectMicrophone,
282
267
 
283
268
  approveJoinRequest,
284
269
  rejectJoinRequest,
270
+ cancelJoinRequest,
285
271
 
286
- sendMessage,
287
- } = useMeeting();
288
- ```
289
-
290
- The hook also supports event handlers:
291
-
292
- ```tsx
293
- const meeting = useMeeting({
294
- onError: (error) => {
295
- console.error(error);
296
- },
297
-
298
- onActiveSpeakersChanged: (participantIds) => {
299
- console.log("Active speakers:", participantIds);
300
- },
272
+ muteAllAudio,
273
+ muteParticipant,
274
+ removeParticipant,
301
275
 
302
- onRecordingStarted: (recording) => {
303
- console.log("Recording started:", recording);
304
- },
276
+ startRecording,
277
+ stopRecording,
278
+ getRecordingStatus,
305
279
 
306
- onRecordingStopped: (recording) => {
307
- console.log("Recording stopped:", recording);
308
- },
309
- });
280
+ getJoinState,
281
+ getTranscript,
282
+ getMeetingSummary,
283
+ } = useMeeting();
310
284
  ```
311
285
 
312
- For normal React rendering, prefer the state exposed by the hook:
286
+ ### Event callbacks available on `useMeeting()`
313
287
 
314
288
  ```tsx
315
- const { activeSpeakerIds } = useMeeting();
289
+ useMeeting({
290
+ onError,
291
+ onEntryRequested,
292
+ onEntryResponded,
293
+ onJoinRequestCanceled,
294
+ onParticipantRemoved,
295
+ onParticipantPermissionsChanged,
296
+ onMeetingLeft,
297
+ onTrackStateChanged,
298
+ onRecordingStarted,
299
+ onRecordingStopped,
300
+ onReaction,
301
+ onActiveSpeakersChanged,
302
+ onConnectionStateChanged,
303
+ onNetworkQuality,
304
+ onParticipantJoined,
305
+ onParticipantLeft,
306
+ onChatMessage,
307
+ });
316
308
  ```
317
309
 
318
- Use event callbacks when the application needs to perform an additional side effect.
319
-
320
310
  ---
321
311
 
322
- # Participants
312
+ ## `useParticipants()`
323
313
 
324
- Use `useParticipants()` when the component only needs the participant list.
314
+ Returns the current participants as an array.
325
315
 
326
316
  ```tsx
327
- import { useParticipants } from "@afosecure/meetingsdk";
317
+ const participants = useParticipants();
328
318
 
329
- function ParticipantList() {
330
- const participants = useParticipants();
331
-
332
- return (
333
- <div>
334
- {participants.map((participant) => (
335
- <div key={participant.id}>{participant.name}</div>
336
- ))}
337
- </div>
338
- );
339
- }
319
+ return (
320
+ <ul>
321
+ {participants.map((participant) => (
322
+ <li key={participant.id}>
323
+ {participant.name}
324
+ </li>
325
+ ))}
326
+ </ul>
327
+ );
340
328
  ```
341
329
 
342
- A participant contains:
330
+ A participant contains information such as:
343
331
 
344
332
  ```ts
345
- type Participant = {
333
+ {
346
334
  id: string;
347
335
  name?: string;
348
336
  isHost?: boolean;
337
+ isModerator?: boolean;
338
+ permissions?: any;
349
339
  isLocal?: boolean;
350
340
  isPresenter?: boolean;
351
-
352
341
  connectionState?: "connected" | "reconnecting" | "disconnected";
353
-
354
342
  media?: {
355
343
  stream?: MediaStream | null;
356
344
  screenStream?: MediaStream | null;
357
-
358
345
  cameraTrack?: MediaStreamTrack;
359
346
  screenTrack?: MediaStreamTrack;
360
347
  audioTrack?: MediaStreamTrack;
361
-
362
348
  micEnabled: boolean;
363
349
  camEnabled: boolean;
364
350
  isScreenSharing: boolean;
365
-
366
- remoteScreenStreamId?: string;
367
- cameraStreamId?: string;
368
351
  };
369
- };
352
+ }
370
353
  ```
371
354
 
372
355
  ---
373
356
 
374
- # Local participant
357
+ ## `useLocalParticipant()`
375
358
 
376
- Use `useLocalParticipant()` when you need the local participant and local video.
359
+ Returns the local participant and a convenient callback ref for rendering the local media stream.
377
360
 
378
361
  ```tsx
379
- import { useLocalParticipant } from "@afosecure/meetingsdk";
380
-
381
- function LocalVideo() {
382
- const { participant, videoRef } = useLocalParticipant();
362
+ const { participant, videoRef } = useLocalParticipant();
383
363
 
384
- return <video ref={videoRef} autoPlay muted playsInline />;
385
- }
386
- ```
387
-
388
- The local participant exposes:
389
-
390
- ```tsx
391
- participant?.media?.micEnabled;
392
- participant?.media?.camEnabled;
393
- participant?.media?.isScreenSharing;
364
+ return (
365
+ <video
366
+ ref={videoRef}
367
+ autoPlay
368
+ muted
369
+ playsInline
370
+ />
371
+ );
394
372
  ```
395
373
 
396
- Use these values to render local microphone/camera state.
397
-
398
374
  ---
399
375
 
400
- # Remote media
376
+ # 5. Pre-join camera and microphone selection
401
377
 
402
- Use `useRemoteMedia(participantId)` for a remote participant.
378
+ Use `getMediaDevices()` before entering the meeting.
403
379
 
404
380
  ```tsx
405
- import { useRemoteMedia } from "@afosecure/meetingsdk";
381
+ const { getMediaDevices, selectCamera, selectMicrophone } = useMeeting();
406
382
 
407
- function RemoteParticipant({ participantId }: { participantId: string }) {
408
- const { videoRef, audioRef, isCamActive, isMicEnabled } =
409
- useRemoteMedia(participantId);
383
+ const devices = await getMediaDevices();
410
384
 
411
- return (
412
- <div>
413
- <video ref={videoRef} autoPlay playsInline muted />
385
+ const cameras = devices.filter((device) => device.kind === "videoinput");
386
+ const microphones = devices.filter(
387
+ (device) => device.kind === "audioinput",
388
+ );
389
+ ```
414
390
 
415
- <audio ref={audioRef} autoPlay />
391
+ Select a camera:
416
392
 
417
- {!isMicEnabled && <span>Muted</span>}
418
- {!isCamActive && <span>Camera off</span>}
419
- </div>
420
- );
421
- }
393
+ ```tsx
394
+ await selectCamera(cameraDeviceId);
422
395
  ```
423
396
 
424
- ### Why are video and audio separate?
397
+ Select a microphone:
425
398
 
426
- The SDK attaches the remote `MediaStream` to both elements:
399
+ ```tsx
400
+ await selectMicrophone(microphoneDeviceId);
401
+ ```
427
402
 
428
- - `<video>` is muted
429
- - `<audio>` handles remote audio
403
+ The SDK replaces the active media track when the meeting is already connected. The same camera-selection API can also be used by a pre-join UI so the application preview can follow the selected device.
430
404
 
431
- This makes media rendering explicit and avoids accidentally routing remote audio through the video element.
405
+ For browsers where device labels are initially empty, request media permission before calling `enumerateDevices()` so the browser can expose useful labels.
432
406
 
433
407
  ---
434
408
 
435
- # Microphone and camera
409
+ # 6. Microphone and camera controls
436
410
 
437
- The SDK exposes:
411
+ Toggle the local microphone:
438
412
 
439
413
  ```tsx
440
- const { toggleMic, toggleCam } = useMeeting();
414
+ const { toggleMic } = useMeeting();
415
+
416
+ <button onClick={toggleMic}>Toggle microphone</button>;
441
417
  ```
442
418
 
443
- Use:
419
+ Toggle the local camera:
444
420
 
445
421
  ```tsx
446
- <button onClick={toggleMic}>
447
- Microphone
448
- </button>
422
+ const { toggleCam } = useMeeting();
449
423
 
450
- <button onClick={toggleCam}>
451
- Camera
452
- </button>
424
+ <button onClick={toggleCam}>Toggle camera</button>;
453
425
  ```
454
426
 
455
- The SDK updates the local media track and sends the corresponding media state to the SFU.
427
+ The local participant state exposes `media.micEnabled` and `media.camEnabled`, so the button can reflect the current state.
428
+
429
+ ---
456
430
 
457
- Remote participants receive the authoritative track state through the meeting state.
431
+ # 7. Screen sharing
458
432
 
459
- For participant UI, use:
433
+ Start sharing:
460
434
 
461
435
  ```tsx
462
- participant.media?.micEnabled;
463
- participant.media?.camEnabled;
436
+ const { startScreenShare } = useMeeting();
437
+
438
+ const stream = await startScreenShare();
464
439
  ```
465
440
 
466
- rather than trying to infer another participant's mute state from RTP or `MediaStreamTrack.muted`.
441
+ Stop sharing:
442
+
443
+ ```tsx
444
+ const { stopScreenShare } = useMeeting();
445
+
446
+ stopScreenShare();
447
+ ```
448
+
449
+ The browser controls the permission prompt for screen capture.
467
450
 
468
451
  ---
469
452
 
470
- # Active speaker detection
453
+ # 8. Chat
471
454
 
472
- DEFCOMM Meet performs active-speaker detection **inside the SDK**.
455
+ Send a message:
456
+
457
+ ```tsx
458
+ const { sendMessage } = useMeeting();
473
459
 
474
- The application does not need to use Web Audio APIs, `AnalyserNode`, audio RMS calculations, timers, or WebRTC statistics.
460
+ sendMessage({
461
+ message: "Hello everyone!",
462
+ });
463
+ ```
475
464
 
476
- The SDK exposes:
465
+ Reply to a message:
477
466
 
478
467
  ```tsx
479
- const { activeSpeakerIds } = useMeeting();
468
+ sendMessage({
469
+ message: "Thanks!",
470
+ reply_to: {
471
+ id: message.id,
472
+ name: message.sender_name ?? "Participant",
473
+ },
474
+ });
480
475
  ```
481
476
 
482
- `activeSpeakerIds` is:
477
+ `ChatMessage` includes:
483
478
 
484
479
  ```ts
485
- string[]
480
+ {
481
+ id: string;
482
+ text: string;
483
+ sender_id: string;
484
+ sender_name?: string;
485
+ timestamp: number;
486
+ reply_to?: { id: string; name: string } | null;
487
+ target?: string | null;
488
+ }
486
489
  ```
487
490
 
488
- containing participant IDs currently detected as speaking.
491
+ For real-time notifications, prefer `onChatMessage` rather than watching the `messages` array manually.
492
+
493
+ ---
489
494
 
490
- ### Participant tile
495
+ # 9. Reactions
496
+
497
+ Send a reaction:
491
498
 
492
499
  ```tsx
493
- function ParticipantTile({ participant }: { participant: Participant }) {
494
- const { activeSpeakerIds } = useMeeting();
500
+ const { sendReaction } = useMeeting();
495
501
 
496
- const isActiveSpeaker = activeSpeakerIds.includes(participant.id);
502
+ sendReaction("👍");
503
+ ```
497
504
 
498
- return (
499
- <div className={isActiveSpeaker ? "ring-2 ring-green-400" : ""}>
500
- {participant.name}
501
- </div>
502
- );
503
- }
505
+ Listen for reactions:
506
+
507
+ ```tsx
508
+ useMeeting({
509
+ onReaction: (reaction) => {
510
+ console.log(reaction.emoji);
511
+ },
512
+ });
504
513
  ```
505
514
 
506
- ### Better pattern
515
+ ---
516
+
517
+ # 10. Private meetings and waiting-room admission
507
518
 
508
- For a participant list:
519
+ The SDK supports rooms where a participant must be approved before entering.
520
+
521
+ Check a participant's current join state:
509
522
 
510
523
  ```tsx
511
- const { activeSpeakerIds } = useMeeting();
524
+ const { getJoinState } = useMeeting();
525
+
526
+ const state = await getJoinState(roomCode);
527
+
528
+ if (state.status === "ready") {
529
+ // The participant can proceed into the meeting.
530
+ }
531
+
532
+ if (state.status === "approval_required") {
533
+ // Show your waiting-room UI.
534
+ }
535
+ ```
512
536
 
537
+ Possible states include:
538
+
539
+ ```ts
513
540
  {
514
- participants.map((participant) => (
515
- <ParticipantTile
516
- key={participant.id}
517
- participant={participant}
518
- isActiveSpeaker={activeSpeakerIds.includes(participant.id)}
519
- />
520
- ));
541
+ status: "ready";
542
+ reason: "admitted" | "open" | "moderator";
521
543
  }
522
544
  ```
523
545
 
524
- The tile should only render the state. It should not implement speaker detection itself.
546
+ or:
525
547
 
526
- ### Active speaker event
548
+ ```ts
549
+ {
550
+ status: "approval_required";
551
+ }
552
+ ```
527
553
 
528
- The SDK also exposes:
554
+ ### Moderator admission events
529
555
 
530
556
  ```tsx
531
557
  useMeeting({
532
- onActiveSpeakersChanged: (participantIds) => {
533
- console.log("Active speakers:", participantIds);
558
+ onEntryRequested: (request) => {
559
+ console.log("Join request:", request);
560
+ },
561
+ onEntryResponded: (payload, decision) => {
562
+ console.log("Admission decision:", decision);
534
563
  },
535
564
  });
536
565
  ```
537
566
 
538
- The event is useful for analytics or application-level side effects. For normal UI rendering, `activeSpeakerIds` is preferred.
567
+ Approve:
539
568
 
540
- ### Multiple speakers
569
+ ```tsx
570
+ await approveJoinRequest(requestId);
571
+ ```
541
572
 
542
- The API intentionally supports multiple active speakers:
573
+ Reject:
543
574
 
544
- ```ts
545
- activeSpeakerIds: string[]
575
+ ```tsx
576
+ await rejectJoinRequest(requestId);
546
577
  ```
547
578
 
548
- rather than:
579
+ Cancel the current participant's request:
549
580
 
550
- ```ts
551
- activeSpeakerId: string | null;
581
+ ```tsx
582
+ await cancelJoinRequest();
552
583
  ```
553
584
 
554
- This allows the UI to handle overlapping speech naturally.
555
-
556
585
  ---
557
586
 
558
- # Screen sharing
587
+ # 11. Moderation
559
588
 
560
- Start:
589
+ Moderator/host applications can use the SDK's moderation methods where the connected participant has permission.
561
590
 
562
- ```tsx
563
- const { startScreenShare } = useMeeting();
591
+ Mute everyone:
564
592
 
565
- await startScreenShare();
593
+ ```tsx
594
+ await muteAllAudio();
566
595
  ```
567
596
 
568
- Stop:
597
+ Mute one participant:
569
598
 
570
599
  ```tsx
571
- const { stopScreenShare } = useMeeting();
572
-
573
- stopScreenShare();
600
+ await muteParticipant(participantId);
574
601
  ```
575
602
 
576
- A participant's screen share is represented separately from their camera:
603
+ Remove a participant:
577
604
 
578
- ```ts
579
- participant.media?.screenStream;
580
- participant.media?.screenTrack;
581
- participant.media?.isScreenSharing;
605
+ ```tsx
606
+ await removeParticipant(participantId);
582
607
  ```
583
608
 
584
- ### Recommended UI
609
+ Listen for permission changes:
585
610
 
586
- When someone shares their screen:
587
-
588
- ```text
589
- ┌──────────────────────────────────────────┐
590
- │ │
591
- │ SCREEN SHARE │
592
- │ │
593
- │ │
594
- └──────────────────────────────────────────┘
595
-
596
- ┌─────────┐ ┌─────────┐ ┌─────────┐
597
- │ Alice │ │ Bob │ │ Charlie │
598
- └─────────┘ └─────────┘ └─────────┘
611
+ ```tsx
612
+ useMeeting({
613
+ onParticipantPermissionsChanged: (participantId, permissions) => {
614
+ console.log(participantId, permissions);
615
+ },
616
+ });
599
617
  ```
600
618
 
601
- Do not treat the screen track as the participant's camera track.
619
+ The server remains the authority for moderation permissions. The SDK should not be treated as the security boundary by itself.
602
620
 
603
621
  ---
604
622
 
605
- # Recording
623
+ # 12. Persistent moderators
606
624
 
607
- DEFCOMM Meet recording is **server-side**.
625
+ The SDK can retrieve and manage moderator assignments for a room.
608
626
 
609
- The browser does not use `MediaRecorder` to create the meeting recording.
627
+ ```tsx
628
+ const { getModerators, addModerator, removeModerator } = useMeeting();
610
629
 
611
- Start recording:
630
+ const moderators = await getModerators(roomCode);
612
631
 
613
- ```tsx
614
- const { startRecording } = useMeeting();
632
+ await addModerator(roomCode, userId);
615
633
 
616
- await startRecording();
634
+ await removeModerator(roomCode, userId);
617
635
  ```
618
636
 
619
- Stop recording:
637
+ Your backend/SFU should enforce which participant is allowed to perform each operation.
620
638
 
621
- ```tsx
622
- const { stopRecording } = useMeeting();
639
+ ---
623
640
 
624
- await stopRecording();
625
- ```
641
+ # 13. Recording
626
642
 
627
- Get current recording status:
643
+ Start:
628
644
 
629
645
  ```tsx
630
- const { getRecordingStatus } = useMeeting();
646
+ await startRecording();
647
+ ```
631
648
 
632
- const result = await getRecordingStatus();
649
+ Stop:
633
650
 
634
- console.log(result.recording);
651
+ ```tsx
652
+ await stopRecording();
635
653
  ```
636
654
 
637
- A recording has:
655
+ Check status:
638
656
 
639
- ```ts
640
- type RecordingInfo = {
641
- recording_id: string;
642
- started_at?: string;
643
- stopped_at?: string;
644
- active?: boolean;
645
- };
657
+ ```tsx
658
+ const status = await getRecordingStatus();
646
659
  ```
647
660
 
648
- ## Recording events
649
-
650
- The SDK exposes:
661
+ Listen for recording events:
651
662
 
652
663
  ```tsx
653
664
  useMeeting({
654
665
  onRecordingStarted: (recording) => {
655
666
  console.log("Recording started", recording);
656
667
  },
657
-
658
668
  onRecordingStopped: (recording) => {
659
669
  console.log("Recording stopped", recording);
660
670
  },
661
671
  });
662
672
  ```
663
673
 
664
- ### Recommended DEFCOMM Meet UI
665
-
666
- The meeting application can place the recording control in the meeting navbar:
674
+ ---
667
675
 
668
- ```text
669
- ┌───────────────────────────────────────────────────────────────┐
670
- │ Engineering 🔴 REC Chat People │
671
- └───────────────────────────────────────────────────────────────┘
672
- ```
676
+ # 14. Transcripts and meeting summaries
673
677
 
674
- Host before recording:
678
+ Retrieve transcript data:
675
679
 
676
- ```text
677
- ┌───────────────────────────────────────────────────────────────┐
678
- │ Engineering [ Start Recording ] Chat People │
679
- └───────────────────────────────────────────────────────────────┘
680
- ```
681
-
682
- Host while recording:
680
+ ```tsx
681
+ const response = await getTranscript(roomCode, sessionId);
683
682
 
684
- ```text
685
- ┌───────────────────────────────────────────────────────────────┐
686
- │ Engineering 🔴 Recording [ Stop ] Chat People │
687
- └───────────────────────────────────────────────────────────────┘
683
+ console.log(response.segments);
688
684
  ```
689
685
 
690
- Other participants should see the recording status but should not automatically receive the host's recording controls.
691
-
692
- ### Important recording rule
686
+ Retrieve a meeting summary:
693
687
 
694
- Do not assume that clicking the button means recording has started.
695
-
696
- The UI should use:
688
+ ```tsx
689
+ const response = await getMeetingSummary(roomCode, sessionId);
697
690
 
698
- ```ts
699
- onRecordingStarted;
691
+ console.log(response.summary);
700
692
  ```
701
693
 
702
- as the authoritative start event and:
694
+ The SDK exposes transcript segments, transcript sessions, and meeting summaries as typed objects.
703
695
 
704
- ```ts
705
- onRecordingStopped;
706
- ```
696
+ ---
707
697
 
708
- as the authoritative stop event.
698
+ # 15. Network quality
709
699
 
710
- Recommended state:
700
+ Read network quality through the hook or subscribe to updates.
711
701
 
712
702
  ```tsx
713
- const [isRecording, setIsRecording] = useState(false);
714
-
715
703
  useMeeting({
716
- onRecordingStarted: () => {
717
- setIsRecording(true);
718
- },
719
-
720
- onRecordingStopped: () => {
721
- setIsRecording(false);
704
+ onNetworkQuality: (quality) => {
705
+ console.log("Network quality:", quality.quality);
706
+ console.log("Packet loss:", quality.packet_loss_percent);
722
707
  },
723
708
  });
724
709
  ```
725
710
 
726
- This prevents the UI from claiming that a recording is active when the SFU has not confirmed it.
711
+ Available quality values are normalized into values such as `excellent`, `good`, `fair`, and `poor`.
727
712
 
728
713
  ---
729
714
 
730
- # Private rooms and join approval
731
-
732
- Private rooms can require host approval.
715
+ # 16. Connection state
733
716
 
734
- A host can listen for entry requests:
717
+ Listen for the meeting connection lifecycle:
735
718
 
736
719
  ```tsx
737
720
  useMeeting({
738
- onEntryRequested: (request) => {
739
- console.log("Join request:", request);
721
+ onConnectionStateChanged: (state) => {
722
+ console.log("Connection:", state);
740
723
  },
741
724
  });
742
725
  ```
743
726
 
744
- Approve:
745
-
746
- ```tsx
747
- const { approveJoinRequest } = useMeeting();
748
-
749
- approveJoinRequest(requestId);
750
- ```
751
-
752
- Reject:
727
+ The SDK exposes states including:
753
728
 
754
- ```tsx
755
- const { rejectJoinRequest } = useMeeting();
756
-
757
- rejectJoinRequest(requestId);
729
+ ```text
730
+ idle
731
+ connecting
732
+ joining
733
+ waiting_approval
734
+ connected
735
+ reconnecting
736
+ leaving
737
+ disconnected
758
738
  ```
759
739
 
760
- The SDK exposes the approval flow, while the SFU remains authoritative for whether a participant is admitted.
740
+ This is useful for displaying connecting/reconnecting UI and for deciding when meeting controls should be enabled.
761
741
 
762
742
  ---
763
743
 
764
- # Reconnection and participant presence
765
-
766
- The SDK distinguishes a temporary connection problem from an intentional leave.
767
-
768
- A participant can be:
744
+ # 17. Pub/Sub
769
745
 
770
- ```ts
771
- "connected";
772
- "reconnecting";
773
- "disconnected";
774
- ```
775
-
776
- Recommended UI:
746
+ The SDK includes typed Pub/Sub helpers for supported real-time topics.
777
747
 
778
748
  ```tsx
779
- if (participant.connectionState === "reconnecting") {
780
- return <ReconnectingBadge />;
781
- }
782
- ```
783
-
784
- A temporary network interruption should not immediately remove a participant from the UI.
785
-
786
- The SDK attempts to reconnect and resume the same logical participant session.
787
-
788
- Conceptually:
749
+ const reactions = usePubSub("REACTION");
789
750
 
790
- ```text
791
- Network interruption
792
- ↓
793
- WebSocket disconnect
794
- ↓
795
- reconnecting
796
- ↓
797
- refresh/reconnect
798
- ↓
799
- resume
800
- ↓
801
- publisher/subscriber recreated
802
- ↓
803
- media subscriptions restored
804
- ↓
805
- connected
751
+ reactions.publish({
752
+ emoji: "🎉",
753
+ });
806
754
  ```
807
755
 
808
- This is why applications should render `reconnecting` as a state rather than treating every WebSocket close as a participant leaving.
756
+ The currently exposed typed topics include:
809
757
 
810
- ---
758
+ - `SECURE_CHAT`
759
+ - `REACTION`
811
760
 
812
- # Stable participant identity
761
+ You can also listen at the provider level:
813
762
 
814
- The SDK stores a stable browser participant ID using:
763
+ ```tsx
764
+ const { onPubSubMessage } = useMeeting();
815
765
 
816
- ```text
817
- defcomm:participant_id
766
+ useEffect(() => {
767
+ return onPubSubMessage("REACTION", (message) => {
768
+ console.log(message.data.emoji);
769
+ });
770
+ }, [onPubSubMessage]);
818
771
  ```
819
772
 
820
- This prevents a browser reload or temporary reconnect from unnecessarily generating a completely new participant identity.
821
-
822
- Room resume state uses:
823
-
824
- ```text
825
- defcomm:sfu:resume:<roomCode>
826
- ```
773
+ ---
827
774
 
828
- The SDK treats temporary network loss differently from an intentional `leave()`.
775
+ # 18. A complete minimal meeting component
829
776
 
830
- Applications should use:
777
+ This is a useful starting point for an application developer.
831
778
 
832
779
  ```tsx
833
- await leave();
834
- ```
835
-
836
- for an intentional departure instead of manually closing the WebSocket.
837
-
838
- ---
839
-
840
- # Leaving a meeting
780
+ "use client";
841
781
 
842
- Use:
782
+ import { useMeeting, useParticipants } from "@defcomm/meetingsdk";
843
783
 
844
- ```tsx
845
- const { leave } = useMeeting();
784
+ export function Meeting() {
785
+ const participants = useParticipants();
846
786
 
847
- await leave();
848
- ```
787
+ const {
788
+ join,
789
+ leave,
790
+ toggleMic,
791
+ toggleCam,
792
+ sendMessage,
793
+ startScreenShare,
794
+ stopScreenShare,
795
+ } = useMeeting({
796
+ onParticipantJoined: (participant) => {
797
+ console.log("Joined:", participant.name);
798
+ },
799
+ onParticipantLeft: (participant) => {
800
+ console.log("Left:", participant.name);
801
+ },
802
+ onChatMessage: (message) => {
803
+ console.log("Message:", message.text);
804
+ },
805
+ onConnectionStateChanged: (state) => {
806
+ console.log("State:", state);
807
+ },
808
+ });
849
809
 
850
- Do not implement the Leave button by doing:
810
+ return (
811
+ <div>
812
+ <button onClick={() => void join()}>Join</button>
813
+ <button onClick={() => toggleMic()}>Mic</button>
814
+ <button onClick={() => toggleCam()}>Camera</button>
815
+ <button onClick={() => void startScreenShare()}>
816
+ Share screen
817
+ </button>
818
+ <button onClick={() => stopScreenShare()}>Stop sharing</button>
819
+ <button
820
+ onClick={() =>
821
+ sendMessage({ message: "Hello from the meeting" })
822
+ }
823
+ >
824
+ Send message
825
+ </button>
826
+ <button onClick={() => leave()}>Leave</button>
851
827
 
852
- ```ts
853
- websocket.close();
828
+ <p>Participants: {participants.length}</p>
829
+ </div>
830
+ );
831
+ }
854
832
  ```
855
833
 
856
- or by directly destroying the SDK's PeerConnections.
857
-
858
- The SDK owns cleanup and the SFU leave lifecycle.
834
+ For a production application, add your own pre-join screen, participant tiles, remote media rendering, chat panel, moderation UI, waiting room, error states, and responsive/mobile controls.
859
835
 
860
836
  ---
861
837
 
862
- # Room management
863
-
864
- Room management is available through `useMeeting()`:
838
+ # 19. Recommended application structure
865
839
 
866
- ```tsx
867
- const { createRoom, getRoom, deleteRoom } = useMeeting();
868
- ```
840
+ A maintainable application can be organized like this:
869
841
 
870
- ### Create
871
-
872
- ```tsx
873
- await createRoom({
874
- name: "Product Review",
875
- description: "Weekly product review",
876
- capacity: 100,
877
- is_private: false,
878
- });
842
+ ```text
843
+ app/
844
+ ├── meeting/
845
+ │ ├── page.tsx
846
+ │ ├── MeetingShell.tsx
847
+ │ ├── PreJoin.tsx
848
+ │ ├── WaitingRoom.tsx
849
+ │ ├── MeetingRoom.tsx
850
+ │ ├── ParticipantGrid.tsx
851
+ │ ├── ParticipantItem.tsx
852
+ │ ├── ChatPanel.tsx
853
+ │ ├── ControlPanel.tsx
854
+ │ └── ModeratorPanel.tsx
855
+ │
856
+ ├── api/
857
+ │ └── meeting-token/
858
+ │ └── route.ts
859
+ │
860
+ └── ...
879
861
  ```
880
862
 
881
- ### Get
863
+ A useful separation is:
882
864
 
883
- ```tsx
884
- await getRoom(roomCode);
865
+ ```text
866
+ Authentication/token creation → your backend
867
+ Room/meeting UI → your application
868
+ Media/session transport → @defcomm/meetingsdk
869
+ SFU protocol → DefComm infrastructure
885
870
  ```
886
871
 
887
- ### Delete
872
+ ---
888
873
 
889
- ```tsx
890
- await deleteRoom(roomCode);
891
- ```
874
+ # 20. Browser permissions
892
875
 
893
- ---
876
+ The SDK uses standard browser media APIs. Your application must run in a secure context such as HTTPS for production camera/microphone access.
894
877
 
895
- # Meeting preview
878
+ Users may deny access to the camera or microphone. Always handle rejected media permissions and display a useful recovery message.
896
879
 
897
- The SDK exports:
880
+ Example:
898
881
 
899
882
  ```tsx
900
- import { useMeetingPreview } from "@afosecure/meetingsdk";
883
+ try {
884
+ await join();
885
+ } catch (error) {
886
+ console.error("Unable to join:", error);
887
+ }
901
888
  ```
902
889
 
903
- Use preview when the application needs a pre-join screen for room information or local setup.
890
+ Common browser requirements include:
904
891
 
905
- The actual live meeting begins when `join()` connects the SDK to the SFU.
892
+ - HTTPS in production
893
+ - Camera permission for video
894
+ - Microphone permission for audio
895
+ - User interaction may be required before playing notification sounds
896
+ - Screen sharing requires browser support and user permission
906
897
 
907
898
  ---
908
899
 
909
- # Chat
910
-
911
- The SDK exposes the current chat interface through:
900
+ # 21. Event design guidance
912
901
 
913
- ```tsx
914
- const { sendMessage } = useMeeting();
915
- ```
902
+ Use SDK events for one-time application reactions and hooks/state for rendering.
916
903
 
917
- Send:
904
+ ### Good use of events
918
905
 
919
906
  ```tsx
920
- sendMessage({
921
- message: "Hello everyone",
907
+ useMeeting({
908
+ onParticipantJoined: () => playSound("/audio/join.mp3"),
909
+ onParticipantLeft: () => showToast("Participant left"),
910
+ onChatMessage: () => playSound("/audio/message.mp3"),
922
911
  });
923
912
  ```
924
913
 
925
- The input type is:
926
-
927
- ```ts
928
- type ChatInput = {
929
- message: string;
930
- reply_to?: {
931
- id: string;
932
- name: string;
933
- } | null;
934
- target?: string | null;
935
- };
936
- ```
937
-
938
- The SDK also exposes:
914
+ ### Good use of state
939
915
 
940
916
  ```tsx
941
- usePubSub("SECURE_CHAT");
917
+ const participants = useParticipants();
918
+ const { messages, localParticipant } = useMeeting();
942
919
  ```
943
920
 
944
- when an application wants the Pub/Sub style interface.
945
-
946
- Example:
947
-
948
- ```tsx
949
- const chat = usePubSub("SECURE_CHAT");
950
-
951
- chat.publish({
952
- message: "Hello",
953
- });
954
- ```
921
+ This distinction keeps UI rendering predictable and avoids implementing your own event-detection logic in every application.
955
922
 
956
923
  ---
957
924
 
958
- # Error handling
925
+ # 22. Error handling
959
926
 
960
- Use the SDK error event:
927
+ Subscribe to the normalized SDK error callback:
961
928
 
962
929
  ```tsx
963
930
  useMeeting({
964
931
  onError: (error) => {
965
- console.error(error);
932
+ console.error(error.code, error.message);
966
933
  },
967
934
  });
968
935
  ```
969
936
 
970
- Errors contain:
937
+ SDK errors include a stable `code`, a human-readable `message`, and additional context where available.
938
+
939
+ Example shape:
971
940
 
972
941
  ```ts
973
- type SDKError = {
942
+ {
974
943
  code: string;
975
944
  message: string;
976
945
  roomId?: string | null;
977
946
  participantId?: string;
978
- raw?: any;
947
+ raw?: unknown;
979
948
  recoverable?: boolean;
980
- };
981
- ```
982
-
983
- A useful UI rule is:
984
-
985
- ```text
986
- recoverable = true
987
- ↓
988
- show retry/reconnecting UI
989
-
990
- recoverable = false
991
- ↓
992
- show a blocking error or return to the appropriate screen
993
- ```
994
-
995
- Always wrap user-triggered asynchronous operations when appropriate:
996
-
997
- ```tsx
998
- async function handleJoin() {
999
- try {
1000
- await join();
1001
- } catch (error) {
1002
- console.error("Unable to join meeting", error);
1003
- }
1004
949
  }
1005
950
  ```
1006
951
 
1007
- ---
1008
-
1009
- # Recommended DEFCOMM Meet structure
1010
-
1011
- A production DEFCOMM Meet page can be structured like this:
1012
-
1013
- ```text
1014
- MeetingPage
1015
- │
1016
- └── MeetingProvider
1017
- │
1018
- ├── MeetingNav
1019
- │ ├── Meeting name
1020
- │ ├── Recording state/control
1021
- │ ├── Chat
1022
- │ └── Participants
1023
- │
1024
- ├── MeetingStage
1025
- │ │
1026
- │ ├── ScreenShareStage
1027
- │ │
1028
- │ └── ParticipantGrid
1029
- │ └── ParticipantTile[]
1030
- │ ├── Video
1031
- │ ├── Name
1032
- │ ├── Mic state
1033
- │ ├── Camera state
1034
- │ ├── Active speaker state
1035
- │ └── Reconnecting state
1036
- │
1037
- └── MeetingControls
1038
- ├── Microphone
1039
- ├── Camera
1040
- ├── Screen share
1041
- └── Leave
1042
- ```
1043
-
1044
- The application should consume SDK state rather than recreating WebRTC state locally.
952
+ Applications should show user-friendly UI and log the technical error details for debugging.
1045
953
 
1046
954
  ---
1047
955
 
1048
- # Recommended participant tile
1049
-
1050
- A participant tile should combine the SDK's states:
956
+ # 23. Leaving a meeting
1051
957
 
1052
958
  ```tsx
1053
- function ParticipantTile({ participant }: { participant: Participant }) {
1054
- const { activeSpeakerIds } = useMeeting();
1055
-
1056
- const isSpeaking = activeSpeakerIds.includes(participant.id);
1057
-
1058
- const isReconnecting = participant.connectionState === "reconnecting";
1059
-
1060
- const micEnabled = participant.media?.micEnabled ?? false;
1061
-
1062
- const camEnabled = participant.media?.camEnabled ?? false;
1063
-
1064
- const isScreenSharing = participant.media?.isScreenSharing ?? false;
1065
-
1066
- return (
1067
- <div
1068
- className={
1069
- isSpeaking ? "participant-tile active-speaker" : "participant-tile"
1070
- }
1071
- >
1072
- {isReconnecting && <div className="reconnecting">Reconnecting...</div>}
959
+ const { leave } = useMeeting();
1073
960
 
1074
- {/* Remote/local media */}
961
+ leave();
962
+ ```
1075
963
 
1076
- <div className="participant-name">{participant.name}</div>
964
+ You can also listen for the lifecycle event:
1077
965
 
1078
- <div className="participant-status">
1079
- {!micEnabled && <span>Muted</span>}
1080
- {!camEnabled && <span>Camera off</span>}
1081
- {isScreenSharing && <span>Sharing</span>}
1082
- </div>
1083
- </div>
1084
- );
1085
- }
966
+ ```tsx
967
+ useMeeting({
968
+ onMeetingLeft: () => {
969
+ console.log("Meeting ended for this client");
970
+ },
971
+ });
1086
972
  ```
1087
973
 
1088
- The important design principle is:
974
+ ---
1089
975
 
1090
- ```text
1091
- SDK state → UI
1092
- ```
976
+ # 24. Security recommendations
1093
977
 
1094
- not:
978
+ The SDK is a client library. Security-sensitive authorization must remain enforced by the backend/SFU.
1095
979
 
1096
- ```text
1097
- UI → WebRTC internals
1098
- ```
980
+ Recommended production practices:
981
+
982
+ 1. Never expose server API secrets in frontend JavaScript.
983
+ 2. Issue room-scoped participant credentials from your backend.
984
+ 3. Enforce room membership and moderation permissions on the server.
985
+ 4. Do not trust a client-provided `isHost` or `isModerator` value as authorization.
986
+ 5. Keep meeting tokens short-lived or otherwise appropriately scoped.
987
+ 6. Validate all room and participant operations server-side.
988
+ 7. Rate-limit sensitive endpoints such as token creation, join requests, moderation, and recording operations.
989
+ 8. Use HTTPS/WSS in production.
1099
990
 
1100
991
  ---
1101
992
 
1102
- # Public exports
993
+ # 25. TypeScript
1103
994
 
1104
- The SDK currently exports:
995
+ The SDK exports the primary public types used by applications, including:
1105
996
 
1106
997
  ```ts
1107
- // Core
1108
- VideoSDKCore;
1109
- SFUClient;
1110
- MeetingState;
1111
- ActiveSpeakerDetector;
1112
-
1113
- // React
1114
- MeetingProvider;
1115
- useMeetingContext;
1116
- useMeeting;
1117
- useParticipants;
1118
- useLocalParticipant;
1119
- useRemoteMedia;
1120
- useMeetingPreview;
1121
-
1122
- // Types
1123
- Participant;
1124
- ChatInput;
1125
- RecordingInfo;
1126
- MeetingConfig;
1127
- SFUConnectionState;
1128
- TrackInfo;
1129
- ParticipantInfo;
1130
- ```
1131
-
1132
- Most DEFCOMM Meet application developers should only need the React API:
998
+ Participant
999
+ ChatInput
1000
+ ChatMessage
1001
+ MeetingConfig
1002
+ Reaction
1003
+ RecordingInfo
1004
+ SFUConnectionState
1005
+ JoinState
1006
+ NetworkQuality
1007
+ TranscriptSegment
1008
+ TranscriptSession
1009
+ TranscriptResponse
1010
+ MeetingSummary
1011
+ PubSubTopic
1012
+ PubSubMessage
1013
+ ```
1014
+
1015
+ For example:
1133
1016
 
1134
- ```tsx
1135
- import {
1136
- MeetingProvider,
1137
- useMeeting,
1138
- useParticipants,
1139
- useLocalParticipant,
1140
- useRemoteMedia,
1141
- useMeetingPreview,
1142
- } from "@afosecure/meetingsdk";
1017
+ ```ts
1018
+ import type {
1019
+ Participant,
1020
+ ChatMessage,
1021
+ MeetingConfig,
1022
+ } from "@defcomm/meetingsdk";
1143
1023
  ```
1144
1024
 
1145
- Direct use of `VideoSDKCore` or `SFUClient` is intended for advanced integrations.
1146
-
1147
1025
  ---
1148
1026
 
1149
- # Important implementation rules
1027
+ # 26. Full sample application
1150
1028
 
1151
- ## 1. Do not build a mesh
1029
+ The recommended way to understand the SDK end-to-end is to compare your integration with the DefComm Meet reference application:
1152
1030
 
1153
- Do not create:
1031
+ **https://meet.defcomm.ng**
1154
1032
 
1155
- ```text
1156
- Participant A ↔ Participant B
1157
- Participant A ↔ Participant C
1158
- Participant B ↔ Participant C
1159
- ```
1033
+ The sample demonstrates how the SDK can be composed into a complete product rather than exposing a single monolithic meeting component.
1160
1034
 
1161
- The SDK is designed for:
1035
+ Typical flow:
1162
1036
 
1163
1037
  ```text
1164
- Participants
1165
- │
1166
- ▼
1167
- DEFCOMM SFU
1168
- │
1169
- ▼
1170
- Participants
1171
- ```
1172
-
1173
- ---
1174
-
1175
- ## 2. Do not manipulate the SDK's PeerConnections
1176
-
1177
- Applications should not directly access:
1178
-
1179
- ```ts
1180
- RTCPeerConnection;
1181
- RTCSessionDescription;
1182
- RTCIceCandidate;
1183
- RTCRtpSender;
1038
+ Landing / authentication
1039
+ ↓
1040
+ Fetch or create room
1041
+ ↓
1042
+ Fetch participant meeting token
1043
+ ↓
1044
+ Pre-join camera + microphone selection
1045
+ ↓
1046
+ Join-state check
1047
+ ├── ready ───────────────→ Meeting room
1048
+ │
1049
+ └── approval_required ───→ Waiting room
1050
+ ↓
1051
+ approved
1052
+ ↓
1053
+ Meeting room
1184
1054
  ```
1185
1055
 
1186
- for normal meeting functionality.
1187
-
1188
- The SDK owns those objects.
1189
-
1190
1056
  ---
1191
1057
 
1192
- ## 3. Do not infer remote mute state from media silence
1058
+ # 27. Troubleshooting
1193
1059
 
1194
- Use:
1060
+ ## Camera or microphone list is empty
1195
1061
 
1196
- ```ts
1197
- participant.media?.micEnabled;
1198
- participant.media?.camEnabled;
1199
- ```
1200
-
1201
- The SFU signaling state is authoritative.
1062
+ Call the media permission flow first, then enumerate devices. Browser privacy rules can hide device labels until permission has been granted.
1202
1063
 
1203
- ---
1064
+ ## Camera changes but the preview does not update
1204
1065
 
1205
- ## 4. Do not implement speaker detection in the application
1066
+ The application should render the current local `MediaStream`/track rather than caching the first stream forever. Use the SDK's local participant stream and re-render/rebind the `<video>` element when the stream changes.
1206
1067
 
1207
- Use:
1068
+ ## Notification sound does not play
1208
1069
 
1209
- ```tsx
1210
- const { activeSpeakerIds } = useMeeting();
1211
- ```
1070
+ Browsers can block autoplay. Trigger sounds after a user interaction, or catch the rejected `audio.play()` promise.
1212
1071
 
1213
- The SDK owns audio analysis and active-speaker state.
1072
+ ## Participant joined callback fires unexpectedly during reconnect
1214
1073
 
1215
- ---
1074
+ Treat the lifecycle event as a participant lifecycle event, not as a low-level WebSocket reconnect notification. Use `onConnectionStateChanged` for connection/reconnection UI.
1216
1075
 
1217
- ## 5. Do not use browser recording for meeting recording
1076
+ ## Private-room join returns `approval_required`
1218
1077
 
1219
- Use:
1078
+ Show the application's waiting-room UI and wait for the admission decision before treating the participant as fully admitted.
1220
1079
 
1221
- ```tsx
1222
- await startRecording();
1223
- await stopRecording();
1224
- ```
1080
+ ## `useMeeting()` throws an error about context
1225
1081
 
1226
- Meeting recording is server-side.
1082
+ Make sure the component using `useMeeting()` is rendered inside `MeetingProvider`.
1227
1083
 
1228
1084
  ---
1229
1085
 
1230
- ## 6. Do not manually close the WebSocket to leave
1086
+ # 28. API summary
1231
1087
 
1232
- Use:
1088
+ ### Provider
1233
1089
 
1234
1090
  ```tsx
1235
- await leave();
1091
+ <MeetingProvider config={...}>
1092
+ {children}
1093
+ </MeetingProvider>
1236
1094
  ```
1237
1095
 
1238
- This allows the SDK to perform the intended cleanup and leave lifecycle.
1239
-
1240
- ---
1241
-
1242
- ## 7. Do not immediately remove reconnecting participants
1243
-
1244
- Use:
1096
+ ### Hooks
1245
1097
 
1246
1098
  ```ts
1247
- participant.connectionState;
1099
+ useMeeting()
1100
+ useParticipants()
1101
+ useLocalParticipant()
1102
+ useRemoteMedia()
1103
+ useMeetingPreview()
1104
+ useNetworkQuality()
1105
+ usePubSub()
1248
1106
  ```
1249
1107
 
1250
- and show:
1108
+ ### Core meeting actions
1251
1109
 
1252
- ```text
1253
- Reconnecting...
1110
+ ```ts
1111
+ join()
1112
+ leave()
1113
+ toggleMic()
1114
+ toggleCam()
1115
+ startScreenShare()
1116
+ stopScreenShare()
1117
+ sendMessage()
1118
+ sendReaction()
1254
1119
  ```
1255
1120
 
1256
- during transient failures.
1257
-
1258
- ---
1259
-
1260
- ## 8. Keep screen share separate from camera
1261
-
1262
- Use:
1121
+ ### Devices
1263
1122
 
1264
1123
  ```ts
1265
- participant.media.screenStream;
1266
- participant.media.screenTrack;
1267
- participant.media.isScreenSharing;
1124
+ getMediaDevices()
1125
+ selectCamera()
1126
+ selectMicrophone()
1268
1127
  ```
1269
1128
 
1270
- instead of replacing the camera stream.
1271
-
1272
- ---
1273
-
1274
- # Build and publish
1129
+ ### Admission and moderation
1275
1130
 
1276
- Install dependencies:
1277
-
1278
- ```bash
1279
- npm install
1131
+ ```ts
1132
+ getJoinState()
1133
+ approveJoinRequest()
1134
+ rejectJoinRequest()
1135
+ cancelJoinRequest()
1136
+ muteAllAudio()
1137
+ muteParticipant()
1138
+ removeParticipant()
1139
+ getModerators()
1140
+ addModerator()
1141
+ removeModerator()
1280
1142
  ```
1281
1143
 
1282
- Build:
1144
+ ### Recording and meeting data
1283
1145
 
1284
- ```bash
1285
- npm run build
1146
+ ```ts
1147
+ startRecording()
1148
+ stopRecording()
1149
+ getRecordingStatus()
1150
+ getTranscript()
1151
+ getMeetingSummary()
1286
1152
  ```
1287
1153
 
1288
- Before publishing a new SDK version, verify:
1154
+ ### Lifecycle events
1289
1155
 
1290
- ```text
1291
- ✓ TypeScript build succeeds
1292
- ✓ React integration works
1293
- ✓ Join/leave works
1294
- ✓ Camera works
1295
- ✓ Microphone works
1296
- ✓ Remote audio/video works
1297
- ✓ Screen sharing works
1298
- ✓ Active speaker detection works
1299
- ✓ Reconnection works
1300
- ✓ Private-room admission works
1301
- ✓ Recording start/stop works
1302
- ✓ Recording callbacks fire
1303
- ✓ Production WSS/API configuration works
1156
+ ```ts
1157
+ onParticipantJoined
1158
+ onParticipantLeft
1159
+ onChatMessage
1160
+ onConnectionStateChanged
1161
+ onMeetingLeft
1162
+ onError
1163
+ onEntryRequested
1164
+ onEntryResponded
1165
+ onJoinRequestCanceled
1166
+ onParticipantRemoved
1167
+ onParticipantPermissionsChanged
1168
+ onTrackStateChanged
1169
+ onRecordingStarted
1170
+ onRecordingStopped
1171
+ onReaction
1172
+ onActiveSpeakersChanged
1173
+ onNetworkQuality
1304
1174
  ```
1305
1175
 
1306
1176
  ---
1307
1177
 
1308
- # DEFCOMM Meet integration principle
1178
+ # 29. Support and examples
1309
1179
 
1310
- The purpose of this SDK is to make the DEFCOMM meeting experience feel like a single integrated platform.
1180
+ For the complete working example, visit:
1311
1181
 
1312
- A consumer should be able to think in terms of:
1182
+ **https://meet.defcomm.ng**
1313
1183
 
1314
- ```tsx
1315
- const {
1316
- join,
1317
- leave,
1318
- toggleMic,
1319
- toggleCam,
1320
- startScreenShare,
1321
- stopScreenShare,
1322
- startRecording,
1323
- stopRecording,
1324
- participants,
1325
- activeSpeakerIds,
1326
- } = useMeeting();
1327
- ```
1184
+ The reference application is the recommended starting point for seeing how the SDK APIs fit together in a production-style meeting interface.
1328
1185
 
1329
- rather than:
1186
+ ---
1330
1187
 
1331
- ```text
1332
- WebSocket messages
1333
- SDP offers/answers
1334
- ICE candidates
1335
- PeerConnections
1336
- RTP tracks
1337
- SSRCs
1338
- audio analysers
1339
- reconnect timers
1340
- resume tokens
1341
- ```
1342
-
1343
- Those implementation details belong inside the SDK/SFU layer.
1344
-
1345
- **DEFCOMM Meet owns the meeting experience.
1346
- The SDK owns the browser media experience.
1347
- The SFU owns the authoritative meeting/media infrastructure.**
1188
+ ## License
1189
+
1190
+ See the package/repository license for the applicable terms.