@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +104 -168
  2. package/dist/index.browser.es.js +99 -54
  3. package/dist/index.browser.es.js.map +1 -1
  4. package/dist/index.cjs.js +99 -54
  5. package/dist/index.cjs.js.map +1 -1
  6. package/dist/index.es.js +99 -54
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/src/Call.d.ts +10 -1
  9. package/dist/src/devices/ScreenShareManager.d.ts +1 -3
  10. package/dist/src/gen/video/sfu/event/events.d.ts +1 -19
  11. package/dist/src/gen/video/sfu/signal_rpc/signal.client.d.ts +2 -21
  12. package/dist/src/gen/video/sfu/signal_rpc/signal.d.ts +1 -9
  13. package/dist/src/helpers/promise.d.ts +2 -2
  14. package/dist/src/store/CallState.d.ts +5 -0
  15. package/package.json +10 -11
  16. package/src/Call.ts +61 -10
  17. package/src/StreamSfuClient.ts +9 -3
  18. package/src/StreamVideoClient.ts +4 -5
  19. package/src/__tests__/Call.test.ts +1 -1
  20. package/src/coordinator/connection/client.ts +2 -3
  21. package/src/coordinator/connection/connection.ts +14 -14
  22. package/src/coordinator/connection/signing.ts +1 -1
  23. package/src/devices/BrowserPermission.ts +3 -2
  24. package/src/devices/MicrophoneManager.ts +1 -1
  25. package/src/devices/ScreenShareManager.ts +1 -3
  26. package/src/devices/__tests__/InputMediaDeviceManager.test.ts +1 -1
  27. package/src/devices/__tests__/MicrophoneManager.test.ts +4 -4
  28. package/src/devices/__tests__/MicrophoneManagerRN.test.ts +4 -4
  29. package/src/devices/devices.ts +3 -1
  30. package/src/events/__tests__/call.test.ts +42 -57
  31. package/src/events/__tests__/internal.test.ts +14 -13
  32. package/src/events/__tests__/mutes.test.ts +7 -3
  33. package/src/events/__tests__/participant.test.ts +16 -20
  34. package/src/events/__tests__/speaker.test.ts +6 -6
  35. package/src/events/internal.ts +1 -0
  36. package/src/gen/coordinator/index.ts +1 -1
  37. package/src/gen/video/sfu/event/events.ts +22 -20
  38. package/src/gen/video/sfu/models/models.ts +0 -1
  39. package/src/gen/video/sfu/signal_rpc/signal.client.ts +27 -23
  40. package/src/gen/video/sfu/signal_rpc/signal.ts +13 -11
  41. package/src/helpers/RNSpeechDetector.ts +3 -4
  42. package/src/helpers/__tests__/DynascaleManager.test.ts +27 -26
  43. package/src/helpers/__tests__/clientUtils.test.ts +0 -1
  44. package/src/helpers/client-details.ts +1 -1
  45. package/src/helpers/promise.ts +4 -4
  46. package/src/rtc/Dispatcher.ts +1 -1
  47. package/src/rtc/Publisher.ts +2 -2
  48. package/src/rtc/__tests__/Publisher.test.ts +8 -8
  49. package/src/rtc/__tests__/Subscriber.test.ts +9 -9
  50. package/src/rtc/__tests__/mocks/webrtc.mocks.ts +2 -2
  51. package/src/rtc/helpers/__tests__/sdp.test.ts +3 -3
  52. package/src/stats/CallStateStatsReporter.ts +2 -3
  53. package/src/store/CallState.ts +9 -1
  54. package/src/store/__tests__/CallState.test.ts +59 -115
  55. package/src/timers/worker.ts +0 -4
@@ -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) => Promise<void>;
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(deviceId: string | undefined): Promise<void>;
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 { ServiceInfo } from '@protobuf-ts/runtime-rpc';
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.7",
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.18",
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.5",
47
+ "@vitest/coverage-v8": "^3.0.9",
49
48
  "dotenv": "^16.4.7",
50
49
  "happy-dom": "^11.0.2",
51
- "prettier": "^3.3.2",
52
- "rimraf": "^5.0.7",
53
- "rollup": "^4.34.7",
54
- "typescript": "^5.5.2",
55
- "vite": "^5.4.6",
56
- "vitest": "^3.0.5",
57
- "vitest-mock-extended": "^2.0.2"
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 (data?: JoinCallData): Promise<void> => {
817
- const connectStartTime = Date.now();
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
- // SFU WS closed before we finished current join, no need to schedule reconnect
1234
- // because join operation will fail
1235
- if (this.state.callingState === CallingState.JOINING) return;
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
- let reconnectStartTime = Date.now();
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.join(this.joinCallData);
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.join(this.joinCallData);
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.join({ ...this.joinCallData, migrating_from: currentSfu });
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
@@ -261,7 +261,10 @@ export class StreamSfuClient {
261
261
  };
262
262
 
263
263
  get isHealthy() {
264
- return this.signalWs.readyState === WebSocket.OPEN;
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 (this.joinResponseTask.isResolved || this.joinResponseTask.isRejected) {
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);
@@ -259,11 +259,10 @@ export class StreamVideoClient {
259
259
  this.connectionConcurrencyTag,
260
260
  async () => {
261
261
  const client = this.streamClient;
262
- const {
263
- maxConnectUserRetries = 5,
264
- onConnectUserError,
265
- persistUserOnConnectionFailure,
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
- let states: CallingState[] = [];
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 (e) {
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
- // eslint-disable-next-line no-throw-literal
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-ignore
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-ignore
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-ignore
137
+ // @ts-expect-error type issue
138
138
  code: error.code,
139
- // @ts-ignore
139
+ // @ts-expect-error type issue
140
140
  StatusCode: error.StatusCode,
141
- // @ts-ignore
141
+ // @ts-expect-error type issue
142
142
  message: error.message,
143
- // @ts-ignore
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 (e) {
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-ignore
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 (e) {
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 (e) {
707
+ } catch {
708
708
  // error will already be detected elsewhere
709
709
  }
710
710
  }, this.pingInterval);
@@ -32,7 +32,7 @@ const decodeBase64 = (s: string): string => {
32
32
  b = (b << 6) + c;
33
33
  l += 6;
34
34
  while (l >= 8) {
35
- ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) && (r += w(a));
35
+ if ((a = (b >>> (l -= 8)) & 0xff) || x < L - 2) r += w(a);
36
36
  }
37
37
  }
38
38
  return r;
@@ -23,7 +23,7 @@ export class BrowserPermission {
23
23
  const signal = this.disposeController.signal;
24
24
 
25
25
  this.ready = (async () => {
26
- const assumeGranted = (error?: unknown) => {
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
- assumeGranted(err);
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(deviceId: string | undefined): Promise<void> {
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
- //@ts-expect-error
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
- let prevMockImplementation = mock.getMockImplementation();
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 = () => async (ms: MediaStream) => ms;
142
+ toFilter = () => (ms: MediaStream) => ({ output: ms });
143
143
  on = () => () => {};
144
144
  off = () => {};
145
145
  })(),
@@ -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
  };