@stream-io/video-client 0.6.10 → 0.7.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 +18 -0
- package/dist/index.browser.es.js +60 -157
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +59 -164
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.es.js +60 -162
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +13 -1
- package/dist/src/coordinator/connection/client.d.ts +0 -11
- package/dist/src/coordinator/connection/signing.d.ts +1 -24
- package/dist/src/coordinator/connection/token_manager.d.ts +2 -3
- package/dist/src/coordinator/connection/types.d.ts +4 -4
- package/dist/src/gen/coordinator/index.d.ts +382 -1018
- package/dist/src/stats/utils.d.ts +11 -0
- package/dist/src/store/CallState.d.ts +2 -2
- package/index.ts +0 -1
- package/package.json +2 -3
- package/src/Call.ts +53 -0
- package/src/__tests__/StreamVideoClient.test.ts +8 -3
- package/src/coordinator/connection/client.ts +1 -40
- package/src/coordinator/connection/signing.ts +10 -89
- package/src/coordinator/connection/token_manager.ts +3 -17
- package/src/coordinator/connection/types.ts +4 -4
- package/src/gen/coordinator/index.ts +361 -983
- package/src/stats/utils.ts +23 -0
- package/src/store/CallState.ts +5 -4
- package/dist/src/StreamVideoServerClient.d.ts +0 -33
- package/src/StreamVideoServerClient.ts +0 -106
- package/src/__tests__/server-side/call-members.test.ts +0 -93
- package/src/__tests__/server-side/call-types.test.ts +0 -127
- package/src/__tests__/server-side/call.test.ts +0 -158
- package/src/__tests__/server-side/create-token.test.ts +0 -44
- package/src/__tests__/server-side/server-side-user.test.ts +0 -28
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import { LocalClientDetailsType } from '../client-details';
|
|
1
2
|
/**
|
|
2
3
|
* Flatten the stats report into an array of stats objects.
|
|
3
4
|
*
|
|
4
5
|
* @param report the report to flatten.
|
|
5
6
|
*/
|
|
6
7
|
export declare const flatten: (report: RTCStatsReport) => RTCStats[];
|
|
8
|
+
export declare const getSdkSignature: (clientDetails: LocalClientDetailsType) => {
|
|
9
|
+
os?: import("../gen/video/sfu/models/models").OS | undefined;
|
|
10
|
+
browser?: import("../gen/video/sfu/models/models").Browser | undefined;
|
|
11
|
+
device?: import("../gen/video/sfu/models/models").Device | undefined;
|
|
12
|
+
webRTCInfo?: {
|
|
13
|
+
version: string;
|
|
14
|
+
} | undefined;
|
|
15
|
+
sdkName: string;
|
|
16
|
+
sdkVersion: string;
|
|
17
|
+
};
|
|
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs';
|
|
|
2
2
|
import type { Patch } from './rxUtils';
|
|
3
3
|
import { StreamVideoParticipant, StreamVideoParticipantPatch, StreamVideoParticipantPatches } from '../types';
|
|
4
4
|
import { CallStatsReport } from '../stats';
|
|
5
|
-
import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse,
|
|
5
|
+
import { CallIngressResponse, CallResponse, CallSessionResponse, CallSettingsResponse, EgressResponse, MemberResponse, OwnCapability, ThumbnailResponse, UserResponse, WSEvent } from '../gen/coordinator';
|
|
6
6
|
import { Pin } from '../gen/video/sfu/models/models';
|
|
7
7
|
import { Comparator } from '../sorting';
|
|
8
8
|
import { Logger } from '../coordinator/connection/types';
|
|
@@ -476,7 +476,7 @@ export declare class CallState {
|
|
|
476
476
|
*
|
|
477
477
|
* @param event the video event that our backend sent us.
|
|
478
478
|
*/
|
|
479
|
-
updateFromEvent: (event:
|
|
479
|
+
updateFromEvent: (event: WSEvent) => void;
|
|
480
480
|
/**
|
|
481
481
|
* Updates the participant pinned state with server side pinning data.
|
|
482
482
|
*
|
package/index.ts
CHANGED
|
@@ -13,7 +13,6 @@ export * from './src/stats/types';
|
|
|
13
13
|
export * from './src/Call';
|
|
14
14
|
export * from './src/CallType';
|
|
15
15
|
export * from './src/StreamVideoClient';
|
|
16
|
-
export * from './src/StreamVideoServerClient';
|
|
17
16
|
export * from './src/StreamSfuClient';
|
|
18
17
|
export * from './src/devices';
|
|
19
18
|
export * from './src/store';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"packageManager": "yarn@3.2.4",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
"axios": "^1.6.0",
|
|
36
36
|
"base64-js": "^1.5.1",
|
|
37
37
|
"isomorphic-ws": "^5.0.0",
|
|
38
|
-
"jsonwebtoken": "^9.0.2",
|
|
39
38
|
"rxjs": "~7.8.1",
|
|
40
39
|
"sdp-transform": "^2.14.1",
|
|
41
40
|
"ua-parser-js": "^1.0.36",
|
|
@@ -46,7 +45,7 @@
|
|
|
46
45
|
"@openapitools/openapi-generator-cli": "^2.7.0",
|
|
47
46
|
"@rollup/plugin-replace": "^5.0.5",
|
|
48
47
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
49
|
-
"@
|
|
48
|
+
"@stream-io/node-sdk": "^0.1.12",
|
|
50
49
|
"@types/sdp-transform": "^2.4.7",
|
|
51
50
|
"@types/ua-parser-js": "^0.7.37",
|
|
52
51
|
"@vitest/coverage-v8": "^0.34.4",
|
package/src/Call.ts
CHANGED
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
AcceptCallResponse,
|
|
23
23
|
BlockUserRequest,
|
|
24
24
|
BlockUserResponse,
|
|
25
|
+
CollectUserFeedbackRequest,
|
|
26
|
+
CollectUserFeedbackResponse,
|
|
25
27
|
EndCallResponse,
|
|
26
28
|
GetCallResponse,
|
|
27
29
|
GetCallStatsResponse,
|
|
@@ -123,6 +125,7 @@ import {
|
|
|
123
125
|
ScreenShareManager,
|
|
124
126
|
SpeakerManager,
|
|
125
127
|
} from './devices';
|
|
128
|
+
import { getSdkSignature } from './stats/utils';
|
|
126
129
|
|
|
127
130
|
/**
|
|
128
131
|
* An object representation of a `Call`.
|
|
@@ -1909,6 +1912,56 @@ export class Call {
|
|
|
1909
1912
|
return this.streamClient.get<GetCallStatsResponse>(endpoint);
|
|
1910
1913
|
};
|
|
1911
1914
|
|
|
1915
|
+
/**
|
|
1916
|
+
* Submit user feedback for the call
|
|
1917
|
+
*
|
|
1918
|
+
* @param rating Rating between 1 and 5 denoting the experience of the user in the call
|
|
1919
|
+
* @param reason The reason/description for the rating
|
|
1920
|
+
* @param custom Custom data
|
|
1921
|
+
* @returns
|
|
1922
|
+
*/
|
|
1923
|
+
submitFeedback = async (
|
|
1924
|
+
rating: number,
|
|
1925
|
+
{
|
|
1926
|
+
reason,
|
|
1927
|
+
custom,
|
|
1928
|
+
}: {
|
|
1929
|
+
reason?: string;
|
|
1930
|
+
custom?: Record<string, any>;
|
|
1931
|
+
} = {},
|
|
1932
|
+
) => {
|
|
1933
|
+
if (rating < 1 || rating > 5) {
|
|
1934
|
+
throw new Error('Rating must be between 1 and 5');
|
|
1935
|
+
}
|
|
1936
|
+
const userSessionId = this.sfuClient?.sessionId;
|
|
1937
|
+
const callSessionId = this.state.session?.id;
|
|
1938
|
+
if (!callSessionId || !userSessionId) {
|
|
1939
|
+
throw new Error(
|
|
1940
|
+
'Feedback can be submitted only in the context of a call session',
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const { sdkName, sdkVersion, ...platform } = getSdkSignature(
|
|
1945
|
+
getClientDetails(),
|
|
1946
|
+
);
|
|
1947
|
+
|
|
1948
|
+
const endpoint = `${this.streamClientBasePath}/feedback/${callSessionId}`;
|
|
1949
|
+
return this.streamClient.post<
|
|
1950
|
+
CollectUserFeedbackResponse,
|
|
1951
|
+
CollectUserFeedbackRequest
|
|
1952
|
+
>(endpoint, {
|
|
1953
|
+
rating,
|
|
1954
|
+
reason,
|
|
1955
|
+
user_session_id: userSessionId,
|
|
1956
|
+
sdk: sdkName,
|
|
1957
|
+
sdk_version: sdkVersion,
|
|
1958
|
+
custom: {
|
|
1959
|
+
...custom,
|
|
1960
|
+
'x-stream-platform-data': platform,
|
|
1961
|
+
},
|
|
1962
|
+
});
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1912
1965
|
/**
|
|
1913
1966
|
* Sends a custom event to all call participants.
|
|
1914
1967
|
*
|
|
@@ -2,18 +2,23 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import { StreamVideoClient } from '../StreamVideoClient';
|
|
3
3
|
import 'dotenv/config';
|
|
4
4
|
import { generateUUIDv4 } from '../coordinator/connection/utils';
|
|
5
|
-
import { StreamVideoServerClient } from '../StreamVideoServerClient';
|
|
6
5
|
import { User } from '../coordinator/connection/types';
|
|
6
|
+
import { StreamClient } from '@stream-io/node-sdk';
|
|
7
7
|
|
|
8
8
|
const apiKey = process.env.STREAM_API_KEY!;
|
|
9
9
|
const secret = process.env.STREAM_SECRET!;
|
|
10
10
|
|
|
11
|
+
const serverClient = new StreamClient(apiKey, secret);
|
|
12
|
+
|
|
11
13
|
const tokenProvider = (userId: string) => {
|
|
12
|
-
const serverClient = new StreamVideoServerClient(apiKey, { secret });
|
|
13
14
|
return async () => {
|
|
14
15
|
return new Promise<string>((resolve) => {
|
|
15
16
|
setTimeout(() => {
|
|
16
|
-
const token = serverClient.createToken(
|
|
17
|
+
const token = serverClient.createToken(
|
|
18
|
+
userId,
|
|
19
|
+
undefined,
|
|
20
|
+
Math.round(Date.now() / 1000 - 10),
|
|
21
|
+
);
|
|
17
22
|
resolve(token);
|
|
18
23
|
}, 100);
|
|
19
24
|
});
|
|
@@ -7,7 +7,7 @@ import axios, {
|
|
|
7
7
|
} from 'axios';
|
|
8
8
|
import https from 'https';
|
|
9
9
|
import { StableWSConnection } from './connection';
|
|
10
|
-
import { DevToken
|
|
10
|
+
import { DevToken } from './signing';
|
|
11
11
|
import { TokenManager } from './token_manager';
|
|
12
12
|
import { WSConnectionFallback } from './connection_fallback';
|
|
13
13
|
import { isErrorResponse, isWSFailure } from './errors';
|
|
@@ -416,7 +416,6 @@ export class StreamClient {
|
|
|
416
416
|
{
|
|
417
417
|
user: {
|
|
418
418
|
...user,
|
|
419
|
-
role: 'guest',
|
|
420
419
|
},
|
|
421
420
|
},
|
|
422
421
|
{ publicEndpoint: true },
|
|
@@ -862,42 +861,4 @@ export class StreamClient {
|
|
|
862
861
|
createAbortControllerForNextRequest = () => {
|
|
863
862
|
return (this.nextRequestAbortController = new AbortController());
|
|
864
863
|
};
|
|
865
|
-
|
|
866
|
-
/**
|
|
867
|
-
* createToken - Creates a token to authenticate this user. This function is used server side.
|
|
868
|
-
* The resulting token should be passed to the client side when the users registers or logs in.
|
|
869
|
-
*
|
|
870
|
-
* @param {string} userID The UserWithId ID
|
|
871
|
-
* @param {number} [exp] The expiration time for the token expressed in the number of seconds since the epoch
|
|
872
|
-
* @param call_cids for anonymous tokens you have to provide the call cids the use can join
|
|
873
|
-
*
|
|
874
|
-
* @return {string} Returns a token
|
|
875
|
-
*/
|
|
876
|
-
createToken = (
|
|
877
|
-
userID: string,
|
|
878
|
-
exp?: number,
|
|
879
|
-
iat?: number,
|
|
880
|
-
call_cids?: string[],
|
|
881
|
-
) => {
|
|
882
|
-
if (this.secret == null) {
|
|
883
|
-
throw Error(
|
|
884
|
-
`tokens can only be created server-side using the API Secret`,
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
const extra: { exp?: number; iat?: number; call_cids?: string[] } = {};
|
|
888
|
-
|
|
889
|
-
if (exp) {
|
|
890
|
-
extra.exp = exp;
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
if (iat) {
|
|
894
|
-
extra.iat = iat;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
if (call_cids) {
|
|
898
|
-
extra.call_cids = call_cids;
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
return JWTUserToken(this.secret, userID, extra, {});
|
|
902
|
-
};
|
|
903
864
|
}
|
|
@@ -1,77 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import crypto from 'crypto';
|
|
3
|
-
import { encodeBase64, decodeBase64 } from './base64';
|
|
4
|
-
import { UR } from './types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates the JWT token that can be used for a UserSession
|
|
8
|
-
* @method JWTUserToken
|
|
9
|
-
* @memberof signing
|
|
10
|
-
* @private
|
|
11
|
-
* @param {Secret} apiSecret - API Secret key
|
|
12
|
-
* @param {string} userId - The user_id key in the JWT payload
|
|
13
|
-
* @param {UR} [extraData] - Extra that should be part of the JWT token
|
|
14
|
-
* @param {SignOptions} [jwtOptions] - Options that can be past to jwt.sign
|
|
15
|
-
* @return {string} JWT Token
|
|
16
|
-
*/
|
|
17
|
-
export function JWTUserToken(
|
|
18
|
-
apiSecret: Secret,
|
|
19
|
-
userId: string,
|
|
20
|
-
extraData: UR = {},
|
|
21
|
-
jwtOptions: SignOptions = {},
|
|
22
|
-
) {
|
|
23
|
-
if (typeof userId !== 'string') {
|
|
24
|
-
throw new TypeError('userId should be a string');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const payload: { user_id: string } & UR = {
|
|
28
|
-
user_id: userId,
|
|
29
|
-
...extraData,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// make sure we return a clear error when jwt is shimmed (ie. browser build)
|
|
33
|
-
if (jwt == null || jwt.sign == null) {
|
|
34
|
-
throw Error(
|
|
35
|
-
`Unable to find jwt crypto, if you are getting this error is probably because you are trying to generate tokens on browser or React Native (or other environment where crypto functions are not available). Please Note: token should only be generated server-side.`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const opts: SignOptions = Object.assign(
|
|
40
|
-
{ algorithm: 'HS256', noTimestamp: true },
|
|
41
|
-
jwtOptions,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
if (payload.iat) {
|
|
45
|
-
opts.noTimestamp = false;
|
|
46
|
-
}
|
|
47
|
-
return jwt.sign(payload, apiSecret, opts);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function JWTServerToken(
|
|
51
|
-
apiSecret: Secret,
|
|
52
|
-
jwtOptions: SignOptions = {},
|
|
53
|
-
) {
|
|
54
|
-
const payload = {
|
|
55
|
-
server: true,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const opts: SignOptions = Object.assign(
|
|
59
|
-
{ algorithm: 'HS256', noTimestamp: true },
|
|
60
|
-
jwtOptions,
|
|
61
|
-
);
|
|
62
|
-
return jwt.sign(payload, apiSecret, opts);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function UserFromToken(token: string) {
|
|
66
|
-
const fragments = token.split('.');
|
|
67
|
-
if (fragments.length !== 3) {
|
|
68
|
-
return '';
|
|
69
|
-
}
|
|
70
|
-
const b64Payload = fragments[1];
|
|
71
|
-
const payload = decodeBase64(b64Payload);
|
|
72
|
-
const data = JSON.parse(payload);
|
|
73
|
-
return data.user_id as string;
|
|
74
|
-
}
|
|
1
|
+
import { decodeBase64, encodeBase64 } from './base64';
|
|
75
2
|
|
|
76
3
|
/**
|
|
77
4
|
*
|
|
@@ -86,19 +13,13 @@ export function DevToken(userId: string) {
|
|
|
86
13
|
].join('.');
|
|
87
14
|
}
|
|
88
15
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
secret: string,
|
|
99
|
-
signature: string,
|
|
100
|
-
) {
|
|
101
|
-
const key = Buffer.from(secret, 'ascii');
|
|
102
|
-
const hash = crypto.createHmac('sha256', key).update(body).digest('hex');
|
|
103
|
-
return hash === signature;
|
|
16
|
+
export function UserFromToken(token: string) {
|
|
17
|
+
const fragments = token.split('.');
|
|
18
|
+
if (fragments.length !== 3) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
const b64Payload = fragments[1];
|
|
22
|
+
const payload = decodeBase64(b64Payload);
|
|
23
|
+
const data = JSON.parse(payload);
|
|
24
|
+
return data.user_id as string;
|
|
104
25
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { JWTServerToken, JWTUserToken, UserFromToken } from './signing';
|
|
1
|
+
import { UserFromToken } from './signing';
|
|
3
2
|
import { isFunction } from './utils';
|
|
4
3
|
import type { TokenOrProvider, UserWithId } from './types';
|
|
5
4
|
|
|
@@ -11,7 +10,7 @@ import type { TokenOrProvider, UserWithId } from './types';
|
|
|
11
10
|
export class TokenManager {
|
|
12
11
|
loadTokenPromise: Promise<string> | null;
|
|
13
12
|
type: 'static' | 'provider';
|
|
14
|
-
secret?:
|
|
13
|
+
secret?: string;
|
|
15
14
|
token?: string;
|
|
16
15
|
tokenProvider?: TokenOrProvider;
|
|
17
16
|
user?: UserWithId;
|
|
@@ -20,17 +19,13 @@ export class TokenManager {
|
|
|
20
19
|
*
|
|
21
20
|
* @param {Secret} secret
|
|
22
21
|
*/
|
|
23
|
-
constructor(secret?:
|
|
22
|
+
constructor(secret?: string) {
|
|
24
23
|
this.loadTokenPromise = null;
|
|
25
24
|
if (secret) {
|
|
26
25
|
this.secret = secret;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
this.type = 'static';
|
|
30
|
-
|
|
31
|
-
if (this.secret) {
|
|
32
|
-
this.token = JWTServerToken(this.secret);
|
|
33
|
-
}
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
/**
|
|
@@ -59,11 +54,6 @@ export class TokenManager {
|
|
|
59
54
|
this.type = 'static';
|
|
60
55
|
}
|
|
61
56
|
|
|
62
|
-
if (!tokenOrProvider && this.user && this.secret) {
|
|
63
|
-
this.token = JWTUserToken(this.secret, user.id, {}, {});
|
|
64
|
-
this.type = 'static';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
57
|
await this.loadToken();
|
|
68
58
|
};
|
|
69
59
|
|
|
@@ -155,10 +145,6 @@ export class TokenManager {
|
|
|
155
145
|
return this.token;
|
|
156
146
|
}
|
|
157
147
|
|
|
158
|
-
if (this.secret) {
|
|
159
|
-
return JWTServerToken(this.secret);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
148
|
throw new Error(
|
|
163
149
|
`Both secret and user tokens are not set. Either client.connectUser wasn't called or client.disconnect was called`,
|
|
164
150
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
2
|
import { StableWSConnection } from './connection';
|
|
3
|
-
import { ConnectedEvent, UserRequest,
|
|
3
|
+
import { ConnectedEvent, UserRequest, WSEvent } from '../../gen/coordinator';
|
|
4
4
|
import { AllSfuEvents } from '../../rtc';
|
|
5
5
|
|
|
6
6
|
export type UR = Record<string, unknown>;
|
|
@@ -62,7 +62,7 @@ export type ConnectionRecoveredEvent = {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
export type StreamVideoEvent = (
|
|
65
|
-
|
|
|
65
|
+
| WSEvent
|
|
66
66
|
| ConnectionChangedEvent
|
|
67
67
|
| TransportChangedEvent
|
|
68
68
|
| ConnectionRecoveredEvent
|
|
@@ -70,7 +70,7 @@ export type StreamVideoEvent = (
|
|
|
70
70
|
|
|
71
71
|
// TODO: we should use WSCallEvent here but that needs fixing
|
|
72
72
|
export type StreamCallEvent = Extract<StreamVideoEvent, { call_cid: string }>;
|
|
73
|
-
export type EventTypes = 'all' |
|
|
73
|
+
export type EventTypes = 'all' | WSEvent['type'];
|
|
74
74
|
|
|
75
75
|
export type AllClientEventTypes = 'all' | StreamVideoEvent['type'];
|
|
76
76
|
export type AllClientEvents = {
|
|
@@ -81,7 +81,7 @@ export type ClientEventListener<E extends keyof AllClientEvents> = (
|
|
|
81
81
|
) => void;
|
|
82
82
|
|
|
83
83
|
export type AllClientCallEvents = {
|
|
84
|
-
[K in EventTypes]: Extract<
|
|
84
|
+
[K in EventTypes]: Extract<WSEvent, { type: K }>;
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
export type AllCallEvents = AllClientCallEvents & AllSfuEvents;
|