@stream-io/video-client 1.61.0 → 1.61.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-client",
3
- "version": "1.61.0",
3
+ "version": "1.61.1",
4
4
  "main": "dist/index.cjs.js",
5
5
  "module": "dist/index.es.js",
6
6
  "browser": "dist/index.browser.es.js",
@@ -597,11 +597,16 @@ export class ClientEventReporter {
597
597
  }
598
598
 
599
599
  if (event.state === 'failed') {
600
+ const iceConnected =
601
+ event.iceConnectionState === 'connected' ||
602
+ event.iceConnectionState === 'completed';
600
603
  this.emitPeerConnectionFailure(
601
604
  cid,
602
605
  role,
603
- 'DTLS_CONNECTIVITY_FAILED',
604
- 'DTLS connectivity checks failed',
606
+ iceConnected ? 'DTLS_CONNECTIVITY_FAILED' : 'ICE_CONNECTIVITY_FAILED',
607
+ iceConnected
608
+ ? 'DTLS connectivity checks failed'
609
+ : 'ICE connectivity checks failed',
605
610
  );
606
611
  return;
607
612
  }
@@ -755,7 +755,7 @@ describe('ClientEventReporter', () => {
755
755
  });
756
756
  });
757
757
 
758
- it('reports a DTLS failure with the real ICE state (never connected)', async () => {
758
+ it('reports an ICE failure when the peer connection fails before ICE connected', async () => {
759
759
  reporter.startCorrelation(cid, 'first-attempt');
760
760
  reportPeerConnectionState(
761
761
  PeerType.PUBLISHER_UNSPECIFIED,
@@ -779,7 +779,7 @@ describe('ClientEventReporter', () => {
779
779
  expect(completed[0]).toMatchObject({
780
780
  outcome: 'failure',
781
781
  peer_connection: 'publish',
782
- retry_failure_code: 'DTLS_CONNECTIVITY_FAILED',
782
+ retry_failure_code: 'ICE_CONNECTIVITY_FAILED',
783
783
  ice_state: 'NOT_CONNECTED',
784
784
  });
785
785
  });
@@ -344,9 +344,14 @@ export abstract class BasePeerConnection {
344
344
 
345
345
  // we can't recover from a failed connection state (contrary to ICE)
346
346
  if (state === 'failed') {
347
+ const reason = this.iceHasEverConnected
348
+ ? ReconnectReason.CONNECTION_FAILED
349
+ : ReconnectReason.ICE_NEVER_CONNECTED;
350
+ clearTimeout(this.preConnectStuckTimeout);
351
+ this.preConnectStuckTimeout = undefined;
347
352
  this.onReconnectionNeeded?.(
348
353
  WebsocketReconnectStrategy.REJOIN,
349
- ReconnectReason.CONNECTION_FAILED,
354
+ reason,
350
355
  this.peerType,
351
356
  );
352
357
  return;
@@ -931,6 +931,56 @@ describe('Publisher', () => {
931
931
  );
932
932
  });
933
933
 
934
+ it.each(['checking', 'failed'] as const)(
935
+ `connection-state 'failed' before ICE connects emits ICE_NEVER_CONNECTED when ICE is '%s'`,
936
+ async (iceState) => {
937
+ vi.spyOn(publisher, 'restartIce').mockResolvedValue();
938
+ publisher['onReconnectionNeeded'] = vi.fn();
939
+ // @ts-expect-error readonly field
940
+ publisher['pc'].iceConnectionState = iceState;
941
+ // @ts-expect-error readonly field
942
+ publisher['pc'].connectionState = 'failed';
943
+
944
+ // No ICE state-change event precedes the aggregate failure.
945
+ await publisher['onConnectionStateChange']();
946
+
947
+ expect(publisher.restartIce).not.toHaveBeenCalled();
948
+ expect(
949
+ publisher['onReconnectionNeeded'],
950
+ ).toHaveBeenCalledExactlyOnceWith(
951
+ WebsocketReconnectStrategy.REJOIN,
952
+ ReconnectReason.ICE_NEVER_CONNECTED,
953
+ PeerType.PUBLISHER_UNSPECIFIED,
954
+ );
955
+ },
956
+ );
957
+
958
+ it(`connection-state 'failed' cancels the pre-connect watchdog`, async () => {
959
+ vi.useFakeTimers();
960
+ publisher['onReconnectionNeeded'] = vi.fn();
961
+ const watchdogMs = publisher['iceRestartDelay'] * 2;
962
+
963
+ // @ts-expect-error readonly field
964
+ publisher['pc'].iceConnectionState = 'disconnected';
965
+ publisher['onIceConnectionStateChange']();
966
+ expect(publisher['preConnectStuckTimeout']).toBeDefined();
967
+ expect(publisher['onReconnectionNeeded']).not.toHaveBeenCalled();
968
+
969
+ // Keep ICE disconnected so an uncanceled watchdog would reconnect again.
970
+ // @ts-expect-error readonly field
971
+ publisher['pc'].connectionState = 'failed';
972
+ await publisher['onConnectionStateChange']();
973
+
974
+ expect(publisher['preConnectStuckTimeout']).toBeUndefined();
975
+ expect(publisher['onReconnectionNeeded']).toHaveBeenCalledExactlyOnceWith(
976
+ WebsocketReconnectStrategy.REJOIN,
977
+ ReconnectReason.ICE_NEVER_CONNECTED,
978
+ PeerType.PUBLISHER_UNSPECIFIED,
979
+ );
980
+ await vi.advanceTimersByTimeAsync(watchdogMs + 100);
981
+ expect(publisher['onReconnectionNeeded']).toHaveBeenCalledTimes(1);
982
+ });
983
+
934
984
  it(`connection-state 'failed' (distinct from ICE state) still fires REJOIN even after ICE was connected`, () => {
935
985
  // mark ICE as connected first
936
986
  // @ts-expect-error private api