Skip to content

Commit

Permalink
fix: do not always error out api calls when web socket initially fail…
Browse files Browse the repository at this point in the history
…ed (#1495)

## Problem

Currently for all axios requests, we wait for `connectionIdPromise`.
This promise is rejected or resolved based on the first WS connect
attempt. If subsequent reconnects were successful but the first connect
was rejected, this promise would still stay rejected.

## Solution

To allow for reconnects to resolve, say for example, in 3rd try the
connection got open, we can wait for the `connectionOpen` promise to
resolve. To allow for 2 or 3 retries, we use `_waitForHealthy` function
here. Alternatively we could await only on `connectionOpen` promise
without allowing for timeouts to wait for reconnections also.
  • Loading branch information
santhoshvai authored Sep 24, 2024
1 parent 5ad5fac commit 7cdb62e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 13 deletions.
38 changes: 25 additions & 13 deletions packages/client/src/coordinator/connection/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,7 @@ export class StreamClient {
return Promise.resolve();
}

this.connectionIdPromise = new Promise<string | undefined>(
(resolve, reject) => {
this.resolveConnectionId = resolve;
this.rejectConnectionId = reject;
},
);
this._setupConnectionIdPromise();

this.clientID = `${this.userID}--${randomId()}`;
this.wsPromise = this.connect();
Expand Down Expand Up @@ -428,12 +423,7 @@ export class StreamClient {
tokenOrProvider: TokenOrProvider,
) => {
addConnectionEventListeners(this.updateNetworkConnectionStatus);
this.connectionIdPromise = new Promise<string | undefined>(
(resolve, reject) => {
this.resolveConnectionId = resolve;
this.rejectConnectionId = reject;
},
);
this._setupConnectionIdPromise();

this.anonymous = true;
await this._setToken(user, tokenOrProvider, this.anonymous);
Expand Down Expand Up @@ -487,6 +477,19 @@ export class StreamClient {
);
};

/**
* sets up the this.connectionIdPromise
*/
_setupConnectionIdPromise = async () => {
/** a promise that is resolved once connection id is set */
this.connectionIdPromise = new Promise<string | undefined>(
(resolve, reject) => {
this.resolveConnectionId = resolve;
this.rejectConnectionId = reject;
},
);
};

_logApiRequest = (
type: string,
url: string,
Expand Down Expand Up @@ -534,8 +537,17 @@ export class StreamClient {
await Promise.all([
this.tokenManager.tokenReady(),
this.guestUserCreatePromise,
this.connectionIdPromise,
]);
// we need to wait for presence of connection id before making requests
try {
await this.connectionIdPromise;
} catch (e) {
// in case connection id was rejected
// reconnection maybe in progress
// we can wait for healthy connection to resolve, which rejects when 15s timeout is reached
await this.wsConnection?._waitForHealthy();
await this.connectionIdPromise;
}
}
const requestConfig = this._enrichAxiosOptions(options);
try {
Expand Down
11 changes: 11 additions & 0 deletions packages/client/src/coordinator/connection/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
addConnectionEventListeners,
convertErrorToJson,
isPromisePending,
KnownCodes,
randomId,
removeConnectionEventListeners,
Expand Down Expand Up @@ -337,6 +338,15 @@ export class StableWSConnection {
await this.client.tokenManager.loadToken();
}

let mustSetupConnectionIdPromise = true;
if (this.client.connectionIdPromise) {
if (await isPromisePending(this.client.connectionIdPromise)) {
mustSetupConnectionIdPromise = false;
}
}
if (mustSetupConnectionIdPromise) {
this.client._setupConnectionIdPromise();
}
this._setupConnectionPromise();
const wsURL = this._buildUrl();
this._log(`_connect() - Connecting to ${wsURL}`, {
Expand Down Expand Up @@ -369,6 +379,7 @@ export class StableWSConnection {
return response;
}
} catch (err) {
await this.client._setupConnectionIdPromise();
this.isConnecting = false;
// @ts-ignore
this._log(`_connect() - Error - `, err);
Expand Down
11 changes: 11 additions & 0 deletions packages/client/src/coordinator/connection/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ export function convertErrorToJson(err: Error) {
return jsonObj;
}

/**
* Informs if a promise is yet to be resolved or rejected
*/
export async function isPromisePending<T>(promise: Promise<T>) {
const emptyObj = {};
return Promise.race([promise, emptyObj]).then(
(value) => (value === emptyObj ? true : false),
() => false,
);
}

/**
* isOnline safely return the navigator.online value for browser env
* if navigator is not in global object, it always return true
Expand Down

0 comments on commit 7cdb62e

Please sign in to comment.