Skip to content

Commit

Permalink
Merge branch 'main' into worker-timers
Browse files Browse the repository at this point in the history
  • Loading branch information
myandrienko committed Dec 3, 2024
2 parents e5d605e + 2695bab commit 0aefda0
Show file tree
Hide file tree
Showing 239 changed files with 10,587 additions and 5,980 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-react-sample-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'

- name: Install Dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/egress-composite-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'
cache-dependency-path: 'yarn.lock'

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/react-native-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
name: Deploy iOS
needs: build_ios
timeout-minutes: 60
if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/PBE-5855-feat/react-native-video-design-v2' }}
if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/use-vp8-on-ios' }}
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'

- name: ESLint Cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/version-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 22.x
cache: 'yarn'

- name: ESLint Cache
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20
v22
56 changes: 56 additions & 0 deletions packages/client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,62 @@

This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).

## [1.11.12](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.11...@stream-io/video-client-1.11.12) (2024-12-03)


### Bug Fixes

* handle timeout on SFU WS connections ([#1600](https://github.com/GetStream/stream-video-js/issues/1600)) ([5f2db7b](https://github.com/GetStream/stream-video-js/commit/5f2db7bd5cfdf57cdc04d6a6ed752f43e5b06657))

## [1.11.11](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.10...@stream-io/video-client-1.11.11) (2024-11-29)


### Bug Fixes

* revert [#1604](https://github.com/GetStream/stream-video-js/issues/1604) ([#1607](https://github.com/GetStream/stream-video-js/issues/1607)) ([567e4fb](https://github.com/GetStream/stream-video-js/commit/567e4fb309509b6b0d814826856d0a15efe16271))

## [1.11.10](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.9...@stream-io/video-client-1.11.10) (2024-11-28)


### Bug Fixes

* ringing calls not being left when ended ([#1601](https://github.com/GetStream/stream-video-js/issues/1601)) ([1c2b9d1](https://github.com/GetStream/stream-video-js/commit/1c2b9d1a54767652acc52cae9bb3d348c9df566f))

## [1.11.9](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.8...@stream-io/video-client-1.11.9) (2024-11-27)


### Bug Fixes

* cover some device selection edge cases ([#1604](https://github.com/GetStream/stream-video-js/issues/1604)) ([a8fc0ea](https://github.com/GetStream/stream-video-js/commit/a8fc0eaf1ed6c79ce24f77f52351a1e90701bd02))

## [1.11.8](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.7...@stream-io/video-client-1.11.8) (2024-11-27)


### Bug Fixes

* **ios:** use vp8 when h264 constrainted baseline isn't available ([#1597](https://github.com/GetStream/stream-video-js/issues/1597)) ([6281216](https://github.com/GetStream/stream-video-js/commit/62812161cef5e9917c504dbc4cd9257709ea5fa1))

## [1.11.7](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.6...@stream-io/video-client-1.11.7) (2024-11-26)


### Bug Fixes

* remove unused code from the coordinator websocket impl ([#1563](https://github.com/GetStream/stream-video-js/issues/1563)) ([921b820](https://github.com/GetStream/stream-video-js/commit/921b820133885dac299dab343cee3fc4b08705ce))

## [1.11.6](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.5...@stream-io/video-client-1.11.6) (2024-11-22)


### Bug Fixes

* force single codec preference in the SDP ([#1588](https://github.com/GetStream/stream-video-js/issues/1588)) ([4afff09](https://github.com/GetStream/stream-video-js/commit/4afff09a778f8567176d22bcc22d36001dca7cd3)), closes [#1581](https://github.com/GetStream/stream-video-js/issues/1581)

## [1.11.5](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.4...@stream-io/video-client-1.11.5) (2024-11-22)


### Bug Fixes

* unhandled promise rejections during reconnect ([#1585](https://github.com/GetStream/stream-video-js/issues/1585)) ([920c4ea](https://github.com/GetStream/stream-video-js/commit/920c4ea3b3f622430b35ac1bade74a6206ee17e5)), closes [/github.com/GetStream/stream-video-js/pull/1585/files#diff-420f6ddab47c1be72fd9ce8c99e1fa2b9f5f0495b7c367546ee0ff634beaed81](https://github.com/GetStream//github.com/GetStream/stream-video-js/pull/1585/files/issues/diff-420f6ddab47c1be72fd9ce8c99e1fa2b9f5f0495b7c367546ee0ff634beaed81)

## [1.11.4](https://github.com/GetStream/stream-video-js/compare/@stream-io/video-client-1.11.3...@stream-io/video-client-1.11.4) (2024-11-21)


Expand Down
16 changes: 6 additions & 10 deletions packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stream-io/video-client",
"version": "1.11.4",
"version": "1.11.12",
"packageManager": "[email protected]",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
Expand Down Expand Up @@ -32,15 +32,11 @@
"@protobuf-ts/runtime": "^2.9.4",
"@protobuf-ts/runtime-rpc": "^2.9.4",
"@protobuf-ts/twirp-transport": "^2.9.4",
"@types/ws": "^8.5.7",
"axios": "^1.6.0",
"base64-js": "^1.5.1",
"isomorphic-ws": "^5.0.0",
"axios": "^1.7.7",
"rxjs": "~7.8.1",
"sdp-transform": "^2.14.1",
"ua-parser-js": "^1.0.36",
"webrtc-adapter": "^8.2.3",
"ws": "^8.14.2"
"webrtc-adapter": "^8.2.3"
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "^2.13.4",
Expand All @@ -50,15 +46,15 @@
"@stream-io/node-sdk": "^0.4.3",
"@types/sdp-transform": "^2.4.7",
"@types/ua-parser-js": "^0.7.37",
"@vitest/coverage-v8": "^0.34.4",
"@vitest/coverage-v8": "^2.1.4",
"dotenv": "^16.3.1",
"happy-dom": "^11.0.2",
"prettier": "^3.3.2",
"rimraf": "^5.0.7",
"rollup": "^4.22.0",
"typescript": "^5.5.2",
"vite": "^5.4.6",
"vitest": "^1.0.0",
"vitest-mock-extended": "^1.2.1"
"vitest": "^2.1.4",
"vitest-mock-extended": "^2.0.2"
}
}
48 changes: 42 additions & 6 deletions packages/client/src/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ import { getSdkSignature } from './stats/utils';
import { withoutConcurrency } from './helpers/concurrency';
import { ensureExhausted } from './helpers/ensureExhausted';
import {
makeSafePromise,
PromiseWithResolvers,
promiseWithResolvers,
} from './helpers/withResolvers';
} from './helpers/promise';

/**
* An object representation of a `Call`.
Expand Down Expand Up @@ -213,6 +214,7 @@ export class Call {
private reconnectAttempts = 0;
private reconnectStrategy = WebsocketReconnectStrategy.UNSPECIFIED;
private fastReconnectDeadlineSeconds: number = 0;
private disconnectionTimeoutSeconds: number = 0;
private lastOfflineTimestamp: number = 0;
private networkAvailableTask: PromiseWithResolvers<void> | undefined;
// maintain the order of publishing tracks to restore them after a reconnection
Expand Down Expand Up @@ -496,7 +498,7 @@ export class Call {
* Leave the call and stop the media streams that were published by the call.
*/
leave = async ({
reject = false,
reject,
reason = 'user is leaving the call',
}: CallLeaveOptions = {}) => {
await withoutConcurrency(this.joinLeaveConcurrencyTag, async () => {
Expand All @@ -516,13 +518,14 @@ export class Call {
await waitUntilCallJoined();
}

if (callingState === CallingState.RINGING) {
if (callingState === CallingState.RINGING && reject !== false) {
if (reject) {
await this.reject(reason);
} else {
// if reject was undefined, we still have to cancel the call automatically
// when I am the creator and everyone else left the call
const hasOtherParticipants = this.state.remoteParticipants.length > 0;
if (this.isCreatedByMe && !hasOtherParticipants) {
// I'm the one who started the call, so I should cancel it when there are no other participants.
await this.reject('cancel');
}
}
Expand Down Expand Up @@ -1050,6 +1053,9 @@ export class Call {
*/
private handleSfuSignalClose = (sfuClient: StreamSfuClient) => {
this.logger('debug', '[Reconnect] SFU signal connection closed');
// SFU WS closed before we finished current join, no need to schedule reconnect
// because join operation will fail
if (this.state.callingState === CallingState.JOINING) return;
// normal close, no need to reconnect
if (sfuClient.isLeaving) return;
this.reconnect(WebsocketReconnectStrategy.REJOIN).catch((err) => {
Expand All @@ -1067,14 +1073,35 @@ export class Call {
private reconnect = async (
strategy: WebsocketReconnectStrategy,
): Promise<void> => {
if (
this.state.callingState === CallingState.RECONNECTING ||
this.state.callingState === CallingState.RECONNECTING_FAILED
)
return;

return withoutConcurrency(this.reconnectConcurrencyTag, async () => {
this.logger(
'info',
`[Reconnect] Reconnecting with strategy ${WebsocketReconnectStrategy[strategy]}`,
);

let reconnectStartTime = Date.now();
this.reconnectStrategy = strategy;

do {
if (
this.disconnectionTimeoutSeconds > 0 &&
(Date.now() - reconnectStartTime) / 1000 >
this.disconnectionTimeoutSeconds
) {
this.logger(
'warn',
'[Reconnect] Stopping reconnection attempts after reaching disconnection timeout',
);
this.state.setCallingState(CallingState.RECONNECTING_FAILED);
return;
}

// we don't increment reconnect attempts for the FAST strategy.
if (this.reconnectStrategy !== WebsocketReconnectStrategy.FAST) {
this.reconnectAttempts++;
Expand Down Expand Up @@ -1192,7 +1219,7 @@ export class Call {
currentSubscriber?.detachEventHandlers();
currentPublisher?.detachEventHandlers();

const migrationTask = currentSfuClient.enterMigration();
const migrationTask = makeSafePromise(currentSfuClient.enterMigration());

try {
const currentSfu = currentSfuClient.edgeName;
Expand All @@ -1210,7 +1237,7 @@ export class Call {
// Wait for the migration to complete, then close the previous SFU client
// and the peer connection instances. In case of failure, the migration
// task would throw an error and REJOIN would be attempted.
await migrationTask;
await migrationTask();

// in MIGRATE, we can consider the call as joined only after
// `participantMigrationComplete` event is received, signaled by
Expand Down Expand Up @@ -2336,4 +2363,13 @@ export class Call {
);
this.dynascaleManager.applyTrackSubscriptions();
};

/**
* Sets the maximum amount of time a user can remain waiting for a reconnect
* after a network disruption
* @param timeoutSeconds Timeout in seconds, or 0 to keep reconnecting indefinetely
*/
setDisconnectionTimeout = (timeoutSeconds: number) => {
this.disconnectionTimeoutSeconds = timeoutSeconds;
};
}
55 changes: 27 additions & 28 deletions packages/client/src/StreamSfuClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ import {
} from './gen/video/sfu/signal_rpc/signal';
import { ICETrickle, TrackType } from './gen/video/sfu/models/models';
import { StreamClient } from './coordinator/connection/client';
import { generateUUIDv4, sleep } from './coordinator/connection/utils';
import { generateUUIDv4 } from './coordinator/connection/utils';
import { Credentials } from './gen/coordinator';
import { Logger } from './coordinator/connection/types';
import { getLogger, getLogLevel } from './logger';
import { withoutConcurrency } from './helpers/concurrency';
import {
promiseWithResolvers,
PromiseWithResolvers,
} from './helpers/withResolvers';
makeSafePromise,
SafePromise,
} from './helpers/promise';
import { getTimers } from './timers';

export type StreamSfuClientConstructor = {
Expand Down Expand Up @@ -102,7 +103,7 @@ export class StreamSfuClient {
/**
* Promise that resolves when the WebSocket connection is ready (open).
*/
private signalReady!: Promise<WebSocket>;
private signalReady!: SafePromise<WebSocket>;

/**
* Flag to indicate if the client is in the process of leaving the call.
Expand All @@ -117,15 +118,14 @@ export class StreamSfuClient {
private pingIntervalInMs = 10 * 1000;
private unhealthyTimeoutInMs = this.pingIntervalInMs + 5 * 1000;
private lastMessageTimestamp?: Date;
private readonly restoreWebSocketConcurrencyTag = Symbol('recoverWebSocket');
private readonly unsubscribeIceTrickle: () => void;
private readonly unsubscribeNetworkChanged: () => void;
private readonly onSignalClose: (() => void) | undefined;
private readonly logger: Logger;
private readonly logTag: string;
private readonly credentials: Credentials;
private readonly dispatcher: Dispatcher;
private readonly joinResponseTimeout?: number;
private readonly joinResponseTimeout: number;
private networkAvailableTask: PromiseWithResolvers<void> | undefined;
/**
* Promise that resolves when the JoinResponse is received.
Expand Down Expand Up @@ -228,32 +228,31 @@ export class StreamSfuClient {
});

this.signalWs.addEventListener('close', this.handleWebSocketClose);
this.signalWs.addEventListener('error', this.restoreWebSocket);

this.signalReady = new Promise((resolve) => {
const onOpen = () => {
this.signalWs.removeEventListener('open', onOpen);
resolve(this.signalWs);
};
this.signalWs.addEventListener('open', onOpen);
});

this.signalReady = makeSafePromise(
Promise.race<WebSocket>([
new Promise((resolve) => {
const onOpen = () => {
this.signalWs.removeEventListener('open', onOpen);
resolve(this.signalWs);
};
this.signalWs.addEventListener('open', onOpen);
}),

new Promise((resolve, reject) => {
setTimeout(
() => reject(new Error('SFU WS connection timed out')),
this.joinResponseTimeout,
);
}),
]),
);
};

private cleanUpWebSocket = () => {
this.signalWs.removeEventListener('error', this.restoreWebSocket);
this.signalWs.removeEventListener('close', this.handleWebSocketClose);
};

private restoreWebSocket = () => {
withoutConcurrency(this.restoreWebSocketConcurrencyTag, async () => {
await this.networkAvailableTask?.promise;
this.logger('debug', 'Restoring SFU WS connection');
this.cleanUpWebSocket();
await sleep(500);
this.createWebSocket();
}).catch((err) => this.logger('debug', `Can't restore WS connection`, err));
};

get isHealthy() {
return this.signalWs.readyState === WebSocket.OPEN;
}
Expand Down Expand Up @@ -410,7 +409,7 @@ export class StreamSfuClient {
data: Omit<JoinRequest, 'sessionId' | 'token'>,
): Promise<JoinResponse> => {
// wait for the signal web socket to be ready before sending "joinRequest"
await this.signalReady;
await this.signalReady();
if (this.joinResponseTask.isResolved || this.joinResponseTask.isRejected) {
// we need to lock the RPC requests until we receive a JoinResponse.
// that's why we have this primitive lock mechanism.
Expand Down Expand Up @@ -479,7 +478,7 @@ export class StreamSfuClient {
};

private send = async (message: SfuRequest) => {
await this.signalReady; // wait for the signal ws to be open
await this.signalReady(); // wait for the signal ws to be open
const msgJson = SfuRequest.toJson(message);
if (this.signalWs.readyState !== WebSocket.OPEN) {
this.logger('debug', 'Signal WS is not open. Skipping message', msgJson);
Expand Down
Loading

0 comments on commit 0aefda0

Please sign in to comment.