60 lines
1.4 KiB
JavaScript
60 lines
1.4 KiB
JavaScript
/**
|
|
* Comma-separated RPC URL pool with round-robin and read-call failover.
|
|
*/
|
|
|
|
export function parseRpcPool(...sources) {
|
|
const seen = new Set();
|
|
const urls = [];
|
|
for (const source of sources) {
|
|
if (!source || String(source).includes('${')) continue;
|
|
for (const part of String(source).split(',')) {
|
|
const url = part.trim();
|
|
if (!url || seen.has(url)) continue;
|
|
seen.add(url);
|
|
urls.push(url);
|
|
}
|
|
}
|
|
return urls;
|
|
}
|
|
|
|
export class RpcUrlPool {
|
|
constructor(urls, logger) {
|
|
this.urls = Array.isArray(urls) ? urls.filter(Boolean) : [];
|
|
this.logger = logger;
|
|
this._cursor = 0;
|
|
}
|
|
|
|
get size() {
|
|
return this.urls.length;
|
|
}
|
|
|
|
primaryUrl() {
|
|
if (this.urls.length === 0) return '';
|
|
return this.urls[0];
|
|
}
|
|
|
|
nextUrl() {
|
|
if (this.urls.length === 0) return '';
|
|
const url = this.urls[this._cursor % this.urls.length];
|
|
this._cursor += 1;
|
|
return url;
|
|
}
|
|
|
|
/** Run async read fn(url) until one succeeds or all fail. */
|
|
async withFailover(label, fn) {
|
|
if (this.urls.length === 0) {
|
|
throw new Error(`${label}: RPC pool is empty`);
|
|
}
|
|
let lastError;
|
|
for (const url of this.urls) {
|
|
try {
|
|
return await fn(url);
|
|
} catch (error) {
|
|
lastError = error;
|
|
this.logger?.warn?.(`${label} RPC failed (${url}): ${error?.message || error}`);
|
|
}
|
|
}
|
|
throw lastError || new Error(`${label}: all RPC pool URLs failed`);
|
|
}
|
|
}
|