sceyt-call 1.1.4 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +185 -213
  2. package/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,24 +1,20 @@
1
1
  # Sceyt Call SDK
2
2
 
3
- JavaScript SDK for real-time voice and video calls with WebRTC. Works alongside the [SceytChat SDK](https://www.npmjs.com/package/sceyt-chat).
3
+ JavaScript SDK for real-time voice and video calls with WebRTC. This SDK works alongside the [SceytChat SDK](https://www.npmjs.com/package/sceyt-chat) to provide calling functionality.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
7
  * [Installation](#installation)
8
+ * [Prerequisites](#prerequisites)
8
9
  * [Quick Start](#quick-start)
9
- * [Core Flow](#core-flow)
10
10
  * [Media Flow Types](#media-flow-types)
11
- * [Creating a Call](#creating-a-call)
12
- * [Joining a Call](#joining-a-call)
13
- * [Answering & Rejecting](#answering--rejecting)
14
- * [Invite Keys](#invite-keys)
15
11
  * [Call Methods](#call-methods)
16
- * [Call Properties](#call-properties)
12
+ * [Error Handling](#error-handling)
17
13
  * [Call Events](#call-events)
18
14
  * [Client Events](#client-events)
19
15
  * [Call Management](#call-management)
20
16
  * [Call History (CDR)](#call-history-cdr)
21
- * [Error Handling](#error-handling)
17
+ * [Participant States](#participant-states)
22
18
  * [Logging](#logging)
23
19
  * [TypeScript Support](#typescript-support)
24
20
  * [License](#license)
@@ -33,7 +29,7 @@ yarn add sceyt-call
33
29
 
34
30
  ## Prerequisites
35
31
 
36
- The Sceyt Call SDK requires the [SceytChat SDK](https://www.npmjs.com/package/sceyt-chat) to be installed and connected before initializing the call client.
32
+ The Sceyt Call SDK requires the [SceytChat SDK](https://www.npmjs.com/package/sceyt-chat) to be installed and connected before initializing the call client. The chat client handles authentication and signaling for calls.
37
33
 
38
34
  ```bash
39
35
  npm install sceyt-chat
@@ -46,52 +42,39 @@ const chatClient = new SceytChat('{apiUrl}', '{appId}', '{clientId}');
46
42
  await chatClient.connect('{accessToken}');
47
43
  ```
48
44
 
49
- For detailed setup, see the [sceyt-chat documentation](https://www.npmjs.com/package/sceyt-chat).
45
+ For detailed chat client setup, see the [sceyt-chat documentation](https://www.npmjs.com/package/sceyt-chat).
50
46
 
51
47
  ## Quick Start
52
48
 
53
49
  ```typescript
54
50
  import SceytCallClient, { MediaFlow } from 'sceyt-call';
55
51
 
52
+ // Initialize call client with connected chat client
56
53
  const callClient = new SceytCallClient(chatClient);
57
54
 
58
55
  // Listen for incoming calls
59
56
  callClient.on('invitedToCall', ({ call }) => {
57
+ console.log(`Incoming call from ${call.createdBy}`);
60
58
  call.sendRinging();
61
- // call.join(...) when user answers
59
+ // Show incoming call UI...
62
60
  });
63
61
 
64
- // Start a call
65
- const result = callClient.prepareCall('my-call-id', {
62
+ // Start a call — prepare first, then join
63
+ const result = callClient.prepareCall('call-123', {
66
64
  mediaFlow: MediaFlow.P2P,
67
65
  participantIds: ['user1', 'user2'],
68
66
  });
69
- if (result.success && result.data) {
70
- result.data.join({
71
- audioSettings: { publishAudio: true },
72
- videoSettings: { publishVideo: true },
73
- });
74
- }
75
- ```
76
-
77
- ## Core Flow
67
+ const call = result.data;
68
+ call?.join({ audioSettings: { publishAudio: true } });
78
69
 
79
- All calls follow a two-step pattern:
80
-
81
- 1. **`callClient.prepareCall(callId, createOptions?)`** — Create a local `Call` object (no media yet). The call is placed in `prepareCalls`.
82
- 2. **`call.join(options)`** or **`call.create(errorCallback?)`** — Start signaling and media. On success the call moves to `activeCalls`.
83
-
84
- ```typescript
85
- // Prepare with full options
86
- const result = callClient.prepareCall('call-123', {
87
- mediaFlow: MediaFlow.SFU,
88
- participantIds: ['user1', 'user2'],
89
- videoCall: true,
90
- settings: { startsAt: Date.now(), expiresAt: Date.now() + 3600000 },
70
+ // Handle call events
71
+ call.on('callStateChanged', ({ state }) => {
72
+ console.log(`Call state: ${state}`);
91
73
  });
92
74
 
93
- // Prepare with just an invite key (mediaFlow resolved from key)
94
- const result = callClient.prepareCall(inviteKey);
75
+ call.on('participantStateChanged', ({ participant, state }) => {
76
+ console.log(`${participant.id} is now ${state}`);
77
+ });
95
78
  ```
96
79
 
97
80
  ## Media Flow Types
@@ -102,189 +85,187 @@ const result = callClient.prepareCall(inviteKey);
102
85
  | `MediaFlow.SFU` | Server-routed media (Selective Forwarding Unit) | Group calls |
103
86
  | `MediaFlow.S2W` | SIP-to-WebRTC media processing | 1:1 calls |
104
87
 
105
- ## Creating a Call
106
-
107
- Use `call.create()` to send invitations without joining yourself. All call options (`participantIds`, `metadata`, `settings`, etc.) are set via `prepareCall`.
88
+ ## Call Methods
108
89
 
109
90
  ```typescript
110
- const result = callClient.prepareCall('my-call-link', {
111
- mediaFlow: MediaFlow.SFU,
112
- participantIds: ['user1', 'user2'],
113
- videoCall: true,
114
- metadata: { topic: 'standup' },
115
- settings: {
116
- expiresAt: Date.now() + 3600000,
117
- persistent: true,
118
- },
119
- });
91
+ // Audio controls
92
+ call.mute(true); // Mute microphone
93
+ call.mute(false); // Unmute
94
+ call.hold(true); // Put on hold
120
95
 
121
- result.data?.create((error) => {
122
- // Optional: handle server-side error
123
- console.error(error.error?.message);
124
- });
125
- ```
96
+ // Video controls
97
+ await call.enableVideo(true);
98
+ await call.startScreenShare();
99
+ await call.stopScreenShare();
126
100
 
127
- ## Joining a Call
101
+ // Device selection
102
+ const cameras = await call.getAvailableVideoDevices();
103
+ await call.selectVideoDevice(cameras[0].deviceId);
128
104
 
129
- ```typescript
130
- // Join only (e.g. incoming call)
131
- call.join({
132
- audioSettings: { publishAudio: true },
133
- videoSettings: { publishVideo: false }
134
- });
105
+ const mics = await call.getAvailableAudioDevices();
106
+ await call.selectAudioDevice(mics[0].deviceId);
135
107
 
136
- // Prepare with participants then join
137
- const result = callClient.prepareCall('call-123', {
138
- mediaFlow: MediaFlow.P2P,
139
- participantIds: ['user1'],
140
- videoCall: true,
141
- });
142
- result.data?.join({
143
- audioSettings: { publishAudio: true },
144
- videoSettings: { publishVideo: true },
145
- });
108
+ // Participants
109
+ await call.addParticipants(['user3', 'user4']);
110
+ await call.switchToSFU(); // Upgrade P2P to SFU
146
111
  ```
147
112
 
148
- ### `JoinCallOptions`
149
-
150
- | Field | Type | Description |
151
- |-------|------|-------------|
152
- | `audioSettings` | `AudioSettings` | Audio publish settings |
153
- | `videoSettings` | `VideoSettings` | Video publish settings |
154
- | `useMediaProxy` | `boolean` | Route media through proxy |
155
- | `localAudioTracks` | `MediaStreamTrack[]` | Custom audio tracks |
156
- | `localVideoTracks` | `MediaStreamTrack[]` | Custom video tracks |
157
-
158
- ### `AudioSettings`
113
+ ## Error Handling
159
114
 
160
- | Field | Type | Description |
161
- |-------|------|-------------|
162
- | `publishAudio` | `boolean` | Publish audio on join. Default: `true` |
163
- | `preferredAudioRoute` | `string \| null` | Preferred microphone device ID |
115
+ All methods return `CallClientResult<T>` with structured error information instead of throwing exceptions:
164
116
 
165
- ### `VideoSettings`
117
+ ### Pattern 1: Check Result Success
166
118
 
167
- | Field | Type | Description |
168
- |-------|------|-------------|
169
- | `publishVideo` | `boolean` | Publish video on join. Default: `true` |
119
+ ```typescript
120
+ const result = await call.enableVideo(true);
121
+ if (!result.success) {
122
+ console.error('Error:', result.error?.message);
123
+ console.error('Code:', result.error?.code);
124
+ }
125
+ ```
170
126
 
171
- ### `CreateCallOptions`
127
+ ### Pattern 2: Use Error Callback
172
128
 
173
- | Field | Type | Description |
174
- |-------|------|-------------|
175
- | `mediaFlow` | `MediaFlow` | Media routing mode (required) |
176
- | `participantIds` | `string[]` | User IDs to invite |
177
- | `videoCall` | `boolean` | Whether this is a video call |
178
- | `metadata` | `Record<string, string \| number>` | Custom metadata |
179
- | `settings` | `CallSettings` | Scheduling and broadcast settings |
129
+ Some methods support optional error callbacks for real-time error feedback:
180
130
 
181
- ### `CallSettings`
131
+ ```typescript
132
+ const handleError = (result) => {
133
+ if (result.error) {
134
+ console.error(result.error.message, result.error.code);
135
+ }
136
+ };
137
+
138
+ // Synchronous methods — check return value
139
+ const prepareResult = callClient.prepareCall('call-123', { mediaFlow: MediaFlow.P2P });
140
+ if (!prepareResult.success) console.error(prepareResult.error?.message);
141
+
142
+ // Join — sync result + optional async signal error callback
143
+ const joinResult = call.join({ audioSettings: { publishAudio: true } }, handleError);
144
+ if (!joinResult.success) console.error(joinResult.error?.message);
145
+
146
+ // Create without joining — optional async signal error callback
147
+ call.create(handleError);
148
+
149
+ // Ringing notification — optional async signal error callback
150
+ call.sendRinging(handleError);
151
+
152
+ // Reject — optional async signal error callback
153
+ call.reject('busy', handleError);
154
+
155
+ // Async methods — await result + optional async signal error callback
156
+ const videoResult = await call.enableVideo(true, handleError);
157
+ await call.startScreenShare(handleError);
158
+ await call.stopScreenShare(handleError);
159
+ call.mute(true, handleError);
160
+ await call.switchToSFU(handleError);
161
+ ```
182
162
 
183
- | Field | Type | Description |
184
- |-------|------|-------------|
185
- | `startsAt` | `number` | Scheduled start time (epoch ms) |
186
- | `expiresAt` | `number` | Expiry time (epoch ms) |
187
- | `broadcastSettings` | `BroadcastSettings` | `{ enabled: boolean }` |
188
- | `persistent` | `boolean` | Keep call alive when all participants leave |
163
+ **Methods Supporting Error Callbacks:**
164
+ - `callClient.prepareCall(callId, options?)` — check return value
165
+ - `call.join(options, errorCallback?)` signal rejection delivered via callback
166
+ - `call.create(errorCallback?)` server CREATE rejection delivered via callback
167
+ - `call.sendRinging(errorCallback?)` signal error delivered via callback
168
+ - `call.reject(reason?, errorCallback?)` signal error delivered via callback
169
+ - `call.enableVideo(enabled, errorCallback?)` — camera/signal errors via callback
170
+ - `call.startScreenShare(errorCallback?)` — screen share errors via callback
171
+ - `call.stopScreenShare(errorCallback?)` — signal errors via callback
172
+ - `call.mute(mute, errorCallback?)` — signal errors via callback
173
+ - `call.switchToSFU(errorCallback?)` — signal errors via callback
174
+ - `call.leave()` — no callback, check return value
175
+
176
+ **All Error Codes:**
177
+
178
+ | Code | Error Type | Cause | Solution |
179
+ |------|-----------|-------|----------|
180
+ | 4000 | `BadSignal` | Invalid signal format | Check signal parameters |
181
+ | 4001 | `CallNotFound` | Call not found in system | Verify call ID exists |
182
+ | 4002 | `ParticipantNotFound` | Participant not in call | Check participant exists |
183
+ | 4003 | `NotAllowed` | Operation not allowed in state | Verify call state |
184
+ | 4004 | `ParticipantAlreadyExists` | Participant already in call | Don't add duplicates |
185
+ | 4005 | `NotAllowed` | Permission denied by user | Enable in browser settings |
186
+ | 4006 | `BadRequest` | Invalid request parameters | Fix parameters |
187
+ | 5001 | `InternalError` | Server-side error | Retry (resendable) |
188
+ | 9901 | `NetworkError` | Network connectivity lost | Check connection (resendable) |
189
+ | 9902 | `Timeout` | Operation timed out | Retry (resendable) |
190
+ | 9903 | `NetworkError` | Network error occurred | Check connection (resendable) |
191
+ | 9904 | `NetworkError` | Network error occurred | Check connection (resendable) |
189
192
 
190
- ## Answering & Rejecting
193
+ ## Call Events
191
194
 
192
195
  ```typescript
193
- callClient.on('invitedToCall', ({ call }) => {
194
- call.sendRinging();
195
-
196
- // Answer
197
- call.join({ audioSettings: { publishAudio: true } });
196
+ call.on('audioTrackAdded', ({ participant, track }) => {
197
+ // Play participant's audio
198
+ const audio = new Audio();
199
+ audio.srcObject = new MediaStream([track]);
200
+ audio.play();
201
+ });
198
202
 
199
- // Reject
200
- call.reject('busy');
203
+ call.on('videoTrackAdded', ({ participant, track }) => {
204
+ // Display participant's video
205
+ videoElement.srcObject = new MediaStream([track]);
201
206
  });
202
- ```
203
207
 
204
- ## Invite Keys
208
+ call.on('participantEvent', ({ participant, event }) => {
209
+ // event: 'Mute' | 'Unmute' | 'Hold' | 'VideoEnabled' | etc.
210
+ });
205
211
 
206
- ```typescript
207
- // Generate a shareable key from an active call
208
- const key = call.generateInviteKey();
212
+ call.on('activeSpeakersChanged', ({ activeSpeakers }) => {
213
+ // Handle active speaker detection
214
+ });
209
215
 
210
- // Join via invite key
211
- const result = callClient.prepareCall(key);
212
- result.data?.join({ audioSettings: { publishAudio: true } });
216
+ call.on('sessionRenewed', ({ call, sessionId }) => {
217
+ // Fired when the server assigns a new sessionId to an existing call
218
+ });
213
219
  ```
214
220
 
215
- ## Call Methods
221
+ ## Client Events
216
222
 
217
223
  ```typescript
218
- call.mute(true / false);
219
- call.hold(true / false);
220
- await call.enableVideo(true / false);
221
- await call.startScreenShare();
222
- await call.stopScreenShare();
223
- call.addParticipants(['user3']);
224
- await call.switchToSFU();
225
- await call.selectVideoDevice(deviceId);
226
- await call.selectAudioDevice(deviceId);
227
- call.leave();
228
- call.reject('busy');
229
- ```
230
-
231
- ## Call Properties
224
+ callClient.on('invitedToCall', ({ call }) => {
225
+ // Handle incoming call
226
+ });
232
227
 
233
- ```typescript
234
- call.id; // Unique call identifier
235
- call.sessionId; // Server-assigned session ID
236
- call.mediaFlow; // Current media flow type
237
- call.state; // Current CallState
238
- call.participants; // All Participant objects
239
- call.localParticipant; // The local user's Participant
240
- call.activeSpeakers; // ActiveSpeakerInfo[] — currently speaking participants
241
- call.settings; // CallSettings (startsAt, expiresAt, persistent, broadcastSettings)
228
+ callClient.on('ongoingCallsUpdated', ({ calls }) => {
229
+ // Handle call list updates
230
+ });
242
231
  ```
243
232
 
244
- ## Call Events
233
+ ## Call Management
245
234
 
246
235
  ```typescript
247
- call.on('callStateChanged', ({ call, state }) => {});
248
- call.on('participantStateChanged', ({ call, participant, state }) => {});
249
- call.on('participantConnectionStateChanged', ({ call, participant, state }) => {});
250
- call.on('participantEvent', ({ call, participant, event }) => {});
251
- // event: 'Mute' | 'Unmute' | 'Hold' | 'Unhold' | 'VideoEnabled' | 'VideoDisabled'
252
- // | 'ScreenSharingStarted' | 'ScreenSharingStopped'
253
- call.on('participantsAdded', ({ call, participants }) => {});
254
- call.on('participantsRemoved', ({ call, participants }) => {});
255
- call.on('audioTrackAdded', ({ call, participant, track }) => {});
256
- call.on('audioTrackRemoved', ({ call, participant }) => {});
257
- call.on('videoTrackAdded', ({ call, participant, track }) => {});
258
- call.on('videoTrackRemoved', ({ call, participant }) => {});
259
- call.on('activeSpeakersChanged', ({ call, activeSpeakers }) => {});
260
- call.on('dominantSpeakerChanged', ({ call, dominantSpeaker }) => {});
261
- call.on('mediaFlowChanged', ({ call, mediaFlow }) => {});
262
- call.on('sessionRenewed', ({ call, sessionId }) => {
263
- // Fired when the server assigns a new sessionId to the call
236
+ // Prepare a call (placed in prepareCalls, no media yet)
237
+ const result = callClient.prepareCall('call-123', {
238
+ mediaFlow: MediaFlow.SFU,
239
+ participantIds: ['user1'],
264
240
  });
265
- ```
241
+ const call = result.data;
266
242
 
267
- ## Client Events
243
+ // Join — moves call from prepareCalls to activeCalls
244
+ call?.join({ audioSettings: { publishAudio: true } });
268
245
 
269
- ```typescript
270
- callClient.on('invitedToCall', ({ call }) => {});
271
- callClient.on('ongoingCallsUpdated', ({ calls }) => {});
272
- ```
246
+ // Create without joining (sends invites, stays in prepareCalls until server confirms)
247
+ call?.create((error) => console.error(error.error?.message));
273
248
 
274
- ## Call Management
249
+ // Accept an incoming call
250
+ callClient.on('invitedToCall', ({ call }) => {
251
+ call.join({ audioSettings: { publishAudio: true } });
252
+ });
275
253
 
276
- ```typescript
277
- // Calls prepared locally but not yet joined/created
278
- callClient.prepareCalls;
254
+ // Reject / leave
255
+ call?.reject('busy');
256
+ call?.leave();
279
257
 
280
- // Calls that are actively joined
281
- callClient.activeCalls;
258
+ // Calls prepared but not yet joined
259
+ const pendingCalls = callClient.prepareCalls;
282
260
 
283
- // Fetch a call by ID (local lookup first, then server)
284
- const result = await callClient.getCall('call-id');
261
+ // Calls actively joined
262
+ const activeCalls = callClient.activeCalls;
285
263
 
286
- // Refresh and return all server-side ongoing calls
287
- callClient.getOngoingCalls();
264
+ // Active speakers on a call (updated via 'activeSpeakersChanged' event)
265
+ const speakers = call?.activeSpeakers;
266
+
267
+ // Fetch a call by ID (local first, then server)
268
+ const { data } = await callClient.getCall('call-id');
288
269
  ```
289
270
 
290
271
  ## Call History (CDR)
@@ -297,59 +278,50 @@ const query = new callClient.RecentCallQueryBuilder()
297
278
  .limit(20)
298
279
  .build();
299
280
 
300
- const { records } = await query.loadNextPage();
301
- ```
302
-
303
- ## Error Handling
304
-
305
- All methods return `CallClientResult<T>`:
281
+ const { records, hasNext } = await query.loadNextPage();
306
282
 
307
- ```typescript
308
- const result = call.join({ audioSettings: { publishAudio: true } });
309
- if (!result.success) {
310
- console.error(result.error?.message, result.error?.code);
311
- }
312
-
313
- // Optional error callback for async signal errors (create/join)
314
- result.data?.create((result) => {
315
- console.error(result.error?.message);
283
+ records.forEach(record => {
284
+ console.log(`Call: ${record.callId}`);
285
+ console.log(`Duration: ${record.endedAt - record.startedAt}ms`);
286
+ console.log(`Participants: ${record.participants.length}`);
316
287
  });
317
288
  ```
318
289
 
290
+ ## Participant States
291
+
292
+ | State | Description |
293
+ |-------|-------------|
294
+ | `Idle` | Not actively participating |
295
+ | `Ringing` | Device is ringing |
296
+ | `Joined` | In the call |
297
+ | `Left` | Left the call |
298
+ | `Declined` | Declined invitation |
299
+ | `Kicked` | Removed by another user |
300
+ | `NoAnswer` | Did not answer |
301
+
319
302
  ## Logging
320
303
 
321
304
  ```typescript
322
- const unsubscribe = callClient.onLog((entry) => {
305
+ callClient.onLog((entry) => {
323
306
  console.log(`[${entry.level}] ${entry.prefix}: ${entry.message}`);
324
307
  });
325
- unsubscribe();
326
308
  ```
327
309
 
328
310
  ## TypeScript Support
329
311
 
312
+ Full TypeScript support with exported types:
313
+
330
314
  ```typescript
331
315
  import type {
332
- JoinCallOptions,
333
- CreateCallOptions,
334
- AudioSettings,
335
- VideoSettings,
336
- CallSettings,
337
- BroadcastSettings,
338
- CallClientResult,
316
+ Call,
317
+ Participant,
318
+ IJoinCallOptions,
339
319
  CallEventMap,
340
320
  CallClientEventMap,
341
321
  ICallDetailRecord,
342
- ICDRParticipant,
343
- Unsubscribe,
344
- } from 'sceyt-call';
345
-
346
- import {
347
- MediaFlow,
348
- CallState,
349
322
  ParticipantState,
350
- ParticipantConnectionState,
351
- MediaConnectionState,
352
- CDRRequestOrder,
323
+ CallState,
324
+ MediaFlow
353
325
  } from 'sceyt-call';
354
326
  ```
355
327