@parallel-protocol/x402 0.10.0 → 0.10.1

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.
@@ -1,4 +1,4 @@
1
- import { X402_ERROR_CODES, X402ConfigError, X402RuntimeError, isPriceRejection } from './chunk-QAECRSWF.js';
1
+ import { X402_ERROR_CODES, X402ConfigError, X402RuntimeError, isPriceRejection } from './chunk-MZCHBBUN.js';
2
2
  import { isKnownChain, isActiveParallelizerChain, ACTIVE_PARALLELIZER_CHAINS, getUptoEscrowAddress, getTokenDecimals as getTokenDecimals$1 } from '@parallel-protocol/chains';
3
3
  import { permit2NonceToBytes32 } from '@parallel-protocol/payment-core';
4
4
  import { z } from 'zod';
@@ -114,18 +114,144 @@ function encodeBase64(obj) {
114
114
  function decodeBase64(str) {
115
115
  return JSON.parse(Buffer.from(str, "base64").toString("utf8"));
116
116
  }
117
- function matchRoute(path, routes) {
118
- if (routes[path]) return routes[path];
117
+ var CONDITIONAL_HEADERS = [
118
+ "if-match",
119
+ "if-none-match",
120
+ "if-modified-since",
121
+ "if-unmodified-since"
122
+ ];
123
+ function dropConditionalHeaders(headers) {
124
+ for (const name of CONDITIONAL_HEADERS) delete headers[name];
125
+ }
126
+ var UTF8 = new TextDecoder();
127
+ var DECODE_ROUNDS = 8;
128
+ function decodePercent(path) {
129
+ try {
130
+ return decodeURIComponent(path);
131
+ } catch {
132
+ return path.replace(
133
+ /(?:%[\da-f]{2})+/gi,
134
+ (run) => UTF8.decode(
135
+ Uint8Array.from(
136
+ run.slice(1).split("%"),
137
+ (hex) => Number.parseInt(hex, 16)
138
+ )
139
+ )
140
+ );
141
+ }
142
+ }
143
+ function hasLongMarkRun(segment) {
144
+ for (const [run] of segment.matchAll(/\p{M}+/gu))
145
+ if (run.length > 8) return true;
146
+ return false;
147
+ }
148
+ function foldCase(segment) {
149
+ const composed = hasLongMarkRun(segment) ? segment : segment.normalize("NFC");
150
+ return composed.toLowerCase().toUpperCase().toLowerCase();
151
+ }
152
+ function pathSegments(path) {
153
+ let decoded = path;
154
+ for (let round = 0; round < DECODE_ROUNDS && decoded.includes("%"); round++) {
155
+ const next = decodePercent(decoded);
156
+ if (next === decoded) break;
157
+ decoded = next;
158
+ }
159
+ const target = decoded.replaceAll("\\", "/").replace(/^[a-z][\d+.a-z-]*:\/\/[^/]*/i, "").split(/[?#]/, 1)[0] ?? "";
160
+ return target.split("/").map((segment) => foldCase(segment).split(";", 1)[0] ?? "").filter((segment) => segment !== "");
161
+ }
162
+ function resolveDots(segments) {
163
+ const resolved = [];
164
+ for (const segment of segments) {
165
+ if (segment === "..") resolved.pop();
166
+ else if (segment !== ".") resolved.push(segment);
167
+ }
168
+ return resolved;
169
+ }
170
+ function normalizeRoutePath(path) {
171
+ return `/${resolveDots(pathSegments(path)).join("/")}`;
172
+ }
173
+ function routeTable(routes) {
174
+ return Object.entries(routes).map(([key, route]) => [
175
+ normalizeRoutePath(key),
176
+ route
177
+ ]);
178
+ }
179
+ function longestMatch(target, table) {
119
180
  let bestMatch;
120
181
  let bestLength = 0;
121
- for (const [pattern, config] of Object.entries(routes)) {
122
- if (path.startsWith(`${pattern}/`) && pattern.length > bestLength) {
123
- bestMatch = config;
124
- bestLength = pattern.length;
182
+ for (const [key, route] of table) {
183
+ if (key === target) return route;
184
+ if (target.startsWith(`${key}/`) && key.length > bestLength) {
185
+ bestMatch = route;
186
+ bestLength = key.length;
125
187
  }
126
188
  }
127
189
  return bestMatch;
128
190
  }
191
+ function findRoute(path, table) {
192
+ const segments = pathSegments(path);
193
+ const dotSegments = segments.some(
194
+ (segment) => segment === "." || segment === ".."
195
+ );
196
+ const routePath = `/${resolveDots(segments).join("/")}`;
197
+ const route = longestMatch(routePath, table) ?? (dotSegments ? longestMatch(`/${segments.join("/")}`, table) : void 0);
198
+ return { route, dotSegments, routePath };
199
+ }
200
+ function isPreflightHeader(name) {
201
+ return /^(?:access-control-|vary$|allow$)/i.test(String(name));
202
+ }
203
+ function preflightHeaders(headers) {
204
+ if (Array.isArray(headers)) {
205
+ if (Array.isArray(headers[0]))
206
+ return headers.filter((pair) => isPreflightHeader(pair[0]));
207
+ return headers.flatMap(
208
+ (item, i) => i % 2 === 0 && isPreflightHeader(item) ? [item, headers[i + 1]] : []
209
+ );
210
+ }
211
+ if (headers !== null && typeof headers === "object")
212
+ return Object.fromEntries(
213
+ Object.entries(headers).filter(([name]) => isPreflightHeader(name))
214
+ );
215
+ return headers;
216
+ }
217
+ function answerAsPreflight(res) {
218
+ const writeHead = res.writeHead.bind(res);
219
+ const end = res.end.bind(res);
220
+ const callbackOf = (args) => args.find((arg) => typeof arg === "function");
221
+ const dropOtherHeaders = () => {
222
+ for (const name of res.getHeaderNames())
223
+ if (!isPreflightHeader(name)) res.removeHeader(name);
224
+ };
225
+ res.writeHead = ((statusCode, ...rest) => {
226
+ dropOtherHeaders();
227
+ res.statusMessage = "";
228
+ const headers = rest.find((arg) => typeof arg !== "string");
229
+ return headers === void 0 ? writeHead(statusCode) : writeHead(statusCode, preflightHeaders(headers));
230
+ });
231
+ res.write = ((...args) => {
232
+ const done = callbackOf(args);
233
+ if (done) queueMicrotask(done);
234
+ return true;
235
+ });
236
+ res.end = ((...args) => {
237
+ if (!res.headersSent) dropOtherHeaders();
238
+ const done = callbackOf(args);
239
+ return done ? end(done) : end();
240
+ });
241
+ res.addTrailers = () => {
242
+ };
243
+ res.writeEarlyHints = ((_hints, done) => {
244
+ if (done) queueMicrotask(done);
245
+ });
246
+ }
247
+ function preflightInit(res) {
248
+ res.body?.cancel().catch(() => {
249
+ });
250
+ const headers = new Headers();
251
+ for (const [name, value] of res.headers)
252
+ if (isPreflightHeader(name)) headers.append(name, value);
253
+ return { status: res.status, headers };
254
+ }
129
255
  function permit2Extra(network, asset) {
130
256
  const domain = USDC_EIP712_DOMAIN[network];
131
257
  const escrow = getUptoEscrowAddress(network);
@@ -296,9 +422,31 @@ var routeConfigSchema = z.object({
296
422
  (route) => route.scheme !== "upto" || !hasMixedDecimals(route.network, route.acceptedTokens) || typeof route.price !== "bigint" && route.decimals === void 0,
297
423
  'An upto route that accepts tokens with different decimals needs a price like "0.01", not a bigint or an explicit decimals'
298
424
  );
425
+ function sameValue(a, b) {
426
+ if (Object.is(a, b)) return true;
427
+ if (typeof a !== "object" || typeof b !== "object" || !a || !b) return false;
428
+ const entries = Object.entries(a);
429
+ return Array.isArray(a) === Array.isArray(b) && entries.length === Object.keys(b).length && entries.every(
430
+ ([key, value]) => sameValue(value, b[key])
431
+ );
432
+ }
433
+ function consistentRoutePaths(routes, ctx) {
434
+ const keyByPath = /* @__PURE__ */ new Map();
435
+ for (const [key, route] of Object.entries(routes)) {
436
+ const path = normalizeRoutePath(key);
437
+ const other = keyByPath.get(path);
438
+ if (other === void 0) keyByPath.set(path, key);
439
+ else if (!sameValue(routes[other], route))
440
+ ctx.addIssue({
441
+ code: "custom",
442
+ path: [key],
443
+ message: `Same path as "${other}" once normalized, with another config: keep one of the two routes`
444
+ });
445
+ }
446
+ }
299
447
  var paymentMiddlewareConfigSchema = z.object({
300
448
  facilitator: facilitatorConfigSchema,
301
- routes: z.record(z.string(), routeConfigSchema).refine((r) => Object.keys(r).length > 0, "At least one route is required")
449
+ routes: z.record(z.string(), routeConfigSchema).refine((r) => Object.keys(r).length > 0, "At least one route is required").superRefine(consistentRoutePaths)
302
450
  });
303
451
  var facilitatorResponseSchema = z.discriminatedUnion("success", [
304
452
  z.object({
@@ -583,10 +731,11 @@ async function raceTimeout(promise, ms = HANDLER_TIMEOUT_MS) {
583
731
 
584
732
  // src/middleware.ts
585
733
  var PRICE_FORMAT = /^\d+(?:\.\d+)?$/;
586
- async function requestContext(adapter) {
734
+ async function requestContext(adapter, routePath) {
587
735
  return {
588
736
  method: adapter.getMethod(),
589
737
  path: adapter.getPath(),
738
+ routePath,
590
739
  url: adapter.getUrl(),
591
740
  header: (name) => adapter.getHeader(name),
592
741
  body: adapter.getBody ? await adapter.getBody() : void 0
@@ -882,13 +1031,28 @@ function createPaymentGate(config) {
882
1031
  );
883
1032
  }
884
1033
  const client = new FacilitatorClient(config.facilitator);
1034
+ const routes = routeTable(config.routes);
885
1035
  return async (adapter) => {
886
- if (adapter.getMethod().toUpperCase() === "OPTIONS")
887
- return { type: "pass" };
888
- const routeConfig = matchRoute(adapter.getPath(), config.routes);
1036
+ const path = adapter.getPath();
1037
+ const fullPath = adapter.getFullPath?.();
1038
+ let match = findRoute(path, routes);
1039
+ if (!match.route && fullPath !== void 0 && fullPath !== path)
1040
+ match = findRoute(fullPath, routes);
1041
+ const { route: routeConfig, dotSegments, routePath } = match;
889
1042
  if (!routeConfig) return { type: "pass" };
1043
+ if (dotSegments) {
1044
+ return {
1045
+ type: "error",
1046
+ result: jsonResult(400, {
1047
+ error: X402_ERROR_CODES.INVALID_PATH,
1048
+ message: "A paid route is not served on a path with . or .. segments: send the path resolved"
1049
+ })
1050
+ };
1051
+ }
1052
+ if (adapter.getMethod().toUpperCase() === "OPTIONS")
1053
+ return { type: "pass", preflight: true };
890
1054
  const resolvedTokens = resolveAcceptedTokens(routeConfig);
891
- let request = typeof routeConfig.price === "function" ? await requestContext(adapter) : void 0;
1055
+ let request = typeof routeConfig.price === "function" ? await requestContext(adapter, routePath) : void 0;
892
1056
  let price;
893
1057
  try {
894
1058
  price = await resolvePrice(routeConfig, request, resolvedTokens);
@@ -943,7 +1107,7 @@ function createPaymentGate(config) {
943
1107
  };
944
1108
  }
945
1109
  const meter = routeConfig.meter && hasMixedDecimals(routeConfig.network, resolvedTokens) ? refusingBigints(routeConfig.meter) : routeConfig.meter;
946
- if (meter && !request) request = await requestContext(adapter);
1110
+ if (meter && !request) request = await requestContext(adapter, routePath);
947
1111
  return {
948
1112
  type: "verified",
949
1113
  scheme: routeConfig.scheme ?? "exact",
@@ -1025,7 +1189,8 @@ function createPaymentMiddleware(config) {
1025
1189
  const gate = createPaymentGate(config);
1026
1190
  return async (adapter) => {
1027
1191
  const gateResult = await gate(adapter);
1028
- if (gateResult.type === "pass") return { pass: true };
1192
+ if (gateResult.type === "pass")
1193
+ return gateResult.preflight ? { pass: true, preflight: true } : { pass: true };
1029
1194
  if (gateResult.type === "error") return gateResult.result;
1030
1195
  let handlerResult;
1031
1196
  try {
@@ -1077,4 +1242,4 @@ function createPaymentMiddleware(config) {
1077
1242
  };
1078
1243
  }
1079
1244
 
1080
- export { FacilitatorClient, HANDLER_TIMEOUT, PARALLEL_TOKENS, SETTLE_AMOUNT_HEADER, createPaymentGate, createPaymentMiddleware, encodeBase64, getDefaultAcceptedTokens, invalidSettleAmountResponse, isJsonContentType, jsonFromResponse, matchRoute, parseJsonBody, parsePrice, raceTimeout, resolveCharge, resolveSettleAmount, settleVerified, settlementFailureResponse, toEip155Network, validateAddress };
1245
+ export { FacilitatorClient, HANDLER_TIMEOUT, PARALLEL_TOKENS, SETTLE_AMOUNT_HEADER, answerAsPreflight, createPaymentGate, createPaymentMiddleware, dropConditionalHeaders, encodeBase64, findRoute, getDefaultAcceptedTokens, invalidSettleAmountResponse, isJsonContentType, jsonFromResponse, normalizeRoutePath, parseJsonBody, parsePrice, preflightInit, raceTimeout, resolveCharge, resolveSettleAmount, routeTable, settleVerified, settlementFailureResponse, toEip155Network, validateAddress };
@@ -3,6 +3,8 @@ var X402_ERROR_CODES = {
3
3
  FACILITATOR_UNAVAILABLE: "FACILITATOR_UNAVAILABLE",
4
4
  FACILITATOR_INVALID_RESPONSE: "FACILITATOR_INVALID_RESPONSE",
5
5
  INVALID_PAYMENT: "INVALID_PAYMENT",
6
+ /** A path with `.` or `..` segments on a paid route, refused with 400. */
7
+ INVALID_PATH: "INVALID_PATH",
6
8
  /** The facilitator did not answer a settle within the client's timeout: the payment may still settle. */
7
9
  SETTLE_TIMEOUT: "SETTLE_TIMEOUT"
8
10
  };
package/dist/express.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkMYCUOJ3H_cjs = require('./chunk-MYCUOJ3H.cjs');
4
- require('./chunk-KZZY5LS6.cjs');
3
+ var chunkAYMDIX7A_cjs = require('./chunk-AYMDIX7A.cjs');
4
+ require('./chunk-4CEMNRCZ.cjs');
5
5
 
6
6
  // src/express/adapter.ts
7
7
  function chunkBytes(args) {
@@ -56,6 +56,9 @@ var ExpressAdapter = class {
56
56
  getPath() {
57
57
  return this.req.path;
58
58
  }
59
+ getFullPath() {
60
+ return this.req.baseUrl + this.req.path;
61
+ }
59
62
  getUrl() {
60
63
  return `${this.req.protocol}://${this.req.headers.host}${this.req.originalUrl}`;
61
64
  }
@@ -67,9 +70,19 @@ var ExpressAdapter = class {
67
70
  async runHandler(options) {
68
71
  const res = this.res;
69
72
  const next = this.next;
73
+ chunkAYMDIX7A_cjs.dropConditionalHeaders(this.req.headers);
70
74
  const originalWriteHead = res.writeHead.bind(res);
71
75
  const originalWrite = res.write.bind(res);
72
76
  const originalEnd = res.end.bind(res);
77
+ const headersBefore = structuredClone(res.getHeaders());
78
+ const dropHandlerHeaders = () => {
79
+ for (const name of res.getHeaderNames())
80
+ if (!Object.hasOwn(headersBefore, name)) res.removeHeader(name);
81
+ for (const [name, value] of Object.entries(headersBefore))
82
+ if (value !== void 0) res.setHeader(name, value);
83
+ res.statusMessage = "";
84
+ };
85
+ const nodeWriters = Object.getPrototypeOf(res);
73
86
  const buffered = [];
74
87
  let settled = false;
75
88
  let discarded = false;
@@ -108,8 +121,8 @@ var ExpressAdapter = class {
108
121
  return originalEnd(...args);
109
122
  });
110
123
  next();
111
- const outcome = await chunkMYCUOJ3H_cjs.raceTimeout(endPromise, options?.timeoutMs);
112
- if (outcome === chunkMYCUOJ3H_cjs.HANDLER_TIMEOUT) {
124
+ const outcome = await chunkAYMDIX7A_cjs.raceTimeout(endPromise, options?.timeoutMs);
125
+ if (outcome === chunkAYMDIX7A_cjs.HANDLER_TIMEOUT) {
113
126
  discarded = true;
114
127
  return {
115
128
  status: 504,
@@ -121,8 +134,17 @@ var ExpressAdapter = class {
121
134
  },
122
135
  sendResponse: () => {
123
136
  if (res.headersSent) return;
124
- originalWriteHead(504, { "Content-Type": "application/json" });
125
- originalEnd(TIMEOUT_BODY);
137
+ dropHandlerHeaders();
138
+ nodeWriters.writeHead.call(res, 504, {
139
+ "Content-Type": "application/json",
140
+ "Content-Length": Buffer.byteLength(TIMEOUT_BODY)
141
+ });
142
+ nodeWriters.end.call(res, TIMEOUT_BODY, "utf8");
143
+ res.setHeader = (() => res);
144
+ res.setHeaders = (() => res);
145
+ res.appendHeader = (() => res);
146
+ res.removeHeader = () => {
147
+ };
126
148
  },
127
149
  discardResponse: () => {
128
150
  }
@@ -146,7 +168,7 @@ var ExpressAdapter = class {
146
168
  const chunk = chunkBytes(args);
147
169
  if (chunk) bytes.push(chunk);
148
170
  }
149
- return chunkMYCUOJ3H_cjs.parseJsonBody(
171
+ return chunkAYMDIX7A_cjs.parseJsonBody(
150
172
  contentType,
151
173
  Buffer.concat(bytes).toString("utf8")
152
174
  );
@@ -162,7 +184,9 @@ var ExpressAdapter = class {
162
184
  },
163
185
  discardResponse: () => {
164
186
  settled = true;
165
- restore();
187
+ dropHandlerHeaders();
188
+ for (const name of ["writeHead", "write", "end"])
189
+ Reflect.deleteProperty(res, name);
166
190
  }
167
191
  };
168
192
  }
@@ -170,11 +194,12 @@ var ExpressAdapter = class {
170
194
 
171
195
  // src/express/index.ts
172
196
  function paymentMiddleware(config) {
173
- const middleware = chunkMYCUOJ3H_cjs.createPaymentMiddleware(config);
197
+ const middleware = chunkAYMDIX7A_cjs.createPaymentMiddleware(config);
174
198
  return async (req, res, next) => {
175
199
  const result = await middleware(new ExpressAdapter(req, res, next));
176
200
  if ("pass" in result) {
177
201
  if (result.handlerRan) return;
202
+ if (result.preflight) chunkAYMDIX7A_cjs.answerAsPreflight(res);
178
203
  return next();
179
204
  }
180
205
  for (const [key, value] of Object.entries(result.headers)) {
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BWL9h1eY.cjs';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CRBMTQaq.cjs';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
@@ -10,6 +10,7 @@ declare class ExpressAdapter implements HTTPAdapter {
10
10
  getHeader(name: string): string | undefined;
11
11
  getMethod(): string;
12
12
  getPath(): string;
13
+ getFullPath(): string;
13
14
  getUrl(): string;
14
15
  getBody(): Promise<unknown>;
15
16
  runHandler(options?: RunHandlerOptions): Promise<RunHandlerResult>;
package/dist/express.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
- import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-BWL9h1eY.js';
2
+ import { H as HTTPAdapter, R as RunHandlerOptions, a as RunHandlerResult, P as PaymentMiddlewareConfig } from './types-CRBMTQaq.js';
3
3
  import 'viem';
4
4
 
5
5
  declare class ExpressAdapter implements HTTPAdapter {
@@ -10,6 +10,7 @@ declare class ExpressAdapter implements HTTPAdapter {
10
10
  getHeader(name: string): string | undefined;
11
11
  getMethod(): string;
12
12
  getPath(): string;
13
+ getFullPath(): string;
13
14
  getUrl(): string;
14
15
  getBody(): Promise<unknown>;
15
16
  runHandler(options?: RunHandlerOptions): Promise<RunHandlerResult>;
package/dist/express.js CHANGED
@@ -1,5 +1,5 @@
1
- import { raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware } from './chunk-VSNDEQGS.js';
2
- import './chunk-QAECRSWF.js';
1
+ import { dropConditionalHeaders, raceTimeout, HANDLER_TIMEOUT, parseJsonBody, createPaymentMiddleware, answerAsPreflight } from './chunk-CNASNROQ.js';
2
+ import './chunk-MZCHBBUN.js';
3
3
 
4
4
  // src/express/adapter.ts
5
5
  function chunkBytes(args) {
@@ -54,6 +54,9 @@ var ExpressAdapter = class {
54
54
  getPath() {
55
55
  return this.req.path;
56
56
  }
57
+ getFullPath() {
58
+ return this.req.baseUrl + this.req.path;
59
+ }
57
60
  getUrl() {
58
61
  return `${this.req.protocol}://${this.req.headers.host}${this.req.originalUrl}`;
59
62
  }
@@ -65,9 +68,19 @@ var ExpressAdapter = class {
65
68
  async runHandler(options) {
66
69
  const res = this.res;
67
70
  const next = this.next;
71
+ dropConditionalHeaders(this.req.headers);
68
72
  const originalWriteHead = res.writeHead.bind(res);
69
73
  const originalWrite = res.write.bind(res);
70
74
  const originalEnd = res.end.bind(res);
75
+ const headersBefore = structuredClone(res.getHeaders());
76
+ const dropHandlerHeaders = () => {
77
+ for (const name of res.getHeaderNames())
78
+ if (!Object.hasOwn(headersBefore, name)) res.removeHeader(name);
79
+ for (const [name, value] of Object.entries(headersBefore))
80
+ if (value !== void 0) res.setHeader(name, value);
81
+ res.statusMessage = "";
82
+ };
83
+ const nodeWriters = Object.getPrototypeOf(res);
71
84
  const buffered = [];
72
85
  let settled = false;
73
86
  let discarded = false;
@@ -119,8 +132,17 @@ var ExpressAdapter = class {
119
132
  },
120
133
  sendResponse: () => {
121
134
  if (res.headersSent) return;
122
- originalWriteHead(504, { "Content-Type": "application/json" });
123
- originalEnd(TIMEOUT_BODY);
135
+ dropHandlerHeaders();
136
+ nodeWriters.writeHead.call(res, 504, {
137
+ "Content-Type": "application/json",
138
+ "Content-Length": Buffer.byteLength(TIMEOUT_BODY)
139
+ });
140
+ nodeWriters.end.call(res, TIMEOUT_BODY, "utf8");
141
+ res.setHeader = (() => res);
142
+ res.setHeaders = (() => res);
143
+ res.appendHeader = (() => res);
144
+ res.removeHeader = () => {
145
+ };
124
146
  },
125
147
  discardResponse: () => {
126
148
  }
@@ -160,7 +182,9 @@ var ExpressAdapter = class {
160
182
  },
161
183
  discardResponse: () => {
162
184
  settled = true;
163
- restore();
185
+ dropHandlerHeaders();
186
+ for (const name of ["writeHead", "write", "end"])
187
+ Reflect.deleteProperty(res, name);
164
188
  }
165
189
  };
166
190
  }
@@ -173,6 +197,7 @@ function paymentMiddleware(config) {
173
197
  const result = await middleware(new ExpressAdapter(req, res, next));
174
198
  if ("pass" in result) {
175
199
  if (result.handlerRan) return;
200
+ if (result.preflight) answerAsPreflight(res);
176
201
  return next();
177
202
  }
178
203
  for (const [key, value] of Object.entries(result.headers)) {
package/dist/fastify.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkMYCUOJ3H_cjs = require('./chunk-MYCUOJ3H.cjs');
4
- require('./chunk-KZZY5LS6.cjs');
3
+ var chunkAYMDIX7A_cjs = require('./chunk-AYMDIX7A.cjs');
4
+ require('./chunk-4CEMNRCZ.cjs');
5
5
  var fp = require('fastify-plugin');
6
6
 
7
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -37,9 +37,39 @@ var FastifyAdapter = class {
37
37
  // src/fastify/index.ts
38
38
  function payloadJson(contentType, payload) {
39
39
  const text = typeof payload === "string" ? payload : payload instanceof Uint8Array ? Buffer.from(payload).toString("utf8") : void 0;
40
- return chunkMYCUOJ3H_cjs.parseJsonBody(contentType, text);
40
+ return chunkAYMDIX7A_cjs.parseJsonBody(contentType, text);
41
41
  }
42
- function refuse(reply, result) {
42
+ function keepCookiesBefore(reply, cookies) {
43
+ const raw = reply.raw;
44
+ const writeHead = raw.writeHead;
45
+ raw.writeHead = ((...args) => {
46
+ for (const arg of args)
47
+ if (typeof arg === "object" && arg !== null && !Array.isArray(arg))
48
+ delete arg["set-cookie"];
49
+ raw.removeHeader("set-cookie");
50
+ if (cookies !== void 0) raw.setHeader("set-cookie", cookies);
51
+ return writeHead.apply(raw, args);
52
+ });
53
+ }
54
+ function release(payload) {
55
+ if (payload instanceof ReadableStream) payload.cancel().catch(() => {
56
+ });
57
+ else if (typeof payload?.destroy === "function")
58
+ payload.destroy();
59
+ }
60
+ function refuse(reply, result, before, payload) {
61
+ release(payload);
62
+ if (before) {
63
+ for (const name of Object.keys(reply.getHeaders()))
64
+ reply.removeHeader(name);
65
+ for (const [name, value] of Object.entries(before.headers))
66
+ if (value !== void 0) reply.header(name, value);
67
+ for (const name of before.trailers) reply.removeTrailer(name);
68
+ for (const name of ["writeHead", "write", "end"])
69
+ Reflect.deleteProperty(reply.raw, name);
70
+ keepCookiesBefore(reply, before.headers["set-cookie"]);
71
+ }
72
+ reply.raw.statusMessage = "";
43
73
  const json = JSON.stringify(result.body);
44
74
  reply.status(result.status);
45
75
  for (const [name, value] of Object.entries(result.headers)) {
@@ -54,35 +84,51 @@ function headerValue(value) {
54
84
  }
55
85
  function paymentMiddleware(config) {
56
86
  return fp__default.default(async (fastify) => {
57
- const gate = chunkMYCUOJ3H_cjs.createPaymentGate(config);
87
+ const gate = chunkAYMDIX7A_cjs.createPaymentGate(config);
58
88
  const verifiedMap = /* @__PURE__ */ new WeakMap();
59
89
  const rawWriters = /* @__PURE__ */ new WeakMap();
90
+ const replyBefore = /* @__PURE__ */ new WeakMap();
91
+ const routes = chunkAYMDIX7A_cjs.routeTable(config.routes);
92
+ const needsBodyByRequest = /* @__PURE__ */ new WeakMap();
60
93
  const needsBody = (req) => {
61
- const route = chunkMYCUOJ3H_cjs.matchRoute(
62
- new FastifyAdapter(req).getPath(),
63
- config.routes
64
- );
65
- return typeof route?.price === "function" || route?.meter !== void 0;
94
+ let needs = needsBodyByRequest.get(req);
95
+ if (needs === void 0) {
96
+ const { route } = chunkAYMDIX7A_cjs.findRoute(new FastifyAdapter(req).getPath(), routes);
97
+ needs = typeof route?.price === "function" || route?.meter !== void 0;
98
+ needsBodyByRequest.set(req, needs);
99
+ }
100
+ return needs;
66
101
  };
67
102
  fastify.addHook("onRequest", async (req, reply) => {
68
103
  if (needsBody(req)) return;
69
- await runGate(req, reply);
104
+ return runGate(req, reply);
70
105
  });
71
106
  fastify.addHook("preHandler", async (req, reply) => {
72
107
  if (!needsBody(req)) return;
73
- await runGate(req, reply);
108
+ return runGate(req, reply);
74
109
  });
75
110
  async function runGate(req, reply) {
76
111
  const gateResult = await gate(new FastifyAdapter(req));
77
- if (gateResult.type === "pass") return;
112
+ if (gateResult.type === "pass") {
113
+ if (gateResult.preflight) chunkAYMDIX7A_cjs.answerAsPreflight(reply.raw);
114
+ return;
115
+ }
78
116
  if (gateResult.type === "error") {
79
117
  for (const [key, value] of Object.entries(gateResult.result.headers)) {
80
118
  reply.header(key, value);
81
119
  }
82
- reply.status(gateResult.result.status).send(gateResult.result.body);
83
- return;
120
+ return reply.status(gateResult.result.status).send(gateResult.result.body);
84
121
  }
85
122
  verifiedMap.set(req, gateResult);
123
+ chunkAYMDIX7A_cjs.dropConditionalHeaders(req.headers);
124
+ const trailers = /* @__PURE__ */ new Set();
125
+ const headers = structuredClone(reply.getHeaders());
126
+ replyBefore.set(req, { headers, trailers });
127
+ const trailer = reply.trailer.bind(reply);
128
+ reply.trailer = ((name, fn) => {
129
+ trailers.add(name.toLowerCase());
130
+ return trailer(name, fn);
131
+ });
86
132
  const raw = reply.raw;
87
133
  rawWriters.set(req, {
88
134
  write: raw.write,
@@ -111,29 +157,39 @@ function paymentMiddleware(config) {
111
157
  if (!verified) return payload;
112
158
  let raw;
113
159
  if (verified.scheme === "upto") {
114
- raw = headerValue(reply.getHeader(chunkMYCUOJ3H_cjs.SETTLE_AMOUNT_HEADER));
115
- reply.removeHeader(chunkMYCUOJ3H_cjs.SETTLE_AMOUNT_HEADER);
160
+ raw = headerValue(reply.getHeader(chunkAYMDIX7A_cjs.SETTLE_AMOUNT_HEADER));
161
+ reply.removeHeader(chunkAYMDIX7A_cjs.SETTLE_AMOUNT_HEADER);
116
162
  }
117
163
  if (reply.statusCode < 200 || reply.statusCode >= 300) {
118
164
  return payload;
119
165
  }
120
166
  let settleOptions;
121
167
  if (verified.scheme === "upto") {
122
- const resolved = await chunkMYCUOJ3H_cjs.resolveCharge(verified, raw, {
168
+ const resolved = await chunkAYMDIX7A_cjs.resolveCharge(verified, raw, {
123
169
  status: reply.statusCode,
124
170
  header: (name) => headerValue(reply.getHeader(name)),
125
171
  body: async () => payloadJson(headerValue(reply.getHeader("content-type")), payload)
126
172
  });
127
173
  if (resolved.kind === "invalid") {
128
- return refuse(reply, chunkMYCUOJ3H_cjs.invalidSettleAmountResponse(resolved.reason));
174
+ return refuse(
175
+ reply,
176
+ chunkAYMDIX7A_cjs.invalidSettleAmountResponse(resolved.reason),
177
+ replyBefore.get(req),
178
+ payload
179
+ );
129
180
  }
130
181
  if (resolved.kind === "skip") return payload;
131
182
  settleOptions = { amount: resolved.amount };
132
183
  }
133
- const outcome = await chunkMYCUOJ3H_cjs.settleVerified(verified, settleOptions);
184
+ const outcome = await chunkAYMDIX7A_cjs.settleVerified(verified, settleOptions);
134
185
  if (!outcome.settled)
135
- return refuse(reply, chunkMYCUOJ3H_cjs.settlementFailureResponse(outcome));
136
- reply.header("payment-response", chunkMYCUOJ3H_cjs.encodeBase64(outcome.confirmation));
186
+ return refuse(
187
+ reply,
188
+ chunkAYMDIX7A_cjs.settlementFailureResponse(outcome),
189
+ replyBefore.get(req),
190
+ payload
191
+ );
192
+ reply.header("payment-response", chunkAYMDIX7A_cjs.encodeBase64(outcome.confirmation));
137
193
  reply.header("access-control-expose-headers", "payment-response");
138
194
  return payload;
139
195
  });
@@ -1,5 +1,5 @@
1
1
  import { FastifyRequest, FastifyPluginAsync } from 'fastify';
2
- import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-BWL9h1eY.cjs';
2
+ import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-CRBMTQaq.cjs';
3
3
  import 'viem';
4
4
 
5
5
  declare class FastifyAdapter implements HTTPAdapter {
package/dist/fastify.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FastifyRequest, FastifyPluginAsync } from 'fastify';
2
- import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-BWL9h1eY.js';
2
+ import { H as HTTPAdapter, P as PaymentMiddlewareConfig } from './types-CRBMTQaq.js';
3
3
  import 'viem';
4
4
 
5
5
  declare class FastifyAdapter implements HTTPAdapter {