@stream-io/video-client 1.18.7 → 1.18.9
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 +104 -168
- package/dist/index.browser.es.js +99 -54
- package/dist/index.browser.es.js.map +1 -1
- package/dist/index.cjs.js +99 -54
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +99 -54
- package/dist/index.es.js.map +1 -1
- package/dist/src/Call.d.ts +10 -1
- package/dist/src/devices/ScreenShareManager.d.ts +1 -3
- package/dist/src/gen/video/sfu/event/events.d.ts +1 -19
- package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +2 -21
- package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +1 -9
- package/dist/src/helpers/promise.d.ts +2 -2
- package/dist/src/store/CallState.d.ts +5 -0
- package/package.json +10 -11
- package/src/Call.ts +61 -10
- package/src/StreamSfuClient.ts +9 -3
- package/src/StreamVideoClient.ts +4 -5
- package/src/__tests__/Call.test.ts +1 -1
- package/src/coordinator/connection/client.ts +2 -3
- package/src/coordinator/connection/connection.ts +14 -14
- package/src/coordinator/connection/signing.ts +1 -1
- package/src/devices/BrowserPermission.ts +3 -2
- package/src/devices/MicrophoneManager.ts +1 -1
- package/src/devices/ScreenShareManager.ts +1 -3
- package/src/devices/__tests__/InputMediaDeviceManager.test.ts +1 -1
- package/src/devices/__tests__/MicrophoneManager.test.ts +4 -4
- package/src/devices/__tests__/MicrophoneManagerRN.test.ts +4 -4
- package/src/devices/devices.ts +3 -1
- package/src/events/__tests__/call.test.ts +42 -57
- package/src/events/__tests__/internal.test.ts +14 -13
- package/src/events/__tests__/mutes.test.ts +7 -3
- package/src/events/__tests__/participant.test.ts +16 -20
- package/src/events/__tests__/speaker.test.ts +6 -6
- package/src/events/internal.ts +1 -0
- package/src/gen/coordinator/index.ts +1 -1
- package/src/gen/video/sfu/event/events.ts +22 -20
- package/src/gen/video/sfu/models/models.ts +0 -1
- package/src/gen/video/sfu/signal_rpc/signal.client.ts +27 -23
- package/src/gen/video/sfu/signal_rpc/signal.ts +13 -11
- package/src/helpers/RNSpeechDetector.ts +3 -4
- package/src/helpers/__tests__/DynascaleManager.test.ts +27 -26
- package/src/helpers/__tests__/clientUtils.test.ts +0 -1
- package/src/helpers/client-details.ts +1 -1
- package/src/helpers/promise.ts +4 -4
- package/src/rtc/Dispatcher.ts +1 -1
- package/src/rtc/Publisher.ts +2 -2
- package/src/rtc/__tests__/Publisher.test.ts +8 -8
- package/src/rtc/__tests__/Subscriber.test.ts +9 -9
- package/src/rtc/__tests__/mocks/webrtc.mocks.ts +2 -2
- package/src/rtc/helpers/__tests__/sdp.test.ts +3 -3
- package/src/stats/CallStateStatsReporter.ts +2 -3
- package/src/store/CallState.ts +9 -1
- package/src/store/__tests__/CallState.test.ts +59 -115
- package/src/timers/worker.ts +0 -4
package/dist/src/Call.d.ts
CHANGED
|
@@ -209,7 +209,16 @@ export declare class Call {
|
|
|
209
209
|
*
|
|
210
210
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
211
211
|
*/
|
|
212
|
-
join: (data?: JoinCallData
|
|
212
|
+
join: ({ maxJoinRetries, ...data }?: JoinCallData & {
|
|
213
|
+
maxJoinRetries?: number;
|
|
214
|
+
}) => Promise<void>;
|
|
215
|
+
/**
|
|
216
|
+
* Will make a single attempt to watch for call related WebSocket events
|
|
217
|
+
* and initiate a call session with the server.
|
|
218
|
+
*
|
|
219
|
+
* @returns a promise which resolves once the call join-flow has finished.
|
|
220
|
+
*/
|
|
221
|
+
doJoin: (data?: JoinCallData) => Promise<void>;
|
|
213
222
|
/**
|
|
214
223
|
* Prepares Reconnect Details object.
|
|
215
224
|
* @internal
|
|
@@ -31,8 +31,6 @@ export declare class ScreenShareManager extends InputMediaDeviceManager<ScreenSh
|
|
|
31
31
|
protected stopPublishStream(): Promise<void>;
|
|
32
32
|
/**
|
|
33
33
|
* Overrides the default `select` method to throw an error.
|
|
34
|
-
*
|
|
35
|
-
* @param deviceId ignored.
|
|
36
34
|
*/
|
|
37
|
-
select(
|
|
35
|
+
select(): Promise<void>;
|
|
38
36
|
}
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import { MessageType } from '@protobuf-ts/runtime';
|
|
2
|
-
import { CallEndedReason } from '../models/models';
|
|
3
|
-
import { GoAwayReason } from '../models/models';
|
|
4
|
-
import { CallGrants } from '../models/models';
|
|
5
|
-
import { Codec } from '../models/models';
|
|
6
|
-
import { ConnectionQuality } from '../models/models';
|
|
7
|
-
import { CallState } from '../models/models';
|
|
2
|
+
import { CallEndedReason, CallGrants, CallState, ClientDetails, Codec, ConnectionQuality, Error as Error$, GoAwayReason, ICETrickle as ICETrickle$, Participant, ParticipantCount, PeerType, Pin, PublishOption, SubscribeOption, TrackInfo, TrackType, TrackUnpublishReason, WebsocketReconnectStrategy } from '../models/models';
|
|
8
3
|
import { TrackSubscriptionDetails } from '../signal_rpc/signal';
|
|
9
|
-
import { TrackInfo } from '../models/models';
|
|
10
|
-
import { SubscribeOption } from '../models/models';
|
|
11
|
-
import { ClientDetails } from '../models/models';
|
|
12
|
-
import { TrackUnpublishReason } from '../models/models';
|
|
13
|
-
import { Participant } from '../models/models';
|
|
14
|
-
import { TrackType } from '../models/models';
|
|
15
|
-
import { ParticipantCount } from '../models/models';
|
|
16
|
-
import { PeerType } from '../models/models';
|
|
17
|
-
import { WebsocketReconnectStrategy } from '../models/models';
|
|
18
|
-
import { Error as Error$ } from '../models/models';
|
|
19
|
-
import { Pin } from '../models/models';
|
|
20
|
-
import { PublishOption } from '../models/models';
|
|
21
|
-
import { ICETrickle as ICETrickle$ } from '../models/models';
|
|
22
4
|
/**
|
|
23
5
|
* SFUEvent is a message that is sent from the SFU to the client.
|
|
24
6
|
*
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
import type { RpcTransport } from '@protobuf-ts/runtime-rpc';
|
|
2
|
-
import type {
|
|
3
|
-
import type { StopNoiseCancellationResponse } from './signal';
|
|
4
|
-
import type { StopNoiseCancellationRequest } from './signal';
|
|
5
|
-
import type { StartNoiseCancellationResponse } from './signal';
|
|
6
|
-
import type { StartNoiseCancellationRequest } from './signal';
|
|
7
|
-
import type { SendStatsResponse } from './signal';
|
|
8
|
-
import type { SendStatsRequest } from './signal';
|
|
9
|
-
import type { ICERestartResponse } from './signal';
|
|
10
|
-
import type { ICERestartRequest } from './signal';
|
|
11
|
-
import type { UpdateMuteStatesResponse } from './signal';
|
|
12
|
-
import type { UpdateMuteStatesRequest } from './signal';
|
|
13
|
-
import type { UpdateSubscriptionsResponse } from './signal';
|
|
14
|
-
import type { UpdateSubscriptionsRequest } from './signal';
|
|
15
|
-
import type { ICETrickleResponse } from './signal';
|
|
1
|
+
import type { RpcOptions, RpcTransport, ServiceInfo, UnaryCall } from '@protobuf-ts/runtime-rpc';
|
|
2
|
+
import type { ICERestartRequest, ICERestartResponse, ICETrickleResponse, SendAnswerRequest, SendAnswerResponse, SendStatsRequest, SendStatsResponse, SetPublisherRequest, SetPublisherResponse, StartNoiseCancellationRequest, StartNoiseCancellationResponse, StopNoiseCancellationRequest, StopNoiseCancellationResponse, UpdateMuteStatesRequest, UpdateMuteStatesResponse, UpdateSubscriptionsRequest, UpdateSubscriptionsResponse } from './signal';
|
|
16
3
|
import type { ICETrickle } from '../models/models';
|
|
17
|
-
import type { SendAnswerResponse } from './signal';
|
|
18
|
-
import type { SendAnswerRequest } from './signal';
|
|
19
|
-
import type { SetPublisherResponse } from './signal';
|
|
20
|
-
import type { SetPublisherRequest } from './signal';
|
|
21
|
-
import type { UnaryCall } from '@protobuf-ts/runtime-rpc';
|
|
22
|
-
import type { RpcOptions } from '@protobuf-ts/runtime-rpc';
|
|
23
4
|
/**
|
|
24
5
|
* @generated from protobuf service stream.video.sfu.signal.SignalServer
|
|
25
6
|
*/
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
+
import { AndroidState, AppleState, Error, InputDevices, PeerType, TrackInfo, TrackType, VideoDimension, WebsocketReconnectStrategy } from '../models/models';
|
|
1
2
|
import { ServiceType } from '@protobuf-ts/runtime-rpc';
|
|
2
3
|
import { MessageType } from '@protobuf-ts/runtime';
|
|
3
|
-
import { TrackInfo } from '../models/models';
|
|
4
|
-
import { VideoDimension } from '../models/models';
|
|
5
|
-
import { TrackType } from '../models/models';
|
|
6
|
-
import { PeerType } from '../models/models';
|
|
7
|
-
import { AppleState } from '../models/models';
|
|
8
|
-
import { AndroidState } from '../models/models';
|
|
9
|
-
import { InputDevices } from '../models/models';
|
|
10
|
-
import { WebsocketReconnectStrategy } from '../models/models';
|
|
11
|
-
import { Error } from '../models/models';
|
|
12
4
|
/**
|
|
13
5
|
* @generated from protobuf message stream.video.sfu.signal.StartNoiseCancellationRequest
|
|
14
6
|
*/
|
|
@@ -20,8 +20,8 @@ export type PromiseWithResolvers<T> = {
|
|
|
20
20
|
promise: Promise<T>;
|
|
21
21
|
resolve: (value: T | PromiseLike<T>) => void;
|
|
22
22
|
reject: (reason: any) => void;
|
|
23
|
-
isResolved: boolean;
|
|
24
|
-
isRejected: boolean;
|
|
23
|
+
isResolved: () => boolean;
|
|
24
|
+
isRejected: () => boolean;
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Creates a new promise with resolvers.
|
|
@@ -360,6 +360,11 @@ export declare class CallState {
|
|
|
360
360
|
* The backstage state.
|
|
361
361
|
*/
|
|
362
362
|
get backstage(): boolean;
|
|
363
|
+
/**
|
|
364
|
+
* Sets the backstage state.
|
|
365
|
+
* @param backstage the backstage state.
|
|
366
|
+
*/
|
|
367
|
+
setBackstage: (backstage: Patch<boolean>) => boolean;
|
|
363
368
|
/**
|
|
364
369
|
* Will provide the list of blocked user IDs.
|
|
365
370
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-client",
|
|
3
|
-
"version": "1.18.
|
|
4
|
-
"packageManager": "yarn@3.2.4",
|
|
3
|
+
"version": "1.18.9",
|
|
5
4
|
"main": "dist/index.cjs.js",
|
|
6
5
|
"module": "dist/index.es.js",
|
|
7
6
|
"browser": "dist/index.browser.es.js",
|
|
@@ -42,18 +41,18 @@
|
|
|
42
41
|
"@rollup/plugin-replace": "^6.0.2",
|
|
43
42
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
44
43
|
"@stream-io/audio-filters-web": "^0.2.3",
|
|
45
|
-
"@stream-io/node-sdk": "^0.4.
|
|
44
|
+
"@stream-io/node-sdk": "^0.4.19",
|
|
46
45
|
"@types/sdp-transform": "^2.4.9",
|
|
47
46
|
"@types/ua-parser-js": "^0.7.39",
|
|
48
|
-
"@vitest/coverage-v8": "^3.0.
|
|
47
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
49
48
|
"dotenv": "^16.4.7",
|
|
50
49
|
"happy-dom": "^11.0.2",
|
|
51
|
-
"prettier": "^3.3
|
|
52
|
-
"rimraf": "^
|
|
53
|
-
"rollup": "^4.
|
|
54
|
-
"typescript": "^5.
|
|
55
|
-
"vite": "^
|
|
56
|
-
"vitest": "^3.0.
|
|
57
|
-
"vitest-mock-extended": "^
|
|
50
|
+
"prettier": "^3.5.3",
|
|
51
|
+
"rimraf": "^6.0.1",
|
|
52
|
+
"rollup": "^4.36.0",
|
|
53
|
+
"typescript": "^5.8.2",
|
|
54
|
+
"vite": "^6.2.2",
|
|
55
|
+
"vitest": "^3.0.9",
|
|
56
|
+
"vitest-mock-extended": "^3.0.1"
|
|
58
57
|
}
|
|
59
58
|
}
|
package/src/Call.ts
CHANGED
|
@@ -120,7 +120,7 @@ import { DynascaleManager } from './helpers/DynascaleManager';
|
|
|
120
120
|
import { PermissionsContext } from './permissions';
|
|
121
121
|
import { CallTypes } from './CallType';
|
|
122
122
|
import { StreamClient } from './coordinator/connection/client';
|
|
123
|
-
import { sleep } from './coordinator/connection/utils';
|
|
123
|
+
import { retryInterval, sleep } from './coordinator/connection/utils';
|
|
124
124
|
import {
|
|
125
125
|
AllCallEvents,
|
|
126
126
|
CallEventListener,
|
|
@@ -813,14 +813,49 @@ export class Call {
|
|
|
813
813
|
*
|
|
814
814
|
* @returns a promise which resolves once the call join-flow has finished.
|
|
815
815
|
*/
|
|
816
|
-
join = async (
|
|
817
|
-
|
|
816
|
+
join = async ({
|
|
817
|
+
maxJoinRetries = 3,
|
|
818
|
+
...data
|
|
819
|
+
}: JoinCallData & {
|
|
820
|
+
maxJoinRetries?: number;
|
|
821
|
+
} = {}): Promise<void> => {
|
|
818
822
|
await this.setup();
|
|
819
823
|
const callingState = this.state.callingState;
|
|
824
|
+
|
|
820
825
|
if ([CallingState.JOINED, CallingState.JOINING].includes(callingState)) {
|
|
821
826
|
throw new Error(`Illegal State: call.join() shall be called only once`);
|
|
822
827
|
}
|
|
823
828
|
|
|
829
|
+
this.state.setCallingState(CallingState.JOINING);
|
|
830
|
+
|
|
831
|
+
maxJoinRetries = Math.max(maxJoinRetries, 1);
|
|
832
|
+
for (let attempt = 0; attempt < maxJoinRetries; attempt++) {
|
|
833
|
+
try {
|
|
834
|
+
this.logger('trace', `Joining call (${attempt})`, this.cid);
|
|
835
|
+
return await this.doJoin(data);
|
|
836
|
+
} catch (err) {
|
|
837
|
+
this.logger('warn', `Failed to join call (${attempt})`, this.cid);
|
|
838
|
+
if (attempt === maxJoinRetries - 1) {
|
|
839
|
+
// restore the previous call state if the join-flow fails
|
|
840
|
+
this.state.setCallingState(callingState);
|
|
841
|
+
throw err;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
await sleep(retryInterval(attempt));
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Will make a single attempt to watch for call related WebSocket events
|
|
851
|
+
* and initiate a call session with the server.
|
|
852
|
+
*
|
|
853
|
+
* @returns a promise which resolves once the call join-flow has finished.
|
|
854
|
+
*/
|
|
855
|
+
doJoin = async (data?: JoinCallData): Promise<void> => {
|
|
856
|
+
const connectStartTime = Date.now();
|
|
857
|
+
const callingState = this.state.callingState;
|
|
858
|
+
|
|
824
859
|
this.joinCallData = data;
|
|
825
860
|
|
|
826
861
|
this.logger('debug', 'Starting join flow');
|
|
@@ -920,6 +955,11 @@ export class Call {
|
|
|
920
955
|
);
|
|
921
956
|
}
|
|
922
957
|
} catch (error) {
|
|
958
|
+
this.logger('warn', 'Join SFU request failed', error);
|
|
959
|
+
sfuClient.close(
|
|
960
|
+
StreamSfuClient.ERROR_CONNECTION_UNHEALTHY,
|
|
961
|
+
'Join request failed, connection considered unhealthy',
|
|
962
|
+
);
|
|
923
963
|
// restore the previous call state if the join-flow fails
|
|
924
964
|
this.state.setCallingState(callingState);
|
|
925
965
|
throw error;
|
|
@@ -1230,9 +1270,20 @@ export class Call {
|
|
|
1230
1270
|
*/
|
|
1231
1271
|
private handleSfuSignalClose = (sfuClient: StreamSfuClient) => {
|
|
1232
1272
|
this.logger('debug', '[Reconnect] SFU signal connection closed');
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1273
|
+
const { callingState } = this.state;
|
|
1274
|
+
if (
|
|
1275
|
+
// SFU WS closed before we finished current join,
|
|
1276
|
+
// no need to schedule reconnecting
|
|
1277
|
+
callingState === CallingState.JOINING ||
|
|
1278
|
+
// we are already in the process of reconnecting,
|
|
1279
|
+
// no need to schedule another one
|
|
1280
|
+
callingState === CallingState.RECONNECTING ||
|
|
1281
|
+
// SFU WS closed as a result of unsuccessful join,
|
|
1282
|
+
// and no further retries need to be made
|
|
1283
|
+
callingState === CallingState.IDLE ||
|
|
1284
|
+
callingState === CallingState.LEFT
|
|
1285
|
+
)
|
|
1286
|
+
return;
|
|
1236
1287
|
// normal close, no need to reconnect
|
|
1237
1288
|
if (sfuClient.isLeaving) return;
|
|
1238
1289
|
this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
|
|
@@ -1262,7 +1313,7 @@ export class Call {
|
|
|
1262
1313
|
`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`,
|
|
1263
1314
|
);
|
|
1264
1315
|
|
|
1265
|
-
|
|
1316
|
+
const reconnectStartTime = Date.now();
|
|
1266
1317
|
this.reconnectStrategy = strategy;
|
|
1267
1318
|
|
|
1268
1319
|
do {
|
|
@@ -1352,7 +1403,7 @@ export class Call {
|
|
|
1352
1403
|
const reconnectStartTime = Date.now();
|
|
1353
1404
|
this.reconnectStrategy = WebsocketReconnectStrategy.FAST;
|
|
1354
1405
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
1355
|
-
await this.
|
|
1406
|
+
await this.doJoin(this.joinCallData);
|
|
1356
1407
|
this.sfuStatsReporter?.sendReconnectionTime(
|
|
1357
1408
|
WebsocketReconnectStrategy.FAST,
|
|
1358
1409
|
(Date.now() - reconnectStartTime) / 1000,
|
|
@@ -1367,7 +1418,7 @@ export class Call {
|
|
|
1367
1418
|
const reconnectStartTime = Date.now();
|
|
1368
1419
|
this.reconnectStrategy = WebsocketReconnectStrategy.REJOIN;
|
|
1369
1420
|
this.state.setCallingState(CallingState.RECONNECTING);
|
|
1370
|
-
await this.
|
|
1421
|
+
await this.doJoin(this.joinCallData);
|
|
1371
1422
|
await this.restorePublishedTracks();
|
|
1372
1423
|
this.restoreSubscribedTracks();
|
|
1373
1424
|
this.sfuStatsReporter?.sendReconnectionTime(
|
|
@@ -1399,7 +1450,7 @@ export class Call {
|
|
|
1399
1450
|
|
|
1400
1451
|
try {
|
|
1401
1452
|
const currentSfu = currentSfuClient.edgeName;
|
|
1402
|
-
await this.
|
|
1453
|
+
await this.doJoin({ ...this.joinCallData, migrating_from: currentSfu });
|
|
1403
1454
|
} finally {
|
|
1404
1455
|
// cleanup the migration_from field after the migration is complete or failed
|
|
1405
1456
|
// as we don't want to keep dirty data in the join call data
|
package/src/StreamSfuClient.ts
CHANGED
|
@@ -261,7 +261,10 @@ export class StreamSfuClient {
|
|
|
261
261
|
};
|
|
262
262
|
|
|
263
263
|
get isHealthy() {
|
|
264
|
-
return
|
|
264
|
+
return (
|
|
265
|
+
this.signalWs.readyState === WebSocket.OPEN &&
|
|
266
|
+
this.joinResponseTask.isResolved()
|
|
267
|
+
);
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
get joinTask() {
|
|
@@ -412,7 +415,10 @@ export class StreamSfuClient {
|
|
|
412
415
|
): Promise<JoinResponse> => {
|
|
413
416
|
// wait for the signal web socket to be ready before sending "joinRequest"
|
|
414
417
|
await this.signalReady();
|
|
415
|
-
if (
|
|
418
|
+
if (
|
|
419
|
+
this.joinResponseTask.isResolved() ||
|
|
420
|
+
this.joinResponseTask.isRejected()
|
|
421
|
+
) {
|
|
416
422
|
// we need to lock the RPC requests until we receive a JoinResponse.
|
|
417
423
|
// that's why we have this primitive lock mechanism.
|
|
418
424
|
// the client starts with already initialized joinResponseTask,
|
|
@@ -424,7 +430,7 @@ export class StreamSfuClient {
|
|
|
424
430
|
// be replaced with a new one in case a second join request is made
|
|
425
431
|
const current = this.joinResponseTask;
|
|
426
432
|
|
|
427
|
-
let timeoutId: NodeJS.Timeout;
|
|
433
|
+
let timeoutId: NodeJS.Timeout | undefined = undefined;
|
|
428
434
|
const unsubscribe = this.dispatcher.on('joinResponse', (joinResponse) => {
|
|
429
435
|
this.logger('debug', 'Received joinResponse', joinResponse);
|
|
430
436
|
clearTimeout(timeoutId);
|
package/src/StreamVideoClient.ts
CHANGED
|
@@ -259,11 +259,10 @@ export class StreamVideoClient {
|
|
|
259
259
|
this.connectionConcurrencyTag,
|
|
260
260
|
async () => {
|
|
261
261
|
const client = this.streamClient;
|
|
262
|
-
const {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
} = client.options;
|
|
262
|
+
const { onConnectUserError, persistUserOnConnectionFailure } =
|
|
263
|
+
client.options;
|
|
264
|
+
let { maxConnectUserRetries = 5 } = client.options;
|
|
265
|
+
maxConnectUserRetries = Math.max(maxConnectUserRetries, 1);
|
|
267
266
|
|
|
268
267
|
const errorQueue: Error[] = [];
|
|
269
268
|
for (let attempt = 0; attempt < maxConnectUserRetries; attempt++) {
|
|
@@ -105,7 +105,7 @@ it('keeps user handlers for SFU and coordinator events', async () => {
|
|
|
105
105
|
|
|
106
106
|
it("doesn't break when joining and leaving the same instance in quick succession", async () => {
|
|
107
107
|
const call = client.call('default', generateUUIDv4());
|
|
108
|
-
|
|
108
|
+
const states: CallingState[] = [];
|
|
109
109
|
call.state.callingState$.subscribe((state) => states.push(state));
|
|
110
110
|
call.getOrCreate();
|
|
111
111
|
call.leave();
|
|
@@ -492,7 +492,7 @@ export class StreamClient {
|
|
|
492
492
|
// we need to wait for presence of connection id before making requests
|
|
493
493
|
try {
|
|
494
494
|
await this.connectionIdPromise;
|
|
495
|
-
} catch
|
|
495
|
+
} catch {
|
|
496
496
|
// in case connection id was rejected
|
|
497
497
|
// reconnection maybe in progress
|
|
498
498
|
// we can wait for healthy connection to resolve, which rejects when 15s timeout is reached
|
|
@@ -529,7 +529,6 @@ export class StreamClient {
|
|
|
529
529
|
this._logApiResponse<T>(type, url, response);
|
|
530
530
|
this.consecutiveFailures = 0;
|
|
531
531
|
return this.handleResponse(response);
|
|
532
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
533
532
|
} catch (e: any /**TODO: generalize error types */) {
|
|
534
533
|
e.client_request_id = requestConfig.headers?.['x-client-request-id'];
|
|
535
534
|
this.consecutiveFailures += 1;
|
|
@@ -549,7 +548,7 @@ export class StreamClient {
|
|
|
549
548
|
return this.handleResponse(e.response);
|
|
550
549
|
} else {
|
|
551
550
|
this._logApiError(type, url, e);
|
|
552
|
-
|
|
551
|
+
|
|
553
552
|
throw e as AxiosError<APIErrorResponse>;
|
|
554
553
|
}
|
|
555
554
|
}
|
|
@@ -120,7 +120,7 @@ export class StableWSConnection {
|
|
|
120
120
|
this.consecutiveFailures += 1;
|
|
121
121
|
|
|
122
122
|
if (
|
|
123
|
-
// @ts-
|
|
123
|
+
// @ts-expect-error type issue
|
|
124
124
|
error.code === KnownCodes.TOKEN_EXPIRED &&
|
|
125
125
|
!this.client.tokenManager.isStatic()
|
|
126
126
|
) {
|
|
@@ -129,18 +129,18 @@ export class StableWSConnection {
|
|
|
129
129
|
);
|
|
130
130
|
this._reconnect({ refreshToken: true });
|
|
131
131
|
} else {
|
|
132
|
-
// @ts-
|
|
132
|
+
// @ts-expect-error type issue
|
|
133
133
|
if (!error.isWSFailure) {
|
|
134
134
|
// API rejected the connection and we should not retry
|
|
135
135
|
throw new Error(
|
|
136
136
|
JSON.stringify({
|
|
137
|
-
// @ts-
|
|
137
|
+
// @ts-expect-error type issue
|
|
138
138
|
code: error.code,
|
|
139
|
-
// @ts-
|
|
139
|
+
// @ts-expect-error type issue
|
|
140
140
|
StatusCode: error.StatusCode,
|
|
141
|
-
// @ts-
|
|
141
|
+
// @ts-expect-error type issue
|
|
142
142
|
message: error.message,
|
|
143
|
-
// @ts-
|
|
143
|
+
// @ts-expect-error type issue
|
|
144
144
|
isWSFailure: error.isWSFailure,
|
|
145
145
|
}),
|
|
146
146
|
);
|
|
@@ -288,7 +288,7 @@ export class StableWSConnection {
|
|
|
288
288
|
this._log(`_connect() - waiting for token`);
|
|
289
289
|
await this.client.tokenManager.tokenReady();
|
|
290
290
|
isTokenReady = true;
|
|
291
|
-
} catch
|
|
291
|
+
} catch {
|
|
292
292
|
// token provider has failed before, so try again
|
|
293
293
|
}
|
|
294
294
|
|
|
@@ -323,7 +323,7 @@ export class StableWSConnection {
|
|
|
323
323
|
} catch (err) {
|
|
324
324
|
this.client._setupConnectionIdPromise();
|
|
325
325
|
this.isConnecting = false;
|
|
326
|
-
// @ts-
|
|
326
|
+
// @ts-expect-error type issue
|
|
327
327
|
this._log(`_connect() - Error - `, err);
|
|
328
328
|
this.client.rejectConnectionId?.(err);
|
|
329
329
|
throw err;
|
|
@@ -543,13 +543,13 @@ export class StableWSConnection {
|
|
|
543
543
|
`WS connection reject with error ${event.reason}`,
|
|
544
544
|
);
|
|
545
545
|
|
|
546
|
-
// @ts-expect-error
|
|
546
|
+
// @ts-expect-error type issue
|
|
547
547
|
error.reason = event.reason;
|
|
548
|
-
// @ts-expect-error
|
|
548
|
+
// @ts-expect-error type issue
|
|
549
549
|
error.code = event.code;
|
|
550
|
-
// @ts-expect-error
|
|
550
|
+
// @ts-expect-error type issue
|
|
551
551
|
error.wasClean = event.wasClean;
|
|
552
|
-
// @ts-expect-error
|
|
552
|
+
// @ts-expect-error type issue
|
|
553
553
|
error.target = event.target;
|
|
554
554
|
|
|
555
555
|
this.rejectConnectionOpen?.(error);
|
|
@@ -665,7 +665,7 @@ export class StableWSConnection {
|
|
|
665
665
|
|
|
666
666
|
try {
|
|
667
667
|
this?.ws?.close();
|
|
668
|
-
} catch
|
|
668
|
+
} catch {
|
|
669
669
|
// we don't care
|
|
670
670
|
}
|
|
671
671
|
}
|
|
@@ -704,7 +704,7 @@ export class StableWSConnection {
|
|
|
704
704
|
// try to send on the connection
|
|
705
705
|
try {
|
|
706
706
|
this.ws?.send(JSON.stringify(data));
|
|
707
|
-
} catch
|
|
707
|
+
} catch {
|
|
708
708
|
// error will already be detected elsewhere
|
|
709
709
|
}
|
|
710
710
|
}, this.pingInterval);
|
|
@@ -23,7 +23,7 @@ export class BrowserPermission {
|
|
|
23
23
|
const signal = this.disposeController.signal;
|
|
24
24
|
|
|
25
25
|
this.ready = (async () => {
|
|
26
|
-
const assumeGranted = (
|
|
26
|
+
const assumeGranted = () => {
|
|
27
27
|
this.setState('prompt');
|
|
28
28
|
};
|
|
29
29
|
|
|
@@ -43,7 +43,8 @@ export class BrowserPermission {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
} catch (err) {
|
|
46
|
-
|
|
46
|
+
this.logger('debug', 'Failed to query permission status', err);
|
|
47
|
+
assumeGranted();
|
|
47
48
|
}
|
|
48
49
|
})();
|
|
49
50
|
}
|
|
@@ -250,7 +250,7 @@ export class MicrophoneManager extends InputMediaDeviceManager<MicrophoneManager
|
|
|
250
250
|
} else {
|
|
251
251
|
// Need to start a new stream that's not connected to publisher
|
|
252
252
|
const stream = await this.getStream({
|
|
253
|
-
deviceId,
|
|
253
|
+
deviceId: { exact: deviceId },
|
|
254
254
|
});
|
|
255
255
|
this.soundDetectorCleanup = createSoundDetector(stream, (event) => {
|
|
256
256
|
this.state.setSpeakingWhileMuted(event.isSoundDetected);
|
|
@@ -88,10 +88,8 @@ export class ScreenShareManager extends InputMediaDeviceManager<
|
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Overrides the default `select` method to throw an error.
|
|
91
|
-
*
|
|
92
|
-
* @param deviceId ignored.
|
|
93
91
|
*/
|
|
94
|
-
async select(
|
|
92
|
+
async select(): Promise<void> {
|
|
95
93
|
throw new Error('This method is not supported in for Screen Share');
|
|
96
94
|
}
|
|
97
95
|
}
|
|
@@ -309,7 +309,7 @@ describe('InputMediaDeviceManager.test', () => {
|
|
|
309
309
|
const device = mockVideoDevices[0];
|
|
310
310
|
await manager.select(device.deviceId);
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
// @ts-expect-error - private method
|
|
313
313
|
vi.spyOn(manager, 'applySettingsToStream');
|
|
314
314
|
|
|
315
315
|
emitDeviceIds([
|
|
@@ -152,7 +152,7 @@ describe('MicrophoneManager', () => {
|
|
|
152
152
|
describe('Speaking While Muted', () => {
|
|
153
153
|
it(`should start sound detection if mic is disabled`, async () => {
|
|
154
154
|
await manager.enable();
|
|
155
|
-
// @ts-expect-error
|
|
155
|
+
// @ts-expect-error private api
|
|
156
156
|
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
157
157
|
await manager.disable();
|
|
158
158
|
|
|
@@ -171,7 +171,7 @@ describe('MicrophoneManager', () => {
|
|
|
171
171
|
it('should update speaking while muted state', async () => {
|
|
172
172
|
const mock = createSoundDetector as Mock;
|
|
173
173
|
let handler: SoundStateChangeHandler;
|
|
174
|
-
|
|
174
|
+
const prevMockImplementation = mock.getMockImplementation();
|
|
175
175
|
mock.mockImplementation((_: MediaStream, h: SoundStateChangeHandler) => {
|
|
176
176
|
handler = h;
|
|
177
177
|
});
|
|
@@ -197,7 +197,7 @@ describe('MicrophoneManager', () => {
|
|
|
197
197
|
await manager.enable();
|
|
198
198
|
await manager.disable();
|
|
199
199
|
|
|
200
|
-
// @ts-expect-error
|
|
200
|
+
// @ts-expect-error private api
|
|
201
201
|
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
202
202
|
manager['call'].state.setOwnCapabilities([]);
|
|
203
203
|
|
|
@@ -211,7 +211,7 @@ describe('MicrophoneManager', () => {
|
|
|
211
211
|
|
|
212
212
|
manager['call'].state.setOwnCapabilities([]);
|
|
213
213
|
|
|
214
|
-
// @ts-expect-error
|
|
214
|
+
// @ts-expect-error private api
|
|
215
215
|
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
216
216
|
manager['call'].state.setOwnCapabilities([OwnCapability.SEND_AUDIO]);
|
|
217
217
|
|
|
@@ -73,7 +73,7 @@ describe('MicrophoneManager React Native', () => {
|
|
|
73
73
|
|
|
74
74
|
it(`should start sound detection if mic is disabled`, async () => {
|
|
75
75
|
await manager.enable();
|
|
76
|
-
// @ts-expect-error
|
|
76
|
+
// @ts-expect-error - private method
|
|
77
77
|
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
78
78
|
await manager.disable();
|
|
79
79
|
|
|
@@ -109,7 +109,7 @@ describe('MicrophoneManager React Native', () => {
|
|
|
109
109
|
await manager.enable();
|
|
110
110
|
await manager.disable();
|
|
111
111
|
|
|
112
|
-
// @ts-expect-error
|
|
112
|
+
// @ts-expect-error private method
|
|
113
113
|
vi.spyOn(manager, 'stopSpeakingWhileMutedDetection');
|
|
114
114
|
manager['call'].state.setOwnCapabilities([]);
|
|
115
115
|
|
|
@@ -122,7 +122,7 @@ describe('MicrophoneManager React Native', () => {
|
|
|
122
122
|
|
|
123
123
|
manager['call'].state.setOwnCapabilities([]);
|
|
124
124
|
|
|
125
|
-
// @ts-expect-error
|
|
125
|
+
// @ts-expect-error - private method
|
|
126
126
|
vi.spyOn(manager, 'startSpeakingWhileMutedDetection');
|
|
127
127
|
manager['call'].state.setOwnCapabilities([OwnCapability.SEND_AUDIO]);
|
|
128
128
|
|
|
@@ -139,7 +139,7 @@ describe('MicrophoneManager React Native', () => {
|
|
|
139
139
|
enable = () => {};
|
|
140
140
|
disable = () => {};
|
|
141
141
|
dispose = () => Promise.resolve(undefined);
|
|
142
|
-
toFilter = () =>
|
|
142
|
+
toFilter = () => (ms: MediaStream) => ({ output: ms });
|
|
143
143
|
on = () => () => {};
|
|
144
144
|
off = () => {};
|
|
145
145
|
})(),
|
package/src/devices/devices.ts
CHANGED
|
@@ -217,6 +217,7 @@ export const getAudioStream = async (
|
|
|
217
217
|
return await getStream(constraints);
|
|
218
218
|
} catch (error) {
|
|
219
219
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
220
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
220
221
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
221
222
|
getLogger(['devices'])(
|
|
222
223
|
'warn',
|
|
@@ -259,6 +260,7 @@ export const getVideoStream = async (
|
|
|
259
260
|
return await getStream(constraints);
|
|
260
261
|
} catch (error) {
|
|
261
262
|
if (isNotFoundOrOverconstrainedError(error) && trackConstraints?.deviceId) {
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
262
264
|
const { deviceId, ...relaxedConstraints } = trackConstraints;
|
|
263
265
|
getLogger(['devices'])(
|
|
264
266
|
'warn',
|
|
@@ -333,7 +335,7 @@ export const disposeOfMediaStream = (stream: MediaStream) => {
|
|
|
333
335
|
});
|
|
334
336
|
// @ts-expect-error release() is present in react-native-webrtc and must be called to dispose the stream
|
|
335
337
|
if (typeof stream.release === 'function') {
|
|
336
|
-
// @ts-expect-error
|
|
338
|
+
// @ts-expect-error - release() is present in react-native-webrtc
|
|
337
339
|
stream.release();
|
|
338
340
|
}
|
|
339
341
|
};
|