@t402/fastify 2.0.0 → 2.3.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/index.js +26 -23
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.mjs +26 -23
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +9 -8
package/dist/cjs/index.js
CHANGED
|
@@ -205,35 +205,38 @@ function paymentMiddleware(routes, server, paywallConfig, paywall, syncFacilitat
|
|
|
205
205
|
case "payment-verified": {
|
|
206
206
|
const { paymentPayload, paymentRequirements } = result;
|
|
207
207
|
const replyWithHooks = reply;
|
|
208
|
-
replyWithHooks.addHook(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
208
|
+
replyWithHooks.addHook(
|
|
209
|
+
"onSend",
|
|
210
|
+
async (_request, reply2, payload) => {
|
|
211
|
+
if (reply2.statusCode >= 400) {
|
|
212
|
+
return payload;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const settleResult = await httpServer.processSettlement(
|
|
216
|
+
paymentPayload,
|
|
217
|
+
paymentRequirements
|
|
218
|
+
);
|
|
219
|
+
if (!settleResult.success) {
|
|
220
|
+
reply2.code(402);
|
|
221
|
+
return JSON.stringify({
|
|
222
|
+
error: "Settlement failed",
|
|
223
|
+
details: settleResult.errorReason
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
227
|
+
reply2.header(key, value);
|
|
228
|
+
});
|
|
229
|
+
return payload;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error(error);
|
|
218
232
|
reply2.code(402);
|
|
219
233
|
return JSON.stringify({
|
|
220
234
|
error: "Settlement failed",
|
|
221
|
-
details:
|
|
235
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
222
236
|
});
|
|
223
237
|
}
|
|
224
|
-
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
225
|
-
reply2.header(key, value);
|
|
226
|
-
});
|
|
227
|
-
return payload;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
console.error(error);
|
|
230
|
-
reply2.code(402);
|
|
231
|
-
return JSON.stringify({
|
|
232
|
-
error: "Settlement failed",
|
|
233
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
234
|
-
});
|
|
235
238
|
}
|
|
236
|
-
|
|
239
|
+
);
|
|
237
240
|
return;
|
|
238
241
|
}
|
|
239
242
|
}
|
package/dist/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/adapter.ts"],"sourcesContent":["import {\n HTTPRequestContext,\n PaywallConfig,\n PaywallProvider,\n t402HTTPResourceServer,\n t402ResourceServer,\n RoutesConfig,\n FacilitatorClient,\n} from \"@t402/core/server\";\nimport { SchemeNetworkServer, Network } from \"@t402/core/types\";\nimport type { FastifyRequest, FastifyReply, preHandlerHookHandler, onSendHookHandler } from \"fastify\";\nimport { FastifyAdapter } from \"./adapter.js\";\n\n// Extend FastifyReply to include addHook which exists at runtime but not in types\ninterface FastifyReplyWithHooks extends FastifyReply {\n addHook(name: \"onSend\", hook: onSendHookHandler): void;\n}\n\n/**\n * Check if any routes in the configuration declare bazaar extensions\n *\n * @param routes - Route configuration\n * @returns True if any route has extensions.bazaar defined\n */\nfunction checkIfBazaarNeeded(routes: RoutesConfig): boolean {\n // Handle single route config\n if (\"accepts\" in routes) {\n return !!(routes.extensions && \"bazaar\" in routes.extensions);\n }\n\n // Handle multiple routes\n return Object.values(routes).some(routeConfig => {\n return !!(routeConfig.extensions && \"bazaar\" in routeConfig.extensions);\n });\n}\n\n/**\n * Configuration for registering a payment scheme with a specific network\n */\nexport interface SchemeRegistration {\n /**\n * The network identifier (e.g., 'eip155:84532', 'solana:mainnet')\n */\n network: Network;\n\n /**\n * The scheme server implementation for this network\n */\n server: SchemeNetworkServer;\n}\n\n/**\n * Fastify payment middleware for t402 protocol (direct server instance).\n *\n * Use this when you want to pass a pre-configured t402ResourceServer instance.\n * This provides more flexibility for testing, custom configuration, and reusing\n * server instances across multiple middlewares.\n *\n * @param routes - Route configurations for protected endpoints\n * @param server - Pre-configured t402ResourceServer instance\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddleware } from \"@t402/fastify\";\n * import { t402ResourceServer } from \"@t402/core/server\";\n * import { registerExactEvmScheme } from \"@t402/evm/exact/server\";\n *\n * const server = new t402ResourceServer(myFacilitatorClient);\n * registerExactEvmScheme(server, {});\n *\n * app.addHook('preHandler', paymentMiddleware(routes, server, paywallConfig));\n * ```\n */\nexport function paymentMiddleware(\n routes: RoutesConfig,\n server: t402ResourceServer,\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n // Create the t402 HTTP server instance with the resource server\n const httpServer = new t402HTTPResourceServer(server, routes);\n\n // Register custom paywall provider if provided\n if (paywall) {\n httpServer.registerPaywallProvider(paywall);\n }\n\n // Store initialization promise (not the result)\n // httpServer.initialize() fetches facilitator support and validates routes\n let initPromise: Promise<void> | null = syncFacilitatorOnStart ? httpServer.initialize() : null;\n\n // Dynamically register bazaar extension if routes declare it\n let bazaarPromise: Promise<void> | null = null;\n if (checkIfBazaarNeeded(routes)) {\n bazaarPromise = import(\"@t402/extensions/bazaar\")\n .then(({ bazaarResourceServerExtension }) => {\n server.registerExtension(bazaarResourceServerExtension);\n })\n .catch(err => {\n console.error(\"Failed to load bazaar extension:\", err);\n });\n }\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n // Create adapter and context\n const adapter = new FastifyAdapter(request);\n const path = adapter.getPath();\n\n const context: HTTPRequestContext = {\n adapter,\n path,\n method: request.method,\n paymentHeader: adapter.getHeader(\"payment-signature\") || adapter.getHeader(\"x-payment\"),\n };\n\n // Check if route requires payment before initializing facilitator\n if (!httpServer.requiresPayment(context)) {\n return; // Continue to route handler\n }\n\n // Only initialize when processing a protected route\n if (initPromise) {\n await initPromise;\n initPromise = null; // Clear after first await\n }\n\n // Await bazaar extension loading if needed\n if (bazaarPromise) {\n await bazaarPromise;\n bazaarPromise = null;\n }\n\n // Process payment requirement check\n const result = await httpServer.processHTTPRequest(context, paywallConfig);\n\n // Handle the different result types\n switch (result.type) {\n case \"no-payment-required\":\n // No payment needed, proceed directly to the route handler\n return;\n\n case \"payment-error\": {\n // Payment required but not provided or invalid\n const { response } = result;\n Object.entries(response.headers).forEach(([key, value]) => {\n reply.header(key, value);\n });\n if (response.isHtml) {\n return reply.code(response.status).type(\"text/html\").send(response.body);\n } else {\n return reply.code(response.status).send(response.body || {});\n }\n }\n\n case \"payment-verified\": {\n // Payment is valid, need to handle settlement after route handler\n const { paymentPayload, paymentRequirements } = result;\n\n // Use onSend hook to handle settlement after route handler completes\n // Cast to extended interface since addHook exists at runtime\n const replyWithHooks = reply as unknown as FastifyReplyWithHooks;\n replyWithHooks.addHook(\"onSend\", async (_request: FastifyRequest, reply: FastifyReply, payload: unknown) => {\n // If the response from the protected route is >= 400, do not settle payment\n if (reply.statusCode >= 400) {\n return payload;\n }\n\n try {\n const settleResult = await httpServer.processSettlement(\n paymentPayload,\n paymentRequirements,\n );\n\n if (!settleResult.success) {\n // Settlement failed - return error response\n reply.code(402);\n return JSON.stringify({\n error: \"Settlement failed\",\n details: settleResult.errorReason,\n });\n }\n\n // Settlement succeeded - add headers to response\n Object.entries(settleResult.headers).forEach(([key, value]) => {\n reply.header(key, value);\n });\n\n return payload;\n } catch (error) {\n console.error(error);\n // If settlement fails, return an error response\n reply.code(402);\n return JSON.stringify({\n error: \"Settlement failed\",\n details: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n });\n\n // Continue to route handler\n return;\n }\n }\n };\n}\n\n/**\n * Fastify payment middleware for t402 protocol (configuration-based).\n *\n * Use this when you want to quickly set up middleware with simple configuration.\n * This function creates and configures the t402ResourceServer internally.\n *\n * @param routes - Route configurations for protected endpoints\n * @param facilitatorClients - Optional facilitator client(s) for payment processing\n * @param schemes - Optional array of scheme registrations for server-side payment processing\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddlewareFromConfig } from \"@t402/fastify\";\n *\n * app.addHook('preHandler', paymentMiddlewareFromConfig(\n * routes,\n * myFacilitatorClient,\n * [{ network: \"eip155:8453\", server: evmSchemeServer }],\n * paywallConfig\n * ));\n * ```\n */\nexport function paymentMiddlewareFromConfig(\n routes: RoutesConfig,\n facilitatorClients?: FacilitatorClient | FacilitatorClient[],\n schemes?: SchemeRegistration[],\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n const ResourceServer = new t402ResourceServer(facilitatorClients);\n\n if (schemes) {\n schemes.forEach(({ network, server: schemeServer }) => {\n ResourceServer.register(network, schemeServer);\n });\n }\n\n // Use the direct paymentMiddleware with the configured server\n // Note: paymentMiddleware handles dynamic bazaar registration\n return paymentMiddleware(routes, ResourceServer, paywallConfig, paywall, syncFacilitatorOnStart);\n}\n\nexport { t402ResourceServer, t402HTTPResourceServer } from \"@t402/core/server\";\n\nexport type {\n PaymentRequired,\n PaymentRequirements,\n PaymentPayload,\n Network,\n SchemeNetworkServer,\n} from \"@t402/core/types\";\n\nexport type { PaywallProvider, PaywallConfig } from \"@t402/core/server\";\n\nexport { RouteConfigurationError } from \"@t402/core/server\";\n\nexport type { RouteValidationError } from \"@t402/core/server\";\n\nexport { FastifyAdapter } from \"./adapter.js\";\n","import { HTTPAdapter } from \"@t402/core/server\";\nimport type { FastifyRequest } from \"fastify\";\n\n/**\n * Fastify adapter implementation\n */\nexport class FastifyAdapter implements HTTPAdapter {\n /**\n * Creates a new FastifyAdapter instance.\n *\n * @param request - The Fastify request object\n */\n constructor(private request: FastifyRequest) {}\n\n /**\n * Gets a header value from the request.\n *\n * @param name - The header name\n * @returns The header value or undefined\n */\n getHeader(name: string): string | undefined {\n const value = this.request.headers[name.toLowerCase()];\n return Array.isArray(value) ? value[0] : value;\n }\n\n /**\n * Gets the HTTP method of the request.\n *\n * @returns The HTTP method\n */\n getMethod(): string {\n return this.request.method;\n }\n\n /**\n * Gets the path of the request (without query string).\n *\n * @returns The request path\n */\n getPath(): string {\n // Split URL to remove query string\n const url = this.request.url;\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : url.substring(0, queryIndex);\n }\n\n /**\n * Gets the full URL of the request.\n *\n * @returns The full request URL\n */\n getUrl(): string {\n return `${this.request.protocol}://${this.request.hostname}${this.request.url}`;\n }\n\n /**\n * Gets the Accept header from the request.\n *\n * @returns The Accept header value or empty string\n */\n getAcceptHeader(): string {\n return this.getHeader(\"accept\") || \"\";\n }\n\n /**\n * Gets the User-Agent header from the request.\n *\n * @returns The User-Agent header value or empty string\n */\n getUserAgent(): string {\n return this.getHeader(\"user-agent\") || \"\";\n }\n\n /**\n * Gets all query parameters from the request URL.\n *\n * @returns Record of query parameter key-value pairs\n */\n getQueryParams(): Record<string, string | string[]> {\n const query = this.request.query as Record<string, string | string[]> | undefined;\n if (!query) return {};\n\n const result: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(query)) {\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Gets a specific query parameter by name.\n *\n * @param name - The query parameter name\n * @returns The query parameter value(s) or undefined\n */\n getQueryParam(name: string): string | string[] | undefined {\n const query = this.request.query as Record<string, string | string[]> | undefined;\n if (!query) return undefined;\n return query[name];\n }\n\n /**\n * Gets the parsed request body.\n * Fastify parses the body automatically.\n *\n * @returns The parsed request body\n */\n async getBody(): Promise<unknown> {\n try {\n return this.request.body;\n } catch {\n return undefined;\n }\n }\n}\n"],"mappings":"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","names":["import_server","reply"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/adapter.ts"],"sourcesContent":["import {\n HTTPRequestContext,\n PaywallConfig,\n PaywallProvider,\n t402HTTPResourceServer,\n t402ResourceServer,\n RoutesConfig,\n FacilitatorClient,\n} from '@t402/core/server'\nimport { SchemeNetworkServer, Network } from '@t402/core/types'\nimport type {\n FastifyRequest,\n FastifyReply,\n preHandlerHookHandler,\n onSendHookHandler,\n} from 'fastify'\nimport { FastifyAdapter } from './adapter.js'\n\n// Extend FastifyReply to include addHook which exists at runtime but not in types\ninterface FastifyReplyWithHooks extends FastifyReply {\n addHook(name: 'onSend', hook: onSendHookHandler): void\n}\n\n/**\n * Check if any routes in the configuration declare bazaar extensions\n *\n * @param routes - Route configuration\n * @returns True if any route has extensions.bazaar defined\n */\nfunction checkIfBazaarNeeded(routes: RoutesConfig): boolean {\n // Handle single route config\n if ('accepts' in routes) {\n return !!(routes.extensions && 'bazaar' in routes.extensions)\n }\n\n // Handle multiple routes\n return Object.values(routes).some((routeConfig) => {\n return !!(routeConfig.extensions && 'bazaar' in routeConfig.extensions)\n })\n}\n\n/**\n * Configuration for registering a payment scheme with a specific network\n */\nexport interface SchemeRegistration {\n /**\n * The network identifier (e.g., 'eip155:84532', 'solana:mainnet')\n */\n network: Network\n\n /**\n * The scheme server implementation for this network\n */\n server: SchemeNetworkServer\n}\n\n/**\n * Fastify payment middleware for t402 protocol (direct server instance).\n *\n * Use this when you want to pass a pre-configured t402ResourceServer instance.\n * This provides more flexibility for testing, custom configuration, and reusing\n * server instances across multiple middlewares.\n *\n * @param routes - Route configurations for protected endpoints\n * @param server - Pre-configured t402ResourceServer instance\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddleware } from \"@t402/fastify\";\n * import { t402ResourceServer } from \"@t402/core/server\";\n * import { registerExactEvmScheme } from \"@t402/evm/exact/server\";\n *\n * const server = new t402ResourceServer(myFacilitatorClient);\n * registerExactEvmScheme(server, {});\n *\n * app.addHook('preHandler', paymentMiddleware(routes, server, paywallConfig));\n * ```\n */\nexport function paymentMiddleware(\n routes: RoutesConfig,\n server: t402ResourceServer,\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n // Create the t402 HTTP server instance with the resource server\n const httpServer = new t402HTTPResourceServer(server, routes)\n\n // Register custom paywall provider if provided\n if (paywall) {\n httpServer.registerPaywallProvider(paywall)\n }\n\n // Store initialization promise (not the result)\n // httpServer.initialize() fetches facilitator support and validates routes\n let initPromise: Promise<void> | null = syncFacilitatorOnStart ? httpServer.initialize() : null\n\n // Dynamically register bazaar extension if routes declare it\n let bazaarPromise: Promise<void> | null = null\n if (checkIfBazaarNeeded(routes)) {\n bazaarPromise = import('@t402/extensions/bazaar')\n .then(({ bazaarResourceServerExtension }) => {\n server.registerExtension(bazaarResourceServerExtension)\n })\n .catch((err) => {\n console.error('Failed to load bazaar extension:', err)\n })\n }\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n // Create adapter and context\n const adapter = new FastifyAdapter(request)\n const path = adapter.getPath()\n\n const context: HTTPRequestContext = {\n adapter,\n path,\n method: request.method,\n paymentHeader: adapter.getHeader('payment-signature') || adapter.getHeader('x-payment'),\n }\n\n // Check if route requires payment before initializing facilitator\n if (!httpServer.requiresPayment(context)) {\n return // Continue to route handler\n }\n\n // Only initialize when processing a protected route\n if (initPromise) {\n await initPromise\n initPromise = null // Clear after first await\n }\n\n // Await bazaar extension loading if needed\n if (bazaarPromise) {\n await bazaarPromise\n bazaarPromise = null\n }\n\n // Process payment requirement check\n const result = await httpServer.processHTTPRequest(context, paywallConfig)\n\n // Handle the different result types\n switch (result.type) {\n case 'no-payment-required':\n // No payment needed, proceed directly to the route handler\n return\n\n case 'payment-error': {\n // Payment required but not provided or invalid\n const { response } = result\n Object.entries(response.headers).forEach(([key, value]) => {\n reply.header(key, value)\n })\n if (response.isHtml) {\n return reply.code(response.status).type('text/html').send(response.body)\n } else {\n return reply.code(response.status).send(response.body || {})\n }\n }\n\n case 'payment-verified': {\n // Payment is valid, need to handle settlement after route handler\n const { paymentPayload, paymentRequirements } = result\n\n // Use onSend hook to handle settlement after route handler completes\n // Cast to extended interface since addHook exists at runtime\n const replyWithHooks = reply as unknown as FastifyReplyWithHooks\n replyWithHooks.addHook(\n 'onSend',\n async (_request: FastifyRequest, reply: FastifyReply, payload: unknown) => {\n // If the response from the protected route is >= 400, do not settle payment\n if (reply.statusCode >= 400) {\n return payload\n }\n\n try {\n const settleResult = await httpServer.processSettlement(\n paymentPayload,\n paymentRequirements,\n )\n\n if (!settleResult.success) {\n // Settlement failed - return error response\n reply.code(402)\n return JSON.stringify({\n error: 'Settlement failed',\n details: settleResult.errorReason,\n })\n }\n\n // Settlement succeeded - add headers to response\n Object.entries(settleResult.headers).forEach(([key, value]) => {\n reply.header(key, value)\n })\n\n return payload\n } catch (error) {\n console.error(error)\n // If settlement fails, return an error response\n reply.code(402)\n return JSON.stringify({\n error: 'Settlement failed',\n details: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n },\n )\n\n // Continue to route handler\n return\n }\n }\n }\n}\n\n/**\n * Fastify payment middleware for t402 protocol (configuration-based).\n *\n * Use this when you want to quickly set up middleware with simple configuration.\n * This function creates and configures the t402ResourceServer internally.\n *\n * @param routes - Route configurations for protected endpoints\n * @param facilitatorClients - Optional facilitator client(s) for payment processing\n * @param schemes - Optional array of scheme registrations for server-side payment processing\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddlewareFromConfig } from \"@t402/fastify\";\n *\n * app.addHook('preHandler', paymentMiddlewareFromConfig(\n * routes,\n * myFacilitatorClient,\n * [{ network: \"eip155:8453\", server: evmSchemeServer }],\n * paywallConfig\n * ));\n * ```\n */\nexport function paymentMiddlewareFromConfig(\n routes: RoutesConfig,\n facilitatorClients?: FacilitatorClient | FacilitatorClient[],\n schemes?: SchemeRegistration[],\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n const ResourceServer = new t402ResourceServer(facilitatorClients)\n\n if (schemes) {\n schemes.forEach(({ network, server: schemeServer }) => {\n ResourceServer.register(network, schemeServer)\n })\n }\n\n // Use the direct paymentMiddleware with the configured server\n // Note: paymentMiddleware handles dynamic bazaar registration\n return paymentMiddleware(routes, ResourceServer, paywallConfig, paywall, syncFacilitatorOnStart)\n}\n\nexport { t402ResourceServer, t402HTTPResourceServer } from '@t402/core/server'\n\nexport type {\n PaymentRequired,\n PaymentRequirements,\n PaymentPayload,\n Network,\n SchemeNetworkServer,\n} from '@t402/core/types'\n\nexport type { PaywallProvider, PaywallConfig } from '@t402/core/server'\n\nexport { RouteConfigurationError } from '@t402/core/server'\n\nexport type { RouteValidationError } from '@t402/core/server'\n\nexport { FastifyAdapter } from './adapter.js'\n","import { HTTPAdapter } from '@t402/core/server'\nimport type { FastifyRequest } from 'fastify'\n\n/**\n * Fastify adapter implementation\n */\nexport class FastifyAdapter implements HTTPAdapter {\n /**\n * Creates a new FastifyAdapter instance.\n *\n * @param request - The Fastify request object\n */\n constructor(private request: FastifyRequest) {}\n\n /**\n * Gets a header value from the request.\n *\n * @param name - The header name\n * @returns The header value or undefined\n */\n getHeader(name: string): string | undefined {\n const value = this.request.headers[name.toLowerCase()]\n return Array.isArray(value) ? value[0] : value\n }\n\n /**\n * Gets the HTTP method of the request.\n *\n * @returns The HTTP method\n */\n getMethod(): string {\n return this.request.method\n }\n\n /**\n * Gets the path of the request (without query string).\n *\n * @returns The request path\n */\n getPath(): string {\n // Split URL to remove query string\n const url = this.request.url\n const queryIndex = url.indexOf('?')\n return queryIndex === -1 ? url : url.substring(0, queryIndex)\n }\n\n /**\n * Gets the full URL of the request.\n *\n * @returns The full request URL\n */\n getUrl(): string {\n return `${this.request.protocol}://${this.request.hostname}${this.request.url}`\n }\n\n /**\n * Gets the Accept header from the request.\n *\n * @returns The Accept header value or empty string\n */\n getAcceptHeader(): string {\n return this.getHeader('accept') || ''\n }\n\n /**\n * Gets the User-Agent header from the request.\n *\n * @returns The User-Agent header value or empty string\n */\n getUserAgent(): string {\n return this.getHeader('user-agent') || ''\n }\n\n /**\n * Gets all query parameters from the request URL.\n *\n * @returns Record of query parameter key-value pairs\n */\n getQueryParams(): Record<string, string | string[]> {\n const query = this.request.query as Record<string, string | string[]> | undefined\n if (!query) return {}\n\n const result: Record<string, string | string[]> = {}\n for (const [key, value] of Object.entries(query)) {\n result[key] = value\n }\n return result\n }\n\n /**\n * Gets a specific query parameter by name.\n *\n * @param name - The query parameter name\n * @returns The query parameter value(s) or undefined\n */\n getQueryParam(name: string): string | string[] | undefined {\n const query = this.request.query as Record<string, string | string[]> | undefined\n if (!query) return undefined\n return query[name]\n }\n\n /**\n * Gets the parsed request body.\n * Fastify parses the body automatically.\n *\n * @returns The parsed request body\n */\n async getBody(): Promise<unknown> {\n try {\n return this.request.body\n } catch {\n return undefined\n }\n }\n}\n"],"mappings":"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","names":["import_server","reply"]}
|
package/dist/esm/index.mjs
CHANGED
|
@@ -169,35 +169,38 @@ function paymentMiddleware(routes, server, paywallConfig, paywall, syncFacilitat
|
|
|
169
169
|
case "payment-verified": {
|
|
170
170
|
const { paymentPayload, paymentRequirements } = result;
|
|
171
171
|
const replyWithHooks = reply;
|
|
172
|
-
replyWithHooks.addHook(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
replyWithHooks.addHook(
|
|
173
|
+
"onSend",
|
|
174
|
+
async (_request, reply2, payload) => {
|
|
175
|
+
if (reply2.statusCode >= 400) {
|
|
176
|
+
return payload;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const settleResult = await httpServer.processSettlement(
|
|
180
|
+
paymentPayload,
|
|
181
|
+
paymentRequirements
|
|
182
|
+
);
|
|
183
|
+
if (!settleResult.success) {
|
|
184
|
+
reply2.code(402);
|
|
185
|
+
return JSON.stringify({
|
|
186
|
+
error: "Settlement failed",
|
|
187
|
+
details: settleResult.errorReason
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
191
|
+
reply2.header(key, value);
|
|
192
|
+
});
|
|
193
|
+
return payload;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(error);
|
|
182
196
|
reply2.code(402);
|
|
183
197
|
return JSON.stringify({
|
|
184
198
|
error: "Settlement failed",
|
|
185
|
-
details:
|
|
199
|
+
details: error instanceof Error ? error.message : "Unknown error"
|
|
186
200
|
});
|
|
187
201
|
}
|
|
188
|
-
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
189
|
-
reply2.header(key, value);
|
|
190
|
-
});
|
|
191
|
-
return payload;
|
|
192
|
-
} catch (error) {
|
|
193
|
-
console.error(error);
|
|
194
|
-
reply2.code(402);
|
|
195
|
-
return JSON.stringify({
|
|
196
|
-
error: "Settlement failed",
|
|
197
|
-
details: error instanceof Error ? error.message : "Unknown error"
|
|
198
|
-
});
|
|
199
202
|
}
|
|
200
|
-
|
|
203
|
+
);
|
|
201
204
|
return;
|
|
202
205
|
}
|
|
203
206
|
}
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/adapter.ts"],"sourcesContent":["import {\n HTTPRequestContext,\n PaywallConfig,\n PaywallProvider,\n t402HTTPResourceServer,\n t402ResourceServer,\n RoutesConfig,\n FacilitatorClient,\n} from \"@t402/core/server\";\nimport { SchemeNetworkServer, Network } from \"@t402/core/types\";\nimport type { FastifyRequest, FastifyReply, preHandlerHookHandler, onSendHookHandler } from \"fastify\";\nimport { FastifyAdapter } from \"./adapter.js\";\n\n// Extend FastifyReply to include addHook which exists at runtime but not in types\ninterface FastifyReplyWithHooks extends FastifyReply {\n addHook(name: \"onSend\", hook: onSendHookHandler): void;\n}\n\n/**\n * Check if any routes in the configuration declare bazaar extensions\n *\n * @param routes - Route configuration\n * @returns True if any route has extensions.bazaar defined\n */\nfunction checkIfBazaarNeeded(routes: RoutesConfig): boolean {\n // Handle single route config\n if (\"accepts\" in routes) {\n return !!(routes.extensions && \"bazaar\" in routes.extensions);\n }\n\n // Handle multiple routes\n return Object.values(routes).some(routeConfig => {\n return !!(routeConfig.extensions && \"bazaar\" in routeConfig.extensions);\n });\n}\n\n/**\n * Configuration for registering a payment scheme with a specific network\n */\nexport interface SchemeRegistration {\n /**\n * The network identifier (e.g., 'eip155:84532', 'solana:mainnet')\n */\n network: Network;\n\n /**\n * The scheme server implementation for this network\n */\n server: SchemeNetworkServer;\n}\n\n/**\n * Fastify payment middleware for t402 protocol (direct server instance).\n *\n * Use this when you want to pass a pre-configured t402ResourceServer instance.\n * This provides more flexibility for testing, custom configuration, and reusing\n * server instances across multiple middlewares.\n *\n * @param routes - Route configurations for protected endpoints\n * @param server - Pre-configured t402ResourceServer instance\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddleware } from \"@t402/fastify\";\n * import { t402ResourceServer } from \"@t402/core/server\";\n * import { registerExactEvmScheme } from \"@t402/evm/exact/server\";\n *\n * const server = new t402ResourceServer(myFacilitatorClient);\n * registerExactEvmScheme(server, {});\n *\n * app.addHook('preHandler', paymentMiddleware(routes, server, paywallConfig));\n * ```\n */\nexport function paymentMiddleware(\n routes: RoutesConfig,\n server: t402ResourceServer,\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n // Create the t402 HTTP server instance with the resource server\n const httpServer = new t402HTTPResourceServer(server, routes);\n\n // Register custom paywall provider if provided\n if (paywall) {\n httpServer.registerPaywallProvider(paywall);\n }\n\n // Store initialization promise (not the result)\n // httpServer.initialize() fetches facilitator support and validates routes\n let initPromise: Promise<void> | null = syncFacilitatorOnStart ? httpServer.initialize() : null;\n\n // Dynamically register bazaar extension if routes declare it\n let bazaarPromise: Promise<void> | null = null;\n if (checkIfBazaarNeeded(routes)) {\n bazaarPromise = import(\"@t402/extensions/bazaar\")\n .then(({ bazaarResourceServerExtension }) => {\n server.registerExtension(bazaarResourceServerExtension);\n })\n .catch(err => {\n console.error(\"Failed to load bazaar extension:\", err);\n });\n }\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n // Create adapter and context\n const adapter = new FastifyAdapter(request);\n const path = adapter.getPath();\n\n const context: HTTPRequestContext = {\n adapter,\n path,\n method: request.method,\n paymentHeader: adapter.getHeader(\"payment-signature\") || adapter.getHeader(\"x-payment\"),\n };\n\n // Check if route requires payment before initializing facilitator\n if (!httpServer.requiresPayment(context)) {\n return; // Continue to route handler\n }\n\n // Only initialize when processing a protected route\n if (initPromise) {\n await initPromise;\n initPromise = null; // Clear after first await\n }\n\n // Await bazaar extension loading if needed\n if (bazaarPromise) {\n await bazaarPromise;\n bazaarPromise = null;\n }\n\n // Process payment requirement check\n const result = await httpServer.processHTTPRequest(context, paywallConfig);\n\n // Handle the different result types\n switch (result.type) {\n case \"no-payment-required\":\n // No payment needed, proceed directly to the route handler\n return;\n\n case \"payment-error\": {\n // Payment required but not provided or invalid\n const { response } = result;\n Object.entries(response.headers).forEach(([key, value]) => {\n reply.header(key, value);\n });\n if (response.isHtml) {\n return reply.code(response.status).type(\"text/html\").send(response.body);\n } else {\n return reply.code(response.status).send(response.body || {});\n }\n }\n\n case \"payment-verified\": {\n // Payment is valid, need to handle settlement after route handler\n const { paymentPayload, paymentRequirements } = result;\n\n // Use onSend hook to handle settlement after route handler completes\n // Cast to extended interface since addHook exists at runtime\n const replyWithHooks = reply as unknown as FastifyReplyWithHooks;\n replyWithHooks.addHook(\"onSend\", async (_request: FastifyRequest, reply: FastifyReply, payload: unknown) => {\n // If the response from the protected route is >= 400, do not settle payment\n if (reply.statusCode >= 400) {\n return payload;\n }\n\n try {\n const settleResult = await httpServer.processSettlement(\n paymentPayload,\n paymentRequirements,\n );\n\n if (!settleResult.success) {\n // Settlement failed - return error response\n reply.code(402);\n return JSON.stringify({\n error: \"Settlement failed\",\n details: settleResult.errorReason,\n });\n }\n\n // Settlement succeeded - add headers to response\n Object.entries(settleResult.headers).forEach(([key, value]) => {\n reply.header(key, value);\n });\n\n return payload;\n } catch (error) {\n console.error(error);\n // If settlement fails, return an error response\n reply.code(402);\n return JSON.stringify({\n error: \"Settlement failed\",\n details: error instanceof Error ? error.message : \"Unknown error\",\n });\n }\n });\n\n // Continue to route handler\n return;\n }\n }\n };\n}\n\n/**\n * Fastify payment middleware for t402 protocol (configuration-based).\n *\n * Use this when you want to quickly set up middleware with simple configuration.\n * This function creates and configures the t402ResourceServer internally.\n *\n * @param routes - Route configurations for protected endpoints\n * @param facilitatorClients - Optional facilitator client(s) for payment processing\n * @param schemes - Optional array of scheme registrations for server-side payment processing\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddlewareFromConfig } from \"@t402/fastify\";\n *\n * app.addHook('preHandler', paymentMiddlewareFromConfig(\n * routes,\n * myFacilitatorClient,\n * [{ network: \"eip155:8453\", server: evmSchemeServer }],\n * paywallConfig\n * ));\n * ```\n */\nexport function paymentMiddlewareFromConfig(\n routes: RoutesConfig,\n facilitatorClients?: FacilitatorClient | FacilitatorClient[],\n schemes?: SchemeRegistration[],\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n const ResourceServer = new t402ResourceServer(facilitatorClients);\n\n if (schemes) {\n schemes.forEach(({ network, server: schemeServer }) => {\n ResourceServer.register(network, schemeServer);\n });\n }\n\n // Use the direct paymentMiddleware with the configured server\n // Note: paymentMiddleware handles dynamic bazaar registration\n return paymentMiddleware(routes, ResourceServer, paywallConfig, paywall, syncFacilitatorOnStart);\n}\n\nexport { t402ResourceServer, t402HTTPResourceServer } from \"@t402/core/server\";\n\nexport type {\n PaymentRequired,\n PaymentRequirements,\n PaymentPayload,\n Network,\n SchemeNetworkServer,\n} from \"@t402/core/types\";\n\nexport type { PaywallProvider, PaywallConfig } from \"@t402/core/server\";\n\nexport { RouteConfigurationError } from \"@t402/core/server\";\n\nexport type { RouteValidationError } from \"@t402/core/server\";\n\nexport { FastifyAdapter } from \"./adapter.js\";\n","import { HTTPAdapter } from \"@t402/core/server\";\nimport type { FastifyRequest } from \"fastify\";\n\n/**\n * Fastify adapter implementation\n */\nexport class FastifyAdapter implements HTTPAdapter {\n /**\n * Creates a new FastifyAdapter instance.\n *\n * @param request - The Fastify request object\n */\n constructor(private request: FastifyRequest) {}\n\n /**\n * Gets a header value from the request.\n *\n * @param name - The header name\n * @returns The header value or undefined\n */\n getHeader(name: string): string | undefined {\n const value = this.request.headers[name.toLowerCase()];\n return Array.isArray(value) ? value[0] : value;\n }\n\n /**\n * Gets the HTTP method of the request.\n *\n * @returns The HTTP method\n */\n getMethod(): string {\n return this.request.method;\n }\n\n /**\n * Gets the path of the request (without query string).\n *\n * @returns The request path\n */\n getPath(): string {\n // Split URL to remove query string\n const url = this.request.url;\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : url.substring(0, queryIndex);\n }\n\n /**\n * Gets the full URL of the request.\n *\n * @returns The full request URL\n */\n getUrl(): string {\n return `${this.request.protocol}://${this.request.hostname}${this.request.url}`;\n }\n\n /**\n * Gets the Accept header from the request.\n *\n * @returns The Accept header value or empty string\n */\n getAcceptHeader(): string {\n return this.getHeader(\"accept\") || \"\";\n }\n\n /**\n * Gets the User-Agent header from the request.\n *\n * @returns The User-Agent header value or empty string\n */\n getUserAgent(): string {\n return this.getHeader(\"user-agent\") || \"\";\n }\n\n /**\n * Gets all query parameters from the request URL.\n *\n * @returns Record of query parameter key-value pairs\n */\n getQueryParams(): Record<string, string | string[]> {\n const query = this.request.query as Record<string, string | string[]> | undefined;\n if (!query) return {};\n\n const result: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(query)) {\n result[key] = value;\n }\n return result;\n }\n\n /**\n * Gets a specific query parameter by name.\n *\n * @param name - The query parameter name\n * @returns The query parameter value(s) or undefined\n */\n getQueryParam(name: string): string | string[] | undefined {\n const query = this.request.query as Record<string, string | string[]> | undefined;\n if (!query) return undefined;\n return query[name];\n }\n\n /**\n * Gets the parsed request body.\n * Fastify parses the body automatically.\n *\n * @returns The parsed request body\n */\n async getBody(): Promise<unknown> {\n try {\n return this.request.body;\n } catch {\n return undefined;\n }\n }\n}\n"],"mappings":"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","names":["t402ResourceServer","t402HTTPResourceServer","reply"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/adapter.ts"],"sourcesContent":["import {\n HTTPRequestContext,\n PaywallConfig,\n PaywallProvider,\n t402HTTPResourceServer,\n t402ResourceServer,\n RoutesConfig,\n FacilitatorClient,\n} from '@t402/core/server'\nimport { SchemeNetworkServer, Network } from '@t402/core/types'\nimport type {\n FastifyRequest,\n FastifyReply,\n preHandlerHookHandler,\n onSendHookHandler,\n} from 'fastify'\nimport { FastifyAdapter } from './adapter.js'\n\n// Extend FastifyReply to include addHook which exists at runtime but not in types\ninterface FastifyReplyWithHooks extends FastifyReply {\n addHook(name: 'onSend', hook: onSendHookHandler): void\n}\n\n/**\n * Check if any routes in the configuration declare bazaar extensions\n *\n * @param routes - Route configuration\n * @returns True if any route has extensions.bazaar defined\n */\nfunction checkIfBazaarNeeded(routes: RoutesConfig): boolean {\n // Handle single route config\n if ('accepts' in routes) {\n return !!(routes.extensions && 'bazaar' in routes.extensions)\n }\n\n // Handle multiple routes\n return Object.values(routes).some((routeConfig) => {\n return !!(routeConfig.extensions && 'bazaar' in routeConfig.extensions)\n })\n}\n\n/**\n * Configuration for registering a payment scheme with a specific network\n */\nexport interface SchemeRegistration {\n /**\n * The network identifier (e.g., 'eip155:84532', 'solana:mainnet')\n */\n network: Network\n\n /**\n * The scheme server implementation for this network\n */\n server: SchemeNetworkServer\n}\n\n/**\n * Fastify payment middleware for t402 protocol (direct server instance).\n *\n * Use this when you want to pass a pre-configured t402ResourceServer instance.\n * This provides more flexibility for testing, custom configuration, and reusing\n * server instances across multiple middlewares.\n *\n * @param routes - Route configurations for protected endpoints\n * @param server - Pre-configured t402ResourceServer instance\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddleware } from \"@t402/fastify\";\n * import { t402ResourceServer } from \"@t402/core/server\";\n * import { registerExactEvmScheme } from \"@t402/evm/exact/server\";\n *\n * const server = new t402ResourceServer(myFacilitatorClient);\n * registerExactEvmScheme(server, {});\n *\n * app.addHook('preHandler', paymentMiddleware(routes, server, paywallConfig));\n * ```\n */\nexport function paymentMiddleware(\n routes: RoutesConfig,\n server: t402ResourceServer,\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n // Create the t402 HTTP server instance with the resource server\n const httpServer = new t402HTTPResourceServer(server, routes)\n\n // Register custom paywall provider if provided\n if (paywall) {\n httpServer.registerPaywallProvider(paywall)\n }\n\n // Store initialization promise (not the result)\n // httpServer.initialize() fetches facilitator support and validates routes\n let initPromise: Promise<void> | null = syncFacilitatorOnStart ? httpServer.initialize() : null\n\n // Dynamically register bazaar extension if routes declare it\n let bazaarPromise: Promise<void> | null = null\n if (checkIfBazaarNeeded(routes)) {\n bazaarPromise = import('@t402/extensions/bazaar')\n .then(({ bazaarResourceServerExtension }) => {\n server.registerExtension(bazaarResourceServerExtension)\n })\n .catch((err) => {\n console.error('Failed to load bazaar extension:', err)\n })\n }\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n // Create adapter and context\n const adapter = new FastifyAdapter(request)\n const path = adapter.getPath()\n\n const context: HTTPRequestContext = {\n adapter,\n path,\n method: request.method,\n paymentHeader: adapter.getHeader('payment-signature') || adapter.getHeader('x-payment'),\n }\n\n // Check if route requires payment before initializing facilitator\n if (!httpServer.requiresPayment(context)) {\n return // Continue to route handler\n }\n\n // Only initialize when processing a protected route\n if (initPromise) {\n await initPromise\n initPromise = null // Clear after first await\n }\n\n // Await bazaar extension loading if needed\n if (bazaarPromise) {\n await bazaarPromise\n bazaarPromise = null\n }\n\n // Process payment requirement check\n const result = await httpServer.processHTTPRequest(context, paywallConfig)\n\n // Handle the different result types\n switch (result.type) {\n case 'no-payment-required':\n // No payment needed, proceed directly to the route handler\n return\n\n case 'payment-error': {\n // Payment required but not provided or invalid\n const { response } = result\n Object.entries(response.headers).forEach(([key, value]) => {\n reply.header(key, value)\n })\n if (response.isHtml) {\n return reply.code(response.status).type('text/html').send(response.body)\n } else {\n return reply.code(response.status).send(response.body || {})\n }\n }\n\n case 'payment-verified': {\n // Payment is valid, need to handle settlement after route handler\n const { paymentPayload, paymentRequirements } = result\n\n // Use onSend hook to handle settlement after route handler completes\n // Cast to extended interface since addHook exists at runtime\n const replyWithHooks = reply as unknown as FastifyReplyWithHooks\n replyWithHooks.addHook(\n 'onSend',\n async (_request: FastifyRequest, reply: FastifyReply, payload: unknown) => {\n // If the response from the protected route is >= 400, do not settle payment\n if (reply.statusCode >= 400) {\n return payload\n }\n\n try {\n const settleResult = await httpServer.processSettlement(\n paymentPayload,\n paymentRequirements,\n )\n\n if (!settleResult.success) {\n // Settlement failed - return error response\n reply.code(402)\n return JSON.stringify({\n error: 'Settlement failed',\n details: settleResult.errorReason,\n })\n }\n\n // Settlement succeeded - add headers to response\n Object.entries(settleResult.headers).forEach(([key, value]) => {\n reply.header(key, value)\n })\n\n return payload\n } catch (error) {\n console.error(error)\n // If settlement fails, return an error response\n reply.code(402)\n return JSON.stringify({\n error: 'Settlement failed',\n details: error instanceof Error ? error.message : 'Unknown error',\n })\n }\n },\n )\n\n // Continue to route handler\n return\n }\n }\n }\n}\n\n/**\n * Fastify payment middleware for t402 protocol (configuration-based).\n *\n * Use this when you want to quickly set up middleware with simple configuration.\n * This function creates and configures the t402ResourceServer internally.\n *\n * @param routes - Route configurations for protected endpoints\n * @param facilitatorClients - Optional facilitator client(s) for payment processing\n * @param schemes - Optional array of scheme registrations for server-side payment processing\n * @param paywallConfig - Optional configuration for the built-in paywall UI\n * @param paywall - Optional custom paywall provider (overrides default)\n * @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)\n * @returns Fastify preHandler hook\n *\n * @example\n * ```typescript\n * import { paymentMiddlewareFromConfig } from \"@t402/fastify\";\n *\n * app.addHook('preHandler', paymentMiddlewareFromConfig(\n * routes,\n * myFacilitatorClient,\n * [{ network: \"eip155:8453\", server: evmSchemeServer }],\n * paywallConfig\n * ));\n * ```\n */\nexport function paymentMiddlewareFromConfig(\n routes: RoutesConfig,\n facilitatorClients?: FacilitatorClient | FacilitatorClient[],\n schemes?: SchemeRegistration[],\n paywallConfig?: PaywallConfig,\n paywall?: PaywallProvider,\n syncFacilitatorOnStart: boolean = true,\n): preHandlerHookHandler {\n const ResourceServer = new t402ResourceServer(facilitatorClients)\n\n if (schemes) {\n schemes.forEach(({ network, server: schemeServer }) => {\n ResourceServer.register(network, schemeServer)\n })\n }\n\n // Use the direct paymentMiddleware with the configured server\n // Note: paymentMiddleware handles dynamic bazaar registration\n return paymentMiddleware(routes, ResourceServer, paywallConfig, paywall, syncFacilitatorOnStart)\n}\n\nexport { t402ResourceServer, t402HTTPResourceServer } from '@t402/core/server'\n\nexport type {\n PaymentRequired,\n PaymentRequirements,\n PaymentPayload,\n Network,\n SchemeNetworkServer,\n} from '@t402/core/types'\n\nexport type { PaywallProvider, PaywallConfig } from '@t402/core/server'\n\nexport { RouteConfigurationError } from '@t402/core/server'\n\nexport type { RouteValidationError } from '@t402/core/server'\n\nexport { FastifyAdapter } from './adapter.js'\n","import { HTTPAdapter } from '@t402/core/server'\nimport type { FastifyRequest } from 'fastify'\n\n/**\n * Fastify adapter implementation\n */\nexport class FastifyAdapter implements HTTPAdapter {\n /**\n * Creates a new FastifyAdapter instance.\n *\n * @param request - The Fastify request object\n */\n constructor(private request: FastifyRequest) {}\n\n /**\n * Gets a header value from the request.\n *\n * @param name - The header name\n * @returns The header value or undefined\n */\n getHeader(name: string): string | undefined {\n const value = this.request.headers[name.toLowerCase()]\n return Array.isArray(value) ? value[0] : value\n }\n\n /**\n * Gets the HTTP method of the request.\n *\n * @returns The HTTP method\n */\n getMethod(): string {\n return this.request.method\n }\n\n /**\n * Gets the path of the request (without query string).\n *\n * @returns The request path\n */\n getPath(): string {\n // Split URL to remove query string\n const url = this.request.url\n const queryIndex = url.indexOf('?')\n return queryIndex === -1 ? url : url.substring(0, queryIndex)\n }\n\n /**\n * Gets the full URL of the request.\n *\n * @returns The full request URL\n */\n getUrl(): string {\n return `${this.request.protocol}://${this.request.hostname}${this.request.url}`\n }\n\n /**\n * Gets the Accept header from the request.\n *\n * @returns The Accept header value or empty string\n */\n getAcceptHeader(): string {\n return this.getHeader('accept') || ''\n }\n\n /**\n * Gets the User-Agent header from the request.\n *\n * @returns The User-Agent header value or empty string\n */\n getUserAgent(): string {\n return this.getHeader('user-agent') || ''\n }\n\n /**\n * Gets all query parameters from the request URL.\n *\n * @returns Record of query parameter key-value pairs\n */\n getQueryParams(): Record<string, string | string[]> {\n const query = this.request.query as Record<string, string | string[]> | undefined\n if (!query) return {}\n\n const result: Record<string, string | string[]> = {}\n for (const [key, value] of Object.entries(query)) {\n result[key] = value\n }\n return result\n }\n\n /**\n * Gets a specific query parameter by name.\n *\n * @param name - The query parameter name\n * @returns The query parameter value(s) or undefined\n */\n getQueryParam(name: string): string | string[] | undefined {\n const query = this.request.query as Record<string, string | string[]> | undefined\n if (!query) return undefined\n return query[name]\n }\n\n /**\n * Gets the parsed request body.\n * Fastify parses the body automatically.\n *\n * @returns The parsed request body\n */\n async getBody(): Promise<unknown> {\n try {\n return this.request.body\n } catch {\n return undefined\n }\n }\n}\n"],"mappings":";AAAA;AAAA,EAIE;AAAA,EACA;AAAA,OAGK;;;ACFA,IAAM,iBAAN,MAA4C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjD,YAAoB,SAAyB;AAAzB;AAAA,EAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ9C,UAAU,MAAkC;AAC1C,UAAM,QAAQ,KAAK,QAAQ,QAAQ,KAAK,YAAY,CAAC;AACrD,WAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAoB;AAClB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAkB;AAEhB,UAAM,MAAM,KAAK,QAAQ;AACzB,UAAM,aAAa,IAAI,QAAQ,GAAG;AAClC,WAAO,eAAe,KAAK,MAAM,IAAI,UAAU,GAAG,UAAU;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAiB;AACf,WAAO,GAAG,KAAK,QAAQ,QAAQ,MAAM,KAAK,QAAQ,QAAQ,GAAG,KAAK,QAAQ,GAAG;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,kBAA0B;AACxB,WAAO,KAAK,UAAU,QAAQ,KAAK;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,eAAuB;AACrB,WAAO,KAAK,UAAU,YAAY,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,iBAAoD;AAClD,UAAM,QAAQ,KAAK,QAAQ;AAC3B,QAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,UAAM,SAA4C,CAAC;AACnD,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,aAAO,GAAG,IAAI;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAc,MAA6C;AACzD,UAAM,QAAQ,KAAK,QAAQ;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,MAAM,IAAI;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAA4B;AAChC,QAAI;AACF,aAAO,KAAK,QAAQ;AAAA,IACtB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;ADwJA,SAAS,sBAAAA,qBAAoB,0BAAAC,+BAA8B;AAY3D,SAAS,+BAA+B;AAzPxC,SAAS,oBAAoB,QAA+B;AAE1D,MAAI,aAAa,QAAQ;AACvB,WAAO,CAAC,EAAE,OAAO,cAAc,YAAY,OAAO;AAAA,EACpD;AAGA,SAAO,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC,gBAAgB;AACjD,WAAO,CAAC,EAAE,YAAY,cAAc,YAAY,YAAY;AAAA,EAC9D,CAAC;AACH;AA2CO,SAAS,kBACd,QACA,QACA,eACA,SACA,yBAAkC,MACX;AAEvB,QAAM,aAAa,IAAI,uBAAuB,QAAQ,MAAM;AAG5D,MAAI,SAAS;AACX,eAAW,wBAAwB,OAAO;AAAA,EAC5C;AAIA,MAAI,cAAoC,yBAAyB,WAAW,WAAW,IAAI;AAG3F,MAAI,gBAAsC;AAC1C,MAAI,oBAAoB,MAAM,GAAG;AAC/B,oBAAgB,OAAO,yBAAyB,EAC7C,KAAK,CAAC,EAAE,8BAA8B,MAAM;AAC3C,aAAO,kBAAkB,6BAA6B;AAAA,IACxD,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,cAAQ,MAAM,oCAAoC,GAAG;AAAA,IACvD,CAAC;AAAA,EACL;AAEA,SAAO,OAAO,SAAyB,UAAwB;AAE7D,UAAM,UAAU,IAAI,eAAe,OAAO;AAC1C,UAAM,OAAO,QAAQ,QAAQ;AAE7B,UAAM,UAA8B;AAAA,MAClC;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,eAAe,QAAQ,UAAU,mBAAmB,KAAK,QAAQ,UAAU,WAAW;AAAA,IACxF;AAGA,QAAI,CAAC,WAAW,gBAAgB,OAAO,GAAG;AACxC;AAAA,IACF;AAGA,QAAI,aAAa;AACf,YAAM;AACN,oBAAc;AAAA,IAChB;AAGA,QAAI,eAAe;AACjB,YAAM;AACN,sBAAgB;AAAA,IAClB;AAGA,UAAM,SAAS,MAAM,WAAW,mBAAmB,SAAS,aAAa;AAGzE,YAAQ,OAAO,MAAM;AAAA,MACnB,KAAK;AAEH;AAAA,MAEF,KAAK,iBAAiB;AAEpB,cAAM,EAAE,SAAS,IAAI;AACrB,eAAO,QAAQ,SAAS,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACzD,gBAAM,OAAO,KAAK,KAAK;AAAA,QACzB,CAAC;AACD,YAAI,SAAS,QAAQ;AACnB,iBAAO,MAAM,KAAK,SAAS,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,IAAI;AAAA,QACzE,OAAO;AACL,iBAAO,MAAM,KAAK,SAAS,MAAM,EAAE,KAAK,SAAS,QAAQ,CAAC,CAAC;AAAA,QAC7D;AAAA,MACF;AAAA,MAEA,KAAK,oBAAoB;AAEvB,cAAM,EAAE,gBAAgB,oBAAoB,IAAI;AAIhD,cAAM,iBAAiB;AACvB,uBAAe;AAAA,UACb;AAAA,UACA,OAAO,UAA0BC,QAAqB,YAAqB;AAEzE,gBAAIA,OAAM,cAAc,KAAK;AAC3B,qBAAO;AAAA,YACT;AAEA,gBAAI;AACF,oBAAM,eAAe,MAAM,WAAW;AAAA,gBACpC;AAAA,gBACA;AAAA,cACF;AAEA,kBAAI,CAAC,aAAa,SAAS;AAEzB,gBAAAA,OAAM,KAAK,GAAG;AACd,uBAAO,KAAK,UAAU;AAAA,kBACpB,OAAO;AAAA,kBACP,SAAS,aAAa;AAAA,gBACxB,CAAC;AAAA,cACH;AAGA,qBAAO,QAAQ,aAAa,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAC7D,gBAAAA,OAAM,OAAO,KAAK,KAAK;AAAA,cACzB,CAAC;AAED,qBAAO;AAAA,YACT,SAAS,OAAO;AACd,sBAAQ,MAAM,KAAK;AAEnB,cAAAA,OAAM,KAAK,GAAG;AACd,qBAAO,KAAK,UAAU;AAAA,gBACpB,OAAO;AAAA,gBACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU;AAAA,cACpD,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAGA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AA4BO,SAAS,4BACd,QACA,oBACA,SACA,eACA,SACA,yBAAkC,MACX;AACvB,QAAM,iBAAiB,IAAI,mBAAmB,kBAAkB;AAEhE,MAAI,SAAS;AACX,YAAQ,QAAQ,CAAC,EAAE,SAAS,QAAQ,aAAa,MAAM;AACrD,qBAAe,SAAS,SAAS,YAAY;AAAA,IAC/C,CAAC;AAAA,EACH;AAIA,SAAO,kBAAkB,QAAQ,gBAAgB,eAAe,SAAS,sBAAsB;AACjG;","names":["t402ResourceServer","t402HTTPResourceServer","reply"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t402/fastify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"main": "./dist/cjs/index.js",
|
|
5
5
|
"module": "./dist/esm/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -26,23 +26,24 @@
|
|
|
26
26
|
"eslint-plugin-import": "^2.31.0",
|
|
27
27
|
"eslint-plugin-jsdoc": "^50.6.9",
|
|
28
28
|
"eslint-plugin-prettier": "^5.2.6",
|
|
29
|
-
"fastify": "^5.
|
|
29
|
+
"fastify": "^5.7.0",
|
|
30
|
+
"glob": "^13.0.0",
|
|
30
31
|
"prettier": "3.5.2",
|
|
31
32
|
"tsup": "^8.4.0",
|
|
32
|
-
"tsx": "^4.
|
|
33
|
+
"tsx": "^4.21.0",
|
|
33
34
|
"typescript": "^5.7.3",
|
|
34
|
-
"vite": "^
|
|
35
|
+
"vite": "^7.3.1",
|
|
35
36
|
"vite-tsconfig-paths": "^5.1.4",
|
|
36
|
-
"vitest": "^3.
|
|
37
|
+
"vitest": "^3.2.4"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
40
|
"zod": "^3.24.2",
|
|
40
|
-
"@t402/core": "
|
|
41
|
-
"@t402/extensions": "
|
|
41
|
+
"@t402/core": "2.3.0",
|
|
42
|
+
"@t402/extensions": "2.3.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"fastify": "^4.0.0 || ^5.0.0",
|
|
45
|
-
"@t402/paywall": "2.
|
|
46
|
+
"@t402/paywall": "2.3.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependenciesMeta": {
|
|
48
49
|
"@t402/paywall": {
|