Skip to content

Commit 483561c

Browse files
committed
Merge branch 'pr-168'
2 parents 6457f3f + 3cd9e6f commit 483561c

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,35 @@ and cleanup behavior consistent across pages.
5555
- **`/api/v1/events`**: Retrieves system event audit logs (`GET`).
5656
- **`/api/v1/webhooks`**: Creates (`POST`), lists (`GET`), and revokes (`DELETE` at `/api/v1/webhooks/:id`) webhook subscriptions.
5757

58+
### API Client Contract
59+
60+
`src/lib/apiClient.ts` centralizes frontend calls to the backend through
61+
`apiGet`, `apiPost`, `apiPatch`, and `apiDelete`, all layered on `apiFetch`.
62+
Paths are resolved relative to `NEXT_PUBLIC_STABLEROUTE_API_BASE`; requests send
63+
`Content-Type: application/json` by default, and JSON request bodies are
64+
serialized by the method helpers.
65+
66+
The backend error envelope is represented by `ApiError`:
67+
68+
```ts
69+
type ApiError = {
70+
error: string;
71+
message: string;
72+
requestId?: string;
73+
};
74+
```
75+
76+
Failed responses reject with an `Error` whose message prefers
77+
`ApiError.message`, whose `status` property carries the HTTP status, and whose
78+
parsed error fields are copied onto the thrown object. `204 No Content`
79+
resolves to `undefined`; non-empty successful responses must parse as JSON.
80+
81+
`registerAuthErrorHandler` stores one global auth callback for `401` and `403`
82+
responses. Registering a new callback replaces the previous one, and the
83+
returned unregister function removes it only if it is still active.
84+
`ApiAuthGuard` mounts this handler inside `ToastProvider` so auth failures show
85+
toasts while the original API call still rejects normally.
86+
5887
### Asset Codes
5988

6089
Stellar asset codes entered through the new-pair form are trimmed, validated as

src/lib/apiClient.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
const API_BASE =
22
process.env.NEXT_PUBLIC_STABLEROUTE_API_BASE ?? "http://localhost:3001";
33

4+
/**
5+
* Canonical JSON error envelope returned by the StableRoute backend.
6+
*
7+
* `requestId` is optional because network failures and some legacy responses
8+
* may not include the backend correlation id.
9+
*/
410
export type ApiError = {
511
error: string;
612
message: string;
@@ -10,14 +16,38 @@ export type ApiError = {
1016
type AuthErrorHandler = (status: 401 | 403) => void;
1117
let _authErrorHandler: AuthErrorHandler | null = null;
1218

13-
/** Called once by <ApiAuthGuard> when it mounts inside <ToastProvider>. */
19+
/**
20+
* Register the single global auth-error handler.
21+
*
22+
* The latest registration replaces any previous handler. `ApiAuthGuard` calls
23+
* this while mounted inside `ToastProvider`, then calls the returned unregister
24+
* function on unmount. The guard is notified for backend `401` and `403`
25+
* responses, but the original request still rejects normally.
26+
*
27+
* @param handler - Callback invoked with the auth failure status.
28+
* @returns A cleanup function that unregisters `handler` if it is still active.
29+
*/
1430
export function registerAuthErrorHandler(handler: AuthErrorHandler): () => void {
1531
_authErrorHandler = handler;
1632
return () => {
1733
if (_authErrorHandler === handler) _authErrorHandler = null;
1834
};
1935
}
2036

37+
/**
38+
* Fetch JSON from the StableRoute API.
39+
*
40+
* Requests are made relative to `NEXT_PUBLIC_STABLEROUTE_API_BASE` and include
41+
* `Content-Type: application/json` by default. `204 No Content` resolves to
42+
* `undefined`. Non-empty successful responses must be valid JSON. Failed
43+
* responses reject with an `Error` whose message comes from the backend
44+
* `ApiError.message` when present, with `status` and any parsed error fields
45+
* attached to the thrown object.
46+
*
47+
* @param path - Backend path beginning with `/`.
48+
* @param init - Optional fetch init merged with the default JSON header.
49+
* @returns The parsed response body typed as `T`.
50+
*/
2151
export async function apiFetch<T>(
2252
path: string,
2353
init: RequestInit = {}
@@ -48,10 +78,14 @@ export async function apiFetch<T>(
4878
return body as T;
4979
}
5080

81+
/** GET a JSON resource from the StableRoute API. */
5182
export const apiGet = <T>(path: string) => apiFetch<T>(path);
83+
/** POST a JSON body and parse the JSON response. */
5284
export const apiPost = <T>(path: string, body: unknown) =>
5385
apiFetch<T>(path, { method: "POST", body: JSON.stringify(body) });
86+
/** PATCH a JSON body and parse the JSON response. */
5487
export const apiPatch = <T>(path: string, body: unknown) =>
5588
apiFetch<T>(path, { method: "PATCH", body: JSON.stringify(body) });
89+
/** DELETE a resource, resolving to `undefined` for a 204 response. */
5690
export const apiDelete = (path: string) =>
5791
apiFetch<void>(path, { method: "DELETE" });

0 commit comments

Comments
 (0)