@bosonprotocol/x402-server 0.2.0 → 0.3.0-alpha-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/challenge/index.d.ts +2 -1
- package/dist/cjs/client-B2yejopi.d.ts +88 -0
- package/dist/cjs/{config-CBr9qMps.d.ts → config-hnCiZiXF.d.ts} +241 -22
- package/dist/cjs/facilitator/index.d.ts +1 -1
- package/dist/cjs/facilitator/index.js +120 -8
- package/dist/cjs/facilitator/index.js.map +1 -1
- package/dist/cjs/handlers/index.d.ts +8 -264
- package/dist/cjs/handlers/index.js +147 -18
- package/dist/cjs/handlers/index.js.map +1 -1
- package/dist/cjs/index-COmLTH-U.d.ts +292 -0
- package/dist/cjs/index.d.ts +91 -7
- package/dist/cjs/index.js +559 -73
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/chunk-OMJEQEOF.js +223 -0
- package/dist/esm/chunk-OMJEQEOF.js.map +1 -0
- package/dist/esm/{chunk-PU5Y7FZG.js → chunk-RAESVNQG.js} +15 -3
- package/dist/esm/chunk-RAESVNQG.js.map +1 -0
- package/dist/esm/{chunk-Y5HFLCAT.js → chunk-SHYOIKYU.js} +138 -21
- package/dist/esm/chunk-SHYOIKYU.js.map +1 -0
- package/dist/esm/facilitator/index.js +2 -2
- package/dist/esm/handlers/index.js +2 -2
- package/dist/esm/index.js +306 -53
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/cjs/client-tnrpqVMW.d.ts +0 -36
- package/dist/esm/chunk-EAKQ4EFZ.js +0 -124
- package/dist/esm/chunk-EAKQ4EFZ.js.map +0 -1
- package/dist/esm/chunk-PU5Y7FZG.js.map +0 -1
- package/dist/esm/chunk-Y5HFLCAT.js.map +0 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { noopLogger, FacilitatorHttpError } from './chunk-RAESVNQG.js';
|
|
2
|
+
|
|
3
|
+
// src/facilitator/client.ts
|
|
4
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
5
|
+
var DEFAULT_RETRY = { attempts: 3, backoffMs: 200 };
|
|
6
|
+
var IDEMPOTENCY_KEY_HEADER = "x-x402b-idempotency-key";
|
|
7
|
+
function createFacilitatorClient(opts) {
|
|
8
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
9
|
+
if (fetchImpl === void 0) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"createFacilitatorClient: no `fetch` implementation available. Pass `opts.fetch` explicitly."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const baseUrl = opts.url.replace(/\/+$/, "");
|
|
15
|
+
const baseHeaders = { "content-type": "application/json", ...opts.headers ?? {} };
|
|
16
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
17
|
+
const retry = opts.retry ?? DEFAULT_RETRY;
|
|
18
|
+
if (!Number.isInteger(retry.attempts) || retry.attempts < 1) {
|
|
19
|
+
throw new Error("createFacilitatorClient: retry.attempts must be an integer >= 1");
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(retry.backoffMs) || retry.backoffMs < 0) {
|
|
22
|
+
throw new Error("createFacilitatorClient: retry.backoffMs must be a finite number >= 0");
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
25
|
+
throw new Error("createFacilitatorClient: timeoutMs must be a finite number > 0");
|
|
26
|
+
}
|
|
27
|
+
const newIdempotencyKey = opts.idempotencyKey ?? (() => globalThis.crypto.randomUUID());
|
|
28
|
+
const setTimeoutImpl = opts.setTimeout ?? setTimeout;
|
|
29
|
+
const clearTimeoutImpl = opts.clearTimeout ?? clearTimeout;
|
|
30
|
+
const logger = opts.logger ?? noopLogger;
|
|
31
|
+
const postOnce = async (path, body, validate, extraHeaders) => {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
|
|
34
|
+
const headers = { ...baseHeaders, ...extraHeaders };
|
|
35
|
+
let res;
|
|
36
|
+
try {
|
|
37
|
+
res = await fetchImpl(`${baseUrl}${path}`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers,
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
signal: controller.signal
|
|
42
|
+
});
|
|
43
|
+
} catch (cause) {
|
|
44
|
+
const aborted = controller.signal.aborted;
|
|
45
|
+
logger.warn(aborted ? "facilitator request timed out" : "facilitator network error", {
|
|
46
|
+
path,
|
|
47
|
+
timeoutMs: aborted ? timeoutMs : void 0,
|
|
48
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
49
|
+
});
|
|
50
|
+
throw new FacilitatorHttpError(
|
|
51
|
+
aborted ? `facilitator request timed out after ${timeoutMs}ms (${path})` : `facilitator network error (${path})`,
|
|
52
|
+
{
|
|
53
|
+
code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
|
|
54
|
+
cause
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
} finally {
|
|
58
|
+
clearTimeoutImpl(timer);
|
|
59
|
+
}
|
|
60
|
+
let text;
|
|
61
|
+
try {
|
|
62
|
+
text = await res.text();
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
throw new FacilitatorHttpError(`facilitator response body could not be read (${path})`, {
|
|
65
|
+
code: "BAD_RESPONSE_BODY",
|
|
66
|
+
status: res.status,
|
|
67
|
+
cause
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = text.length === 0 ? null : JSON.parse(text);
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
throw new FacilitatorHttpError(`facilitator returned non-JSON body (${path})`, {
|
|
75
|
+
code: "BAD_RESPONSE_BODY",
|
|
76
|
+
status: res.status,
|
|
77
|
+
cause
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
if (res.status === 400 && isFailureBranch(parsed) && validate(parsed)) {
|
|
82
|
+
return parsed;
|
|
83
|
+
}
|
|
84
|
+
const facilitatorCode = extractFacilitatorCode(parsed);
|
|
85
|
+
logger.warn("facilitator HTTP non-2xx", {
|
|
86
|
+
path,
|
|
87
|
+
status: res.status,
|
|
88
|
+
facilitatorCode,
|
|
89
|
+
reason: reasonString(parsed)
|
|
90
|
+
});
|
|
91
|
+
throw new FacilitatorHttpError(
|
|
92
|
+
`facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,
|
|
93
|
+
{
|
|
94
|
+
code: "BAD_HTTP_STATUS",
|
|
95
|
+
status: res.status,
|
|
96
|
+
...facilitatorCode !== void 0 ? { facilitatorCode } : {}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (!validate(parsed)) {
|
|
101
|
+
throw new FacilitatorHttpError(`facilitator returned unexpected body shape (${path})`, {
|
|
102
|
+
code: "BAD_RESPONSE_BODY",
|
|
103
|
+
status: res.status
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return parsed;
|
|
107
|
+
};
|
|
108
|
+
const post = async (path, body, validate, extraHeaders = {}) => {
|
|
109
|
+
let lastError;
|
|
110
|
+
for (let attempt = 0; attempt < retry.attempts; attempt += 1) {
|
|
111
|
+
if (attempt > 0) {
|
|
112
|
+
await sleep(retry.backoffMs * attempt, setTimeoutImpl);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
return await postOnce(path, body, validate, extraHeaders);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
if (e instanceof FacilitatorHttpError && isRetryable(e)) {
|
|
118
|
+
lastError = e;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
throw e;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw lastError;
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
verify: (input) => post("/verify", input, isVerifyResult),
|
|
128
|
+
settle: (input) => post("/settle", input, isSettleResult, {
|
|
129
|
+
[IDEMPOTENCY_KEY_HEADER]: newIdempotencyKey()
|
|
130
|
+
}),
|
|
131
|
+
performAction: (input) => post(
|
|
132
|
+
`/perform-action?action=${encodeURIComponent(input.action)}`,
|
|
133
|
+
input,
|
|
134
|
+
isPerformActionResult
|
|
135
|
+
),
|
|
136
|
+
async healthCheck() {
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);
|
|
139
|
+
let res;
|
|
140
|
+
try {
|
|
141
|
+
res = await fetchImpl(`${baseUrl}/healthz`, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
headers: { ...opts.headers ?? {} },
|
|
144
|
+
signal: controller.signal
|
|
145
|
+
});
|
|
146
|
+
} catch (cause) {
|
|
147
|
+
const aborted = controller.signal.aborted;
|
|
148
|
+
throw new FacilitatorHttpError(
|
|
149
|
+
aborted ? `facilitator request timed out after ${timeoutMs}ms (/healthz)` : "facilitator network error (/healthz)",
|
|
150
|
+
{
|
|
151
|
+
code: aborted ? "TIMEOUT" : "NETWORK_ERROR",
|
|
152
|
+
cause
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
} finally {
|
|
156
|
+
clearTimeoutImpl(timer);
|
|
157
|
+
}
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
throw new FacilitatorHttpError(`facilitator HTTP ${res.status} (/healthz)`, {
|
|
160
|
+
code: "BAD_HTTP_STATUS",
|
|
161
|
+
status: res.status
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function isRetryable(e) {
|
|
168
|
+
if (e.code === "NETWORK_ERROR" || e.code === "TIMEOUT") return true;
|
|
169
|
+
if (e.code === "BAD_HTTP_STATUS" && e.status !== void 0 && e.status >= 500) return true;
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
function sleep(ms, setTimeoutImpl) {
|
|
173
|
+
if (ms <= 0) return Promise.resolve();
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
setTimeoutImpl(resolve, ms);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function isObject(v) {
|
|
179
|
+
return v !== null && typeof v === "object";
|
|
180
|
+
}
|
|
181
|
+
function isFailureBranch(v) {
|
|
182
|
+
return isObject(v) && v.ok === false && typeof v.code === "string" && typeof v.reason === "string";
|
|
183
|
+
}
|
|
184
|
+
function isVerifyResult(parsed) {
|
|
185
|
+
if (!isObject(parsed)) return false;
|
|
186
|
+
if (parsed.ok === true) return true;
|
|
187
|
+
return isFailureBranch(parsed);
|
|
188
|
+
}
|
|
189
|
+
function isSettleResult(parsed) {
|
|
190
|
+
if (!isObject(parsed)) return false;
|
|
191
|
+
if (parsed.ok === true) {
|
|
192
|
+
return typeof parsed.exchangeId === "string" && typeof parsed.txHash === "string";
|
|
193
|
+
}
|
|
194
|
+
return isFailureBranch(parsed);
|
|
195
|
+
}
|
|
196
|
+
function isPerformActionResult(parsed) {
|
|
197
|
+
if (!isObject(parsed)) return false;
|
|
198
|
+
if (parsed.ok === true) {
|
|
199
|
+
if (typeof parsed.txHash !== "string") return false;
|
|
200
|
+
if (parsed.newExchangeState !== void 0 && typeof parsed.newExchangeState !== "string") {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (parsed.newDisputeState !== void 0 && typeof parsed.newDisputeState !== "string") {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
return isFailureBranch(parsed);
|
|
209
|
+
}
|
|
210
|
+
function extractFacilitatorCode(body) {
|
|
211
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
212
|
+
const code = body.code;
|
|
213
|
+
return typeof code === "string" ? code : void 0;
|
|
214
|
+
}
|
|
215
|
+
function reasonString(body) {
|
|
216
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
217
|
+
const reason = body.reason;
|
|
218
|
+
return typeof reason === "string" ? reason : void 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export { IDEMPOTENCY_KEY_HEADER, createFacilitatorClient };
|
|
222
|
+
//# sourceMappingURL=chunk-OMJEQEOF.js.map
|
|
223
|
+
//# sourceMappingURL=chunk-OMJEQEOF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/facilitator/client.ts"],"names":[],"mappings":"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","file":"chunk-OMJEQEOF.js","sourcesContent":["// HTTP client for the Boson facilitator service. The facilitator runs\n// as a remote process implementing the three endpoints in\n// docs/boson-impl-07-facilitator.md (`/verify`, `/settle`,\n// `/perform-action?action=<action>`); this module wraps `fetch` with the typed\n// request/response shapes exported by `@bosonprotocol/x402-facilitator`.\n//\n// Domain results (`{ ok: true, ... }` or `{ ok: false, code, reason }`)\n// are returned verbatim — both shapes are answers the caller branches\n// on. The facilitator-express adapter emits domain failures over HTTP\n// 400 (so curl users still see a 4xx), but the wire body is still a\n// well-formed result; we recognise that shape on HTTP 400 responses and\n// hand it back rather than throwing. HTTP-transport failures (network\n// down, non-JSON body, schema-mismatched body) throw\n// `FacilitatorHttpError` so the composition layer can distinguish\n// \"facilitator said no\" from \"we couldn't reach the facilitator\".\n//\n// Transport hardening: every request enforces a per-attempt timeout via\n// `AbortController` and retries `NETWORK_ERROR` / `TIMEOUT` / 5xx with\n// linear backoff. `/settle` calls additionally carry a stable\n// `x-x402b-idempotency-key` header so a facilitator-side dedup table\n// (separate package) can recognise a retried request as the same\n// underlying intent.\n\nimport type {\n FacilitatorErrorCode,\n FacilitatorPerformActionInput,\n FacilitatorPerformActionResult,\n FacilitatorSettleInput,\n FacilitatorSettleResult,\n FacilitatorVerifyInput,\n FacilitatorVerifyResult,\n} from \"@bosonprotocol/x402-facilitator\";\n\nimport { FacilitatorHttpError } from \"./errors.js\";\nimport { noopLogger, type Logger } from \"../logger.js\";\n\nexport type FetchLike = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text: () => Promise<string>;\n json?: () => Promise<unknown>;\n}>;\n\nexport interface FacilitatorRetryOptions {\n /** Total attempt budget (initial try + retries). Default 3. */\n attempts: number;\n /** Linear-backoff multiplier in ms: sleep `backoffMs * attemptIndex` between attempts. Default 200. */\n backoffMs: number;\n}\n\nexport interface CreateFacilitatorClientOptions {\n /** Base URL of the facilitator service (no trailing slash needed). */\n url: string;\n /** Optional `fetch` override — defaults to the global `fetch`. Useful for tests + non-`fetch` runtimes. */\n fetch?: FetchLike;\n /** Optional headers attached to every request — e.g. an `Authorization` for hosted facilitators. */\n headers?: Record<string, string>;\n /**\n * Per-attempt timeout in ms. Default 30_000. The `AbortController`\n * aborts in-flight fetches when this elapses; the error surfaces as\n * `FacilitatorHttpError` with code `TIMEOUT` and is retried under\n * the same policy as `NETWORK_ERROR`.\n */\n timeoutMs?: number;\n /**\n * Retry policy applied to `NETWORK_ERROR`, `TIMEOUT`, and HTTP 5xx\n * responses. Defaults to `{ attempts: 3, backoffMs: 200 }` — three\n * attempts total at 0/200/400 ms. Set `{ attempts: 1, backoffMs: 0 }`\n * to disable.\n */\n retry?: FacilitatorRetryOptions;\n /**\n * Factory for the `x-x402b-idempotency-key` header attached to\n * `/settle` calls. Called once per logical request — *not* once per\n * retry attempt — so the facilitator's dedup table can recognise\n * retries of the same intent. Defaults to `crypto.randomUUID`.\n */\n idempotencyKey?: () => string;\n /** Override for `setTimeout` — tests use this to skip real backoff sleeps. */\n setTimeout?: typeof setTimeout;\n /** Override for `clearTimeout` — paired with `setTimeout`. */\n clearTimeout?: typeof clearTimeout;\n /** Optional structured logger. Defaults to no-op. Receives `warn` on errored requests; `debug` on each call. */\n logger?: Logger;\n}\n\nexport interface FacilitatorClient {\n verify(input: FacilitatorVerifyInput): Promise<FacilitatorVerifyResult>;\n settle(input: FacilitatorSettleInput): Promise<FacilitatorSettleResult>;\n performAction(input: FacilitatorPerformActionInput): Promise<FacilitatorPerformActionResult>;\n /**\n * GET `/healthz` liveness probe. Resolves on any 2xx; throws on\n * network error or non-2xx. Designed for the server SDK's\n * `healthCheck()` helper — hosts mount that one behind whatever\n * `/healthz` route their framework uses.\n */\n healthCheck(): Promise<void>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_RETRY: FacilitatorRetryOptions = { attempts: 3, backoffMs: 200 };\n\n/** Idempotency-key header name. Stable so a facilitator-side dedup table can recognise it. */\nexport const IDEMPOTENCY_KEY_HEADER = \"x-x402b-idempotency-key\";\n\n/**\n * Construct a typed HTTP client for the facilitator. The returned\n * methods stringify the input as JSON and POST to the matching path\n * on the configured URL. Bodies matching the well-formed result shape\n * (whether HTTP 2xx success or HTTP 400 domain rejection) are returned\n * verbatim; transport failures (network, non-JSON body,\n * schema-mismatched body) raise `FacilitatorHttpError`.\n */\nexport function createFacilitatorClient(opts: CreateFacilitatorClientOptions): FacilitatorClient {\n const fetchImpl = opts.fetch ?? (globalThis.fetch as FetchLike | undefined);\n if (fetchImpl === undefined) {\n throw new Error(\n \"createFacilitatorClient: no `fetch` implementation available. Pass `opts.fetch` explicitly.\",\n );\n }\n const baseUrl = opts.url.replace(/\\/+$/, \"\");\n const baseHeaders = { \"content-type\": \"application/json\", ...(opts.headers ?? {}) };\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const retry = opts.retry ?? DEFAULT_RETRY;\n // Defensive validation: `x402bServerConfigSchema` enforces these\n // bounds when the client is built via `createX402bServer`, but\n // `createFacilitatorClient` is also exported for direct use — a bad\n // `attempts` would silently fall through the retry loop and throw\n // `undefined` instead of a `FacilitatorHttpError`.\n if (!Number.isInteger(retry.attempts) || retry.attempts < 1) {\n throw new Error(\"createFacilitatorClient: retry.attempts must be an integer >= 1\");\n }\n if (!Number.isFinite(retry.backoffMs) || retry.backoffMs < 0) {\n throw new Error(\"createFacilitatorClient: retry.backoffMs must be a finite number >= 0\");\n }\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(\"createFacilitatorClient: timeoutMs must be a finite number > 0\");\n }\n const newIdempotencyKey = opts.idempotencyKey ?? (() => globalThis.crypto.randomUUID());\n const setTimeoutImpl = opts.setTimeout ?? setTimeout;\n const clearTimeoutImpl = opts.clearTimeout ?? clearTimeout;\n const logger: Logger = opts.logger ?? noopLogger;\n\n const postOnce = async <Req, Res>(\n path: string,\n body: Req,\n validate: (parsed: unknown) => parsed is Res,\n extraHeaders: Record<string, string>,\n ): Promise<Res> => {\n const controller = new AbortController();\n const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);\n const headers = { ...baseHeaders, ...extraHeaders };\n\n let res: Awaited<ReturnType<FetchLike>>;\n try {\n res = await fetchImpl(`${baseUrl}${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n logger.warn(aborted ? \"facilitator request timed out\" : \"facilitator network error\", {\n path,\n timeoutMs: aborted ? timeoutMs : undefined,\n error: cause instanceof Error ? cause.message : String(cause),\n });\n throw new FacilitatorHttpError(\n aborted\n ? `facilitator request timed out after ${timeoutMs}ms (${path})`\n : `facilitator network error (${path})`,\n {\n code: aborted ? \"TIMEOUT\" : \"NETWORK_ERROR\",\n cause,\n },\n );\n } finally {\n clearTimeoutImpl(timer);\n }\n\n let text: string;\n try {\n text = await res.text();\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator response body could not be read (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = text.length === 0 ? null : JSON.parse(text);\n } catch (cause) {\n throw new FacilitatorHttpError(`facilitator returned non-JSON body (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n cause,\n });\n }\n\n if (!res.ok) {\n // The facilitator-express adapter returns domain failures as\n // HTTP 400 with the `{ok:false, code, reason}` body. Detect that\n // shape and surface it as the typed result — every endpoint's\n // `Res` union includes the same `{ok:false, code, reason}`\n // variant, so the cast through `validate` (which also accepts\n // that shape) preserves type-safety. Any other non-2xx status is\n // a genuine transport failure even if its body happens to look\n // like a facilitator result.\n if (res.status === 400 && isFailureBranch(parsed) && validate(parsed)) {\n return parsed;\n }\n const facilitatorCode = extractFacilitatorCode(parsed);\n logger.warn(\"facilitator HTTP non-2xx\", {\n path,\n status: res.status,\n facilitatorCode,\n reason: reasonString(parsed),\n });\n throw new FacilitatorHttpError(\n `facilitator HTTP ${res.status} (${path}): ${reasonString(parsed) ?? text}`,\n {\n code: \"BAD_HTTP_STATUS\",\n status: res.status,\n ...(facilitatorCode !== undefined ? { facilitatorCode } : {}),\n },\n );\n }\n\n if (!validate(parsed)) {\n throw new FacilitatorHttpError(`facilitator returned unexpected body shape (${path})`, {\n code: \"BAD_RESPONSE_BODY\",\n status: res.status,\n });\n }\n return parsed;\n };\n\n const post = async <Req, Res>(\n path: string,\n body: Req,\n validate: (parsed: unknown) => parsed is Res,\n extraHeaders: Record<string, string> = {},\n ): Promise<Res> => {\n let lastError: FacilitatorHttpError | undefined;\n for (let attempt = 0; attempt < retry.attempts; attempt += 1) {\n if (attempt > 0) {\n await sleep(retry.backoffMs * attempt, setTimeoutImpl);\n }\n try {\n return await postOnce(path, body, validate, extraHeaders);\n } catch (e) {\n if (e instanceof FacilitatorHttpError && isRetryable(e)) {\n lastError = e;\n continue;\n }\n throw e;\n }\n }\n // Non-null assertion: the loop above sets `lastError` on every retryable\n // failure and only falls through after the final attempt also failed.\n throw lastError as FacilitatorHttpError;\n };\n\n return {\n verify: (input) =>\n post<FacilitatorVerifyInput, FacilitatorVerifyResult>(\"/verify\", input, isVerifyResult),\n settle: (input) =>\n post<FacilitatorSettleInput, FacilitatorSettleResult>(\"/settle\", input, isSettleResult, {\n [IDEMPOTENCY_KEY_HEADER]: newIdempotencyKey(),\n }),\n performAction: (input) =>\n post<FacilitatorPerformActionInput, FacilitatorPerformActionResult>(\n `/perform-action?action=${encodeURIComponent(input.action)}`,\n input,\n isPerformActionResult,\n ),\n async healthCheck() {\n // GET /healthz — body is ignored. Any 2xx is healthy; anything\n // else (network error, timeout, non-2xx) raises FacilitatorHttpError\n // so the `createHealthCheck` helper maps to \"down\". The abort\n // timer mirrors `postOnce` so a stuck connection can't hang the\n // host's /healthz route indefinitely. `opts.headers` (e.g.\n // `Authorization` for hosted facilitators) must ride along here\n // too — otherwise normal POSTs would succeed while /healthz\n // would always 401 → \"down\".\n const controller = new AbortController();\n const timer = setTimeoutImpl(() => controller.abort(), timeoutMs);\n let res: Awaited<ReturnType<FetchLike>>;\n try {\n res = await fetchImpl(`${baseUrl}/healthz`, {\n method: \"GET\",\n headers: { ...(opts.headers ?? {}) },\n signal: controller.signal,\n });\n } catch (cause) {\n const aborted = controller.signal.aborted;\n throw new FacilitatorHttpError(\n aborted\n ? `facilitator request timed out after ${timeoutMs}ms (/healthz)`\n : \"facilitator network error (/healthz)\",\n {\n code: aborted ? \"TIMEOUT\" : \"NETWORK_ERROR\",\n cause,\n },\n );\n } finally {\n clearTimeoutImpl(timer);\n }\n if (!res.ok) {\n throw new FacilitatorHttpError(`facilitator HTTP ${res.status} (/healthz)`, {\n code: \"BAD_HTTP_STATUS\",\n status: res.status,\n });\n }\n },\n };\n}\n\nfunction isRetryable(e: FacilitatorHttpError): boolean {\n if (e.code === \"NETWORK_ERROR\" || e.code === \"TIMEOUT\") return true;\n if (e.code === \"BAD_HTTP_STATUS\" && e.status !== undefined && e.status >= 500) return true;\n return false;\n}\n\nfunction sleep(ms: number, setTimeoutImpl: typeof setTimeout): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve) => {\n setTimeoutImpl(resolve, ms);\n });\n}\n\n// --- Response-shape type guards ----------------------------------------\n//\n// A buggy or malicious facilitator could return any 2xx JSON; without a\n// runtime check, `parsed as Res` would silently slip an unexpected shape\n// past the type system and the convenience handlers (PR 4) would\n// dereference fields that aren't there. These guards mirror the\n// discriminated unions in `@bosonprotocol/x402-facilitator`'s types and\n// fail any response that doesn't fit — surfaced as\n// `BAD_RESPONSE_BODY` so the caller treats it as a transport-layer\n// failure rather than a domain answer.\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\";\n}\n\n/**\n * Failure branch is identical across all three endpoints. Accepts\n * `unknown` so the non-2xx path can pre-check the parsed body without\n * narrowing first.\n */\nfunction isFailureBranch(v: unknown): v is { ok: false; code: string; reason: string } {\n return (\n isObject(v) && v.ok === false && typeof v.code === \"string\" && typeof v.reason === \"string\"\n );\n}\n\nfunction isVerifyResult(parsed: unknown): parsed is FacilitatorVerifyResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) return true; // `{ ok: true }` carries no further fields\n return isFailureBranch(parsed);\n}\n\nfunction isSettleResult(parsed: unknown): parsed is FacilitatorSettleResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n return typeof parsed.exchangeId === \"string\" && typeof parsed.txHash === \"string\";\n }\n return isFailureBranch(parsed);\n}\n\nfunction isPerformActionResult(parsed: unknown): parsed is FacilitatorPerformActionResult {\n if (!isObject(parsed)) return false;\n if (parsed.ok === true) {\n if (typeof parsed.txHash !== \"string\") return false;\n // Entity-keyed actions (e.g. `boson-withdrawFunds`) return just\n // `{ ok: true, txHash }` — no exchange state transition. Accept\n // that shape; only require `newExchangeState` when present.\n if (parsed.newExchangeState !== undefined && typeof parsed.newExchangeState !== \"string\") {\n return false;\n }\n if (parsed.newDisputeState !== undefined && typeof parsed.newDisputeState !== \"string\") {\n return false;\n }\n return true;\n }\n return isFailureBranch(parsed);\n}\n\nfunction extractFacilitatorCode(body: unknown): FacilitatorErrorCode | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const code = (body as { code?: unknown }).code;\n return typeof code === \"string\" ? (code as FacilitatorErrorCode) : undefined;\n}\n\nfunction reasonString(body: unknown): string | undefined {\n if (body === null || typeof body !== \"object\") return undefined;\n const reason = (body as { reason?: unknown }).reason;\n return typeof reason === \"string\" ? reason : undefined;\n}\n"]}
|
|
@@ -9,6 +9,18 @@ var FacilitatorHttpError = class extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
// src/logger.ts
|
|
13
|
+
var noopLogger = {
|
|
14
|
+
debug: () => {
|
|
15
|
+
},
|
|
16
|
+
info: () => {
|
|
17
|
+
},
|
|
18
|
+
warn: () => {
|
|
19
|
+
},
|
|
20
|
+
error: () => {
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { FacilitatorHttpError, noopLogger };
|
|
25
|
+
//# sourceMappingURL=chunk-RAESVNQG.js.map
|
|
26
|
+
//# sourceMappingURL=chunk-RAESVNQG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/facilitator/errors.ts","../../src/logger.ts"],"names":[],"mappings":";AAqBO,IAAM,oBAAA,GAAN,cAAmC,KAAA,CAAM;AAAA,EAM9C,WAAA,CACE,SACA,IAAA,EAMA;AACA,IAAA,KAAA,CAAM,OAAA,EAAS,KAAK,KAAA,KAAU,MAAA,GAAY,EAAE,KAAA,EAAO,IAAA,CAAK,KAAA,EAAM,GAAI,MAAS,CAAA;AAC3E,IAAA,IAAA,CAAK,IAAA,GAAO,sBAAA;AACZ,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,MAAA,EAAW,IAAA,CAAK,SAAS,IAAA,CAAK,MAAA;AAClD,IAAA,IAAI,IAAA,CAAK,eAAA,KAAoB,MAAA,EAAW,IAAA,CAAK,kBAAkB,IAAA,CAAK,eAAA;AAAA,EACtE;AACF;;;AC3BO,IAAM,UAAA,GAAqB;AAAA,EAChC,OAAO,MAAM;AAAA,EAAC,CAAA;AAAA,EACd,MAAM,MAAM;AAAA,EAAC,CAAA;AAAA,EACb,MAAM,MAAM;AAAA,EAAC,CAAA;AAAA,EACb,OAAO,MAAM;AAAA,EAAC;AAChB","file":"chunk-RAESVNQG.js","sourcesContent":["// Typed error class for HTTP-level facilitator failures. Distinct from\n// the `{ ok: false, code }` shape the facilitator itself returns —\n// those are domain errors and surface in the typed result. This class\n// is thrown only when the HTTP transport fails (network error,\n// non-2xx response with an unparseable body, etc.).\n\nimport type { FacilitatorErrorCode } from \"@bosonprotocol/x402-facilitator\";\n\n/**\n * Stable, x402-server-side codes for HTTP-transport failures.\n * Distinct from the facilitator-side `FacilitatorErrorCode` enum so\n * callers can branch on \"the facilitator answered with X\" vs \"we\n * never reached the facilitator\".\n */\nexport type FacilitatorHttpErrorCode =\n | \"NETWORK_ERROR\"\n | \"BAD_HTTP_STATUS\"\n | \"BAD_RESPONSE_BODY\"\n | \"TIMEOUT\";\n\n/** Network- or transport-layer failure when calling the facilitator. */\nexport class FacilitatorHttpError extends Error {\n readonly code: FacilitatorHttpErrorCode;\n readonly status?: number;\n /** Original facilitator-side error code, if the body parsed cleanly. */\n readonly facilitatorCode?: FacilitatorErrorCode;\n\n constructor(\n message: string,\n init: {\n code: FacilitatorHttpErrorCode;\n status?: number;\n facilitatorCode?: FacilitatorErrorCode;\n cause?: unknown;\n },\n ) {\n super(message, init.cause !== undefined ? { cause: init.cause } : undefined);\n this.name = \"FacilitatorHttpError\";\n this.code = init.code;\n if (init.status !== undefined) this.status = init.status;\n if (init.facilitatorCode !== undefined) this.facilitatorCode = init.facilitatorCode;\n }\n}\n","// Minimal four-method logger interface threaded through the server.\n// Matches the de-facto shape every observability library in the JS\n// ecosystem (pino, winston, console, bunyan, …) so hosts adapt their\n// existing logger in one line. The default is `noopLogger`, which\n// discards every event — no overhead, no surprise output, opt-in\n// observability.\n\nexport interface Logger {\n debug(msg: string, meta?: Record<string, unknown>): void;\n info(msg: string, meta?: Record<string, unknown>): void;\n warn(msg: string, meta?: Record<string, unknown>): void;\n error(msg: string, meta?: Record<string, unknown>): void;\n}\n\n/** No-op logger — the default when the host doesn't supply one. */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FacilitatorHttpError } from './chunk-
|
|
1
|
+
import { noopLogger, FacilitatorHttpError } from './chunk-RAESVNQG.js';
|
|
2
2
|
import { verifyExchange } from './chunk-4NQ3VKC3.js';
|
|
3
3
|
import { decodeXPaymentHeader, validatePaymentPayload } from './chunk-CGVXQX5V.js';
|
|
4
4
|
import { ExchangeState, deriveNextActions } from '@bosonprotocol/x402-actions';
|
|
@@ -58,6 +58,21 @@ function emitNextActions(input, registry, facilitatorUrl) {
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/handlers/fulfillment-result.ts
|
|
62
|
+
function serializeFulfillmentResult(result) {
|
|
63
|
+
if (result.kind === "inline") {
|
|
64
|
+
return {
|
|
65
|
+
kind: "inline",
|
|
66
|
+
body: Buffer.from(result.body).toString("base64"),
|
|
67
|
+
contentType: result.contentType
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
kind: "async",
|
|
72
|
+
...result.pointer !== void 0 ? { pointer: result.pointer } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
// src/handlers/types.ts
|
|
62
77
|
function handlerOk(body) {
|
|
63
78
|
return { ok: true, status: 200, body };
|
|
@@ -85,6 +100,7 @@ async function handleCommitAndRedeem(input, ctx) {
|
|
|
85
100
|
});
|
|
86
101
|
}
|
|
87
102
|
async function handleCommitImpl(input, ctx, expected) {
|
|
103
|
+
const logger = ctx.logger ?? noopLogger;
|
|
88
104
|
const decoded = decodeXPaymentHeader(input.paymentHeader);
|
|
89
105
|
if (!decoded.ok) {
|
|
90
106
|
const status = decoded.code === "MISSING_HEADER" ? 402 : 400;
|
|
@@ -173,28 +189,38 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
173
189
|
);
|
|
174
190
|
}
|
|
175
191
|
if (expected.expectedState === ExchangeState.COMMITTED) {
|
|
176
|
-
ctx.exchangeFulfillmentOptionStore.set(
|
|
192
|
+
await ctx.exchangeFulfillmentOptionStore.set(
|
|
177
193
|
settleResult.exchangeId,
|
|
178
194
|
input.requirements.fulfillment?.options.map((option) => option.id) ?? []
|
|
179
195
|
);
|
|
180
196
|
}
|
|
181
197
|
const warnings = [];
|
|
198
|
+
let delivery;
|
|
182
199
|
if (expected.expectedState === ExchangeState.REDEEMED && decoded.payload.fulfillment !== void 0 && decoded.payload.fulfillment.data !== void 0) {
|
|
183
200
|
const pending = {
|
|
184
201
|
exchangeId: settleResult.exchangeId,
|
|
185
202
|
option: decoded.payload.fulfillment.option,
|
|
186
203
|
data: decoded.payload.fulfillment.data,
|
|
187
204
|
redeemer: decoded.payload.payload.buyer,
|
|
188
|
-
recordedAt: Date.now()
|
|
205
|
+
recordedAt: Date.now(),
|
|
206
|
+
phase: "commit"
|
|
189
207
|
};
|
|
190
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
|
|
208
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, pending);
|
|
209
|
+
logger.debug("x402-server: fulfillment recovery entry recorded (Flow B)", {
|
|
210
|
+
exchangeId: settleResult.exchangeId,
|
|
211
|
+
option: decoded.payload.fulfillment.option
|
|
212
|
+
});
|
|
191
213
|
const channel = channelById.get(decoded.payload.fulfillment.option);
|
|
192
214
|
if (channel === void 0) {
|
|
193
215
|
const reason = "no channel adapter is registered";
|
|
194
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
216
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
195
217
|
...pending,
|
|
196
218
|
error: reason
|
|
197
219
|
});
|
|
220
|
+
logger.error("x402-server: Flow B channel adapter missing post-settle", {
|
|
221
|
+
exchangeId: settleResult.exchangeId,
|
|
222
|
+
option: decoded.payload.fulfillment.option
|
|
223
|
+
});
|
|
198
224
|
warnings.push({
|
|
199
225
|
code: "FULFILLMENT_COMMIT_DEFERRED",
|
|
200
226
|
reason: "atomic redeem succeeded on-chain, but no channel adapter is registered",
|
|
@@ -207,13 +233,54 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
207
233
|
} else {
|
|
208
234
|
try {
|
|
209
235
|
await channel.onCommit(settleResult.exchangeId, decoded.payload.fulfillment.data);
|
|
210
|
-
|
|
236
|
+
if (channel.onFulfill !== void 0) {
|
|
237
|
+
const deliveryPending = {
|
|
238
|
+
...pending,
|
|
239
|
+
phase: "delivery",
|
|
240
|
+
recordedAt: Date.now()
|
|
241
|
+
};
|
|
242
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, deliveryPending);
|
|
243
|
+
try {
|
|
244
|
+
delivery = serializeFulfillmentResult(await channel.onFulfill(settleResult.exchangeId));
|
|
245
|
+
await ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
|
|
246
|
+
logger.debug("x402-server: Flow B channel onCommit succeeded", {
|
|
247
|
+
exchangeId: settleResult.exchangeId,
|
|
248
|
+
option: decoded.payload.fulfillment.option
|
|
249
|
+
});
|
|
250
|
+
} catch (e) {
|
|
251
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
252
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
253
|
+
...deliveryPending,
|
|
254
|
+
error: reason
|
|
255
|
+
});
|
|
256
|
+
warnings.push({
|
|
257
|
+
code: "FULFILLMENT_DELIVERY_DEFERRED",
|
|
258
|
+
reason: "atomic redeem succeeded on-chain, but the channel's delivery dispatch failed",
|
|
259
|
+
details: {
|
|
260
|
+
exchangeId: settleResult.exchangeId,
|
|
261
|
+
option: decoded.payload.fulfillment.option,
|
|
262
|
+
error: reason
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
await ctx.fulfillmentRecoveryStore.delete(settleResult.exchangeId);
|
|
268
|
+
logger.debug("x402-server: Flow B channel onCommit succeeded", {
|
|
269
|
+
exchangeId: settleResult.exchangeId,
|
|
270
|
+
option: decoded.payload.fulfillment.option
|
|
271
|
+
});
|
|
272
|
+
}
|
|
211
273
|
} catch (e) {
|
|
212
274
|
const reason = e instanceof Error ? e.message : String(e);
|
|
213
|
-
ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
275
|
+
await ctx.fulfillmentRecoveryStore.set(settleResult.exchangeId, {
|
|
214
276
|
...pending,
|
|
215
277
|
error: reason
|
|
216
278
|
});
|
|
279
|
+
logger.warn("x402-server: Flow B channel onCommit failed; recovery entry retained", {
|
|
280
|
+
exchangeId: settleResult.exchangeId,
|
|
281
|
+
option: decoded.payload.fulfillment.option,
|
|
282
|
+
error: reason
|
|
283
|
+
});
|
|
217
284
|
warnings.push({
|
|
218
285
|
code: "FULFILLMENT_COMMIT_DEFERRED",
|
|
219
286
|
reason: "atomic redeem succeeded on-chain, but the channel adapter rejected the data",
|
|
@@ -238,7 +305,8 @@ async function handleCommitImpl(input, ctx, expected) {
|
|
|
238
305
|
exchangeId: settleResult.exchangeId,
|
|
239
306
|
txHash: settleResult.txHash,
|
|
240
307
|
nextActions,
|
|
241
|
-
...warnings.length > 0 ? { warnings } : {}
|
|
308
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
309
|
+
...delivery !== void 0 ? { fulfillment: delivery } : {}
|
|
242
310
|
});
|
|
243
311
|
}
|
|
244
312
|
function buildExpectedFromRequirements(requirements, state) {
|
|
@@ -338,7 +406,7 @@ async function handleRedeem(input, ctx) {
|
|
|
338
406
|
}
|
|
339
407
|
let resolvedChannel;
|
|
340
408
|
if (input.fulfillment !== void 0) {
|
|
341
|
-
const advertisedOptions = ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
|
|
409
|
+
const advertisedOptions = await ctx.exchangeFulfillmentOptionStore.get(input.exchangeId);
|
|
342
410
|
if (advertisedOptions !== void 0 && !advertisedOptions.includes(input.fulfillment.option)) {
|
|
343
411
|
return handlerErr(
|
|
344
412
|
400,
|
|
@@ -381,23 +449,40 @@ async function handleRedeem(input, ctx) {
|
|
|
381
449
|
}
|
|
382
450
|
const result = await handlePerformAction("boson-redeem", input, ctx);
|
|
383
451
|
if (!result.ok) return result;
|
|
384
|
-
|
|
452
|
+
const warnings = [];
|
|
453
|
+
let delivery;
|
|
385
454
|
if (resolvedChannel !== void 0 && input.fulfillment !== void 0) {
|
|
386
455
|
const pending = {
|
|
387
456
|
exchangeId: input.exchangeId,
|
|
388
457
|
option: input.fulfillment.option,
|
|
389
458
|
data: input.fulfillment.data,
|
|
390
459
|
redeemer,
|
|
391
|
-
recordedAt: Date.now()
|
|
460
|
+
recordedAt: Date.now(),
|
|
461
|
+
phase: "commit"
|
|
392
462
|
};
|
|
393
|
-
ctx.
|
|
463
|
+
const logger = ctx.logger ?? noopLogger;
|
|
464
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, pending);
|
|
465
|
+
logger.debug("x402-server: fulfillment recovery entry recorded (Flow A redeem)", {
|
|
466
|
+
exchangeId: input.exchangeId,
|
|
467
|
+
option: input.fulfillment.option
|
|
468
|
+
});
|
|
469
|
+
let onCommitOk = false;
|
|
394
470
|
try {
|
|
395
471
|
await resolvedChannel.onCommit(input.exchangeId, input.fulfillment.data);
|
|
396
|
-
|
|
472
|
+
onCommitOk = true;
|
|
473
|
+
logger.debug("x402-server: Flow A channel onCommit succeeded", {
|
|
474
|
+
exchangeId: input.exchangeId,
|
|
475
|
+
option: input.fulfillment.option
|
|
476
|
+
});
|
|
397
477
|
} catch (e) {
|
|
398
478
|
const reason = errorMessage(e);
|
|
399
|
-
ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
|
|
400
|
-
|
|
479
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, { ...pending, error: reason });
|
|
480
|
+
logger.warn("x402-server: Flow A channel onCommit failed; recovery entry retained", {
|
|
481
|
+
exchangeId: input.exchangeId,
|
|
482
|
+
option: input.fulfillment.option,
|
|
483
|
+
error: reason
|
|
484
|
+
});
|
|
485
|
+
warnings.push({
|
|
401
486
|
code: "FULFILLMENT_UPDATE_DEFERRED",
|
|
402
487
|
reason: "redeem succeeded on-chain, but the server could not persist the fulfillment update",
|
|
403
488
|
details: {
|
|
@@ -405,16 +490,48 @@ async function handleRedeem(input, ctx) {
|
|
|
405
490
|
option: input.fulfillment.option,
|
|
406
491
|
error: reason
|
|
407
492
|
}
|
|
408
|
-
};
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (onCommitOk) {
|
|
496
|
+
if (resolvedChannel.onFulfill !== void 0) {
|
|
497
|
+
const deliveryPending = {
|
|
498
|
+
...pending,
|
|
499
|
+
phase: "delivery",
|
|
500
|
+
recordedAt: Date.now()
|
|
501
|
+
};
|
|
502
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, deliveryPending);
|
|
503
|
+
try {
|
|
504
|
+
delivery = serializeFulfillmentResult(await resolvedChannel.onFulfill(input.exchangeId));
|
|
505
|
+
await ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
|
|
506
|
+
} catch (e) {
|
|
507
|
+
const reason = errorMessage(e);
|
|
508
|
+
await ctx.fulfillmentRecoveryStore.set(input.exchangeId, {
|
|
509
|
+
...deliveryPending,
|
|
510
|
+
error: reason
|
|
511
|
+
});
|
|
512
|
+
warnings.push({
|
|
513
|
+
code: "FULFILLMENT_DELIVERY_DEFERRED",
|
|
514
|
+
reason: "redeem succeeded on-chain, but the fulfillment channel's delivery dispatch failed",
|
|
515
|
+
details: {
|
|
516
|
+
exchangeId: input.exchangeId,
|
|
517
|
+
option: input.fulfillment.option,
|
|
518
|
+
error: reason
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
} else {
|
|
523
|
+
await ctx.fulfillmentRecoveryStore.delete(input.exchangeId);
|
|
524
|
+
}
|
|
409
525
|
}
|
|
410
526
|
}
|
|
411
|
-
ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
|
|
412
|
-
if (
|
|
527
|
+
await ctx.exchangeFulfillmentOptionStore.delete(input.exchangeId);
|
|
528
|
+
if (warnings.length > 0 || delivery !== void 0) {
|
|
413
529
|
return {
|
|
414
530
|
...result,
|
|
415
531
|
body: {
|
|
416
532
|
...result.body,
|
|
417
|
-
|
|
533
|
+
...delivery !== void 0 ? { fulfillment: delivery } : {},
|
|
534
|
+
...warnings.length > 0 ? { warnings: [...result.body.warnings ?? [], ...warnings] } : {}
|
|
418
535
|
}
|
|
419
536
|
};
|
|
420
537
|
}
|
|
@@ -653,5 +770,5 @@ function omitErrorMetadata(resolved) {
|
|
|
653
770
|
}
|
|
654
771
|
|
|
655
772
|
export { ADDRESS_RE, DECIMAL_UINT_RE, HEX_BYTES_RE, emitNextActions, handleCommit, handleCommitAndRedeem, handleComplete, handleDisputeEscalate, handleDisputeRaise, handleDisputeResolve, handleDisputeRetract, handleGetAvailableFunds, handlePerformAction, handleRedeem, handleWithdrawFunds, handlerErr, handlerOk, plainHandlerOk, resolveEntityId, stampFacilitatorEndpoints };
|
|
656
|
-
//# sourceMappingURL=chunk-
|
|
657
|
-
//# sourceMappingURL=chunk-
|
|
773
|
+
//# sourceMappingURL=chunk-SHYOIKYU.js.map
|
|
774
|
+
//# sourceMappingURL=chunk-SHYOIKYU.js.map
|