@stream-io/video-client 0.0.13 → 0.0.15
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/dist/index.browser.es.js +237 -130
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +239 -129
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +237 -130
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +2 -1
- package/dist/src/StreamSfuClient.d.ts +1 -0
- package/dist/src/StreamVideoClient.d.ts +5 -1
- package/dist/src/coordinator/connection/types.d.ts +3 -2
- package/dist/src/coordinator/connection/utils.d.ts +2 -1
- package/dist/src/logger.d.ts +4 -0
- package/dist/src/rtc/Dispatcher.d.ts +2 -0
- package/dist/src/rtc/IceTrickleBuffer.d.ts +2 -0
- package/dist/src/rtc/publisher.d.ts +1 -0
- package/dist/src/store/CallState.d.ts +2 -0
- package/dist/src/types.d.ts +1 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/Call.ts +69 -42
- package/src/StreamSfuClient.ts +70 -29
- package/src/StreamVideoClient.ts +46 -3
- package/src/coordinator/connection/client.ts +22 -29
- package/src/coordinator/connection/connection.ts +2 -3
- package/src/coordinator/connection/connection_fallback.ts +0 -1
- package/src/coordinator/connection/types.ts +4 -2
- package/src/coordinator/connection/utils.ts +5 -2
- package/src/devices/devices.ts +10 -3
- package/src/events/__tests__/call-permissions.test.ts +2 -2
- package/src/events/call.ts +11 -4
- package/src/events/sessions.ts +7 -2
- package/src/logger.ts +45 -0
- package/src/rtc/Dispatcher.ts +14 -4
- package/src/rtc/IceTrickleBuffer.ts +8 -1
- package/src/rtc/__tests__/publisher.test.ts +1 -1
- package/src/rtc/codecs.ts +7 -5
- package/src/rtc/flows/join.ts +4 -1
- package/src/rtc/publisher.ts +31 -12
- package/src/rtc/signal.ts +8 -7
- package/src/rtc/subscriber.ts +16 -10
- package/src/stats/state-store-stats-reporter.ts +12 -4
- package/src/store/CallState.ts +7 -2
- package/src/types.ts +3 -2
package/src/StreamVideoClient.ts
CHANGED
|
@@ -24,12 +24,15 @@ import type {
|
|
|
24
24
|
ConnectionChangedEvent,
|
|
25
25
|
EventHandler,
|
|
26
26
|
EventTypes,
|
|
27
|
+
LogLevel,
|
|
28
|
+
Logger,
|
|
27
29
|
StreamClientOptions,
|
|
28
30
|
TokenOrProvider,
|
|
29
31
|
TokenProvider,
|
|
30
32
|
User,
|
|
31
33
|
UserWithId,
|
|
32
34
|
} from './coordinator/connection/types';
|
|
35
|
+
import { getLogger, logToConsole, setLogger } from './logger';
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
38
|
* A `StreamVideoClient` instance lets you communicate with our API, and authenticate users.
|
|
@@ -41,12 +44,16 @@ export class StreamVideoClient {
|
|
|
41
44
|
readonly readOnlyStateStore: StreamVideoReadOnlyStateStore;
|
|
42
45
|
readonly user?: User;
|
|
43
46
|
readonly token?: TokenOrProvider;
|
|
47
|
+
readonly logLevel: LogLevel = 'warn';
|
|
48
|
+
readonly logger: Logger;
|
|
49
|
+
|
|
44
50
|
private readonly writeableStateStore: StreamVideoWriteableStateStore;
|
|
45
51
|
streamClient: StreamClient;
|
|
46
52
|
|
|
47
53
|
private eventHandlersToUnregister: Array<() => void> = [];
|
|
48
54
|
private connectionPromise: Promise<void | ConnectedEvent> | undefined;
|
|
49
55
|
private disconnectionPromise: Promise<void> | undefined;
|
|
56
|
+
private logLevels: LogLevel[] = ['debug', 'info', 'warn', 'error'];
|
|
50
57
|
|
|
51
58
|
/**
|
|
52
59
|
* You should create only one instance of `StreamVideoClient`.
|
|
@@ -71,15 +78,32 @@ export class StreamVideoClient {
|
|
|
71
78
|
},
|
|
72
79
|
opts?: StreamClientOptions,
|
|
73
80
|
) {
|
|
81
|
+
let defaultLogger: Logger = logToConsole;
|
|
82
|
+
if (typeof apiKeyOrArgs === 'string') {
|
|
83
|
+
this.logLevel = opts?.logLevel || this.logLevel;
|
|
84
|
+
this.logger = opts?.logger || defaultLogger;
|
|
85
|
+
} else {
|
|
86
|
+
this.logLevel = apiKeyOrArgs.options?.logLevel || this.logLevel;
|
|
87
|
+
this.logger = apiKeyOrArgs.options?.logger || defaultLogger;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
setLogger(this.filterLogs(defaultLogger));
|
|
91
|
+
|
|
92
|
+
const clientLogger = getLogger(['client']);
|
|
93
|
+
|
|
74
94
|
if (typeof apiKeyOrArgs === 'string') {
|
|
75
95
|
this.streamClient = new StreamClient(apiKeyOrArgs, {
|
|
76
96
|
persistUserOnConnectionFailure: true,
|
|
77
97
|
...opts,
|
|
98
|
+
logLevel: this.logLevel,
|
|
99
|
+
logger: clientLogger,
|
|
78
100
|
});
|
|
79
101
|
} else {
|
|
80
102
|
this.streamClient = new StreamClient(apiKeyOrArgs.apiKey, {
|
|
81
103
|
persistUserOnConnectionFailure: true,
|
|
82
104
|
...apiKeyOrArgs.options,
|
|
105
|
+
logLevel: this.logLevel,
|
|
106
|
+
logger: clientLogger,
|
|
83
107
|
});
|
|
84
108
|
|
|
85
109
|
this.user = apiKeyOrArgs.user;
|
|
@@ -155,7 +179,7 @@ export class StreamVideoClient {
|
|
|
155
179
|
},
|
|
156
180
|
sort: [{ field: 'cid', direction: 1 }],
|
|
157
181
|
}).catch((err) => {
|
|
158
|
-
|
|
182
|
+
this.logger('error', 'Failed to re-watch calls', err);
|
|
159
183
|
});
|
|
160
184
|
}
|
|
161
185
|
}
|
|
@@ -167,7 +191,10 @@ export class StreamVideoClient {
|
|
|
167
191
|
if (event.type !== 'call.created') return;
|
|
168
192
|
const { call, members } = event;
|
|
169
193
|
if (userToConnect.id === call.created_by.id) {
|
|
170
|
-
|
|
194
|
+
this.logger(
|
|
195
|
+
'warn',
|
|
196
|
+
'Received `call.created` sent by the current user',
|
|
197
|
+
);
|
|
171
198
|
return;
|
|
172
199
|
}
|
|
173
200
|
|
|
@@ -189,7 +216,7 @@ export class StreamVideoClient {
|
|
|
189
216
|
if (event.type !== 'call.ring') return;
|
|
190
217
|
const { call, members } = event;
|
|
191
218
|
if (userToConnect.id === call.created_by.id) {
|
|
192
|
-
|
|
219
|
+
this.logger('warn', 'Received `call.ring` sent by the current user');
|
|
193
220
|
return;
|
|
194
221
|
}
|
|
195
222
|
|
|
@@ -465,4 +492,20 @@ export class StreamVideoClient {
|
|
|
465
492
|
this.connectionPromise.finally(() => (this.connectionPromise = undefined));
|
|
466
493
|
return this.connectionPromise;
|
|
467
494
|
};
|
|
495
|
+
|
|
496
|
+
private filterLogs = (logMethod: Logger) => {
|
|
497
|
+
return (
|
|
498
|
+
logLevel: LogLevel,
|
|
499
|
+
messeage: string,
|
|
500
|
+
extraData?: Record<string, unknown>,
|
|
501
|
+
tags?: string[],
|
|
502
|
+
) => {
|
|
503
|
+
if (
|
|
504
|
+
this.logLevels.indexOf(logLevel) >=
|
|
505
|
+
this.logLevels.indexOf(this.logLevel)
|
|
506
|
+
) {
|
|
507
|
+
logMethod(logLevel, messeage, extraData, tags);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
};
|
|
468
511
|
}
|
|
@@ -212,7 +212,8 @@ export class StreamClient {
|
|
|
212
212
|
* If the user id remains the same we don't throw error
|
|
213
213
|
*/
|
|
214
214
|
if (this.userID === user.id && this.setUserPromise) {
|
|
215
|
-
|
|
215
|
+
this.logger(
|
|
216
|
+
'warn',
|
|
216
217
|
'Consecutive calls to connectUser is detected, ideally you should only call this function once in your app.',
|
|
217
218
|
);
|
|
218
219
|
return this.setUserPromise;
|
|
@@ -228,7 +229,8 @@ export class StreamClient {
|
|
|
228
229
|
(this._isUsingServerAuth() || this.node) &&
|
|
229
230
|
!this.options.allowServerSideConnect
|
|
230
231
|
) {
|
|
231
|
-
|
|
232
|
+
this.logger(
|
|
233
|
+
'warn',
|
|
232
234
|
'Please do not use connectUser server side. connectUser impacts MAU and concurrent connection usage and thus your bill. If you have a valid use-case, add "allowServerSideConnect: true" to the client options to disable this warning.',
|
|
233
235
|
);
|
|
234
236
|
}
|
|
@@ -331,9 +333,6 @@ export class StreamClient {
|
|
|
331
333
|
this.logger(
|
|
332
334
|
'info',
|
|
333
335
|
'client:openConnection() - connection already in progress',
|
|
334
|
-
{
|
|
335
|
-
tags: ['connection', 'client'],
|
|
336
|
-
},
|
|
337
336
|
);
|
|
338
337
|
return this.wsPromise;
|
|
339
338
|
}
|
|
@@ -345,9 +344,6 @@ export class StreamClient {
|
|
|
345
344
|
this.logger(
|
|
346
345
|
'info',
|
|
347
346
|
'client:openConnection() - openConnection called twice, healthy connection already exists',
|
|
348
|
-
{
|
|
349
|
-
tags: ['connection', 'client'],
|
|
350
|
-
},
|
|
351
347
|
);
|
|
352
348
|
|
|
353
349
|
return Promise.resolve();
|
|
@@ -379,9 +375,7 @@ export class StreamClient {
|
|
|
379
375
|
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
|
380
376
|
*/
|
|
381
377
|
disconnectUser = async (timeout?: number) => {
|
|
382
|
-
this.logger('info', 'client:disconnect() - Disconnecting the client'
|
|
383
|
-
tags: ['connection', 'client'],
|
|
384
|
-
});
|
|
378
|
+
this.logger('info', 'client:disconnect() - Disconnecting the client');
|
|
385
379
|
|
|
386
380
|
// remove the user specific fields
|
|
387
381
|
delete this.user;
|
|
@@ -440,9 +434,7 @@ export class StreamClient {
|
|
|
440
434
|
if (!(key in this.listeners)) {
|
|
441
435
|
this.listeners[key] = [];
|
|
442
436
|
}
|
|
443
|
-
this.logger('info', `Attaching listener for ${key} event
|
|
444
|
-
tags: ['event', 'client'],
|
|
445
|
-
});
|
|
437
|
+
this.logger('info', `Attaching listener for ${key} event`);
|
|
446
438
|
this.listeners[key].push(callback);
|
|
447
439
|
|
|
448
440
|
return () => {
|
|
@@ -466,9 +458,7 @@ export class StreamClient {
|
|
|
466
458
|
this.listeners[key] = [];
|
|
467
459
|
}
|
|
468
460
|
|
|
469
|
-
this.logger('info', `Removing listener for ${key} event
|
|
470
|
-
tags: ['event', 'client'],
|
|
471
|
-
});
|
|
461
|
+
this.logger('info', `Removing listener for ${key} event`);
|
|
472
462
|
this.listeners[key] = this.listeners[key].filter(
|
|
473
463
|
(value) => value !== callback,
|
|
474
464
|
);
|
|
@@ -482,9 +472,8 @@ export class StreamClient {
|
|
|
482
472
|
config?: AxiosRequestConfig & { maxBodyLength?: number };
|
|
483
473
|
},
|
|
484
474
|
) {
|
|
485
|
-
this.logger('info', `client: ${type} - Request - ${url}
|
|
486
|
-
|
|
487
|
-
url,
|
|
475
|
+
this.logger('info', `client: ${type} - Request - ${url}`);
|
|
476
|
+
this.logger('debug', `client: ${type} - Request payload`, {
|
|
488
477
|
payload: data,
|
|
489
478
|
config,
|
|
490
479
|
});
|
|
@@ -495,16 +484,16 @@ export class StreamClient {
|
|
|
495
484
|
'info',
|
|
496
485
|
`client:${type} - Response - url: ${url} > status ${response.status}`,
|
|
497
486
|
{
|
|
498
|
-
tags: ['api', 'api_response', 'client'],
|
|
499
|
-
url,
|
|
500
487
|
response,
|
|
501
488
|
},
|
|
502
489
|
);
|
|
490
|
+
this.logger('debug', `client:${type} - Response payload`, {
|
|
491
|
+
response,
|
|
492
|
+
});
|
|
503
493
|
}
|
|
504
494
|
|
|
505
495
|
_logApiError(type: string, url: string, error: unknown) {
|
|
506
496
|
this.logger('error', `client:${type} - Error - url: ${url}`, {
|
|
507
|
-
tags: ['api', 'api_response', 'client'],
|
|
508
497
|
url,
|
|
509
498
|
error,
|
|
510
499
|
});
|
|
@@ -629,7 +618,8 @@ export class StreamClient {
|
|
|
629
618
|
dispatchEvent = (event: StreamVideoEvent) => {
|
|
630
619
|
if (!event.received_at) event.received_at = new Date();
|
|
631
620
|
|
|
632
|
-
|
|
621
|
+
this.logger('info', `Dispatching event: ${event.type}`);
|
|
622
|
+
this.logger('debug', 'Event payload:', event);
|
|
633
623
|
this._callClientListeners(event);
|
|
634
624
|
};
|
|
635
625
|
|
|
@@ -696,7 +686,7 @@ export class StreamClient {
|
|
|
696
686
|
if (this.wsFallback) {
|
|
697
687
|
return await this.wsFallback.connect();
|
|
698
688
|
}
|
|
699
|
-
|
|
689
|
+
this.logger('info', 'StreamClient.connect: this.wsConnection.connect()');
|
|
700
690
|
// if WSFallback is enabled, ws connect should timeout faster so fallback can try
|
|
701
691
|
return await this.wsConnection.connect(
|
|
702
692
|
this.options.enableWSFallback
|
|
@@ -706,12 +696,15 @@ export class StreamClient {
|
|
|
706
696
|
} catch (err) {
|
|
707
697
|
// run fallback only if it's WS/Network error and not a normal API error
|
|
708
698
|
// make sure browser is online before even trying the longpoll
|
|
709
|
-
|
|
710
|
-
|
|
699
|
+
if (
|
|
700
|
+
this.options.enableWSFallback &&
|
|
701
|
+
// @ts-ignore
|
|
702
|
+
isWSFailure(err) &&
|
|
703
|
+
isOnline(this.logger)
|
|
704
|
+
) {
|
|
711
705
|
this.logger(
|
|
712
|
-
'
|
|
706
|
+
'warn',
|
|
713
707
|
'client:connect() - WS failed, fallback to longpoll',
|
|
714
|
-
{ tags: ['connection', 'client'] },
|
|
715
708
|
);
|
|
716
709
|
this.dispatchEvent({ type: 'transport.changed', mode: 'longpoll' });
|
|
717
710
|
|
|
@@ -107,7 +107,6 @@ export class StableWSConnection {
|
|
|
107
107
|
|
|
108
108
|
_log(msg: string, extra: UR = {}, level: LogLevel = 'info') {
|
|
109
109
|
this.client.logger(level, 'connection:' + msg, {
|
|
110
|
-
tags: ['connection'],
|
|
111
110
|
...extra,
|
|
112
111
|
});
|
|
113
112
|
}
|
|
@@ -496,13 +495,13 @@ export class StableWSConnection {
|
|
|
496
495
|
|
|
497
496
|
const user = this.client.user;
|
|
498
497
|
if (!user) {
|
|
499
|
-
|
|
498
|
+
this.client.logger('error', `User not set, can't connect to WS`);
|
|
500
499
|
return;
|
|
501
500
|
}
|
|
502
501
|
|
|
503
502
|
const token = this.client._getToken();
|
|
504
503
|
if (!token) {
|
|
505
|
-
|
|
504
|
+
this.client.logger('error', `Token not set, can't connect authenticate`);
|
|
506
505
|
return;
|
|
507
506
|
}
|
|
508
507
|
|
|
@@ -24,7 +24,7 @@ export type { OwnUserResponse } from '../../gen/coordinator';
|
|
|
24
24
|
|
|
25
25
|
export type ConnectAPIResponse = Promise<void | ConnectedEvent>;
|
|
26
26
|
|
|
27
|
-
export type LogLevel = 'info' | 'error' | 'warn';
|
|
27
|
+
export type LogLevel = 'debug' | 'info' | 'error' | 'warn';
|
|
28
28
|
|
|
29
29
|
type ErrorResponseDetails = {
|
|
30
30
|
code: number;
|
|
@@ -81,7 +81,8 @@ export type CallEventTypes = StreamCallEvent['type'];
|
|
|
81
81
|
export type Logger = (
|
|
82
82
|
logLevel: LogLevel,
|
|
83
83
|
message: string,
|
|
84
|
-
extraData?:
|
|
84
|
+
extraData?: any,
|
|
85
|
+
tags?: string[],
|
|
85
86
|
) => void;
|
|
86
87
|
|
|
87
88
|
export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
@@ -101,6 +102,7 @@ export type StreamClientOptions = Partial<AxiosRequestConfig> & {
|
|
|
101
102
|
/** experimental feature, please contact support if you want this feature enabled for you */
|
|
102
103
|
enableWSFallback?: boolean;
|
|
103
104
|
logger?: Logger;
|
|
105
|
+
logLevel?: LogLevel;
|
|
104
106
|
/**
|
|
105
107
|
* When true, user will be persisted on client. Otherwise if `connectUser` call fails, then you need to
|
|
106
108
|
* call `connectUser` again to retry.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Logger } from './types';
|
|
2
|
+
|
|
1
3
|
export const sleep = (m: number): Promise<void> =>
|
|
2
4
|
new Promise((r) => setTimeout(r, m));
|
|
3
5
|
|
|
@@ -111,7 +113,7 @@ export function convertErrorToJson(err: Error) {
|
|
|
111
113
|
* isOnline safely return the navigator.online value for browser env
|
|
112
114
|
* if navigator is not in global object, it always return true
|
|
113
115
|
*/
|
|
114
|
-
export function isOnline() {
|
|
116
|
+
export function isOnline(logger: Logger) {
|
|
115
117
|
const nav =
|
|
116
118
|
typeof navigator !== 'undefined'
|
|
117
119
|
? navigator
|
|
@@ -120,7 +122,8 @@ export function isOnline() {
|
|
|
120
122
|
: undefined;
|
|
121
123
|
|
|
122
124
|
if (!nav) {
|
|
123
|
-
|
|
125
|
+
logger(
|
|
126
|
+
'warn',
|
|
124
127
|
'isOnline failed to access window.navigator and assume browser is online',
|
|
125
128
|
);
|
|
126
129
|
return true;
|
package/src/devices/devices.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
pairwise,
|
|
11
11
|
shareReplay,
|
|
12
12
|
} from 'rxjs';
|
|
13
|
+
import { getLogger } from '../logger';
|
|
13
14
|
|
|
14
15
|
const getDevices = (constraints?: MediaStreamConstraints) => {
|
|
15
16
|
return new Observable<MediaDeviceInfo[]>((subscriber) => {
|
|
@@ -26,7 +27,10 @@ const getDevices = (constraints?: MediaStreamConstraints) => {
|
|
|
26
27
|
});
|
|
27
28
|
})
|
|
28
29
|
.catch((error) => {
|
|
29
|
-
|
|
30
|
+
const logger = getLogger(['devices']);
|
|
31
|
+
if (logger) {
|
|
32
|
+
logger('error', 'Failed to get devices', error);
|
|
33
|
+
}
|
|
30
34
|
subscriber.error(error);
|
|
31
35
|
});
|
|
32
36
|
});
|
|
@@ -134,7 +138,10 @@ const getStream = async (constraints: MediaStreamConstraints) => {
|
|
|
134
138
|
try {
|
|
135
139
|
return await navigator.mediaDevices.getUserMedia(constraints);
|
|
136
140
|
} catch (e) {
|
|
137
|
-
|
|
141
|
+
getLogger(['devices'])?.('error', `Failed get user media`, {
|
|
142
|
+
error: e,
|
|
143
|
+
constraints: constraints,
|
|
144
|
+
});
|
|
138
145
|
throw e;
|
|
139
146
|
}
|
|
140
147
|
};
|
|
@@ -199,7 +206,7 @@ export const getScreenShareStream = async (
|
|
|
199
206
|
...options,
|
|
200
207
|
});
|
|
201
208
|
} catch (e) {
|
|
202
|
-
|
|
209
|
+
getLogger(['devices'])?.('error', 'Failed to get screen share stream', e);
|
|
203
210
|
throw e;
|
|
204
211
|
}
|
|
205
212
|
};
|
|
@@ -14,7 +14,7 @@ describe('Call Permission Events', () => {
|
|
|
14
14
|
// @ts-expect-error
|
|
15
15
|
state.updateOrAddParticipant('session-id', {
|
|
16
16
|
userId: 'test',
|
|
17
|
-
|
|
17
|
+
isLocalParticipant: true,
|
|
18
18
|
});
|
|
19
19
|
const handler = watchCallPermissionRequest(state);
|
|
20
20
|
handler({
|
|
@@ -59,7 +59,7 @@ describe('Call Permission Events', () => {
|
|
|
59
59
|
connectionQuality: ConnectionQuality.EXCELLENT,
|
|
60
60
|
roles: [],
|
|
61
61
|
trackLookupPrefix: '',
|
|
62
|
-
|
|
62
|
+
isLocalParticipant: true,
|
|
63
63
|
},
|
|
64
64
|
]);
|
|
65
65
|
const handler = watchCallPermissionsUpdated(state);
|
package/src/events/call.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CallingState, CallState } from '../store';
|
|
2
2
|
import { StreamVideoEvent } from '../coordinator/connection/types';
|
|
3
3
|
import { Call } from '../Call';
|
|
4
|
+
import { getLogger } from '../logger';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Event handler that watched the delivery of `call.accepted`.
|
|
@@ -35,15 +36,21 @@ export const watchCallRejected = (call: Call) => {
|
|
|
35
36
|
const { session: callSession } = eventCall;
|
|
36
37
|
|
|
37
38
|
if (!callSession) {
|
|
38
|
-
|
|
39
|
+
call.logger(
|
|
40
|
+
'warn',
|
|
41
|
+
'No call session provided. Ignoring call.rejected event.',
|
|
42
|
+
event,
|
|
43
|
+
);
|
|
39
44
|
return;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
const rejectedBy = callSession.rejected_by;
|
|
43
48
|
const { members, callingState } = call.state;
|
|
44
49
|
if (callingState !== CallingState.RINGING) {
|
|
45
|
-
|
|
50
|
+
call.logger(
|
|
51
|
+
'warn',
|
|
46
52
|
'Call is not in ringing mode (it is either accepted or rejected already). Ignoring call.rejected event.',
|
|
53
|
+
event,
|
|
47
54
|
);
|
|
48
55
|
return;
|
|
49
56
|
}
|
|
@@ -52,12 +59,12 @@ export const watchCallRejected = (call: Call) => {
|
|
|
52
59
|
.filter((m) => m.user_id !== call.currentUserId)
|
|
53
60
|
.every((m) => rejectedBy[m.user_id]);
|
|
54
61
|
if (everyoneElseRejected) {
|
|
55
|
-
|
|
62
|
+
call.logger('info', 'everyone rejected, leaving the call');
|
|
56
63
|
await call.leave();
|
|
57
64
|
}
|
|
58
65
|
} else {
|
|
59
66
|
if (rejectedBy[eventCall.created_by.id]) {
|
|
60
|
-
|
|
67
|
+
call.logger('info', 'call creator rejected, leaving call');
|
|
61
68
|
await call.leave();
|
|
62
69
|
}
|
|
63
70
|
}
|
package/src/events/sessions.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CallState } from '../store';
|
|
2
2
|
import { StreamVideoEvent } from '../coordinator/connection/types';
|
|
3
|
+
import { getLogger } from '../logger';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Watch for call.session_started events and update the call metadata.
|
|
@@ -36,8 +37,10 @@ export const watchCallSessionParticipantJoined = (state: CallState) => {
|
|
|
36
37
|
const { user } = event;
|
|
37
38
|
state.setMetadata((metadata) => {
|
|
38
39
|
if (!metadata || !metadata.session) {
|
|
39
|
-
|
|
40
|
+
state.logger(
|
|
41
|
+
'warn',
|
|
40
42
|
`Received call.session_participant_joined event but the metadata structure is invalid.`,
|
|
43
|
+
event,
|
|
41
44
|
);
|
|
42
45
|
return metadata;
|
|
43
46
|
}
|
|
@@ -76,8 +79,10 @@ export const watchCallSessionParticipantLeft = (state: CallState) => {
|
|
|
76
79
|
const { user } = event;
|
|
77
80
|
state.setMetadata((metadata) => {
|
|
78
81
|
if (!metadata || !metadata.session) {
|
|
79
|
-
|
|
82
|
+
state.logger(
|
|
83
|
+
'warn',
|
|
80
84
|
`Received call.session_participant_left event but the metadata structure is invalid.`,
|
|
85
|
+
event,
|
|
81
86
|
);
|
|
82
87
|
return metadata;
|
|
83
88
|
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { LogLevel, Logger } from './coordinator/connection/types';
|
|
2
|
+
|
|
3
|
+
let logger: Logger | undefined;
|
|
4
|
+
|
|
5
|
+
export const logToConsole: Logger = (
|
|
6
|
+
logLevel: LogLevel,
|
|
7
|
+
message: string,
|
|
8
|
+
extraData?: Record<string, unknown>,
|
|
9
|
+
tags?: string[],
|
|
10
|
+
) => {
|
|
11
|
+
let logMethod;
|
|
12
|
+
if (logLevel === 'error') {
|
|
13
|
+
logMethod = console.error;
|
|
14
|
+
} else if (logLevel === 'warn') {
|
|
15
|
+
logMethod = console.warn;
|
|
16
|
+
} else {
|
|
17
|
+
logMethod = console.log;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
logMethod(
|
|
21
|
+
logLevel,
|
|
22
|
+
`${tags?.join(':')} - ${message}`,
|
|
23
|
+
extraData ? extraData : '',
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const setLogger = (l: Logger) => {
|
|
28
|
+
logger = l;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const getLogger = (withTags?: string[]) => {
|
|
32
|
+
const loggerMethod = logger || (() => {});
|
|
33
|
+
const result: Logger = (
|
|
34
|
+
logLevel: LogLevel,
|
|
35
|
+
messeage: string,
|
|
36
|
+
extraData?: Record<string, unknown>,
|
|
37
|
+
tags?: string[],
|
|
38
|
+
) => {
|
|
39
|
+
loggerMethod(logLevel, messeage, extraData, [
|
|
40
|
+
...(tags || []),
|
|
41
|
+
...(withTags || []),
|
|
42
|
+
]);
|
|
43
|
+
};
|
|
44
|
+
return result;
|
|
45
|
+
};
|
package/src/rtc/Dispatcher.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CallEventTypes } from '../coordinator/connection/types';
|
|
1
|
+
import { CallEventTypes, Logger } from '../coordinator/connection/types';
|
|
2
2
|
import type { SfuEvent } from '../gen/video/sfu/event/events';
|
|
3
|
+
import { getLogger } from '../logger';
|
|
3
4
|
|
|
4
5
|
export type SfuEventKinds = NonNullable<SfuEvent['eventPayload']['oneofKind']>;
|
|
5
6
|
|
|
@@ -33,18 +34,27 @@ export class Dispatcher {
|
|
|
33
34
|
private subscribers: {
|
|
34
35
|
[eventName: string]: SfuEventListener[] | undefined;
|
|
35
36
|
} = {};
|
|
37
|
+
private logger?: Logger;
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
this.logger = getLogger(['sfu-client']);
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
dispatch = (message: SfuEvent) => {
|
|
38
44
|
const eventKind = message.eventPayload.oneofKind;
|
|
39
45
|
if (eventKind) {
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
this.logger?.('info', `Dispatching ${eventKind}`);
|
|
47
|
+
this.logger?.(
|
|
48
|
+
'debug',
|
|
49
|
+
`Event payload`,
|
|
50
|
+
(message.eventPayload as any)[eventKind],
|
|
51
|
+
);
|
|
42
52
|
const listeners = this.subscribers[eventKind];
|
|
43
53
|
listeners?.forEach((fn) => {
|
|
44
54
|
try {
|
|
45
55
|
fn(message);
|
|
46
56
|
} catch (e) {
|
|
47
|
-
|
|
57
|
+
this.logger?.('warn', 'Listener failed with error', e);
|
|
48
58
|
}
|
|
49
59
|
});
|
|
50
60
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ReplaySubject } from 'rxjs';
|
|
2
2
|
import { ICETrickle, PeerType } from '../gen/video/sfu/models/models';
|
|
3
|
+
import { getLogger } from '../logger';
|
|
4
|
+
import { Logger } from '../coordinator/connection/types';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* A buffer for ICE Candidates. Used for ICE Trickle:
|
|
@@ -8,6 +10,11 @@ import { ICETrickle, PeerType } from '../gen/video/sfu/models/models';
|
|
|
8
10
|
export class IceTrickleBuffer {
|
|
9
11
|
readonly subscriberCandidates = new ReplaySubject<ICETrickle>();
|
|
10
12
|
readonly publisherCandidates = new ReplaySubject<ICETrickle>();
|
|
13
|
+
private logger?: Logger;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
this.logger = getLogger(['sfu-client']);
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
push = (iceTrickle: ICETrickle) => {
|
|
13
20
|
if (iceTrickle.peerType === PeerType.SUBSCRIBER) {
|
|
@@ -15,7 +22,7 @@ export class IceTrickleBuffer {
|
|
|
15
22
|
} else if (iceTrickle.peerType === PeerType.PUBLISHER_UNSPECIFIED) {
|
|
16
23
|
this.publisherCandidates.next(iceTrickle);
|
|
17
24
|
} else {
|
|
18
|
-
|
|
25
|
+
this.logger?.('warn', `ICETrickle, Unknown peer type`, iceTrickle);
|
|
19
26
|
}
|
|
20
27
|
};
|
|
21
28
|
}
|
package/src/rtc/codecs.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { isReactNative } from '../helpers/platforms';
|
|
2
2
|
import { removeCodec, setPreferredCodec } from '../helpers/sdp-munging';
|
|
3
|
+
import { getLogger } from '../logger';
|
|
3
4
|
|
|
4
5
|
export const getPreferredCodecs = (
|
|
5
6
|
kind: 'audio' | 'video',
|
|
6
7
|
preferredCodec: string,
|
|
7
8
|
codecToRemove?: string,
|
|
8
9
|
): RTCRtpCodecCapability[] | undefined => {
|
|
10
|
+
const logger = getLogger(['codecs']);
|
|
9
11
|
if (!('getCapabilities' in RTCRtpSender)) {
|
|
10
|
-
|
|
12
|
+
logger?.('warn', 'RTCRtpSender.getCapabilities is not supported');
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
15
|
const cap = RTCRtpSender.getCapabilities(kind);
|
|
14
|
-
console.log('s4e');
|
|
15
16
|
if (!cap) return;
|
|
16
17
|
const matched: RTCRtpCodecCapability[] = [];
|
|
17
18
|
const partialMatched: RTCRtpCodecCapability[] = [];
|
|
18
19
|
const unmatched: RTCRtpCodecCapability[] = [];
|
|
19
20
|
cap.codecs.forEach((c) => {
|
|
20
21
|
const codec = c.mimeType.toLowerCase();
|
|
21
|
-
|
|
22
|
+
logger?.('debug', `Found supported codec: ${codec}`);
|
|
22
23
|
const shouldRemoveCodec =
|
|
23
24
|
codecToRemove && codec === `${kind}/${codecToRemove}`;
|
|
24
25
|
if (shouldRemoveCodec) return;
|
|
@@ -37,11 +38,12 @@ export const getPreferredCodecs = (
|
|
|
37
38
|
}
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
|
-
console.log('matched', matched);
|
|
41
41
|
matched.push(c);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const result = [...matched, ...partialMatched, ...unmatched];
|
|
45
|
+
logger?.('info', `Preffered codecs: `, result);
|
|
46
|
+
return result;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
export const getGenericSdp = async (
|
package/src/rtc/flows/join.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from '../../gen/coordinator';
|
|
6
6
|
import { JoinCallData } from '../../types';
|
|
7
7
|
import { StreamClient } from '../../coordinator/connection/client';
|
|
8
|
+
import { getLogger } from '../../logger';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Collects all necessary information to join a call, talks to the coordinator
|
|
@@ -71,15 +72,17 @@ const getLocationHint = async () => {
|
|
|
71
72
|
const hintURL = `https://hint.stream-io-video.com/`;
|
|
72
73
|
const abortController = new AbortController();
|
|
73
74
|
const timeoutId = setTimeout(() => abortController.abort(), 1000);
|
|
75
|
+
const logger = getLogger(['call']);
|
|
74
76
|
try {
|
|
75
77
|
const response = await fetch(hintURL, {
|
|
76
78
|
method: 'HEAD',
|
|
77
79
|
signal: abortController.signal,
|
|
78
80
|
});
|
|
79
81
|
const awsPop = response.headers.get('x-amz-cf-pop') || 'ERR';
|
|
82
|
+
logger?.('info', `Location header: ${awsPop}`);
|
|
80
83
|
return awsPop.substring(0, 3); // AMS1-P2 -> AMS
|
|
81
84
|
} catch (e) {
|
|
82
|
-
|
|
85
|
+
logger?.('error', `Failed to get location hint from ${hintURL}`, e);
|
|
83
86
|
return 'ERR';
|
|
84
87
|
} finally {
|
|
85
88
|
clearTimeout(timeoutId);
|