- Add services/{http,chain138,explorer,proxmox,dbisCore} + hooks/{useLiveChain,useOnChainBalances}
- Add BackendStatusBar + LiveNetworkPanel components on DashboardPage
- Overlay on-chain META balance on account rows carrying a walletAddress
- Normalize EIP-55 checksum in chain138.getNativeBalance so hand-typed
sample custody addresses (e.g. 0x742d35Cc...bD38) don't silently drop
out of the balance map
- Default RPC: https://rpc.d-bis.org (user-preferred gateway)
- proxmox.ts stays mocked (CF-Access, needs BFF); dbisCore.ts stays
mocked (no public deployment yet)
Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
/**
|
|
* Thin fetch wrapper with timeout + JSON handling + typed errors.
|
|
* Keep this dependency-free so every service can share it.
|
|
*/
|
|
|
|
export class HttpError extends Error {
|
|
readonly status: number;
|
|
readonly statusText: string;
|
|
readonly url: string;
|
|
readonly body?: unknown;
|
|
constructor(status: number, statusText: string, url: string, body?: unknown) {
|
|
super(`HTTP ${status} ${statusText} (${url})`);
|
|
this.name = 'HttpError';
|
|
this.status = status;
|
|
this.statusText = statusText;
|
|
this.url = url;
|
|
this.body = body;
|
|
}
|
|
}
|
|
|
|
export interface HttpOptions extends Omit<RequestInit, 'body'> {
|
|
/** Request body — automatically JSON-stringified when an object. */
|
|
body?: unknown;
|
|
/** Abort the request after N ms. Default 10000. */
|
|
timeoutMs?: number;
|
|
}
|
|
|
|
export async function httpJson<T>(url: string, opts: HttpOptions = {}): Promise<T> {
|
|
const { body, timeoutMs = 10_000, headers, ...rest } = opts;
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
|
|
try {
|
|
const res = await fetch(url, {
|
|
...rest,
|
|
signal: controller.signal,
|
|
headers: {
|
|
Accept: 'application/json',
|
|
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
...headers,
|
|
},
|
|
body: body === undefined ? undefined : typeof body === 'string' ? body : JSON.stringify(body),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
let parsed: unknown;
|
|
try { parsed = await res.json(); } catch { parsed = await res.text().catch(() => undefined); }
|
|
throw new HttpError(res.status, res.statusText, url, parsed);
|
|
}
|
|
|
|
const ct = res.headers.get('content-type') ?? '';
|
|
if (ct.includes('application/json')) return (await res.json()) as T;
|
|
return (await res.text()) as unknown as T;
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|