@stream-io/video-client 0.2.2 → 0.3.0

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 (53) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.browser.es.js +325 -421
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +325 -421
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +325 -421
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +8 -10
  9. package/dist/src/StreamVideoClient.d.ts +3 -1
  10. package/dist/src/events/call-permissions.d.ts +0 -5
  11. package/dist/src/events/call.d.ts +0 -6
  12. package/dist/src/events/index.d.ts +0 -6
  13. package/dist/src/gen/coordinator/index.d.ts +25 -0
  14. package/dist/src/rtc/Dispatcher.d.ts +2 -2
  15. package/dist/src/rtc/Publisher.d.ts +0 -1
  16. package/dist/src/store/CallState.d.ts +164 -89
  17. package/dist/src/types.d.ts +1 -7
  18. package/dist/version.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/Call.ts +37 -44
  21. package/src/StreamVideoClient.ts +14 -17
  22. package/src/events/__tests__/call-permissions.test.ts +1 -61
  23. package/src/events/__tests__/call.test.ts +5 -50
  24. package/src/events/call-permissions.ts +0 -14
  25. package/src/events/call.ts +5 -16
  26. package/src/events/callEventHandlers.ts +2 -57
  27. package/src/events/index.ts +0 -6
  28. package/src/gen/coordinator/index.ts +25 -0
  29. package/src/rtc/Dispatcher.ts +2 -2
  30. package/src/rtc/Publisher.ts +4 -6
  31. package/src/store/CallState.ts +475 -119
  32. package/src/store/__tests__/CallState.test.ts +447 -1
  33. package/src/types.ts +0 -8
  34. package/dist/src/events/__tests__/backstage.test.d.ts +0 -1
  35. package/dist/src/events/__tests__/members.test.d.ts +0 -1
  36. package/dist/src/events/__tests__/recording.test.d.ts +0 -1
  37. package/dist/src/events/__tests__/sessions.test.d.ts +0 -1
  38. package/dist/src/events/backstage.d.ts +0 -6
  39. package/dist/src/events/members.d.ts +0 -18
  40. package/dist/src/events/moderation.d.ts +0 -14
  41. package/dist/src/events/reactions.d.ts +0 -8
  42. package/dist/src/events/recording.d.ts +0 -18
  43. package/dist/src/events/sessions.d.ts +0 -26
  44. package/src/events/__tests__/backstage.test.ts +0 -15
  45. package/src/events/__tests__/members.test.ts +0 -135
  46. package/src/events/__tests__/recording.test.ts +0 -65
  47. package/src/events/__tests__/sessions.test.ts +0 -135
  48. package/src/events/backstage.ts +0 -15
  49. package/src/events/members.ts +0 -62
  50. package/src/events/moderation.ts +0 -35
  51. package/src/events/reactions.ts +0 -30
  52. package/src/events/recording.ts +0 -64
  53. package/src/events/sessions.ts +0 -102
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { anyNumber } from 'vitest-mock-extended';
3
3
  import { StreamVideoParticipant, VisibilityState } from '../../types';
4
4
  import { CallState } from '../CallState';
5
+ import { ConnectionQuality } from '../../gen/video/sfu/models/models';
5
6
  import {
6
7
  combineComparators,
7
8
  conditional,
@@ -13,7 +14,13 @@ import {
13
14
  publishingVideo,
14
15
  screenSharing,
15
16
  } from '../../sorting';
16
-
17
+ import {
18
+ CallAcceptedEvent,
19
+ CallEndedEvent,
20
+ CallUpdatedEvent,
21
+ MemberResponse,
22
+ OwnCapability,
23
+ } from '../../gen/coordinator';
17
24
  import * as TestData from '../../sorting/__tests__/participant-data';
18
25
 
19
26
  describe('CallState', () => {
@@ -157,4 +164,443 @@ describe('CallState', () => {
157
164
  ]);
158
165
  });
159
166
  });
