diff --git a/src/components/ws/SyncMOTD.tsx b/src/components/ws/SyncMOTD.tsx index f03687b..fa72c84 100644 --- a/src/components/ws/SyncMOTD.tsx +++ b/src/components/ws/SyncMOTD.tsx @@ -8,7 +8,9 @@ import * as nodeActions from "../../store/actions/NodeActions"; import { AppDispatch } from "../../App"; -import { APIResponse, KristMOTD } from "../../krist/api/types"; + +import * as api from "../../krist/api/api"; +import { KristMOTD } from "../../krist/api/types"; import { recalculateWallets } from "../../krist/wallets/Wallet"; @@ -17,13 +19,7 @@ export async function updateMOTD(dispatch: AppDispatch, syncNode: string): Promise { debug("updating motd"); - - const res = await fetch(syncNode + "/motd"); - if (!res.ok || res.status !== 200) // TODO: handle API errors - throw new Error("error fetching motd"); - - const data: APIResponse = await res.json(); - if (!data?.ok) throw new Error("error fetching motd"); + const data = await api.get(syncNode, "motd"); debug("motd: %s", data.motd); dispatch(nodeActions.setCurrency(data.currency)); diff --git a/src/components/ws/SyncWork.tsx b/src/components/ws/SyncWork.tsx index edd9e45..914dd72 100644 --- a/src/components/ws/SyncWork.tsx +++ b/src/components/ws/SyncWork.tsx @@ -8,20 +8,16 @@ import * as nodeActions from "../../store/actions/NodeActions"; import { AppDispatch } from "../../App"; -import { APIResponse, KristWorkDetailed } from "../../krist/api/types"; + +import * as api from "../../krist/api/api"; +import { KristWorkDetailed } from "../../krist/api/types"; import Debug from "debug"; const debug = Debug("kristweb:sync-work"); export async function updateDetailedWork(dispatch: AppDispatch, syncNode: string): Promise { debug("updating detailed work"); - - const res = await fetch(syncNode + "/work/detailed"); - if (!res.ok || res.status !== 200) // TODO: handle API errors - throw new Error("error fetching detailed work"); - - const data: APIResponse = await res.json(); - if (!data?.ok) throw new Error("error fetching detailed work"); + const data = await api.get(syncNode, "work/detailed"); debug("work: %d", data.work); dispatch(nodeActions.setDetailedWork(data)); diff --git a/src/components/ws/WebsocketService.tsx b/src/components/ws/WebsocketService.tsx index d22b862..130c8ea 100644 --- a/src/components/ws/WebsocketService.tsx +++ b/src/components/ws/WebsocketService.tsx @@ -10,6 +10,7 @@ import * as wsActions from "../../store/actions/WebsocketActions"; import * as nodeActions from "../../store/actions/NodeActions"; +import * as api from "../../krist/api/api"; import { APIResponse, KristAddress, KristBlock, KristTransaction, WSConnectionState, WSIncomingMessage, WSSubscriptionLevel } from "../../krist/api/types"; import { findWalletByAddress, syncWallet, syncWalletUpdate } from "../../krist/wallets/Wallet"; import WebSocketAsPromised from "websocket-as-promised"; @@ -50,15 +51,12 @@ this.setConnectionState("disconnected"); // Get a websocket token - const res = await fetch(this.syncNode + "/ws/start", { method: "POST" }); - if (!res.ok || res.status !== 200) throw new Error("ws.errorToken"); - const data: APIResponse<{ url: string }> = await res.json(); - if (!data.ok || data.error) throw new Error("ws.errorToken"); + const { url } = await api.post<{ url: string }>(this.syncNode, "ws/start"); this.setConnectionState("connecting"); // Connect to the websocket server - this.ws = new WebSocketAsPromised(data.url, { + this.ws = new WebSocketAsPromised(url, { packMessage: data => JSON.stringify(data), unpackMessage: data => JSON.parse(data.toString()) }); diff --git a/src/krist/api/api.ts b/src/krist/api/api.ts new file mode 100644 index 0000000..800aa7b --- /dev/null +++ b/src/krist/api/api.ts @@ -0,0 +1,26 @@ +import { APIResponse } from "./types"; + +export class APIError extends Error { + constructor(message: string, public parameter?: string) { + super(message); + } +} + +export async function request(syncNode: string, method: string, endpoint: string, options?: RequestInit): Promise> { + // Let the fetch bubble its error upwards + const res = await fetch(syncNode + "/" + endpoint, { + method, + ...options + }); + + const data: APIResponse = await res.json(); + if (!data.ok || data.error) + throw new APIError(data.error || "unknown_error", data.parameter); + + return data; +} + +export const get = (syncNode: string, endpoint: string, options?: RequestInit): Promise> => + request(syncNode, "GET", endpoint, options); +export const post = (syncNode: string, endpoint: string, options?: RequestInit): Promise> => + request(syncNode, "POST", endpoint, options); diff --git a/src/krist/api/lookup.ts b/src/krist/api/lookup.ts index 8b9a434..aaea9b1 100644 --- a/src/krist/api/lookup.ts +++ b/src/krist/api/lookup.ts @@ -1,7 +1,8 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import { APIResponse, KristAddress, KristTransaction } from "./types"; +import { KristAddress, KristTransaction } from "./types"; +import * as api from "./api"; interface LookupAddressesResponse { found: number; @@ -16,16 +17,12 @@ if (!addresses || addresses.length === 0) return {}; try { - const res = await fetch( - syncNode - + "/lookup/addresses/" + const data = await api.get( + syncNode, + "lookup/addresses/" + encodeURIComponent(addresses.join(",")) + (fetchNames ? "?fetchNames" : "") ); - if (!res.ok || res.status !== 200) throw new Error(res.statusText); - - const data: APIResponse = await res.json(); - if (!data.ok || data.error) throw new Error(data.error); return data.addresses; } catch (err) { @@ -60,16 +57,10 @@ if (opts.orderBy) qs.append("orderBy", opts.orderBy); if (opts.order) qs.append("order", opts.order); - const res = await fetch( - syncNode - + "/lookup/transactions/" + return await api.get( + syncNode, + "lookup/transactions/" + encodeURIComponent(addresses.join(",")) + "?" + qs ); - if (!res.ok || res.status !== 200) throw new Error(res.statusText); - - const data: APIResponse = await res.json(); - if (!data.ok || data.error) throw new Error(data.error); - - return data; } diff --git a/src/krist/api/types.ts b/src/krist/api/types.ts index 8505efb..2e4428c 100644 --- a/src/krist/api/types.ts +++ b/src/krist/api/types.ts @@ -92,6 +92,7 @@ export type APIResponse> = T & { ok: boolean; error?: string; + parameter?: string; } export type WSConnectionState = "connected" | "disconnected" | "connecting"; diff --git a/src/pages/dashboard/BlockDifficultyCard.tsx b/src/pages/dashboard/BlockDifficultyCard.tsx index 61922e3..9c55f07 100644 --- a/src/pages/dashboard/BlockDifficultyCard.tsx +++ b/src/pages/dashboard/BlockDifficultyCard.tsx @@ -10,7 +10,7 @@ import { Line } from "react-chartjs-2"; -import { APIResponse } from "../../krist/api/types"; +import * as api from "../../krist/api/api"; import { throttle } from "lodash-es"; import { estimateHashRate } from "../../utils/currency"; @@ -124,12 +124,7 @@ async function _fetchWorkOverTime(): Promise { try { debug("fetching work over time"); - - const res = await fetch(syncNode + "/work/day"); - if (!res.ok || res.status !== 200) throw new Error(res.statusText); - - const data: APIResponse<{ work: number[] }> = await res.json(); - if (!data.ok || data.error) throw new Error(data.error); + const data = await api.get<{ work: number[] }>(syncNode, "work/day"); // Convert the array indices to Dates, based on the fact that the array // should contain one block per secondsPerBlock (typically 1440 elements,