@stream-io/video-client 0.1.4 → 0.1.6
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 +14 -0
- package/README.md +54 -8
- package/dist/index.browser.es.js +40 -61
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +39 -60
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +40 -61
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -2
- package/dist/src/StreamVideoClient.d.ts +4 -5
- package/dist/src/__tests__/server-side/call-types.test.d.ts +1 -0
- package/dist/src/__tests__/server-side/call.test.d.ts +1 -0
- package/dist/src/__tests__/server-side/create-token.test.d.ts +1 -0
- package/dist/src/__tests__/server-side/server-side-user.test.d.ts +1 -0
- package/dist/src/coordinator/connection/client.d.ts +0 -6
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Call.ts +18 -11
- package/src/StreamVideoClient.ts +33 -31
- package/src/__tests__/StreamVideoClient.test.ts +9 -9
- package/src/__tests__/server-side/call-members.test.ts +93 -0
- package/src/__tests__/server-side/call-types.test.ts +78 -0
- package/src/__tests__/server-side/call.test.ts +147 -0
- package/src/__tests__/server-side/create-token.test.ts +44 -0
- package/src/__tests__/server-side/server-side-user.test.ts +28 -0
- package/src/coordinator/connection/client.ts +2 -29
- package/src/__tests__/StreamVideoServerClient.test.ts +0 -110
- /package/dist/src/__tests__/{StreamVideoServerClient.test.d.ts → server-side/call-members.test.d.ts} +0 -0
package/dist/src/Call.d.ts
CHANGED
|
@@ -164,7 +164,6 @@ export declare class Call {
|
|
|
164
164
|
*/
|
|
165
165
|
join: (data?: JoinCallData) => Promise<void>;
|
|
166
166
|
private waitForJoinResponse;
|
|
167
|
-
private assertCallJoined;
|
|
168
167
|
/**
|
|
169
168
|
* Starts publishing the given video stream to the call.
|
|
170
169
|
* The stream will be stopped if the user changes an input device, or if the user leaves the call.
|
|
@@ -276,6 +275,7 @@ export declare class Call {
|
|
|
276
275
|
* @returns
|
|
277
276
|
*/
|
|
278
277
|
updatePublishQuality: (enabledRids: string[]) => Promise<void | undefined>;
|
|
278
|
+
private assertCallJoined;
|
|
279
279
|
/**
|
|
280
280
|
* Sends a reaction to the other call participants.
|
|
281
281
|
*
|
|
@@ -406,7 +406,7 @@ export declare class Call {
|
|
|
406
406
|
* @param request
|
|
407
407
|
* @returns
|
|
408
408
|
*/
|
|
409
|
-
queryMembers: (request
|
|
409
|
+
queryMembers: (request?: Omit<QueryMembersRequest, 'type' | 'id'>) => Promise<QueryMembersResponse>;
|
|
410
410
|
/**
|
|
411
411
|
* Will update the call members.
|
|
412
412
|
*
|
|
@@ -2,7 +2,7 @@ import { Call } from './Call';
|
|
|
2
2
|
import { StreamClient } from './coordinator/connection/client';
|
|
3
3
|
import { StreamVideoReadOnlyStateStore, StreamVideoWriteableStateStore } from './store';
|
|
4
4
|
import type { ConnectedEvent, CreateDeviceRequest, CreateGuestRequest, CreateGuestResponse, GetEdgesResponse, ListDevicesResponse, QueryCallsRequest } from './gen/coordinator';
|
|
5
|
-
import type { EventHandler, EventTypes, Logger, StreamClientOptions, TokenOrProvider, TokenProvider, User, UserWithId } from './coordinator/connection/types';
|
|
5
|
+
import type { EventHandler, EventTypes, Logger, LogLevel, StreamClientOptions, TokenOrProvider, TokenProvider, User, UserWithId } from './coordinator/connection/types';
|
|
6
6
|
/**
|
|
7
7
|
* A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
|
|
8
8
|
*/
|
|
@@ -11,8 +11,7 @@ export declare class StreamVideoClient {
|
|
|
11
11
|
* A reactive store that exposes all the state variables in a reactive manner - you can subscribe to changes of the different state variables. Our library is built in a way that all state changes are exposed in this store, so all UI changes in your application should be handled by subscribing to these variables.
|
|
12
12
|
*/
|
|
13
13
|
readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
|
|
14
|
-
readonly
|
|
15
|
-
readonly token?: TokenOrProvider;
|
|
14
|
+
readonly logLevel: LogLevel;
|
|
16
15
|
readonly logger: Logger;
|
|
17
16
|
protected readonly writeableStateStore: StreamVideoWriteableStateStore;
|
|
18
17
|
streamClient: StreamClient;
|
|
@@ -38,7 +37,7 @@ export declare class StreamVideoClient {
|
|
|
38
37
|
* @param user the user to connect.
|
|
39
38
|
* @param token a token or a function that returns a token.
|
|
40
39
|
*/
|
|
41
|
-
connectUser(user
|
|
40
|
+
connectUser(user: User, token?: TokenOrProvider): Promise<void | ConnectedEvent>;
|
|
42
41
|
/**
|
|
43
42
|
* Disconnects the currently connected user from the client.
|
|
44
43
|
*
|
|
@@ -83,7 +82,7 @@ export declare class StreamVideoClient {
|
|
|
83
82
|
*
|
|
84
83
|
* @param data the query data.
|
|
85
84
|
*/
|
|
86
|
-
queryCalls: (data
|
|
85
|
+
queryCalls: (data?: QueryCallsRequest) => Promise<{
|
|
87
86
|
calls: Call[];
|
|
88
87
|
duration: string;
|
|
89
88
|
next?: string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
@@ -39,8 +39,6 @@ export declare class StreamClient {
|
|
|
39
39
|
rejectConnectionId?: Function;
|
|
40
40
|
connectionIdPromise?: Promise<string | undefined>;
|
|
41
41
|
private nextRequestAbortController;
|
|
42
|
-
private waitForConnectPromise?;
|
|
43
|
-
private resolveConnectPromise?;
|
|
44
42
|
/**
|
|
45
43
|
* Initialize a client.
|
|
46
44
|
*
|
|
@@ -60,10 +58,6 @@ export declare class StreamClient {
|
|
|
60
58
|
getLocationHint: (hintUrl?: string, timeout?: number) => Promise<string>;
|
|
61
59
|
_getConnectionID: () => string | undefined;
|
|
62
60
|
_hasConnectionID: () => boolean;
|
|
63
|
-
/**
|
|
64
|
-
* This will start a promise to hold API calls until `connectUser` is called, useful when user is set in `StreamVideoClient constructor`
|
|
65
|
-
*/
|
|
66
|
-
startWaitingForConnection: () => void;
|
|
67
61
|
/**
|
|
68
62
|
* connectUser - Set the current user and open a WebSocket connection
|
|
69
63
|
*
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "0.1.
|
|
1
|
+
export declare const version = "0.1.6";
|
package/package.json
CHANGED
package/src/Call.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
import {
|
|
73
73
|
BehaviorSubject,
|
|
74
74
|
debounce,
|
|
75
|
+
filter,
|
|
75
76
|
map,
|
|
76
77
|
pairwise,
|
|
77
78
|
Subject,
|
|
@@ -371,12 +372,15 @@ export class Call {
|
|
|
371
372
|
* Leave the call and stop the media streams that were published by the call.
|
|
372
373
|
*/
|
|
373
374
|
leave = async ({ reject = false }: CallLeaveOptions = {}) => {
|
|
374
|
-
// TODO: handle case when leave is called during JOINING
|
|
375
375
|
const callingState = this.state.callingState;
|
|
376
376
|
if (callingState === CallingState.LEFT) {
|
|
377
377
|
throw new Error('Cannot leave call that has already been left.');
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
if (callingState === CallingState.JOINING) {
|
|
381
|
+
await this.assertCallJoined();
|
|
382
|
+
}
|
|
383
|
+
|
|
380
384
|
if (this.ringing) {
|
|
381
385
|
// I'm the one who started the call, so I should cancel it.
|
|
382
386
|
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
|
|
@@ -930,14 +934,6 @@ export class Call {
|
|
|
930
934
|
});
|
|
931
935
|
};
|
|
932
936
|
|
|
933
|
-
private assertCallJoined = () => {
|
|
934
|
-
return new Promise<void>((resolve) => {
|
|
935
|
-
this.state.callingState$
|
|
936
|
-
.pipe(takeWhile((state) => state !== CallingState.JOINED, true))
|
|
937
|
-
.subscribe(() => resolve());
|
|
938
|
-
});
|
|
939
|
-
};
|
|
940
|
-
|
|
941
937
|
/**
|
|
942
938
|
* Starts publishing the given video stream to the call.
|
|
943
939
|
* The stream will be stopped if the user changes an input device, or if the user leaves the call.
|
|
@@ -1223,6 +1219,17 @@ export class Call {
|
|
|
1223
1219
|
return this.publisher?.updateVideoPublishQuality(enabledRids);
|
|
1224
1220
|
};
|
|
1225
1221
|
|
|
1222
|
+
private assertCallJoined = () => {
|
|
1223
|
+
return new Promise<void>((resolve) => {
|
|
1224
|
+
this.state.callingState$
|
|
1225
|
+
.pipe(
|
|
1226
|
+
takeWhile((state) => state !== CallingState.JOINED, true),
|
|
1227
|
+
filter((s) => s === CallingState.JOINED),
|
|
1228
|
+
)
|
|
1229
|
+
.subscribe(() => resolve());
|
|
1230
|
+
});
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1226
1233
|
/**
|
|
1227
1234
|
* Sends a reaction to the other call participants.
|
|
1228
1235
|
*
|
|
@@ -1510,11 +1517,11 @@ export class Call {
|
|
|
1510
1517
|
* @param request
|
|
1511
1518
|
* @returns
|
|
1512
1519
|
*/
|
|
1513
|
-
queryMembers = (request
|
|
1520
|
+
queryMembers = (request?: Omit<QueryMembersRequest, 'type' | 'id'>) => {
|
|
1514
1521
|
return this.streamClient.post<QueryMembersResponse, QueryMembersRequest>(
|
|
1515
1522
|
'/call/members',
|
|
1516
1523
|
{
|
|
1517
|
-
...request,
|
|
1524
|
+
...(request || {}),
|
|
1518
1525
|
id: this.id,
|
|
1519
1526
|
type: this.type,
|
|
1520
1527
|
},
|
package/src/StreamVideoClient.ts
CHANGED
|
@@ -38,8 +38,7 @@ export class StreamVideoClient {
|
|
|
38
38
|
* A reactive store that exposes all the state variables in a reactive manner - you can subscribe to changes of the different state variables. Our library is built in a way that all state changes are exposed in this store, so all UI changes in your application should be handled by subscribing to these variables.
|
|
39
39
|
*/
|
|
40
40
|
readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
|
|
41
|
-
readonly
|
|
42
|
-
readonly token?: TokenOrProvider;
|
|
41
|
+
readonly logLevel: LogLevel = 'warn';
|
|
43
42
|
readonly logger: Logger;
|
|
44
43
|
|
|
45
44
|
protected readonly writeableStateStore: StreamVideoWriteableStateStore;
|
|
@@ -109,17 +108,20 @@ export class StreamVideoClient {
|
|
|
109
108
|
}.${sdkInfo.minor}.${sdkInfo.patch}`,
|
|
110
109
|
);
|
|
111
110
|
}
|
|
112
|
-
this.user = apiKeyOrArgs.user;
|
|
113
|
-
this.token = apiKeyOrArgs.token || apiKeyOrArgs.tokenProvider;
|
|
114
|
-
if (this.user) {
|
|
115
|
-
this.streamClient.startWaitingForConnection();
|
|
116
|
-
}
|
|
117
111
|
}
|
|
118
112
|
|
|
119
113
|
this.writeableStateStore = new StreamVideoWriteableStateStore();
|
|
120
114
|
this.readOnlyStateStore = new StreamVideoReadOnlyStateStore(
|
|
121
115
|
this.writeableStateStore,
|
|
122
116
|
);
|
|
117
|
+
|
|
118
|
+
if (typeof apiKeyOrArgs !== 'string') {
|
|
119
|
+
const user = apiKeyOrArgs.user;
|
|
120
|
+
const token = apiKeyOrArgs.token || apiKeyOrArgs.tokenProvider;
|
|
121
|
+
if (user) {
|
|
122
|
+
this.connectUser(user, token);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
/**
|
|
@@ -131,30 +133,30 @@ export class StreamVideoClient {
|
|
|
131
133
|
* @param token a token or a function that returns a token.
|
|
132
134
|
*/
|
|
133
135
|
async connectUser(
|
|
134
|
-
user
|
|
136
|
+
user: User,
|
|
135
137
|
token?: TokenOrProvider,
|
|
136
138
|
): Promise<void | ConnectedEvent> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
throw new Error('Connect user is called without user');
|
|
141
|
-
}
|
|
142
|
-
if (userToConnect.type === 'anonymous') {
|
|
143
|
-
userToConnect.id = '!anon';
|
|
144
|
-
return this.connectAnonymousUser(userToConnect as UserWithId, tokenToUse);
|
|
139
|
+
if (user.type === 'anonymous') {
|
|
140
|
+
user.id = '!anon';
|
|
141
|
+
return this.connectAnonymousUser(user as UserWithId, token);
|
|
145
142
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
user: {
|
|
149
|
-
...userToConnect,
|
|
150
|
-
role: 'guest',
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
return this.connectUser(response.user, response.access_token);
|
|
154
|
-
}
|
|
155
|
-
const connectUser = () => {
|
|
156
|
-
return this.streamClient.connectUser(userToConnect, tokenToUse);
|
|
143
|
+
let connectUser = () => {
|
|
144
|
+
return this.streamClient.connectUser(user, token);
|
|
157
145
|
};
|
|
146
|
+
if (user.type === 'guest') {
|
|
147
|
+
connectUser = async () => {
|
|
148
|
+
const response = await this.createGuestUser({
|
|
149
|
+
user: {
|
|
150
|
+
...user,
|
|
151
|
+
role: 'guest',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return this.streamClient.connectUser(
|
|
155
|
+
response.user,
|
|
156
|
+
response.access_token,
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
158
160
|
this.connectionPromise = this.disconnectionPromise
|
|
159
161
|
? this.disconnectionPromise.then(() => connectUser())
|
|
160
162
|
: connectUser();
|
|
@@ -199,7 +201,7 @@ export class StreamVideoClient {
|
|
|
199
201
|
this.on('call.created', (event) => {
|
|
200
202
|
if (event.type !== 'call.created') return;
|
|
201
203
|
const { call, members } = event;
|
|
202
|
-
if (
|
|
204
|
+
if (user.id === call.created_by.id) {
|
|
203
205
|
this.logger(
|
|
204
206
|
'warn',
|
|
205
207
|
'Received `call.created` sent by the current user',
|
|
@@ -225,7 +227,7 @@ export class StreamVideoClient {
|
|
|
225
227
|
this.on('call.ring', async (event) => {
|
|
226
228
|
if (event.type !== 'call.ring') return;
|
|
227
229
|
const { call, members } = event;
|
|
228
|
-
if (
|
|
230
|
+
if (user.id === call.created_by.id) {
|
|
229
231
|
this.logger(
|
|
230
232
|
'debug',
|
|
231
233
|
'Received `call.ring` sent by the current user so ignoring the event',
|
|
@@ -267,7 +269,7 @@ export class StreamVideoClient {
|
|
|
267
269
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
268
270
|
*/
|
|
269
271
|
disconnectUser = async (timeout?: number) => {
|
|
270
|
-
if (!this.streamClient.user) {
|
|
272
|
+
if (!this.streamClient.user && !this.connectionPromise) {
|
|
271
273
|
return;
|
|
272
274
|
}
|
|
273
275
|
const disconnectUser = () => this.streamClient.disconnectUser(timeout);
|
|
@@ -338,7 +340,7 @@ export class StreamVideoClient {
|
|
|
338
340
|
*
|
|
339
341
|
* @param data the query data.
|
|
340
342
|
*/
|
|
341
|
-
queryCalls = async (data: QueryCallsRequest) => {
|
|
343
|
+
queryCalls = async (data: QueryCallsRequest = {}) => {
|
|
342
344
|
const response = await this.streamClient.post<
|
|
343
345
|
QueryCallsResponse,
|
|
344
346
|
QueryCallsRequest
|
|
@@ -2,20 +2,20 @@ 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 {
|
|
5
|
+
import { StreamVideoServerClient } from '../StreamVideoServerClient';
|
|
6
6
|
|
|
7
7
|
const apiKey = process.env.STREAM_API_KEY!;
|
|
8
|
-
const
|
|
8
|
+
const secret = process.env.STREAM_SECRET!;
|
|
9
9
|
|
|
10
10
|
const tokenProvider = (userId: string) => {
|
|
11
|
+
const serverClient = new StreamVideoServerClient(apiKey, { secret });
|
|
11
12
|
return async () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return data.token;
|
|
13
|
+
return new Promise<string>((resolve) => {
|
|
14
|
+
setTimeout(() => {
|
|
15
|
+
const token = serverClient.createToken(userId);
|
|
16
|
+
resolve(token);
|
|
17
|
+
}, 100);
|
|
18
|
+
});
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { StreamVideoServerClient } from '../../StreamVideoServerClient';
|
|
4
|
+
import { generateUUIDv4 } from '../../coordinator/connection/utils';
|
|
5
|
+
import { Call } from '../../Call';
|
|
6
|
+
|
|
7
|
+
const apiKey = process.env.STREAM_API_KEY!;
|
|
8
|
+
const secret = process.env.STREAM_SECRET!;
|
|
9
|
+
|
|
10
|
+
describe('call members API', () => {
|
|
11
|
+
let client: StreamVideoServerClient;
|
|
12
|
+
const callId = `call${generateUUIDv4()}`;
|
|
13
|
+
let call: Call;
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
client = new StreamVideoServerClient(apiKey, {
|
|
17
|
+
secret,
|
|
18
|
+
logLevel: 'error',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
call = client.call('default', callId);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('create with members', async () => {
|
|
25
|
+
const response = await call.getOrCreate({
|
|
26
|
+
data: {
|
|
27
|
+
created_by_id: 'john',
|
|
28
|
+
members: [{ user_id: 'john', role: 'admin' }, { user_id: 'jack' }],
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(response.members[0].user_id).toBe('jack');
|
|
33
|
+
expect(response.members[1].user_id).toBe('john');
|
|
34
|
+
expect(response.members[1].role).toBe('admin');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('add or update members', async () => {
|
|
38
|
+
const response = await call.updateCallMembers({
|
|
39
|
+
update_members: [
|
|
40
|
+
{ user_id: 'sara' },
|
|
41
|
+
{ user_id: 'jane', role: 'admin' },
|
|
42
|
+
{ user_id: 'john', role: 'user' },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(response.members[0].user_id).toBe('sara');
|
|
47
|
+
expect(response.members[1].user_id).toBe('jane');
|
|
48
|
+
expect(response.members[1].role).toBe('admin');
|
|
49
|
+
expect(response.members[2].user_id).toBe('john');
|
|
50
|
+
expect(response.members[2].role).toBe('user');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('remove members', async () => {
|
|
54
|
+
const response = await call.updateCallMembers({
|
|
55
|
+
remove_members: ['sara'],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(response.duration).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('query members', async () => {
|
|
62
|
+
let response = await call.queryMembers();
|
|
63
|
+
|
|
64
|
+
let members = response.members;
|
|
65
|
+
expect(members.length).toBe(3);
|
|
66
|
+
|
|
67
|
+
const queryMembersReq = {
|
|
68
|
+
sort: [{ field: 'user_id', direction: 1 }],
|
|
69
|
+
limit: 2,
|
|
70
|
+
};
|
|
71
|
+
response = await call.queryMembers(queryMembersReq);
|
|
72
|
+
|
|
73
|
+
members = response.members;
|
|
74
|
+
expect(members.length).toBe(2);
|
|
75
|
+
expect(members[0].user_id).toBe('jack');
|
|
76
|
+
expect(members[1].user_id).toBe('jane');
|
|
77
|
+
|
|
78
|
+
response = await call.queryMembers({
|
|
79
|
+
...queryMembersReq,
|
|
80
|
+
next: response.next,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(response.members.length).toBe(1);
|
|
84
|
+
|
|
85
|
+
response = await call.queryMembers({
|
|
86
|
+
filter_conditions: { role: { $eq: 'admin' } },
|
|
87
|
+
});
|
|
88
|
+
members = response.members;
|
|
89
|
+
|
|
90
|
+
expect(members.length).toBe(1);
|
|
91
|
+
members.forEach((m) => expect(m.role).toBe('admin'));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { beforeAll, describe, expect, it } from 'vitest';
|
|
3
|
+
import { StreamVideoServerClient } from '../../StreamVideoServerClient';
|
|
4
|
+
import { generateUUIDv4 } from '../../coordinator/connection/utils';
|
|
5
|
+
import { LogLevel } from '../../coordinator/connection/types';
|
|
6
|
+
import { OwnCapability, RecordSettingsModeEnum } from '../../gen/coordinator';
|
|
7
|
+
|
|
8
|
+
const apiKey = process.env.STREAM_API_KEY!;
|
|
9
|
+
const secret = process.env.STREAM_SECRET!;
|
|
10
|
+
|
|
11
|
+
describe('call types CRUD API', () => {
|
|
12
|
+
let client: StreamVideoServerClient;
|
|
13
|
+
const callTypeName = `calltype${generateUUIDv4()}`;
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
client = new StreamVideoServerClient(apiKey, {
|
|
17
|
+
secret,
|
|
18
|
+
logLevel: 'trace',
|
|
19
|
+
logger: (logLevel: LogLevel, message: string, ...args: unknown[]) => {
|
|
20
|
+
console.log(new Date().toISOString(), message, ...args);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('create', async () => {
|
|
26
|
+
const createResponse = await client.createCallType({
|
|
27
|
+
name: callTypeName,
|
|
28
|
+
settings: {
|
|
29
|
+
audio: { mic_default_on: true, default_device: 'speaker' },
|
|
30
|
+
},
|
|
31
|
+
grants: {
|
|
32
|
+
admin: [
|
|
33
|
+
OwnCapability.SEND_AUDIO,
|
|
34
|
+
OwnCapability.SEND_VIDEO,
|
|
35
|
+
OwnCapability.MUTE_USERS,
|
|
36
|
+
],
|
|
37
|
+
user: [OwnCapability.SEND_AUDIO, OwnCapability.SEND_VIDEO],
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(createResponse.name).toBe(callTypeName);
|
|
42
|
+
expect(createResponse.settings.audio.mic_default_on).toBe(true);
|
|
43
|
+
expect(createResponse.settings.audio.default_device).toBe('speaker');
|
|
44
|
+
expect(createResponse.grants.admin).toBeDefined();
|
|
45
|
+
expect(createResponse.grants.user).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('read', async () => {
|
|
49
|
+
const readResponse = await client.getCallTypes();
|
|
50
|
+
|
|
51
|
+
expect(readResponse.call_types[callTypeName]).toContain({
|
|
52
|
+
name: callTypeName,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('update', async () => {
|
|
57
|
+
const updateResponse = await client.updateCallType(callTypeName, {
|
|
58
|
+
settings: {
|
|
59
|
+
audio: { mic_default_on: false, default_device: 'earpiece' },
|
|
60
|
+
recording: {
|
|
61
|
+
mode: RecordSettingsModeEnum.DISABLED,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(updateResponse.settings.audio.mic_default_on).toBeFalsy();
|
|
67
|
+
expect(updateResponse.settings.audio.default_device).toBe('earpiece');
|
|
68
|
+
expect(updateResponse.settings.recording.mode).toBe(
|
|
69
|
+
RecordSettingsModeEnum.DISABLED,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('delete', async () => {
|
|
74
|
+
await client.deleteCallType(callTypeName);
|
|
75
|
+
|
|
76
|
+
await expect(() => client.getCallType(callTypeName)).rejects.toThrowError();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { beforeAll, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { StreamVideoServerClient } from '../../StreamVideoServerClient';
|
|
4
|
+
import { generateUUIDv4 } from '../../coordinator/connection/utils';
|
|
5
|
+
import { Call } from '../../Call';
|
|
6
|
+
import {
|
|
7
|
+
RecordSettingsRequestModeEnum,
|
|
8
|
+
RecordSettingsRequestQualityEnum,
|
|
9
|
+
} from '../../gen/coordinator';
|
|
10
|
+
|
|
11
|
+
const apiKey = process.env.STREAM_API_KEY!;
|
|
12
|
+
const secret = process.env.STREAM_SECRET!;
|
|
13
|
+
|
|
14
|
+
describe('call API', () => {
|
|
15
|
+
let client: StreamVideoServerClient;
|
|
16
|
+
const callId = `call${generateUUIDv4()}`;
|
|
17
|
+
let call: Call;
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
client = new StreamVideoServerClient(apiKey, {
|
|
21
|
+
secret,
|
|
22
|
+
logLevel: 'error',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
call = client.call('default', callId);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('create', async () => {
|
|
29
|
+
const response = await call.getOrCreate({
|
|
30
|
+
data: { created_by_id: 'john' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(response.call.created_by.id).toBe('john');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('update', async () => {
|
|
37
|
+
const response = await call.update({
|
|
38
|
+
settings_override: {
|
|
39
|
+
audio: { mic_default_on: true, default_device: 'speaker' },
|
|
40
|
+
},
|
|
41
|
+
custom: { color: 'red' },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(response.call.settings.audio.mic_default_on).toBe(true);
|
|
45
|
+
expect(response.call.custom.color).toBe('red');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('RTMP address', async () => {
|
|
49
|
+
const resp = await call.getOrCreate();
|
|
50
|
+
const address = resp.call.ingress.rtmp.address;
|
|
51
|
+
|
|
52
|
+
expect(address).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('query calls', async () => {
|
|
56
|
+
let response = await client.queryCalls();
|
|
57
|
+
|
|
58
|
+
let calls = response.calls;
|
|
59
|
+
expect(calls.length).toBeGreaterThanOrEqual(1);
|
|
60
|
+
|
|
61
|
+
const queryCallsReq = {
|
|
62
|
+
sort: [{ field: 'starts_at', direction: -1 }],
|
|
63
|
+
limit: 2,
|
|
64
|
+
};
|
|
65
|
+
response = await client.queryCalls(queryCallsReq);
|
|
66
|
+
|
|
67
|
+
calls = response.calls;
|
|
68
|
+
expect(calls.length).toBe(2);
|
|
69
|
+
|
|
70
|
+
response = await client.queryCalls({
|
|
71
|
+
...queryCallsReq,
|
|
72
|
+
next: response.next,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(response.calls.length).toBeLessThanOrEqual(2);
|
|
76
|
+
|
|
77
|
+
response = await client.queryCalls({
|
|
78
|
+
filter_conditions: { backstage: { $eq: false } },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(response.calls.length).toBeGreaterThanOrEqual(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('recording', () => {
|
|
85
|
+
it('enable call recording', async () => {
|
|
86
|
+
let response = await call.update({
|
|
87
|
+
settings_override: {
|
|
88
|
+
recording: {
|
|
89
|
+
mode: RecordSettingsRequestModeEnum.DISABLED,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
let settings = response.call.settings.recording;
|
|
94
|
+
|
|
95
|
+
expect(settings.mode).toBe(RecordSettingsRequestModeEnum.DISABLED);
|
|
96
|
+
|
|
97
|
+
response = await call.update({
|
|
98
|
+
settings_override: {
|
|
99
|
+
recording: {
|
|
100
|
+
mode: RecordSettingsRequestModeEnum.AVAILABLE,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
settings = response.call.settings.recording;
|
|
106
|
+
expect(settings.mode).toBe(RecordSettingsRequestModeEnum.AVAILABLE);
|
|
107
|
+
|
|
108
|
+
response = await call.update({
|
|
109
|
+
settings_override: {
|
|
110
|
+
recording: {
|
|
111
|
+
audio_only: false,
|
|
112
|
+
quality: RecordSettingsRequestQualityEnum._1080P,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
settings = response.call.settings.recording;
|
|
118
|
+
expect(settings.audio_only).toBe(false);
|
|
119
|
+
expect(settings.quality).toBe(RecordSettingsRequestQualityEnum._1080P);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('start recording', async () => {
|
|
123
|
+
// somewhat dummy test, we should do a proper test in the future where we join a call and start recording
|
|
124
|
+
await expect(() => call.startRecording()).rejects.toThrowError(
|
|
125
|
+
'Stream error code 4: StartRecording failed with error: "cannot record inactive call"',
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('stop recording', async () => {
|
|
130
|
+
// somewhat dummy test, we should do a proper test in the future
|
|
131
|
+
await expect(() => call.stopRecording()).rejects.toThrowError(
|
|
132
|
+
'Stream error code 4: StopRecording failed with error: "call is not being recorded"',
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('query recordings', async () => {
|
|
137
|
+
// somewhat dummy test, we should do a proper test in the future
|
|
138
|
+
let response = await call.queryRecordings();
|
|
139
|
+
|
|
140
|
+
expect(response.recordings).toBeDefined();
|
|
141
|
+
|
|
142
|
+
response = await call.queryRecordings('session123');
|
|
143
|
+
|
|
144
|
+
expect(response.recordings).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|