@stream-io/video-client 1.61.0 → 2.0.0-beta.1
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/CHANGELOG.md +10 -10
- package/dist/index.browser.es.js +358 -611
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +357 -611
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +358 -611
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +3 -3
- package/dist/src/StreamVideoClient.d.ts +15 -21
- package/dist/src/store/CallState.d.ts +108 -266
- package/dist/src/store/ClientState.d.ts +62 -0
- package/dist/src/store/index.d.ts +1 -1
- package/dist/src/store/subjects.d.ts +15 -0
- package/dist/src/store/types.d.ts +22 -0
- package/dist/src/types.d.ts +3 -3
- package/package.json +2 -2
- package/src/Call.ts +14 -18
- package/src/StreamVideoClient.ts +40 -47
- package/src/__tests__/Call.autodrop.test.ts +4 -4
- package/src/__tests__/Call.getRingState.test.ts +2 -2
- package/src/__tests__/Call.lifecycle.test.ts +8 -8
- package/src/__tests__/Call.publishing.test.ts +2 -2
- package/src/__tests__/Call.ringSettled.test.ts +4 -4
- package/src/__tests__/Call.ringingLifecycle.test.ts +2 -2
- package/src/__tests__/StreamVideoClient.disconnect.test.ts +62 -0
- package/src/devices/__tests__/CameraManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManager.test.ts +2 -2
- package/src/devices/__tests__/DeviceManagerFilters.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManager.test.ts +2 -2
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +2 -2
- package/src/devices/__tests__/ScreenShareManager.test.ts +3 -3
- package/src/devices/__tests__/SpeakerManager.test.ts +4 -4
- package/src/events/__tests__/call.test.ts +3 -3
- package/src/helpers/__tests__/AudioBindingsWatchdog.test.ts +2 -2
- package/src/helpers/__tests__/DynascaleManager.test.ts +2 -2
- package/src/helpers/__tests__/ViewportTracker.test.ts +2 -2
- package/src/ringing/__tests__/RingStatePoller.test.ts +7 -7
- package/src/ringing/__tests__/RingTimeout.test.ts +4 -4
- package/src/ringing/__tests__/reconcileRingState.test.ts +3 -3
- package/src/rtc/__tests__/Call.reconnect.test.ts +5 -5
- package/src/store/CallState.ts +281 -551
- package/src/store/ClientState.ts +101 -0
- package/src/store/__tests__/CallState.test.ts +1 -1
- package/src/store/__tests__/ClientState.test.ts +35 -0
- package/src/store/__tests__/rxUtils.test.ts +67 -1
- package/src/store/index.ts +1 -1
- package/src/store/rxUtils.ts +8 -4
- package/src/store/subjects.ts +30 -0
- package/src/store/types.ts +45 -0
- package/src/types.ts +3 -3
- package/dist/src/store/stateStore.d.ts +0 -84
- package/src/store/stateStore.ts +0 -156
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Patch } from './rxUtils';
|
|
2
|
+
import type { Call } from '../Call';
|
|
3
|
+
import type { OwnUserResponse } from '../gen/coordinator';
|
|
4
|
+
/**
|
|
5
|
+
* Holds the state of the client, and the calls it created or tracks.
|
|
6
|
+
* You can subscribe to the exposed observables to get notified about changes.
|
|
7
|
+
*/
|
|
8
|
+
export declare class ClientState {
|
|
9
|
+
readonly logger: import("@stream-io/logger").Logger<string & {}>;
|
|
10
|
+
private connectedUserSubject;
|
|
11
|
+
private callsSubject;
|
|
12
|
+
/** Data describing a user successfully connected over WS to the coordinator. */
|
|
13
|
+
connectedUser$: import("rxjs").Observable<OwnUserResponse | undefined>;
|
|
14
|
+
/** A list of {@link Call} objects created/tracked by this client. */
|
|
15
|
+
calls$: import("rxjs").Observable<Call[]>;
|
|
16
|
+
/** The currently connected user. */
|
|
17
|
+
get connectedUser(): OwnUserResponse | undefined;
|
|
18
|
+
/** A list of {@link Call} objects created/tracked by this client. */
|
|
19
|
+
get calls(): Call[];
|
|
20
|
+
/**
|
|
21
|
+
* Sets the currently connected user.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
* @param user the user to set as connected.
|
|
25
|
+
*/
|
|
26
|
+
setConnectedUser: (user: Patch<OwnUserResponse | undefined>) => OwnUserResponse | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Sets the list of {@link Call} objects created/tracked by this client.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
* @param calls the calls to set.
|
|
32
|
+
*/
|
|
33
|
+
setCalls: (calls: Patch<Call[]>) => Call[];
|
|
34
|
+
/**
|
|
35
|
+
* Adds a {@link Call} object to the list of calls tracked by this client.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
* @param call the call to add.
|
|
39
|
+
*/
|
|
40
|
+
registerCall: (call: Call) => void;
|
|
41
|
+
/**
|
|
42
|
+
* Registers a {@link Call} object if it doesn't exist, otherwise updates it.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
* @param call the call to register or update.
|
|
46
|
+
*/
|
|
47
|
+
registerOrUpdateCall: (call: Call) => void | Call[];
|
|
48
|
+
/**
|
|
49
|
+
* Removes a {@link Call} object from the list of calls tracked by this client.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
* @param call the call to remove.
|
|
53
|
+
*/
|
|
54
|
+
unregisterCall: (call: Call) => Call[];
|
|
55
|
+
/**
|
|
56
|
+
* Finds a {@link Call} object in the list of calls tracked by this client.
|
|
57
|
+
*
|
|
58
|
+
* @param type the type of call to find.
|
|
59
|
+
* @param id the id of the call to find.
|
|
60
|
+
*/
|
|
61
|
+
findCall: (type: string, id: string) => Call | undefined;
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BehaviorSubject, Observable } from 'rxjs';
|
|
2
|
+
/** Creates a BehaviorSubject seeded with the given value. */
|
|
3
|
+
export declare function subject<T>(initialValue: T): BehaviorSubject<T>;
|
|
4
|
+
/** Creates a BehaviorSubject which starts out holding `undefined`. */
|
|
5
|
+
export declare function subject<T>(): BehaviorSubject<T | undefined>;
|
|
6
|
+
/**
|
|
7
|
+
* Creates an Observable from the given subject by piping it to the
|
|
8
|
+
* `distinctUntilChanged()` operator.
|
|
9
|
+
*/
|
|
10
|
+
export declare const duc: <T>(source: BehaviorSubject<T>, comparator?: (a: T, b: T) => boolean) => Observable<T>;
|
|
11
|
+
/**
|
|
12
|
+
* Multicasts the given Observable, replaying the latest computed value to
|
|
13
|
+
* every new subscriber instead of re-running the pipeline for each of them.
|
|
14
|
+
*/
|
|
15
|
+
export declare const shared: <T>(source: Observable<T>) => Observable<T>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VideoEvent } from '../gen/coordinator';
|
|
2
|
+
/** Handles a single video event of the given type. */
|
|
3
|
+
export type EventHandler<T extends VideoEvent['type']> = (event: Extract<VideoEvent, {
|
|
4
|
+
type: T;
|
|
5
|
+
}>) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Identity type constrained to real event names, so that a name which no
|
|
8
|
+
* longer exists in `VideoEvent` fails to compile instead of being ignored.
|
|
9
|
+
*/
|
|
10
|
+
type EventTypes<T extends VideoEvent['type']> = T;
|
|
11
|
+
/**
|
|
12
|
+
* Events that intentionally leave the call state untouched. Listing a name
|
|
13
|
+
* here excuses it from `CallStateEventHandlers`, which is what makes a newly
|
|
14
|
+
* introduced event fail to compile until it has been triaged.
|
|
15
|
+
*/
|
|
16
|
+
type UnhandledEventType = EventTypes<'call.frame_recording_ready' | 'call.kicked_user' | 'call.moderation_blur' | 'call.moderation_warning' | 'call.permission_request' | 'call.recording_ready' | 'call.rtmp_broadcast_failed' | 'call.rtmp_broadcast_started' | 'call.rtmp_broadcast_stopped' | 'call.stats_report_ready' | 'call.transcription_ready' | 'call.user_feedback_submitted' | 'call.user_muted' | 'connection.error' | 'connection.ok' | 'health.check' | 'user.updated' | 'custom'>;
|
|
17
|
+
export type AllEventHandlers = {
|
|
18
|
+
[T in VideoEvent['type']]: EventHandler<T>;
|
|
19
|
+
};
|
|
20
|
+
/** A handler for every event that does update the call state. */
|
|
21
|
+
export type CallStateEventHandlers = Omit<AllEventHandlers, UnhandledEventType>;
|
|
22
|
+
export {};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { StreamClient } from './coordinator/connection/client';
|
|
|
4
4
|
import type { ClientEventReporter } from './reporting';
|
|
5
5
|
import type { RejectReason, StreamClientOptions, TokenProvider, User } from './coordinator/connection/types';
|
|
6
6
|
import type { Comparator } from './sorting';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ClientState } from './store';
|
|
8
8
|
import { AxiosError } from 'axios';
|
|
9
9
|
import type { Call } from './Call';
|
|
10
10
|
export type StreamReaction = Pick<VideoReactionResponse, 'type' | 'emoji_code' | 'custom'>;
|
|
@@ -293,9 +293,9 @@ export type CallConstructor = {
|
|
|
293
293
|
*/
|
|
294
294
|
sortParticipantsBy?: Comparator<StreamVideoParticipant>;
|
|
295
295
|
/**
|
|
296
|
-
* The state
|
|
296
|
+
* The state of the client.
|
|
297
297
|
*/
|
|
298
|
-
|
|
298
|
+
clientState: ClientState;
|
|
299
299
|
};
|
|
300
300
|
type StreamVideoClientBaseOptions = {
|
|
301
301
|
apiKey: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
4
|
"main": "dist/index.cjs.js",
|
|
5
5
|
"module": "dist/index.es.js",
|
|
6
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@openapitools/openapi-generator-cli": "^2.40.1",
|
|
47
47
|
"@rollup/plugin-replace": "^6.0.3",
|
|
48
48
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
49
|
-
"@stream-io/audio-filters-web": "^0.
|
|
49
|
+
"@stream-io/audio-filters-web": "^1.0.0-beta.0",
|
|
50
50
|
"@stream-io/node-sdk": "^0.7.63",
|
|
51
51
|
"@stream-io/typescript-config": "^0.0.0",
|
|
52
52
|
"@total-typescript/shoehorn": "^0.1.2",
|
package/src/Call.ts
CHANGED
|
@@ -22,11 +22,7 @@ import {
|
|
|
22
22
|
registerEventHandlers,
|
|
23
23
|
registerRingingCallEventHandlers,
|
|
24
24
|
} from './events/callEventHandlers';
|
|
25
|
-
import {
|
|
26
|
-
CallingState,
|
|
27
|
-
CallState,
|
|
28
|
-
StreamVideoWriteableStateStore,
|
|
29
|
-
} from './store';
|
|
25
|
+
import { CallingState, CallState, ClientState } from './store';
|
|
30
26
|
import {
|
|
31
27
|
createSafeAsyncSubscription,
|
|
32
28
|
createSubscription,
|
|
@@ -307,7 +303,7 @@ export class Call {
|
|
|
307
303
|
private ringTimeout: RingTimeout | undefined;
|
|
308
304
|
private ringStatePoller: RingStatePoller | undefined;
|
|
309
305
|
|
|
310
|
-
private readonly
|
|
306
|
+
private readonly clientState: ClientState;
|
|
311
307
|
public readonly streamClient: StreamClient;
|
|
312
308
|
public readonly clientEventReporter: ClientEventReporter;
|
|
313
309
|
private sfuClient?: StreamSfuClient;
|
|
@@ -390,7 +386,7 @@ export class Call {
|
|
|
390
386
|
members,
|
|
391
387
|
ownCapabilities,
|
|
392
388
|
sortParticipantsBy,
|
|
393
|
-
|
|
389
|
+
clientState,
|
|
394
390
|
ringing = false,
|
|
395
391
|
watching = false,
|
|
396
392
|
}: CallConstructor) {
|
|
@@ -401,7 +397,7 @@ export class Call {
|
|
|
401
397
|
this.watching = watching;
|
|
402
398
|
this.streamClient = streamClient;
|
|
403
399
|
this.clientEventReporter = clientEventReporter;
|
|
404
|
-
this.
|
|
400
|
+
this.clientState = clientState;
|
|
405
401
|
this.streamClientBasePath = `/call/${this.type}/${this.id}`;
|
|
406
402
|
this.logger = videoLoggerSystem.getLogger('Call');
|
|
407
403
|
|
|
@@ -560,7 +556,7 @@ export class Call {
|
|
|
560
556
|
|
|
561
557
|
private handleRingingCall = () => {
|
|
562
558
|
const callSession = this.state.session;
|
|
563
|
-
const receiver_id = this.
|
|
559
|
+
const receiver_id = this.clientState.connectedUser?.id;
|
|
564
560
|
const ended_at = callSession?.ended_at;
|
|
565
561
|
const created_by_id = this.state.createdBy?.id;
|
|
566
562
|
|
|
@@ -825,7 +821,7 @@ export class Call {
|
|
|
825
821
|
this.ringingSubject.next(false);
|
|
826
822
|
this.cancelAutoDrop();
|
|
827
823
|
this.cancelRingStatePolling();
|
|
828
|
-
this.
|
|
824
|
+
this.clientState.unregisterCall(this);
|
|
829
825
|
|
|
830
826
|
globalThis.streamRNVideoSDK?.callManager.stop({
|
|
831
827
|
isRingingTypeCall: this.ringing,
|
|
@@ -892,7 +888,7 @@ export class Call {
|
|
|
892
888
|
* Retrieves the current user ID.
|
|
893
889
|
*/
|
|
894
890
|
get currentUserId() {
|
|
895
|
-
return this.
|
|
891
|
+
return this.clientState.connectedUser?.id;
|
|
896
892
|
}
|
|
897
893
|
|
|
898
894
|
/**
|
|
@@ -957,8 +953,8 @@ export class Call {
|
|
|
957
953
|
this.ringingSubject.next(true);
|
|
958
954
|
// we remove the instance from the calls list to enable the following filter in useCalls hook
|
|
959
955
|
// const calls = useCalls().filter((c) => c.ringing);
|
|
960
|
-
const calls = this.
|
|
961
|
-
this.
|
|
956
|
+
const calls = this.clientState.calls.filter((c) => c.cid !== this.cid);
|
|
957
|
+
this.clientState.setCalls([this, ...calls]);
|
|
962
958
|
await this.applyDeviceConfig(settings, { publish: false });
|
|
963
959
|
};
|
|
964
960
|
|
|
@@ -1005,7 +1001,7 @@ export class Call {
|
|
|
1005
1001
|
|
|
1006
1002
|
if (this.streamClient._hasConnectionID()) {
|
|
1007
1003
|
this.watching = true;
|
|
1008
|
-
this.
|
|
1004
|
+
this.clientState.registerOrUpdateCall(this);
|
|
1009
1005
|
}
|
|
1010
1006
|
await this.applyDeviceConfig(response.call.settings, { publish: false });
|
|
1011
1007
|
|
|
@@ -1033,7 +1029,7 @@ export class Call {
|
|
|
1033
1029
|
|
|
1034
1030
|
if (this.streamClient._hasConnectionID()) {
|
|
1035
1031
|
this.watching = true;
|
|
1036
|
-
this.
|
|
1032
|
+
this.clientState.registerOrUpdateCall(this);
|
|
1037
1033
|
}
|
|
1038
1034
|
|
|
1039
1035
|
await this.applyDeviceConfig(response.call.settings, { publish: false });
|
|
@@ -1207,7 +1203,7 @@ export class Call {
|
|
|
1207
1203
|
// for Android/iOS, we need to start the call in the callingx library as soon as possible
|
|
1208
1204
|
await callingX.joinCall(
|
|
1209
1205
|
this,
|
|
1210
|
-
this.
|
|
1206
|
+
this.clientState.calls,
|
|
1211
1207
|
supersededByLeave,
|
|
1212
1208
|
);
|
|
1213
1209
|
if (supersededByLeave()) {
|
|
@@ -1873,7 +1869,7 @@ export class Call {
|
|
|
1873
1869
|
|
|
1874
1870
|
if (this.streamClient._hasConnectionID()) {
|
|
1875
1871
|
this.watching = true;
|
|
1876
|
-
this.
|
|
1872
|
+
this.clientState.registerOrUpdateCall(this);
|
|
1877
1873
|
}
|
|
1878
1874
|
|
|
1879
1875
|
return joinResponse;
|
|
@@ -2696,7 +2692,7 @@ export class Call {
|
|
|
2696
2692
|
};
|
|
2697
2693
|
|
|
2698
2694
|
/**
|
|
2699
|
-
* Will enhance the reported stats with additional participant-specific information (`callStatsReport$` state
|
|
2695
|
+
* Will enhance the reported stats with additional participant-specific information (the `callStatsReport$` state variable).
|
|
2700
2696
|
* This is usually helpful when detailed stats for a specific participant are needed.
|
|
2701
2697
|
*
|
|
2702
2698
|
* @param sessionId the sessionId to start reporting for.
|
package/src/StreamVideoClient.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { Call } from './Call';
|
|
2
2
|
import { StreamClient } from './coordinator/connection/client';
|
|
3
|
-
import {
|
|
4
|
-
CallingState,
|
|
5
|
-
StreamVideoReadOnlyStateStore,
|
|
6
|
-
StreamVideoWriteableStateStore,
|
|
7
|
-
} from './store';
|
|
3
|
+
import { CallingState, ClientState } from './store';
|
|
8
4
|
import type {
|
|
9
5
|
CallCreatedEvent,
|
|
10
6
|
CallRingEvent,
|
|
@@ -48,19 +44,14 @@ import { ClientEventReporter } from './reporting';
|
|
|
48
44
|
*/
|
|
49
45
|
export class StreamVideoClient {
|
|
50
46
|
/**
|
|
51
|
-
*
|
|
47
|
+
* The reactive state of this client.
|
|
52
48
|
* You can subscribe to changes of the different state variables.
|
|
53
|
-
* Our library is built in a way that all state changes are exposed in this store,
|
|
54
|
-
* o all UI changes in your application should be handled by subscribing to these variables.
|
|
55
|
-
*
|
|
56
|
-
* @deprecated use the `client.state` getter.
|
|
57
49
|
*/
|
|
58
|
-
readonly
|
|
50
|
+
readonly state = new ClientState();
|
|
59
51
|
readonly logger: ScopedLogger;
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
readonly clientEventReporter: ClientEventReporter;
|
|
53
|
+
readonly streamClient: StreamClient;
|
|
54
|
+
private readonly clientEventReporter: ClientEventReporter;
|
|
64
55
|
|
|
65
56
|
private effectsRegistered = false;
|
|
66
57
|
private eventHandlersToUnregister: Array<() => void> = [];
|
|
@@ -103,11 +94,6 @@ export class StreamVideoClient {
|
|
|
103
94
|
enabled: clientOptions?.clientEventsReportingEnabled ?? true,
|
|
104
95
|
});
|
|
105
96
|
|
|
106
|
-
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
107
|
-
this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(
|
|
108
|
-
this.writeableStateStore,
|
|
109
|
-
);
|
|
110
|
-
|
|
111
97
|
if (typeof apiKeyOrArgs !== 'string' && apiKeyOrArgs.user) {
|
|
112
98
|
const user = apiKeyOrArgs.user;
|
|
113
99
|
if (user.type === 'anonymous') user.id = '!anon';
|
|
@@ -158,13 +144,6 @@ export class StreamVideoClient {
|
|
|
158
144
|
StreamVideoClient._instances.set(instanceKey, this);
|
|
159
145
|
};
|
|
160
146
|
|
|
161
|
-
/**
|
|
162
|
-
* Return the reactive state store, use this if you want to be notified about changes to the client state
|
|
163
|
-
*/
|
|
164
|
-
get state() {
|
|
165
|
-
return this.readOnlyStateStore;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
147
|
private registerEffects = () => {
|
|
169
148
|
if (this.effectsRegistered) return;
|
|
170
149
|
|
|
@@ -174,7 +153,7 @@ export class StreamVideoClient {
|
|
|
174
153
|
this.on('connection.changed', (event) => {
|
|
175
154
|
if (!event.online) return;
|
|
176
155
|
|
|
177
|
-
const callsToReWatch = this.
|
|
156
|
+
const callsToReWatch = this.state.calls
|
|
178
157
|
.filter((call) => call.watching)
|
|
179
158
|
.map((call) => call.cid);
|
|
180
159
|
if (callsToReWatch.length <= 0) return;
|
|
@@ -197,7 +176,7 @@ export class StreamVideoClient {
|
|
|
197
176
|
const concurrencyTag = getCallInitConcurrencyTag(e.call_cid);
|
|
198
177
|
await withoutConcurrency(concurrencyTag, async () => {
|
|
199
178
|
const ringing = e.type === 'call.ring';
|
|
200
|
-
let call = this.
|
|
179
|
+
let call = this.state.findCall(e.call.type, e.call.id);
|
|
201
180
|
if (call) {
|
|
202
181
|
if (ringing) {
|
|
203
182
|
if (this.shouldRejectCall(call.cid)) {
|
|
@@ -224,7 +203,7 @@ export class StreamVideoClient {
|
|
|
224
203
|
type: e.call.type,
|
|
225
204
|
id: e.call.id,
|
|
226
205
|
members: e.members,
|
|
227
|
-
|
|
206
|
+
clientState: this.state,
|
|
228
207
|
ringing,
|
|
229
208
|
});
|
|
230
209
|
|
|
@@ -239,7 +218,7 @@ export class StreamVideoClient {
|
|
|
239
218
|
}
|
|
240
219
|
} else {
|
|
241
220
|
call.state.updateFromCallResponse(e.call);
|
|
242
|
-
this.
|
|
221
|
+
this.state.registerCall(call);
|
|
243
222
|
this.logger.info(`New call created and registered: ${call.cid}`);
|
|
244
223
|
}
|
|
245
224
|
});
|
|
@@ -278,10 +257,7 @@ export class StreamVideoClient {
|
|
|
278
257
|
});
|
|
279
258
|
|
|
280
259
|
for (const c of response.calls) {
|
|
281
|
-
const call = this.
|
|
282
|
-
c.call.type,
|
|
283
|
-
c.call.id,
|
|
284
|
-
);
|
|
260
|
+
const call = this.state.findCall(c.call.type, c.call.id);
|
|
285
261
|
|
|
286
262
|
if (call) {
|
|
287
263
|
call.updateFromCallStateResponse(c);
|
|
@@ -310,8 +286,6 @@ export class StreamVideoClient {
|
|
|
310
286
|
|
|
311
287
|
/**
|
|
312
288
|
* Connects the given user to the client.
|
|
313
|
-
* Only one user can connect at a time, if you want to change users, call `disconnectUser` before connecting a new user.
|
|
314
|
-
* If the connection is successful, the connected user [state variable](#readonlystatestore) will be updated accordingly.
|
|
315
289
|
*
|
|
316
290
|
* @param user the user to connect.
|
|
317
291
|
* @param tokenOrProvider a token or a function that returns a token.
|
|
@@ -369,7 +343,7 @@ export class StreamVideoClient {
|
|
|
369
343
|
|
|
370
344
|
// connectUserResponse will be void if connectUser called twice for the same user
|
|
371
345
|
if (connectUserResponse?.me) {
|
|
372
|
-
this.
|
|
346
|
+
this.state.setConnectedUser(connectUserResponse.me);
|
|
373
347
|
}
|
|
374
348
|
|
|
375
349
|
this.registerEffects();
|
|
@@ -377,19 +351,36 @@ export class StreamVideoClient {
|
|
|
377
351
|
return connectUserResponse;
|
|
378
352
|
};
|
|
379
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Leaves every call this client still tracks. Runs while the coordinator
|
|
356
|
+
* connection is alive, so that the leave requests each call sends reach the
|
|
357
|
+
* backend.
|
|
358
|
+
*/
|
|
359
|
+
private leaveAllCalls = async () => {
|
|
360
|
+
for (const call of this.state.calls) {
|
|
361
|
+
if (call.state.callingState === CallingState.LEFT) continue;
|
|
362
|
+
|
|
363
|
+
this.logger.info(`User disconnected, leaving call: ${call.cid}`);
|
|
364
|
+
await call
|
|
365
|
+
.leave({ message: 'client.disconnectUser() called' })
|
|
366
|
+
.catch((err) => {
|
|
367
|
+
this.logger.error(`Error leaving call: ${call.cid}`, err);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
380
372
|
/**
|
|
381
373
|
* Disconnects the currently connected user from the client.
|
|
382
|
-
*
|
|
383
|
-
* If the connection is successfully disconnected, the connected user [state variable](#readonlystatestore) will be updated accordingly
|
|
374
|
+
* Leaves every call the user is still in, and updates `client.state`accordingly.
|
|
384
375
|
*
|
|
385
376
|
* @param timeout Max number of ms, to wait for close event of websocket, before forcefully assuming successful disconnection.
|
|
386
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
387
377
|
*/
|
|
388
378
|
disconnectUser = async (timeout?: number) => {
|
|
389
379
|
await withoutConcurrency(this.connectionConcurrencyTag, async () => {
|
|
390
380
|
const { user, key } = this.streamClient;
|
|
391
381
|
if (!user) return;
|
|
392
382
|
|
|
383
|
+
await this.leaveAllCalls();
|
|
393
384
|
await this.streamClient.disconnectUser(timeout);
|
|
394
385
|
|
|
395
386
|
if (user.id) {
|
|
@@ -398,14 +389,16 @@ export class StreamVideoClient {
|
|
|
398
389
|
this.eventHandlersToUnregister.forEach((unregister) => unregister());
|
|
399
390
|
this.eventHandlersToUnregister = [];
|
|
400
391
|
this.effectsRegistered = false;
|
|
401
|
-
this.
|
|
392
|
+
this.state.setConnectedUser(undefined);
|
|
402
393
|
});
|
|
403
394
|
};
|
|
404
395
|
|
|
405
396
|
/**
|
|
406
397
|
* You can subscribe to WebSocket events provided by the API.
|
|
407
398
|
* To remove a subscription, call the `off` method or, execute the returned unsubscribe function.
|
|
408
|
-
* Please note that subscribing to WebSocket events is an advanced use-case,
|
|
399
|
+
* Please note that subscribing to WebSocket events is an advanced use-case,
|
|
400
|
+
* for most use-cases it should be enough to watch for changes in the reactive
|
|
401
|
+
* `client.state`.
|
|
409
402
|
*
|
|
410
403
|
* @param eventName the event name or 'all'.
|
|
411
404
|
* @param callback the callback which will be called when the event is emitted.
|
|
@@ -444,7 +437,7 @@ export class StreamVideoClient {
|
|
|
444
437
|
options: { reuseInstance?: boolean } = {},
|
|
445
438
|
) => {
|
|
446
439
|
const call = options.reuseInstance
|
|
447
|
-
? this.
|
|
440
|
+
? this.state.findCall(type, id)
|
|
448
441
|
: undefined;
|
|
449
442
|
return (
|
|
450
443
|
call ??
|
|
@@ -453,7 +446,7 @@ export class StreamVideoClient {
|
|
|
453
446
|
clientEventReporter: this.clientEventReporter,
|
|
454
447
|
id: id,
|
|
455
448
|
type: type,
|
|
456
|
-
|
|
449
|
+
clientState: this.state,
|
|
457
450
|
})
|
|
458
451
|
);
|
|
459
452
|
};
|
|
@@ -492,7 +485,7 @@ export class StreamVideoClient {
|
|
|
492
485
|
members: c.members,
|
|
493
486
|
ownCapabilities: c.own_capabilities,
|
|
494
487
|
watching: data.watch,
|
|
495
|
-
|
|
488
|
+
clientState: this.state,
|
|
496
489
|
});
|
|
497
490
|
call.state.updateFromCallResponse(c.call);
|
|
498
491
|
await call.applyDeviceConfig(c.call.settings, {
|
|
@@ -501,7 +494,7 @@ export class StreamVideoClient {
|
|
|
501
494
|
});
|
|
502
495
|
if (data.watch) {
|
|
503
496
|
await call.setup();
|
|
504
|
-
this.
|
|
497
|
+
this.state.registerCall(call);
|
|
505
498
|
}
|
|
506
499
|
calls.push(call);
|
|
507
500
|
}
|
|
@@ -636,7 +629,7 @@ export class StreamVideoClient {
|
|
|
636
629
|
clientEventReporter: this.clientEventReporter,
|
|
637
630
|
type: callType,
|
|
638
631
|
id: callId,
|
|
639
|
-
|
|
632
|
+
clientState: this.state,
|
|
640
633
|
ringing: true,
|
|
641
634
|
});
|
|
642
635
|
await call.get();
|
|
@@ -6,7 +6,7 @@ import { Call } from '../Call';
|
|
|
6
6
|
import { StreamClient } from '../coordinator/connection/client';
|
|
7
7
|
import { ClientEventReporter } from '../reporting';
|
|
8
8
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
9
|
-
import { CallingState,
|
|
9
|
+
import { CallingState, ClientState } from '../store';
|
|
10
10
|
import { CallSettingsResponse } from '../gen/coordinator';
|
|
11
11
|
|
|
12
12
|
const TIMEOUT_MS = 30_000;
|
|
@@ -19,18 +19,18 @@ describe('Auto drop ringing calls', () => {
|
|
|
19
19
|
let call: Call;
|
|
20
20
|
|
|
21
21
|
const ringingCall = () => {
|
|
22
|
-
const
|
|
22
|
+
const clientState = new ClientState();
|
|
23
23
|
const streamClient = new StreamClient('abc');
|
|
24
24
|
const newCall = new Call({
|
|
25
25
|
type: 'test',
|
|
26
26
|
id: generateUUIDv4(),
|
|
27
27
|
streamClient,
|
|
28
28
|
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
29
|
-
|
|
29
|
+
clientState,
|
|
30
30
|
ringing: true,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
clientState.setConnectedUser(fromPartial({ id: userId }));
|
|
34
34
|
newCall.state['createdBySubject'].next(fromPartial({ id: userId }));
|
|
35
35
|
newCall.state['settingsSubject'].next(
|
|
36
36
|
fromPartial<CallSettingsResponse>({
|
|
@@ -6,7 +6,7 @@ import { Call } from '../Call';
|
|
|
6
6
|
import { StreamClient } from '../coordinator/connection/client';
|
|
7
7
|
import { ClientEventReporter } from '../reporting';
|
|
8
8
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
9
|
-
import {
|
|
9
|
+
import { ClientState } from '../store';
|
|
10
10
|
import { CallSessionResponse } from '../gen/coordinator';
|
|
11
11
|
|
|
12
12
|
describe('Call.getRingState', () => {
|
|
@@ -19,7 +19,7 @@ describe('Call.getRingState', () => {
|
|
|
19
19
|
id: callId,
|
|
20
20
|
streamClient,
|
|
21
21
|
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
22
|
-
|
|
22
|
+
clientState: new ClientState(),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
if (sessionId) {
|
|
@@ -15,7 +15,7 @@ import { ClientEventReporter } from '../reporting';
|
|
|
15
15
|
import type { GetCallResponse, JoinCallResponse } from '../gen/coordinator';
|
|
16
16
|
import { WebsocketReconnectStrategy } from '../gen/video/sfu/models/models';
|
|
17
17
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
18
|
-
import { CallingState,
|
|
18
|
+
import { CallingState, ClientState } from '../store';
|
|
19
19
|
import { promiseWithResolvers } from '../helpers/promise';
|
|
20
20
|
|
|
21
21
|
// A controlled stand-in for the retry backoff. Unless a test installs a hook,
|
|
@@ -74,7 +74,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
74
74
|
streamClient,
|
|
75
75
|
enabled: false,
|
|
76
76
|
}),
|
|
77
|
-
|
|
77
|
+
clientState: new ClientState(),
|
|
78
78
|
});
|
|
79
79
|
internalCall = call as unknown as typeof internalCall;
|
|
80
80
|
});
|
|
@@ -283,7 +283,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
283
283
|
const accept = vi
|
|
284
284
|
.spyOn(call, 'accept')
|
|
285
285
|
.mockImplementation(() => responseAt('acceptance', { duration: '0ms' }));
|
|
286
|
-
const registerCall = vi.spyOn(call.
|
|
286
|
+
const registerCall = vi.spyOn(call.clientState, 'registerOrUpdateCall');
|
|
287
287
|
const request = vi.spyOn(call.streamClient, 'post').mockImplementation(() =>
|
|
288
288
|
responseAt(
|
|
289
289
|
'coordinator request',
|
|
@@ -361,7 +361,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
361
361
|
expect(call['currentPublishOptions']).toBe(publishOptions);
|
|
362
362
|
expect(call['fastReconnectDeadlineSeconds']).toBe(reconnectDeadline);
|
|
363
363
|
expect(call['sfuClient']).toBeUndefined();
|
|
364
|
-
expect(call.
|
|
364
|
+
expect(call.clientState.calls).not.toContain(call);
|
|
365
365
|
expect(call.ringing).toBe(false);
|
|
366
366
|
expect(call.state.callingState).toBe(CallingState.LEFT);
|
|
367
367
|
if (phase === 'SFU rejection') {
|
|
@@ -401,7 +401,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
401
401
|
call['sfuClient'] = oldSfu;
|
|
402
402
|
call['subscriber'] = oldSubscriber;
|
|
403
403
|
call['publisher'] = oldPublisher;
|
|
404
|
-
call.
|
|
404
|
+
call.clientState.registerOrUpdateCall(call);
|
|
405
405
|
const task = call['reconnect'](
|
|
406
406
|
WebsocketReconnectStrategy[strategy],
|
|
407
407
|
'test',
|
|
@@ -419,7 +419,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
419
419
|
await task;
|
|
420
420
|
|
|
421
421
|
expect(call.state.callingState).toBe(CallingState.LEFT);
|
|
422
|
-
expect(call.
|
|
422
|
+
expect(call.clientState.calls).not.toContain(call);
|
|
423
423
|
expect(get).not.toHaveBeenCalled();
|
|
424
424
|
expect(restoreSubscribed).not.toHaveBeenCalled();
|
|
425
425
|
if (phase === 'join') expect(restorePublished).not.toHaveBeenCalled();
|
|
@@ -461,7 +461,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
461
461
|
await task;
|
|
462
462
|
|
|
463
463
|
expect(call.state.callingState).toBe(CallingState.LEFT);
|
|
464
|
-
expect(call.
|
|
464
|
+
expect(call.clientState.calls).not.toContain(call);
|
|
465
465
|
expect(get).toHaveBeenCalledTimes(phase === 'failure refresh' ? 1 : 0);
|
|
466
466
|
expect(reconnect).toHaveBeenCalledOnce();
|
|
467
467
|
expect(call['reconnectStrategy']).toBe(
|
|
@@ -492,7 +492,7 @@ describe('Call lifecycle wiring', () => {
|
|
|
492
492
|
pending.resolve(response);
|
|
493
493
|
await expect(task).resolves.toBe(response);
|
|
494
494
|
|
|
495
|
-
expect(call.
|
|
495
|
+
expect(call.clientState.calls.includes(call)).toBe(!leaveDuringFetch);
|
|
496
496
|
expect(update).toHaveBeenCalledTimes(leaveDuringFetch ? 0 : 1);
|
|
497
497
|
expect(configure).toHaveBeenCalledTimes(leaveDuringFetch ? 0 : 1);
|
|
498
498
|
expect(call.state.callingState).toBe(
|
|
@@ -8,7 +8,7 @@ import { ClientEventReporter } from '../reporting';
|
|
|
8
8
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
9
9
|
import { PermissionsContext } from '../permissions';
|
|
10
10
|
import { OwnCapability } from '../gen/coordinator';
|
|
11
|
-
import {
|
|
11
|
+
import { ClientState } from '../store';
|
|
12
12
|
import { TrackType } from '../gen/video/sfu/models/models';
|
|
13
13
|
import { StreamSfuClient } from '../StreamSfuClient';
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ describe('Publishing and Unpublishing tracks', () => {
|
|
|
22
22
|
id: generateUUIDv4(),
|
|
23
23
|
streamClient,
|
|
24
24
|
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
25
|
-
|
|
25
|
+
clientState: new ClientState(),
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const ctx = new PermissionsContext();
|
|
@@ -6,7 +6,7 @@ import { Call } from '../Call';
|
|
|
6
6
|
import { StreamClient } from '../coordinator/connection/client';
|
|
7
7
|
import { ClientEventReporter } from '../reporting';
|
|
8
8
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
9
|
-
import { CallingState,
|
|
9
|
+
import { CallingState, ClientState } from '../store';
|
|
10
10
|
import { CallSessionResponse } from '../gen/coordinator';
|
|
11
11
|
|
|
12
12
|
const ME = 'jane';
|
|
@@ -18,18 +18,18 @@ describe('Leaving a call settled by the current user', () => {
|
|
|
18
18
|
let call: Call;
|
|
19
19
|
|
|
20
20
|
const createCall = async (ringing: boolean) => {
|
|
21
|
-
const
|
|
21
|
+
const clientState = new ClientState();
|
|
22
22
|
const streamClient = new StreamClient('abc');
|
|
23
23
|
call = new Call({
|
|
24
24
|
type: 'test',
|
|
25
25
|
id: generateUUIDv4(),
|
|
26
26
|
streamClient,
|
|
27
27
|
clientEventReporter: new ClientEventReporter({ streamClient }),
|
|
28
|
-
|
|
28
|
+
clientState,
|
|
29
29
|
ringing,
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
clientState.setConnectedUser(fromPartial({ id: ME }));
|
|
33
33
|
vi.spyOn(call, 'leave').mockResolvedValue(undefined);
|
|
34
34
|
await call.setup();
|
|
35
35
|
return call;
|