@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
@@ -11,10 +11,26 @@ import {
11
11
  } from '../types';
12
12
  import { CallStatsReport } from '../stats/types';
13
13
  import {
14
- CallRecording,
14
+ BlockedUserEvent,
15
+ CallBroadcastingStartedEvent,
16
+ CallIngressResponse,
17
+ CallMemberAddedEvent,
18
+ CallMemberRemovedEvent,
19
+ CallMemberUpdatedEvent,
20
+ CallMemberUpdatedPermissionEvent,
21
+ CallReactionEvent,
15
22
  CallResponse,
23
+ CallSessionParticipantJoinedEvent,
24
+ CallSessionParticipantLeftEvent,
25
+ CallSessionResponse,
26
+ CallSettingsResponse,
27
+ EgressResponse,
16
28
  MemberResponse,
17
29
  OwnCapability,
30
+ UnblockedUserEvent,
31
+ UpdatedCallPermissionsEvent,
32
+ UserResponse,
33
+ VideoEvent,
18
34
  } from '../gen/coordinator';
19
35
  import { Pin, TrackType } from '../gen/video/sfu/models/models';
20
36
  import { Comparator } from '../sorting';
@@ -82,89 +98,48 @@ export enum CallingState {
82
98
  * @react You don't have to use this class directly, as we are exposing the state through Hooks.
83
99
  */
84
100
  export class CallState {
85
- /**
86
- * The raw call metadata object, as defined on the backend.
87
- *
88
- * @internal
89
- */
90
- private metadataSubject = new BehaviorSubject<CallResponse | undefined>(
101
+ private backstageSubject = new BehaviorSubject<boolean>(false);
102
+ private blockedUserIdsSubject = new BehaviorSubject<string[]>([]);
103
+ private createdAtSubject = new BehaviorSubject<Date>(new Date());
104
+ private endedAtSubject = new BehaviorSubject<Date | undefined>(undefined);
105
+ private startsAtSubject = new BehaviorSubject<Date | undefined>(undefined);
106
+ private updatedAtSubject = new BehaviorSubject<Date>(new Date());
107
+ private createdBySubject = new BehaviorSubject<UserResponse | undefined>(
108
+ undefined,
109
+ );
110
+ private customSubject = new BehaviorSubject<Record<string, any>>({});
111
+ private egressSubject = new BehaviorSubject<EgressResponse | undefined>(
112
+ undefined,
113
+ );
114
+ private ingressSubject = new BehaviorSubject<CallIngressResponse | undefined>(
115
+ undefined,
116
+ );
117
+ private recordingSubject = new BehaviorSubject<boolean>(false);
118
+ private sessionSubject = new BehaviorSubject<CallSessionResponse | undefined>(
119
+ undefined,
120
+ );
121
+ private settingsSubject = new BehaviorSubject<
122
+ CallSettingsResponse | undefined
123
+ >(undefined);
124
+ private transcribingSubject = new BehaviorSubject<boolean>(false);
125
+ private endedBySubject = new BehaviorSubject<UserResponse | undefined>(
91
126
  undefined,
92
127
  );
93
-
94
- /**
95
- * The list of members of the current call.
96
- *
97
- * @internal
98
- */
99
128
  private membersSubject = new BehaviorSubject<MemberResponse[]>([]);
100
-
101
- /**
102
- * The list of capabilities of the current user.
103
- *
104
- * @private
105
- */
106
129
  private ownCapabilitiesSubject = new BehaviorSubject<OwnCapability[]>([]);
107
-
108
- /**
109
- * The calling state.
110
- *
111
- * @internal
112
- */
113
130
  private callingStateSubject = new BehaviorSubject<CallingState>(
114
131
  CallingState.UNKNOWN,
115
132
  );
116
-
117
- /**
118
- * The time the call session actually started.
119
- *
120
- * @internal
121
- */
122
133
  private startedAtSubject = new BehaviorSubject<Date | undefined>(undefined);
123
-
124
- /**
125
- * The server-side counted number of participants connected to the current call.
126
- * This number includes the anonymous participants as well.
127
- *
128
- * @internal
129
- */
130
134
  private participantCountSubject = new BehaviorSubject<number>(0);
131
-
132
- /**
133
- * The server-side counted number of anonymous participants connected to the current call.
134
- * This number excludes the regular participants.
135
- *
136
- * @internal
137
- */
138
135
  private anonymousParticipantCountSubject = new BehaviorSubject<number>(0);
139
-
140
- /**
141
- * All participants of the current call (including the logged-in user).
142
- *
143
- * @internal
144
- */
145
136
  private participantsSubject = new BehaviorSubject<
146
137
  (StreamVideoParticipant | StreamVideoLocalParticipant)[]
147
138
  >([]);
148
-
149
- /**
150
- * The latest stats report of the current call.
151
- * When stats gathering is enabled, this observable will emit a new value
152
- * at a regular (configurable) interval.
153
- *
154
- * Consumers of this observable can implement their own batching logic
155
- * in case they want to show historical stat data.
156
- *
157
- * @internal
158
- */
159
139
  private callStatsReportSubject = new BehaviorSubject<
160
140
  CallStatsReport | undefined
161
141
  >(undefined);
162
142
 
163
- /**
164
- * Emits a list of details about recordings performed for the current call.
165
- */
166
- private callRecordingListSubject = new BehaviorSubject<CallRecording[]>([]);
167
-
168
143
  // Derived state
169
144
 
170
145
  /**
@@ -233,16 +208,6 @@ export class CallState {
233
208
  */
234
209
  callStatsReport$: Observable<CallStatsReport | undefined>;
235
210
 
236
- /**
237
- * Emits a list of details about recordings performed for the current call
238
- */
239
- callRecordingList$: Observable<CallRecording[]>;
240
-
241
- /**
242
- * The raw call metadata object, as defined on the backend.
243
- */
244
- metadata$: Observable<CallResponse | undefined>;
245
-
246
211
  /**
247
212
  * The list of members in the current call.
248
213
  */
@@ -258,6 +223,81 @@ export class CallState {
258
223
  */
259
224
  callingState$: Observable<CallingState>;
260
225
 
226
+ /**
227
+ * The backstage state.
228
+ */
229
+ backstage$: Observable<boolean>;
230
+
231
+ /**
232
+ * Will provide the list of blocked user IDs.
233
+ */
234
+ blockedUserIds$: Observable<string[]>;
235
+
236
+ /**
237
+ * Will provide the time when this call has been created.
238
+ */
239
+ createdAt$: Observable<Date>;
240
+
241
+ /**
242
+ * Will provide the time when this call has been ended.
243
+ */
244
+ endedAt$: Observable<Date | undefined>;
245
+
246
+ /**
247
+ * Will provide the time when this call has been scheduled to start.
248
+ */
249
+ startsAt$: Observable<Date | undefined>;
250
+
251
+ /**
252
+ * Will provide the time when this call has been updated.
253
+ */
254
+ updatedAt$: Observable<Date>;
255
+
256
+ /**
257
+ * Will provide the user who created this call.
258
+ */
259
+ createdBy$: Observable<UserResponse | undefined>;
260
+
261
+ /**
262
+ * Will provide the custom data of this call.
263
+ */
264
+ custom$: Observable<Record<string, any>>;
265
+
266
+ /**
267
+ * Will provide the egress data of this call.
268
+ */
269
+ egress$: Observable<EgressResponse | undefined>;
270
+
271
+ /**
272
+ * Will provide the ingress data of this call.
273
+ */
274
+ ingress$: Observable<CallIngressResponse | undefined>;
275
+
276
+ /**
277
+ * Will provide the recording state of this call.
278
+ */
279
+ recording$: Observable<boolean>;
280
+
281
+ /**
282
+ * Will provide the session data of this call.
283
+ */
284
+ session$: Observable<CallSessionResponse | undefined>;
285
+
286
+ /**
287
+ * Will provide the settings of this call.
288
+ */
289
+ settings$: Observable<CallSettingsResponse | undefined>;
290
+
291
+ /**
292
+ * Will provide the transcribing state of this call.
293
+ */
294
+ transcribing$: Observable<boolean>;
295
+
296
+ /**
297
+ * Will provide the user who ended this call.
298
+ */
299
+ endedBy$: Observable<UserResponse | undefined>;
300
+
261
301
  readonly logger: Logger;
262
302
 
263
303
  /**
@@ -268,12 +308,18 @@ export class CallState {
268
308
  private sortParticipantsBy: Comparator<StreamVideoParticipant> =
269
309
  SortingPreset.defaultSortPreset;
270
310
 
311
+ private readonly eventHandlers: {
312
+ [EventType in VideoEvent['type']]:
313
+ | ((event: Extract<VideoEvent, { type: EventType }>) => void)
314
+ | undefined;
315
+ };
316
+
271
317
  /**
272
318
  * Creates a new instance of the CallState class.
273
319
  *
274
320
  */
275
321
  constructor() {
276
- this.logger = getLogger(['call-state']);
322
+ this.logger = getLogger(['CallState']);
277
323
  this.participants$ = this.participantsSubject.pipe(
278
324
  map((ps) => ps.sort(this.sortParticipantsBy)),
279
325
  );
@@ -295,11 +341,11 @@ export class CallState {
295
341
  );
296
342
 
297
343
  this.hasOngoingScreenShare$ = this.participants$.pipe(
298
- map((participants) => {
299
- return participants.some((p) =>
344
+ map((participants) =>
345
+ participants.some((p) =>
300
346
  p.publishedTracks.includes(TrackType.SCREEN_SHARE),
301
- );
302
- }),
347
+ ),
348
+ ),
303
349
  distinctUntilChanged(),
304
350
  );
305
351
 
@@ -309,11 +355,67 @@ export class CallState {
309
355
  this.anonymousParticipantCountSubject.asObservable();
310
356
 
311
357
  this.callStatsReport$ = this.callStatsReportSubject.asObservable();
312
- this.callRecordingList$ = this.callRecordingListSubject.asObservable();
313
- this.metadata$ = this.metadataSubject.asObservable();
314
358
  this.members$ = this.membersSubject.asObservable();
315
359
  this.ownCapabilities$ = this.ownCapabilitiesSubject.asObservable();
316
360
  this.callingState$ = this.callingStateSubject.asObservable();
361
+
362
+ this.backstage$ = this.backstageSubject.asObservable();
363
+ this.blockedUserIds$ = this.blockedUserIdsSubject.asObservable();
364
+ this.createdAt$ = this.createdAtSubject.asObservable();
365
+ this.endedAt$ = this.endedAtSubject.asObservable();
366
+ this.startsAt$ = this.startsAtSubject.asObservable();
367
+ this.updatedAt$ = this.updatedAtSubject.asObservable();
368
+ this.createdBy$ = this.createdBySubject.asObservable();
369
+ this.custom$ = this.customSubject.asObservable();
370
+ this.egress$ = this.egressSubject.asObservable();
371
+ this.ingress$ = this.ingressSubject.asObservable();
372
+ this.recording$ = this.recordingSubject.asObservable();
373
+ this.session$ = this.sessionSubject.asObservable();
374
+ this.settings$ = this.settingsSubject.asObservable();
375
+ this.transcribing$ = this.transcribingSubject.asObservable();
376
+ this.endedBy$ = this.endedBySubject.asObservable();
377
+
378
+ this.eventHandlers = {
379
+ // these events are not updating the call state:
380
+ 'call.permission_request': undefined,
381
+ 'call.user_muted': undefined,
382
+ 'connection.error': undefined,
383
+ 'connection.ok': undefined,
384
+ 'health.check': undefined,
385
+ custom: undefined,
386
+
387
+ // events that update call state:
388
+ 'call.accepted': (e) => this.updateFromCallResponse(e.call),
389
+ 'call.created': (e) => this.updateFromCallResponse(e.call),
390
+ 'call.notification': (e) => this.updateFromCallResponse(e.call),
391
+ 'call.rejected': (e) => this.updateFromCallResponse(e.call),
392
+ 'call.ring': (e) => this.updateFromCallResponse(e.call),
393
+ 'call.live_started': (e) => this.updateFromCallResponse(e.call),
394
+ 'call.updated': (e) => this.updateFromCallResponse(e.call),
395
+ 'call.session_started': (e) => this.updateFromCallResponse(e.call),
396
+ 'call.session_ended': (e) => this.updateFromCallResponse(e.call),
397
+ 'call.ended': (e) => {
398
+ this.updateFromCallResponse(e.call);
399
+ this.setCurrentValue(this.endedBySubject, e.user);
400
+ },
401
+ 'call.recording_started': () =>
402
+ this.setCurrentValue(this.recordingSubject, true),
403
+ 'call.recording_stopped': () =>
404
+ this.setCurrentValue(this.recordingSubject, false),
405
+ 'call.broadcasting_started': this.updateFromBroadcastStarted,
406
+ 'call.broadcasting_stopped': this.updateFromBroadcastStopped,
407
+ 'call.session_participant_joined':
408
+ this.updateFromSessionParticipantJoined,
409
+ 'call.session_participant_left': this.updateFromSessionParticipantLeft,
410
+ 'call.blocked_user': this.blockUser,
411
+ 'call.unblocked_user': this.unblockUser,
412
+ 'call.permissions_updated': this.updateOwnCapabilities,
413
+ 'call.member_added': this.updateFromMemberAdded,
414
+ 'call.member_removed': this.updateFromMemberRemoved,
415
+ 'call.member_updated': this.updateMembers,
416
+ 'call.member_updated_permission': this.updateMembers,
417
+ 'call.reaction_new': this.updateParticipantReaction,
418
+ };
317
419
  }
318
420
 
319
421
  /**
@@ -473,23 +575,6 @@ export class CallState {
473
575
  return this.setCurrentValue(this.callingStateSubject, state);
474
576
  };
475
577
 
476
- /**
477
- * The list of call recordings.
478
- */
479
- get callRecordingsList() {
480
- return this.getCurrentValue(this.callRecordingList$);
481
- }
482
-
483
- /**
484
- * Sets the list of call recordings.
485
- *
486
- * @internal
487
- * @param recordings the list of call recordings.
488
- */
489
- setCallRecordingsList = (recordings: Patch<CallRecording[]>) => {
490
- return this.setCurrentValue(this.callRecordingListSubject, recordings);
491
- };
492
-
493
578
  /**
494
579
  * The call stats report.
495
580
  */
@@ -507,24 +592,6 @@ export class CallState {
507
592
  return this.setCurrentValue(this.callStatsReportSubject, report);
508
593
  };
509
594
 
510
- /**
511
- * The metadata of the current call.
512
- */
513
- get metadata() {
514
- return this.getCurrentValue(this.metadata$);
515
- }
516
-
517
- /**
518
- * Sets the metadata of the current call.
519
- *
520
- * @internal
521
- *
522
- * @param metadata the metadata to set.
523
- */
524
- setMetadata = (metadata: Patch<CallResponse | undefined>) => {
525
- return this.setCurrentValue(this.metadataSubject, metadata);
526
- };
527
-
528
595
  /**
529
596
  * The members of the current call.
530
597
  */
@@ -559,6 +626,111 @@ export class CallState {
559
626
  return this.setCurrentValue(this.ownCapabilitiesSubject, capabilities);
560
627
  };
561
628
 
629
+ /**
630
+ * The backstage state.
631
+ */
632
+ get backstage() {
633
+ return this.getCurrentValue(this.backstage$);
634
+ }
635
+
636
+ /**
637
+ * Will provide the list of blocked user IDs.
638
+ */
639
+ get blockedUserIds() {
640
+ return this.getCurrentValue(this.blockedUserIds$);
641
+ }
642
+
643
+ /**
644
+ * Will provide the time when this call has been created.
645
+ */
646
+ get createdAt() {
647
+ return this.getCurrentValue(this.createdAt$);
648
+ }
649
+
650
+ /**
651
+ * Will provide the time when this call has been ended.
652
+ */
653
+ get endedAt() {
654
+ return this.getCurrentValue(this.endedAt$);
655
+ }
656
+
657
+ /**
658
+ * Will provide the time when this call has been scheduled to start.
659
+ */
660
+ get startsAt() {
661
+ return this.getCurrentValue(this.startsAt$);
662
+ }
663
+
664
+ /**
665
+ * Will provide the time when this call has been updated.
666
+ */
667
+ get updatedAt() {
668
+ return this.getCurrentValue(this.updatedAt$);
669
+ }
670
+
671
+ /**
672
+ * Will provide the user who created this call.
673
+ */
674
+ get createdBy() {
675
+ return this.getCurrentValue(this.createdBy$);
676
+ }
677
+
678
+ /**
679
+ * Will provide the custom data of this call.
680
+ */
681
+ get custom() {
682
+ return this.getCurrentValue(this.custom$);
683
+ }
684
+
685
+ /**
686
+ * Will provide the egress data of this call.
687
+ */
688
+ get egress() {
689
+ return this.getCurrentValue(this.egress$);
690
+ }
691
+
692
+ /**
693
+ * Will provide the ingress data of this call.
694
+ */
695
+ get ingress() {
696
+ return this.getCurrentValue(this.ingress$);
697
+ }
698
+
699
+ /**
700
+ * Will provide the recording state of this call.
701
+ */
702
+ get recording() {
703
+ return this.getCurrentValue(this.recording$);
704
+ }
705
+
706
+ /**
707
+ * Will provide the session data of this call.
708
+ */
709
+ get session() {
710
+ return this.getCurrentValue(this.session$);
711
+ }
712
+
713
+ /**
714
+ * Will provide the settings of this call.
715
+ */
716
+ get settings() {
717
+ return this.getCurrentValue(this.settings$);
718
+ }
719
+
720
+ /**
721
+ * Will provide the transcribing state of this call.
722
+ */
723
+ get transcribing() {
724
+ return this.getCurrentValue(this.transcribing$);
725
+ }
726
+
727
+ /**
728
+ * Will provide the user who ended this call.
729
+ */
730
+ get endedBy() {
731
+ return this.getCurrentValue(this.endedBy$);
732
+ }
733
+
562
734
  /**
563
735
  * Will try to find the participant with the given sessionId in the current call.
564
736
  *
@@ -679,6 +851,20 @@ export class CallState {
679
851
  );
680
852
  };
681
853
 
854
+ /**
855
+ * Updates the call state with the data received from the server.
856
+ *
857
+ * @internal
858
+ *
859
+ * @param event the video event that our backend sent us.
860
+ */
861
+ updateFromEvent = (event: VideoEvent) => {
862
+ const update = this.eventHandlers[event.type];
863
+ if (update) {
864
+ update(event as any);
865
+ }
866
+ };
867
+
682
868
  /**
683
869
  * Updates the participant pinned state with server side pinning data.
684
870
  *
@@ -719,4 +905,174 @@ export class CallState {
719
905
  }),
720
906
  );
721
907
  };
908
+
909
+ /**
910
+ * Updates the call state with the data received from the server.
911
+ *
912
+ * @internal
913
+ *
914
+ * @param call the call response from the server.
915
+ */
916
+ updateFromCallResponse = (call: CallResponse) => {
917
+ this.setCurrentValue(this.backstageSubject, call.backstage);
918
+ this.setCurrentValue(this.blockedUserIdsSubject, call.blocked_user_ids);
919
+ this.setCurrentValue(this.createdAtSubject, new Date(call.created_at));
920
+ this.setCurrentValue(this.updatedAtSubject, new Date(call.updated_at));
921
+ this.setCurrentValue(
922
+ this.startsAtSubject,
923
+ call.starts_at ? new Date(call.starts_at) : undefined,
924
+ );
925
+ this.setCurrentValue(
926
+ this.endedAtSubject,
927
+ call.ended_at ? new Date(call.ended_at) : undefined,
928
+ );
929
+ this.setCurrentValue(this.createdBySubject, call.created_by);
930
+ this.setCurrentValue(this.customSubject, call.custom);
931
+ this.setCurrentValue(this.egressSubject, call.egress);
932
+ this.setCurrentValue(this.ingressSubject, call.ingress);
933
+ this.setCurrentValue(this.recordingSubject, call.recording);
934
+ this.setCurrentValue(this.sessionSubject, call.session);
935
+ this.setCurrentValue(this.settingsSubject, call.settings);
936
+ this.setCurrentValue(this.transcribingSubject, call.transcribing);
937
+ };
938
+
939
+ private updateFromMemberRemoved = (event: CallMemberRemovedEvent) => {
940
+ this.setCurrentValue(this.membersSubject, (members) =>
941
+ members.filter((m) => event.members.indexOf(m.user_id) === -1),
942
+ );
943
+ };
944
+
945
+ private updateFromMemberAdded = (event: CallMemberAddedEvent) => {
946
+ this.setCurrentValue(this.membersSubject, (members) => [
947
+ ...members,
948
+ ...event.members,
949
+ ]);
950
+ };
951
+
952
+ private updateFromBroadcastStopped = () => {
953
+ this.setCurrentValue(this.egressSubject, (egress) => ({
954
+ ...egress!,
955
+ broadcasting: false,
956
+ }));
957
+ };
958
+
959
+ private updateFromBroadcastStarted = (
960
+ event: CallBroadcastingStartedEvent,
961
+ ) => {
962
+ this.setCurrentValue(this.egressSubject, (egress) => ({
963
+ ...egress!,
964
+ broadcasting: true,
965
+ hls: {
966
+ ...egress!.hls,
967
+ playlist_url: event.hls_playlist_url,
968
+ },
969
+ }));
970
+ };
971
+
972
+ private updateFromSessionParticipantLeft = (
973
+ event: CallSessionParticipantLeftEvent,
974
+ ) => {
975
+ this.setCurrentValue(this.sessionSubject, (session) => {
976
+ if (!session) {
977
+ this.logger(
978
+ 'warn',
979
+ `Received call.session_participant_left event but no session is available.`,
980
+ event,
981
+ );
982
+ return session;
983
+ }
984
+ const { participants, participants_count_by_role } = session;
985
+ const { user, user_session_id } = event.participant;
986
+ return {
987
+ ...session,
988
+ participants: participants.filter(
989
+ (p) => p.user_session_id !== user_session_id,
990
+ ),
991
+ participants_count_by_role: {
992
+ ...participants_count_by_role,
993
+ [user.role]: Math.max(
994
+ 0,
995
+ (participants_count_by_role[user.role] || 0) - 1,
996
+ ),
997
+ },
998
+ };
999
+ });
1000
+ };
1001
+
1002
+ private updateFromSessionParticipantJoined = (
1003
+ event: CallSessionParticipantJoinedEvent,
1004
+ ) => {
1005
+ this.setCurrentValue(this.sessionSubject, (session) => {
1006
+ if (!session) {
1007
+ this.logger(
1008
+ 'warn',
1009
+ `Received call.session_participant_joined event but no session is available.`,
1010
+ event,
1011
+ );
1012
+ return session;
1013
+ }
1014
+ const { participants, participants_count_by_role } = session;
1015
+ const { user } = event.participant;
1016
+ return {
1017
+ ...session,
1018
+ participants: [...participants, event.participant],
1019
+ participants_count_by_role: {
1020
+ ...participants_count_by_role,
1021
+ [user.role]: (participants_count_by_role[user.role] || 0) + 1,
1022
+ },
1023
+ };
1024
+ });
1025
+ };
1026
+
1027
+ private updateMembers = (
1028
+ event: CallMemberUpdatedEvent | CallMemberUpdatedPermissionEvent,
1029
+ ) => {
1030
+ this.setCurrentValue(this.membersSubject, (members) =>
1031
+ members.map((member) => {
1032
+ const memberUpdate = event.members.find(
1033
+ (m) => m.user_id === member.user_id,
1034
+ );
1035
+ return memberUpdate ? memberUpdate : member;
1036
+ }),
1037
+ );
1038
+ };
1039
+
1040
+ private updateParticipantReaction = (event: CallReactionEvent) => {
1041
+ const { user, custom, type, emoji_code } = event.reaction;
1042
+ this.setParticipants((participants) => {
1043
+ return participants.map((p) => {
1044
+ // skip if the reaction is not for this participant
1045
+ if (p.userId !== user.id) return p;
1046
+ // update the participant with the new reaction
1047
+ return {
1048
+ ...p,
1049
+ reaction: {
1050
+ type,
1051
+ emoji_code,
1052
+ custom,
1053
+ },
1054
+ };
1055
+ });
1056
+ });
1057
+ };
1058
+
1059
+ private unblockUser = (event: UnblockedUserEvent) => {
1060
+ this.setCurrentValue(this.blockedUserIdsSubject, (current) => {
1061
+ if (!current) return current;
1062
+ return current.filter((id) => id !== event.user.id);
1063
+ });
1064
+ };
1065
+
1066
+ private blockUser = (event: BlockedUserEvent) => {
1067
+ this.setCurrentValue(this.blockedUserIdsSubject, (current) => [
1068
+ ...(current || []),
1069
+ event.user.id,
1070
+ ]);
1071
+ };
1072
+
1073
+ private updateOwnCapabilities = (event: UpdatedCallPermissionsEvent) => {
1074
+ if (event.user.id === this.localParticipant?.userId) {
1075
+ this.setCurrentValue(this.ownCapabilitiesSubject, event.own_capabilities);
1076
+ }
1077
+ };
722
1078
  }