167
+
168
+ describe('events', () => {
169
+ describe('call.live and backstage events', () => {
170
+ it('handles call.live_started events', () => {
171
+ const state = new CallState();
172
+ // @ts-ignore
173
+ state.updateFromEvent({
174
+ type: 'call.live_started',
175
+ // @ts-ignore
176
+ call: {
177
+ backstage: false,
178
+ },
179
+ });
180
+ expect(state.backstage).toBe(false);
181
+ });
182
+ });
183
+
184
+ describe('Call ringing events', () => {
185
+ describe('call.updated', () => {
186
+ it(`will update the call's metadata`, () => {
187
+ const state = new CallState();
188
+ const event: CallUpdatedEvent = {
189
+ type: 'call.updated',
190
+ call_cid: 'development:12345',
191
+ // @ts-expect-error
192
+ call: {
193
+ cid: 'development:12345',
194
+ custom: {
195
+ test: 'value',
196
+ },
197
+ },
198
+ };
199
+
200
+ // @ts-ignore
201
+ state.updateFromEvent(event);
202
+ expect(state.custom).toEqual(event.call.custom);
203
+ });
204
+ });
205
+
206
+ describe(`call.accepted`, () => {
207
+ it(`will update state`, () => {
208
+ const state = new CallState();
209
+ const event: CallAcceptedEvent = {
210
+ type: 'call.accepted',
211
+ // @ts-expect-error
212
+ call: {
213
+ custom: {
214
+ test: 'value',
215
+ },
216
+ },
217
+ };
218
+ // @ts-ignore
219
+ state.updateFromEvent(event);
220
+
221
+ expect(state.custom).toEqual(event.call.custom);
222
+ });
223
+ });
224
+
225
+ describe(`call.rejected`, () => {
226
+ it(`will update state`, () => {
227
+ const state = new CallState();
228
+ const event: CallEndedEvent = {
229
+ type: 'call.rejected',
230
+ // @ts-expect-error
231
+ call: {
232
+ custom: {
233
+ test: 'value',
234
+ },
235
+ },
236
+ };
237
+ // @ts-ignore
238
+ state.updateFromEvent(event);
239
+
240
+ expect(state.custom).toEqual(event.call.custom);
241
+ });
242
+ });
243
+
244
+ describe(`call.ended`, () => {
245
+ it(`will update state`, () => {
246
+ const state = new CallState();
247
+ const event: CallEndedEvent = {
248
+ type: 'call.ended',
249
+ // @ts-expect-error
250
+ call: {
251
+ custom: {
252
+ test: 'value',
253
+ },
254
+ },
255
+ };
256
+ // @ts-ignore
257
+ state.updateFromEvent(event);
258
+
259
+ expect(state.custom).toEqual(event.call.custom);
260
+ });
261
+ });
262
+ });
263
+
264
+ describe('Call Permission Events', () => {
265
+ it('handles call.permissions_updated', () => {
266
+ const state = new CallState();
267
+ state.setParticipants([
268
+ {
269
+ userId: 'test',
270
+ name: 'test',
271
+ sessionId: 'test',
272
+ isDominantSpeaker: false,
273
+ isSpeaking: false,
274
+ audioLevel: 0,
275
+ image: '',
276
+ publishedTracks: [],
277
+ connectionQuality: ConnectionQuality.EXCELLENT,
278
+ roles: [],
279
+ trackLookupPrefix: '',
280
+ isLocalParticipant: true,
281
+ },
282
+ ]);
283
+
284
+ state.updateFromEvent({
285
+ type: 'call.permissions_updated',
286
+ created_at: '',
287
+ call_cid: 'development:12345',
288
+ own_capabilities: [
289
+ OwnCapability.SEND_AUDIO,
290
+ OwnCapability.SEND_VIDEO,
291
+ ],
292
+ user: {
293
+ id: 'test',
294
+ created_at: '',
295
+ role: '',
296
+ updated_at: '',
297
+ custom: {},
298
+ teams: [],
299
+ },
300
+ });
301
+
302
+ expect(state.ownCapabilities).toEqual([
303
+ OwnCapability.SEND_AUDIO,
304
+ OwnCapability.SEND_VIDEO,
305
+ ]);
306
+
307
+ state.updateFromEvent({
308
+ type: 'call.permissions_updated',
309
+ created_at: '',
310
+ call_cid: 'development:12345',
311
+ own_capabilities: [OwnCapability.SEND_VIDEO],
312
+ user: {
313
+ id: 'test',
314
+ created_at: '',
315
+ role: '',
316
+ updated_at: '',
317
+ custom: {},
318
+ teams: [],
319
+ },
320
+ });
321
+ expect(state.ownCapabilities).toEqual([OwnCapability.SEND_VIDEO]);
322
+ });
323
+ });
324
+
325
+ describe('member events', () => {
326
+ it('handles call.member_added events', () => {
327
+ const state = new CallState();
328
+ const initialMembers: MemberResponse[] = [
329
+ {
330
+ user_id: 'user0',
331
+ } as MemberResponse,
332
+ ];
333
+ state.setMembers(initialMembers);
334
+ state.updateFromEvent({
335
+ type: 'call.member_added',
336
+ // @ts-ignore
337
+ members: [{ user_id: 'user1' }, { user_id: 'user2' }],
338
+ });
339
+
340
+ const updatedMembers = state.members;
341
+ updatedMembers.forEach((member, index) =>
342
+ expect(member.user_id).toBe(`user${index}`),
343
+ );
344
+ });
345
+
346
+ it('handles call.member_removed events', () => {
347
+ const state = new CallState();
348
+ const initialMembers: MemberResponse[] = [
349
+ // @ts-ignore
350
+ { user_id: 'user0' },
351
+ // @ts-ignore
352
+ { user_id: 'user1' },
353
+ // @ts-ignore
354
+ { user_id: 'user2' },
355
+ ];
356
+ state.setMembers(initialMembers);
357
+ const removedMembers = ['user1'];
358
+ // @ts-ignore
359
+ state.updateFromEvent({
360
+ type: 'call.member_removed',
361
+ members: removedMembers,
362
+ });
363
+
364
+ const updatedMembers = state.members;
365
+ expect(updatedMembers[0].user_id).toBe('user0');
366
+ expect(updatedMembers[1].user_id).toBe('user2');
367
+ expect(updatedMembers.length).toBe(
368
+ initialMembers.length - removedMembers.length,
369
+ );
370
+ });
371
+
372
+ it('handles call.member_updated_permission events', () => {
373
+ const state = new CallState();
374
+ const user0 = {
375
+ user_id: 'user0',
376
+ user: {
377
+ role: 'viewer',
378
+ },
379
+ } as MemberResponse;
380
+ const user1 = {
381
+ user_id: 'user1',
382
+ user: {
383
+ role: 'host',
384
+ },
385
+ } as MemberResponse;
386
+ const user2 = {
387
+ user_id: 'user2',
388
+ user: {
389
+ role: 'viewer',
390
+ },
391
+ } as MemberResponse;
392
+ const initialMembers: MemberResponse[] = [user0, user1, user2];
393
+ state.setMembers(initialMembers);
394
+ // @ts-ignore
395
+ state.updateFromEvent({
396
+ type: 'call.member_updated_permission',
397
+ members: [
398
+ {
399
+ user_id: user1.user_id,
400
+ // @ts-ignore
401
+ user: { ...user1, role: 'viewer' },
402
+ role: 'viewer',
403
+ },
404
+ {
405
+ user_id: user0.user_id,
406
+ // @ts-ignore
407
+ user: { ...user0, role: 'host' },
408
+ role: 'host',
409
+ },
410
+ ],
411
+ });
412
+
413
+ const updatedMembers = state.members;
414
+ expect(updatedMembers[0].user.role).toBe('host');
415
+ expect(updatedMembers[1].user.role).toBe('viewer');
416
+ expect(updatedMembers[2].user.role).toBe('viewer');
417
+ });
418
+
419
+ it('handles call.member_updated events', () => {
420
+ const state = new CallState();
421
+ const user0 = {
422
+ user_id: 'user0',
423
+ user: {
424
+ name: 'Jane',
425
+ },
426
+ } as MemberResponse;
427
+ const user1 = {
428
+ user_id: 'user1',
429
+ user: {
430
+ name: 'Jack',
431
+ },
432
+ } as MemberResponse;
433
+ const user2 = {
434
+ user_id: 'user2',
435
+ user: {
436
+ name: 'Adam',
437
+ },
438
+ } as MemberResponse;
439
+ const initialMembers: MemberResponse[] = [user0, user1, user2];
440
+ state.setMembers(initialMembers);
441
+ state.updateFromEvent({
442
+ type: 'call.member_updated',
443
+ // @ts-ignore
444
+ members: [{ ...user1, user: { name: 'John' } }],
445
+ });
446
+
447
+ const updatedMembers = state.members;
448
+ expect(updatedMembers[0].user.name).toBe('Jane');
449
+ expect(updatedMembers[1].user.name).toBe('John');
450
+ expect(updatedMembers[2].user.name).toBe('Adam');
451
+ });
452
+ });
453
+
454
+ describe('recording and broadcasting events', () => {
455
+ it('handles call.recording_started events', () => {
456
+ const state = new CallState();
457
+ // @ts-ignore
458
+ state.updateFromEvent({
459
+ type: 'call.recording_started',
460
+ });
461
+ expect(state.recording).toBe(true);
462
+ });
463
+
464
+ it('handles call.recording_stopped events', () => {
465
+ const state = new CallState();
466
+ // @ts-ignore
467
+ state.updateFromEvent({
468
+ type: 'call.recording_stopped',
469
+ });
470
+ expect(state.recording).toBe(false);
471
+ });
472
+
473
+ it('handles call.broadcasting_started events', () => {
474
+ const state = new CallState();
475
+ state.updateFromCallResponse({
476
+ // @ts-ignore
477
+ egress: {
478
+ broadcasting: false,
479
+ hls: {
480
+ playlist_url: '',
481
+ },
482
+ },
483
+ });
484
+ // @ts-ignore
485
+ state.updateFromEvent({
486
+ type: 'call.broadcasting_started',
487
+ hls_playlist_url: 'https://example.com/playlist.m3u8',
488
+ });
489
+ expect(state.egress?.broadcasting).toBe(true);
490
+ expect(state.egress?.hls?.playlist_url).toBe(
491
+ 'https://example.com/playlist.m3u8',
492
+ );
493
+ });
494
+
495
+ it('handles call.broadcasting_stopped events', () => {
496
+ const state = new CallState();
497
+ // @ts-ignore
498
+ state.updateFromCallResponse({});
499
+ // @ts-ignore
500
+ state.updateFromEvent({
501
+ type: 'call.broadcasting_stopped',
502
+ });
503
+ expect(state.egress?.broadcasting).toBe(false);
504
+ });
505
+ });
506
+
507
+ describe('call.session events', () => {
508
+ it('should update the call metadata when a session starts', () => {
509
+ const state = new CallState();
510
+ state.updateFromEvent({
511
+ type: 'call.session_started',
512
+ // @ts-ignore
513
+ call: { session: { id: 'session-id' } },
514
+ });
515
+
516
+ expect(state.session).toEqual({ id: 'session-id' });
517
+ });
518
+
519
+ it('should update the call metadata when a session ends', () => {
520
+ const state = new CallState();
521
+ state.updateFromEvent({
522
+ type: 'call.session_ended',
523
+ // @ts-ignore
524
+ call: { session: { id: 'session-id' } },
525
+ });
526
+ expect(state.session).toEqual({ id: 'session-id' });
527
+ });
528
+
529
+ it('should update the call metadata when a participant joins', () => {
530
+ const state = new CallState();
531
+ state.updateFromCallResponse({
532
+ // @ts-ignore
533
+ session: {
534
+ participants: [],
535
+ participants_count_by_role: {},
536
+ },
537
+ });
538
+ state.updateFromEvent({
539
+ type: 'call.session_participant_joined',
540
+ participant: {
541
+ // @ts-ignore
542
+ user: {
543
+ id: 'user-id',
544
+ role: 'user',
545
+ },
546
+ user_session_id: '123',
547
+ },
548
+ });
549
+ expect(state.session).toEqual({
550
+ participants: [
551
+ {
552
+ user: {
553
+ id: 'user-id',
554
+ role: 'user',
555
+ },
556
+ user_session_id: '123',
557
+ },
558
+ ],
559
+ participants_count_by_role: {
560
+ user: 1,
561
+ },
562
+ });
563
+ });
564
+
565
+ it('should update the call metadata when a participant leaves', () => {
566
+ const state = new CallState();
567
+ state.updateFromCallResponse({
568
+ // @ts-ignore
569
+ session: {
570
+ participants: [
571
+ {
572
+ joined_at: '2021-01-01T00:00:00.000Z',
573
+ // @ts-ignore
574
+ user: {
575
+ id: 'user-id',
576
+ role: 'user',
577
+ },
578
+ user_session_id: '123',
579
+ },
580
+ ],
581
+ participants_count_by_role: {
582
+ user: 1,
583
+ },
584
+ },
585
+ });
586
+ state.updateFromEvent({
587
+ type: 'call.session_participant_left',
588
+ participant: {
589
+ // @ts-ignore
590
+ user: {
591
+ id: 'user-id',
592
+ role: 'user',
593
+ },
594
+ user_session_id: '123',
595
+ },
596
+ });
597
+ expect(state.session).toEqual({
598
+ participants: [],
599
+ participants_count_by_role: {
600
+ user: 0,
601
+ },
602
+ });
603
+ });
604
+ });
605
+ });
160
606
  });
