Improve graphql ws client connection loss handling
- Retry on all disconnect events - Retry infinitely - Cap retry backoff strategy delay at heartbeat interval (20s) - Recreate client in case reconnecting isn't working
This commit is contained in:
@@ -193,6 +193,8 @@ export class GraphQLClient extends BaseClient<
|
|||||||
|
|
||||||
private wsClient!: Client;
|
private wsClient!: Client;
|
||||||
|
|
||||||
|
private wsClientAliveCheckInterval: NodeJS.Timeout | undefined = undefined;
|
||||||
|
|
||||||
constructor(handleRefreshToken: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>) {
|
constructor(handleRefreshToken: (refreshToken: string) => AbortableApolloMutationResponse<UserRefreshMutation>) {
|
||||||
super(handleRefreshToken);
|
super(handleRefreshToken);
|
||||||
|
|
||||||
@@ -268,13 +270,22 @@ export class GraphQLClient extends BaseClient<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createClient() {
|
private createWSClient(lazy: boolean = true): void {
|
||||||
const heartbeatInterval = 1000 * 20;
|
const heartbeatInterval = 1000 * 20;
|
||||||
|
|
||||||
this.wsClient = createClient({
|
this.wsClient = createClient({
|
||||||
|
lazy,
|
||||||
url: () => this.getBaseUrl().replace(/http(|s)/g, 'ws'),
|
url: () => this.getBaseUrl().replace(/http(|s)/g, 'ws'),
|
||||||
keepAlive: heartbeatInterval,
|
keepAlive: heartbeatInterval,
|
||||||
retryAttempts: 10,
|
retryAttempts: Number.MAX_SAFE_INTEGER,
|
||||||
|
shouldRetry: () => true,
|
||||||
|
retryWait: async (retries) => {
|
||||||
|
const delay = Math.min(1000 * 2 ** retries, heartbeatInterval);
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, delay);
|
||||||
|
});
|
||||||
|
},
|
||||||
connectionParams: () => {
|
connectionParams: () => {
|
||||||
const isAuthRequired = AuthManager.isAuthRequired();
|
const isAuthRequired = AuthManager.isAuthRequired();
|
||||||
const accessToken = AuthManager.getAccessToken();
|
const accessToken = AuthManager.getAccessToken();
|
||||||
@@ -285,23 +296,26 @@ export class GraphQLClient extends BaseClient<
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let lastHeartbeat: number = 0;
|
let lastHeartbeat: number = Date.now();
|
||||||
this.wsClient.on('connected', () => {
|
|
||||||
lastHeartbeat = Date.now();
|
|
||||||
});
|
|
||||||
this.wsClient.on('pong', () => {
|
this.wsClient.on('pong', () => {
|
||||||
lastHeartbeat = Date.now();
|
lastHeartbeat = Date.now();
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkHeartbeatInterval = heartbeatInterval + 1000 * 10;
|
const checkHeartbeatInterval = heartbeatInterval + 1000 * 30;
|
||||||
setInterval(() => {
|
clearInterval(this.wsClientAliveCheckInterval);
|
||||||
|
this.wsClientAliveCheckInterval = setInterval(() => {
|
||||||
const isHeartbeatMissing = Date.now() - lastHeartbeat > checkHeartbeatInterval * 1.1;
|
const isHeartbeatMissing = Date.now() - lastHeartbeat > checkHeartbeatInterval * 1.1;
|
||||||
if (isHeartbeatMissing) {
|
if (isHeartbeatMissing) {
|
||||||
// force a reconnect
|
this.wsClient.dispose();
|
||||||
this.wsClient.terminate();
|
|
||||||
|
this.createWSClient(false);
|
||||||
|
this.client.setLink(this.createLink());
|
||||||
}
|
}
|
||||||
}, checkHeartbeatInterval);
|
}, checkHeartbeatInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createClient() {
|
||||||
|
this.createWSClient();
|
||||||
this.client = new ApolloClient({
|
this.client = new ApolloClient({
|
||||||
cache: new InMemoryCache({
|
cache: new InMemoryCache({
|
||||||
// for whatever reason there is some weird TypeError complaining that
|
// for whatever reason there is some weird TypeError complaining that
|
||||||
|
|||||||
Reference in New Issue
Block a user