@stream-io/video-client 0.0.2-alpha.6 → 0.0.2-alpha.8

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.
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  CallAcceptedEvent,
15
15
  CallEndedEvent,
16
+ CallResponse,
16
17
  CallUpdatedEvent,
17
18
  } from '../../gen/coordinator';
18
19
  import { Call } from '../../Call';
@@ -93,28 +94,58 @@ describe('Call ringing events', () => {
93
94
  });
94
95
 
95
96
  describe(`call.rejected`, () => {
96
- it(`will leave the call if all callees have rejected`, async () => {
97
- const call = fakeCall();
98
- // @ts-expect-error
99
- call.state.setMembers([{ user_id: 'm1' }, { user_id: 'm2' }]);
97
+ it(`caller will leave the call if all callees have rejected`, async () => {
98
+ const call = fakeCall({ currentUserId: 'm1' });
99
+ call.state.setMetadata({
100
+ ...fakeMetadata(),
101
+ // @ts-ignore
102
+ created_by: { id: 'm1' },
103
+ });
104
+ call.state.setMembers([
105
+ // @ts-expect-error
106
+ { user_id: 'm1' },
107
+ // @ts-expect-error
108
+ { user_id: 'm2' },
109
+ // @ts-expect-error
110
+ { user_id: 'm3' },
111
+ ]);
112
+ call.state.setCallingState(CallingState.RINGING);
100
113
  vi.spyOn(call, 'leave').mockImplementation(async () => {
101
114
  console.log(`TEST: leave() called`);
102
115
  });
103
116
 
104
117
  const handler = watchCallRejected(call);
105
118
  // all members reject the call
106
- call.state.members.forEach(() => {
107
- // @ts-ignore
108
- const event: CallAcceptedEvent = { type: 'call.rejected' };
119
+ await handler({
120
+ type: 'call.rejected',
109
121
  // @ts-ignore
110
- handler(event);
122
+ user: {
123
+ id: 'm2',
124
+ },
125
+ call: {
126
+ // @ts-ignore
127
+ created_by: {
128
+ id: 'm1',
129
+ },
130
+ // @ts-ignore
131
+ session: {
132
+ rejected_by: {
133
+ m2: new Date().toISOString(),
134
+ m3: new Date().toISOString(),
135
+ },
136
+ },
137
+ },
111
138
  });
112
-
113
139
  expect(call.leave).toHaveBeenCalled();
114
140
  });
115
141
 
116
- it(`will not leave the call if only one callee rejects`, async () => {
142
+ it(`caller will not leave the call if only one callee rejects`, async () => {
117
143
  const call = fakeCall();
144
+ call.state.setMetadata({
145
+ ...fakeMetadata(),
146
+ // @ts-ignore
147
+ created_by: { id: 'm0' },
148
+ });
118
149
  // @ts-expect-error
119
150
  call.state.setMembers([{ user_id: 'm1' }, { user_id: 'm2' }]);
120
151
  vi.spyOn(call, 'leave').mockImplementation(async () => {
@@ -124,12 +155,71 @@ describe('Call ringing events', () => {
124
155
 
125
156
  // only one member rejects the call
126
157
  // @ts-ignore
127
- const event: CallAcceptedEvent = { type: 'call.rejected' };
158
+ const event: CallAcceptedEvent = {
159
+ type: 'call.rejected',
160
+ // @ts-ignore
161
+ user: {
162
+ id: 'm2',
163
+ },
164
+ call: {
165
+ // @ts-ignore
166
+ created_by: {
167
+ id: 'm0',
168
+ },
169
+ // @ts-ignore
170
+ session: {
171
+ rejected_by: {
172
+ m2: new Date().toISOString(),
173
+ },
174
+ },
175
+ },
176
+ };
128
177
  // @ts-ignore
129
178
  await handler(event);
130
179
 
131
180
  expect(call.leave).not.toHaveBeenCalled();
132
181
  });
182
+
183
+ it('callee will leave the call if caller rejects', async () => {
184
+ const call = fakeCall({ currentUserId: 'm1' });
185
+ call.state.setMetadata({
186
+ ...fakeMetadata(),
187
+ // @ts-ignore
188
+ created_by: { id: 'm0' },
189
+ });
190
+ // @ts-expect-error
191
+ call.state.setMembers([{ user_id: 'm1' }, { user_id: 'm2' }]);
192
+ vi.spyOn(call, 'leave').mockImplementation(async () => {
193
+ console.log(`TEST: leave() called`);
194
+ });
195
+ const handler = watchCallRejected(call);
196
+
197
+ // only one member rejects the call
198
+ // @ts-ignore
199
+ const event: CallAcceptedEvent = {
200
+ type: 'call.rejected',
201
+ // @ts-ignore
202
+ user: {
203
+ id: 'm0',
204
+ },
205
+ call: {
206
+ // @ts-ignore
207
+ created_by: {
208
+ id: 'm0',
209
+ },
210
+ // @ts-ignore
211
+ session: {
212
+ rejected_by: {
213
+ m0: new Date().toISOString(),
214
+ },
215
+ },
216
+ },
217
+ };
218
+ // @ts-ignore
219
+ await handler(event);
220
+
221
+ expect(call.leave).toHaveBeenCalled();
222
+ });
133
223
  });
134
224
 
135
225
  describe(`call.ended`, () => {
@@ -171,8 +261,7 @@ describe('Call ringing events', () => {
171
261
  });
172
262
 
173
263
  it(`will not leave the call if idle`, async () => {
174
- const ringing = false;
175
- const call = fakeCall(ringing);
264
+ const call = fakeCall({ ring: false });
176
265
  vi.spyOn(call, 'leave').mockImplementation(async () => {
177
266
  console.log(`TEST: leave() called`);
178
267
  });
@@ -189,10 +278,10 @@ describe('Call ringing events', () => {
189
278
  });
190
279
  });
191
280
 
192
- const fakeCall = (ring = true) => {
281
+ const fakeCall = ({ ring = true, currentUserId = 'test-user-id' } = {}) => {
193
282
  const store = new StreamVideoWriteableStateStore();
194
283
  store.setConnectedUser({
195
- id: 'test-user-id',
284
+ id: currentUserId,
196
285
  });
197
286
  const client = new StreamClient('api-key');
198
287
  return new Call({
@@ -203,3 +292,26 @@ const fakeCall = (ring = true) => {
203
292
  ringing: ring,
204
293
  });
205
294
  };
295
+
296
+ const fakeMetadata = (): CallResponse => {
297
+ return {
298
+ id: '12345',
299
+ type: 'development',
300
+ cid: 'development:12345',
301
+
302
+ // @ts-ignore
303
+ created_by: {
304
+ id: 'test-user-id',
305
+ },
306
+ own_capabilities: [],
307
+ blocked_user_ids: [],
308
+
309
+ // @ts-ignore
310
+ settings: {
311
+ ring: {
312
+ auto_cancel_timeout_ms: 30000,
313
+ incoming_call_timeout_ms: 30000,
314
+ },
315
+ },
316
+ };
317
+ };
@@ -27,16 +27,39 @@ export const watchCallAccepted = (call: Call) => {
27
27
  * Once the event is received, the call is left.
28
28
  */
29
29
  export const watchCallRejected = (call: Call) => {
30
- let totalRejections = 0;
31
30
  return async function onCallRejected(event: StreamVideoEvent) {
32
31
  if (event.type !== 'call.rejected') return;
33
- totalRejections++;
34
- const { state } = call;
35
- if (
36
- totalRejections >= state.members.length &&
37
- state.callingState === CallingState.RINGING
38
- ) {
39
- await call.leave();
32
+ // We want to discard the event if it's from the current user
33
+ if (event.user.id === call.currentUserId) return;
34
+ const { call: eventCall } = event;
35
+ const { session: callSession } = eventCall;
36
+
37
+ if (!callSession) {
38
+ console.log('No call session provided. Ignoring call.rejected event.');
39
+ return;
40
+ }
41
+
42
+ const rejectedBy = callSession.rejected_by;
43
+ const { members, callingState } = call.state;
44
+ if (callingState !== CallingState.RINGING) {
45
+ console.log(
46
+ 'Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.',
47
+ );
48
+ return;
49
+ }
50
+ if (call.isCreatedByMe) {
51
+ const everyoneElseRejected = members
52
+ .filter((m) => m.user_id !== call.currentUserId)
53
+ .every((m) => rejectedBy[m.user_id]);
54
+ if (everyoneElseRejected) {
55
+ console.log('everyone rejected, leaving the call');
56
+ await call.leave();
57
+ }
58
+ } else {
59
+ if (rejectedBy[eventCall.created_by.id]) {
60
+ console.log('call creator rejected, leaving call');
61
+ await call.leave();
62
+ }
40
63
  }
41
64
  };
42
65
  };
@@ -45,13 +45,22 @@ type RingCallEvents = Extract<
45
45
  'call.accepted' | 'call.rejected'
46
46
  >;
47
47
 
48
- // call.created is handled by the StreamVideoClient
49
- // custom events should be handled by integrators
50
48
  type AllCallEvents = Exclude<
51
49
  CallEventTypes,
52
- 'call.created' | 'custom' | RingCallEvents
50
+ | 'call.created' // handled by StreamVideoClient
51
+ | 'call.ring' // handled by StreamVideoClient
52
+ | 'call.notification' // not used currently
53
+ | 'custom' // integrators should handle custom events
54
+ | RingCallEvents // handled by registerRingingCallEventHandlers
53
55
  >;
54
56
 
57
+ /**
58
+ * Registers the default event handlers for a call during its lifecycle.
59
+ *
60
+ * @param call the call to register event handlers for.
61
+ * @param state the call state.
62
+ * @param dispatcher the dispatcher.
63
+ */
55
64
  export const registerEventHandlers = (
56
65
  call: Call,
57
66
  state: CallState,
@@ -80,10 +89,6 @@ export const registerEventHandlers = (
80
89
  'call.session_participant_left': watchCallSessionParticipantLeft(state),
81
90
  'call.unblocked_user': watchUnblockedUser(state),
82
91
  'call.updated': watchCallUpdated(state),
83
- 'call.notification': (event: StreamCallEvent) =>
84
- console.log(`Received ${event.type} event`, event),
85
- 'call.ring': (event: StreamCallEvent) =>
86
- console.log(`Received ${event.type} event`, event),
87
92
  };
88
93
  const eventHandlers = [
89
94
  watchChangePublishQuality(dispatcher, call),
@@ -117,6 +122,11 @@ export const registerEventHandlers = (
117
122
  };
118
123
  };
119
124
 
125
+ /**
126
+ * Registers event handlers for a call that is of ringing type.
127
+ *
128
+ * @param call the call to register event handlers for.
129
+ */
120
130
  export const registerRingingCallEventHandlers = (call: Call) => {
121
131
  const coordinatorRingEvents: {
122
132
  [key in RingCallEvents]: (e: StreamCallEvent) => any;
@@ -708,6 +708,12 @@ export interface CallNotificationEvent {
708
708
  * @memberof CallNotificationEvent
709
709
  */
710
710
  created_at: string;
711
+ /**
712
+ * Call members
713
+ * @type {Array<MemberResponse>}
714
+ * @memberof CallNotificationEvent
715
+ */
716
+ members: Array<MemberResponse>;
711
717
  /**
712
718
  * Call session ID
713
719
  * @type {string}
@@ -1101,6 +1107,12 @@ export interface CallRingEvent {
1101
1107
  * @memberof CallRingEvent
1102
1108
  */
1103
1109
  created_at: string;
1110
+ /**
1111
+ * Call members
1112
+ * @type {Array<MemberResponse>}
1113
+ * @memberof CallRingEvent
1114
+ */
1115
+ members: Array<MemberResponse>;
1104
1116
  /**
1105
1117
  * Call session ID
1106
1118
  * @type {string}
@@ -3213,12 +3225,6 @@ export interface RingSettings {
3213
3225
  * @memberof RingSettings
3214
3226
  */
3215
3227
  auto_cancel_timeout_ms: number;
3216
- /**
3217
- *
3218
- * @type {number}
3219
- * @memberof RingSettings
3220
- */
3221
- auto_reject_timeout_ms: number;
3222
3228
  /**
3223
3229
  *
3224
3230
  * @type {number}
@@ -3238,12 +3244,6 @@ export interface RingSettingsRequest {
3238
3244
  * @memberof RingSettingsRequest
3239
3245
  */
3240
3246
  auto_cancel_timeout_ms?: number;
3241
- /**
3242
- *
3243
- * @type {number}
3244
- * @memberof RingSettingsRequest
3245
- */
3246
- auto_reject_timeout_ms?: number;
3247
3247
  /**
3248
3248
  *
3249
3249
  * @type {number}
@@ -137,6 +137,16 @@ export class StreamVideoWriteableStateStore {
137
137
  return this.setCalls((calls) => calls.filter((c) => c !== call));
138
138
  };
139
139
 
140
+ /**
141
+ * Finds a {@link Call} object in the list of {@link Call} objects created/tracked by this client.
142
+ *
143
+ * @param type the type of call to find.
144
+ * @param id the id of the call to find.
145
+ */
146
+ findCall = (type: string, id: string) => {
147
+ return this.calls.find((c) => c.type === type && c.id === id);
148
+ };
149
+
140
150
  /**
141
151
  * A list of objects describing incoming calls.
142
152
  * @deprecated derive from calls$ instead.