package/src/types.ts CHANGED
@@ -3,7 +3,6 @@ import type {
3
3
  VideoDimension,
4
4
  } from './gen/video/sfu/models/models';
5
5
  import type {
6
- CallResponse,
7
6
  JoinCallRequest,
8
7
  MemberResponse,
9
8
  OwnCapability,
@@ -187,13 +186,6 @@ export type CallConstructor = {
187
186
  */
188
187
  id: string;
189
188
 
190
- /**
191
- * An optional {@link CallResponse} metadata from the backend.
192
- * If provided, the call will be initialized with the data from this object.
193
- * This is useful when initializing a new "pending call" from an event.
194
- */
195
- metadata?: CallResponse;
196
-
197
189
  /**
198
190
  * An optional list of {@link MemberResponse} from the backend.
199
191
  * If provided, the call will be initialized with the data from this object.
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- import { StreamVideoEvent } from '../coordinator/connection/types';
2
- import { CallState } from '../store';
3
- /**
4
- * Watches for `call.live_started` events.
5
- */
6
- export declare const watchCallLiveStarted: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,18 +0,0 @@
1
- import { StreamVideoEvent } from '../coordinator/connection/types';
2
- import { CallState } from '../store';
3
- /**
4
- * Watches for `call.member_added` events.
5
- */
6
- export declare const watchCallMemberAdded: (state: CallState) => (event: StreamVideoEvent) => void;
7
- /**
8
- * Watches for `call.member_removed` events.
9
- */
10
- export declare const watchCallMemberRemoved: (state: CallState) => (event: StreamVideoEvent) => void;
11
- /**
12
- * Watches for `call.member_updated_permission` events.
13
- */
14
- export declare const watchCallMemberUpdatedPermission: (state: CallState) => (event: StreamVideoEvent) => void;
15
- /**
16
- * Watches for `call.member_updated` events.
17
- */
18
- export declare const watchCallMemberUpdated: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,14 +0,0 @@
1
- import { StreamVideoEvent } from '../coordinator/connection/types';
2
- import { CallState } from '../store';
3
- /**
4
- * Event handler that watches for `call.blocked_user` events,
5
- * updates the call store `blocked_user_ids` property by adding
6
- * `event.user_id` to the list
7
- */
8
- export declare const watchBlockedUser: (state: CallState) => (event: StreamVideoEvent) => void;
9
- /**
10
- * Event handler that watches for `call.unblocked_user` events,
11
- * updates the call store `blocked_user_ids` property by
12
- * removing `event.user_id` from the list
13
- */
14
- export declare const watchUnblockedUser: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,8 +0,0 @@
1
- import { StreamVideoEvent } from '../coordinator/connection/types';
2
- import { CallState } from '../store';
3
- /**
4
- * Watches the delivery of CallReactionEvent.
5
- *
6
- * @param state the state store to update.
7
- */
8
- export declare const watchNewReactions: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,18 +0,0 @@
1
- import { CallState } from '../store';
2
- import { StreamVideoEvent } from '../coordinator/connection/types';
3
- /**
4
- * Watches for `call.recording_started` events.
5
- */
6
- export declare const watchCallRecordingStarted: (state: CallState) => (event: StreamVideoEvent) => void;
7
- /**
8
- * Watches for `call.recording_stopped` events.
9
- */
10
- export declare const watchCallRecordingStopped: (state: CallState) => (event: StreamVideoEvent) => void;
11
- /**
12
- * Watches for `call.broadcasting_started` events.
13
- */
14
- export declare const watchCallBroadcastingStarted: (state: CallState) => (event: StreamVideoEvent) => void;
15
- /**
16
- * Watches for `call.broadcasting_stopped` events.
17
- */
18
- export declare const watchCallBroadcastingStopped: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,26 +0,0 @@
1
- import { CallState } from '../store';
2
- import { StreamVideoEvent } from '../coordinator/connection/types';
3
- /**
4
- * Watch for call.session_started events and update the call metadata.
5
- *
6
- * @param state the call state.
7
- */
8
- export declare const watchCallSessionStarted: (state: CallState) => (event: StreamVideoEvent) => void;
9
- /**
10
- * Watch for call.session_ended events and update the call metadata.
11
- *
12
- * @param state the call state.
13
- */
14
- export declare const watchCallSessionEnded: (state: CallState) => (event: StreamVideoEvent) => void;
15
- /**
16
- * Watch for call.session_participant_joined events and update the call metadata.
17
- *
18
- * @param state the call state.
19
- */
20
- export declare const watchCallSessionParticipantJoined: (state: CallState) => (event: StreamVideoEvent) => void;
21
- /**
22
- * Watch for call.session_participant_left events and update the call metadata.
23
- *
24
- * @param state the call state.
25
- */
26
- export declare const watchCallSessionParticipantLeft: (state: CallState) => (event: StreamVideoEvent) => void;
@@ -1,15 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { CallState } from '../../store';
3
- import { watchCallLiveStarted } from '../backstage';
4
-
5
- describe('backstage events', () => {
6
- it('handles call.live_started events', () => {
7
- const state = new CallState();
8
- const handler = watchCallLiveStarted(state);
9
- // @ts-ignore
10
- handler({
11
- type: 'call.live_started',
12
- });
13
- expect(state.metadata?.backstage).toBe(false);
14
- });
15
- });