Skip to content

Instantly share code, notes, and snippets.

@Yuripetusko
Last active April 11, 2022 20:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Yuripetusko/53a8fb069d77e86beea7274f1dae7882 to your computer and use it in GitHub Desktop.
Save Yuripetusko/53a8fb069d77e86beea7274f1dae7882 to your computer and use it in GitHub Desktop.
If ws provider fails, recheck it's status up to 5 times, before reconnecting with a different ws endpoint
import { ApiPromise, WsProvider } from '@polkadot/api';
const WS_ENDPOINTS = [
'wss://kusama-rpc.polkadot.io',
'wss://kusama.api.onfinality.io/public-ws',
'wss://kusama-rpc.dwellir.com'
];
export const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => {
setTimeout(() => resolve(), ms);
});
};
const MAX_RETRIES = 5;
const WS_DISCONNECT_TIMEOUT_SECONDS = 20;
let wsProvider: WsProvider;
let polkadotApi: ApiPromise;
let healthCheckInProgress = false;
/**
*
* @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
*/
const providerHealthCheck = async (wsEndpoints: string[]) => {
const [primaryEndpoint, secondaryEndpoint, ...otherEndpoints] = wsEndpoints;
console.log(
`Performing ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds health check for WS Provider fro rpc ${primaryEndpoint}.`,
);
healthCheckInProgress = true;
await sleep(WS_DISCONNECT_TIMEOUT_SECONDS * 1000);
if (wsProvider.isConnected) {
console.log(`All good. Connected back to ${primaryEndpoint}`);
healthCheckInProgress = false;
return true;
} else {
console.log(
`rpc endpoint ${primaryEndpoint} still disconnected after ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds. Disconnecting from ${primaryEndpoint} and switching to a backup rpc endpoint ${secondaryEndpoint}`,
);
await wsProvider.disconnect();
healthCheckInProgress = false;
throw new Error(
`rpc endpoint ${primaryEndpoint} still disconnected after ${WS_DISCONNECT_TIMEOUT_SECONDS} seconds.`,
);
}
};
/**
*
* @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
*/
const getProvider = async (wsEndpoints: string[]) => {
const [primaryEndpoint, ...otherEndpoints] = wsEndpoints;
return await new Promise<WsProvider | undefined>((resolve, reject) => {
wsProvider = new WsProvider(primaryEndpoint);
wsProvider.on('disconnected', async () => {
console.log(`WS provider for rpc ${primaryEndpoint} disconnected!`);
if (!healthCheckInProgress) {
try {
await providerHealthCheck(wsEndpoints);
resolve(wsProvider);
} catch (error: any) {
reject(error);
}
}
});
wsProvider.on('connected', () => {
console.log(`WS provider for rpc ${primaryEndpoint} connected`);
resolve(wsProvider);
});
wsProvider.on('error', async () => {
console.log(`Error thrown for rpc ${primaryEndpoint}`);
if (!healthCheckInProgress) {
try {
await providerHealthCheck(wsEndpoints);
resolve(wsProvider);
} catch (error: any) {
reject(error);
}
}
});
});
};
/**
*
* @param wsEndpoints - array of rpc ws endpoints. In the order of their priority
* @param retry - retry count
*/
export const getApi = async (
wsEndpoints: string[] = WS_ENDPOINTS,
retry = 0,
): Promise<ApiPromise> => {
if (wsProvider && polkadotApi && polkadotApi.isConnected) return polkadotApi;
const [primaryEndpoint, secondaryEndpoint, ...otherEndpoints] = wsEndpoints;
try {
const provider = await getProvider(wsEndpoints);
polkadotApi = await ApiPromise.create({ provider });
await polkadotApi.isReady;
return polkadotApi;
} catch (error: any) {
if (retry < MAX_RETRIES) {
// If we have reached maximum number of retries on the primaryEndpoint, let's move it to the end of array and try the secondary endpoint
return await getApi([secondaryEndpoint, ...otherEndpoints, primaryEndpoint], retry + 1);
} else {
return polkadotApi;
}
}
};
// const api = await getPolkadotApi();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment