@foldset/cloudflare 0.0.1 → 0.0.3
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/ai-crawlers/index.d.ts +15 -0
- package/dist/ai-crawlers/index.d.ts.map +1 -0
- package/dist/ai-crawlers/index.js +26 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +1 -0
- package/dist/facilitators/index.d.ts +16 -0
- package/dist/facilitators/index.d.ts.map +1 -0
- package/dist/facilitators/index.js +35 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/hono.js +1 -0
- package/dist/index.d.ts +8 -37
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -237
- package/dist/payment/adapter.d.ts +16 -0
- package/dist/payment/adapter.d.ts.map +1 -0
- package/dist/payment/adapter.js +43 -0
- package/dist/payment/handler.d.ts +6 -0
- package/dist/payment/handler.d.ts.map +1 -0
- package/dist/payment/handler.js +86 -0
- package/dist/payment/paywall.d.ts +4 -0
- package/dist/payment/paywall.d.ts.map +1 -0
- package/dist/payment/paywall.js +99 -0
- package/dist/payment/routes.d.ts +5 -0
- package/dist/payment/routes.d.ts.map +1 -0
- package/dist/payment/routes.js +31 -0
- package/dist/payment/settlement.d.ts +8 -0
- package/dist/payment/settlement.d.ts.map +1 -0
- package/dist/payment/settlement.js +27 -0
- package/dist/payment/setup.d.ts +7 -0
- package/dist/payment/setup.d.ts.map +1 -0
- package/dist/payment/setup.js +85 -0
- package/dist/payment-methods/index.d.ts +18 -0
- package/dist/payment-methods/index.d.ts.map +1 -0
- package/dist/payment-methods/index.js +18 -0
- package/dist/restrictions/index.d.ts +16 -0
- package/dist/restrictions/index.d.ts.map +1 -0
- package/dist/restrictions/index.js +18 -0
- package/dist/telemetry/logging.d.ts +18 -0
- package/dist/telemetry/logging.d.ts.map +1 -0
- package/dist/telemetry/logging.js +47 -0
- package/dist/telemetry/sentry.d.ts +8 -0
- package/dist/telemetry/sentry.d.ts.map +1 -0
- package/dist/telemetry/sentry.js +19 -0
- package/dist/types.d.ts +5 -9
- package/dist/types.d.ts.map +1 -0
- package/dist/webhooks/index.d.ts +23 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +50 -0
- package/package.json +21 -18
- package/README.md +0 -1
- package/dist/adapter.d.ts +0 -71
- package/dist/adapter.js +0 -99
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { Env } from "../types";
|
|
3
|
+
export interface AiCrawler {
|
|
4
|
+
user_agent: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getAiCrawlers(c: Context<{
|
|
7
|
+
Bindings: Env;
|
|
8
|
+
}>): Promise<AiCrawler[]>;
|
|
9
|
+
export declare function storeAiCrawlers(c: Context<{
|
|
10
|
+
Bindings: Env;
|
|
11
|
+
}>, aiCrawlers: AiCrawler[]): Promise<void>;
|
|
12
|
+
export declare function isAiCrawler(c: Context<{
|
|
13
|
+
Bindings: Env;
|
|
14
|
+
}>): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai-crawlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,wBAAsB,aAAa,CACjC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,SAAS,EAAE,CAAC,CAWtB;AAED,wBAAsB,eAAe,CACnC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,UAAU,EAAE,SAAS,EAAE,GACtB,OAAO,CAAC,IAAI,CAAC,CAIf;AAED,wBAAsB,WAAW,CAC/B,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAKjD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const CACHE_TTL_MS = 30_000;
|
|
2
|
+
let cachedAiCrawlers = [];
|
|
3
|
+
let cacheTimestamp = 0;
|
|
4
|
+
export async function getAiCrawlers(c) {
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
if (cachedAiCrawlers.length > 0 && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
7
|
+
return cachedAiCrawlers;
|
|
8
|
+
}
|
|
9
|
+
const response = await c.env.FOLDSET_CONFIG.get("ai-crawlers");
|
|
10
|
+
const parsed = response ? JSON.parse(response) : [];
|
|
11
|
+
cachedAiCrawlers = parsed.map((c) => ({ user_agent: c.user_agent.toLowerCase() }));
|
|
12
|
+
cacheTimestamp = now;
|
|
13
|
+
return cachedAiCrawlers;
|
|
14
|
+
}
|
|
15
|
+
export async function storeAiCrawlers(c, aiCrawlers) {
|
|
16
|
+
await c.env.FOLDSET_CONFIG.put("ai-crawlers", JSON.stringify(aiCrawlers), {
|
|
17
|
+
expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function isAiCrawler(c) {
|
|
21
|
+
const aiCrawlers = await getAiCrawlers(c);
|
|
22
|
+
const userAgent = c.req.header("User-Agent")?.toLowerCase();
|
|
23
|
+
if (!userAgent)
|
|
24
|
+
return false;
|
|
25
|
+
return aiCrawlers.some((crawler) => userAgent.includes(crawler.user_agent));
|
|
26
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,+BAA+B,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const API_BASE_URL = "https://api.foldset.com/v1";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
import type { Env } from "../types";
|
|
3
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
4
|
+
export interface FacilitatorConfig {
|
|
5
|
+
url: string;
|
|
6
|
+
verifyHeaders?: Record<string, string>;
|
|
7
|
+
settleHeaders?: Record<string, string>;
|
|
8
|
+
supportedHeaders?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function getFacilitator(c: Context<{
|
|
11
|
+
Bindings: Env;
|
|
12
|
+
}>): Promise<HTTPFacilitatorClient | null>;
|
|
13
|
+
export declare function storeFacilitator(c: Context<{
|
|
14
|
+
Bindings: Env;
|
|
15
|
+
}>, facilitatorConfig: FacilitatorConfig): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/facilitators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAOD,wBAAsB,cAAc,CAClC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAC5B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA8BvC;AAED,wBAAsB,gBAAgB,CACpC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,EAC7B,iBAAiB,EAAE,iBAAiB,GACnC,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { HTTPFacilitatorClient } from "@x402/core/server";
|
|
2
|
+
const CACHE_TTL_MS = 30_000;
|
|
3
|
+
let cachedFacilitator = null;
|
|
4
|
+
let cacheTimestamp = 0;
|
|
5
|
+
export async function getFacilitator(c) {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
if (cachedFacilitator && now - cacheTimestamp < CACHE_TTL_MS) {
|
|
8
|
+
return cachedFacilitator;
|
|
9
|
+
}
|
|
10
|
+
const response = await c.env.FOLDSET_CONFIG.get("facilitator");
|
|
11
|
+
const facilitatorConfig = response ? JSON.parse(response) : null;
|
|
12
|
+
if (!facilitatorConfig) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const hasAuthHeaders = facilitatorConfig.verifyHeaders ||
|
|
16
|
+
facilitatorConfig.settleHeaders ||
|
|
17
|
+
facilitatorConfig.supportedHeaders;
|
|
18
|
+
cachedFacilitator = new HTTPFacilitatorClient({
|
|
19
|
+
url: facilitatorConfig.url,
|
|
20
|
+
...(hasAuthHeaders && {
|
|
21
|
+
createAuthHeaders: async () => ({
|
|
22
|
+
verify: facilitatorConfig.verifyHeaders ?? {},
|
|
23
|
+
settle: facilitatorConfig.settleHeaders ?? {},
|
|
24
|
+
supported: facilitatorConfig.supportedHeaders ?? {},
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
cacheTimestamp = now;
|
|
29
|
+
return cachedFacilitator;
|
|
30
|
+
}
|
|
31
|
+
export async function storeFacilitator(c, facilitatorConfig) {
|
|
32
|
+
await c.env.FOLDSET_CONFIG.put("facilitator", JSON.stringify(facilitatorConfig), {
|
|
33
|
+
expirationTtl: 60 * 60 * 3 + 60 * 30, // 3 hours + 30 minutes
|
|
34
|
+
});
|
|
35
|
+
}
|
package/dist/hono.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/hono.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { paymentHandler } from "./payment/handler";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
* Use this when you want to pass a pre-configured x402ResourceServer instance.
|
|
11
|
-
* This provides more flexibility for testing, custom configuration, and reusing
|
|
12
|
-
* server instances across multiple middlewares.
|
|
13
|
-
*
|
|
14
|
-
* @param routes - Route configurations for protected endpoints
|
|
15
|
-
* @param server - Pre-configured x402ResourceServer instance
|
|
16
|
-
* @param paywallConfig - Optional configuration for the built-in paywall UI
|
|
17
|
-
* @param paywall - Optional custom paywall provider (overrides default)
|
|
18
|
-
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
|
|
19
|
-
* @returns Hono middleware handler
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```typescript
|
|
23
|
-
* import { paymentMiddleware } from "@x402/hono";
|
|
24
|
-
* import { x402ResourceServer } from "@x402/core/server";
|
|
25
|
-
* import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
26
|
-
*
|
|
27
|
-
* const server = new x402ResourceServer(myFacilitatorClient);
|
|
28
|
-
* registerExactEvmScheme(server, {});
|
|
29
|
-
*
|
|
30
|
-
* app.use(paymentMiddleware(routes, server, paywallConfig));
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
|
-
export declare function paymentMiddleware(routes: RoutesConfig, server: x402ResourceServer, paywallConfig?: PaywallConfig, paywall?: PaywallProvider, syncFacilitatorOnStart?: boolean): MiddlewareHandler;
|
|
34
|
-
declare const app: Hono<{
|
|
35
|
-
Bindings: Env;
|
|
36
|
-
}, import("hono/types").BlankSchema, "/">;
|
|
37
|
-
export default app;
|
|
1
|
+
export type { Env } from "./types";
|
|
2
|
+
export type { Restriction } from "./restrictions";
|
|
3
|
+
export type { PaymentMethod } from "./payment-methods";
|
|
4
|
+
export type { FoldsetWebhook } from "./webhooks";
|
|
5
|
+
export type { EventPayload } from "./telemetry/logging";
|
|
6
|
+
export type { AiCrawler } from "./ai-crawlers";
|
|
7
|
+
export type { FacilitatorConfig } from "./facilitators";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,237 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Hono } from 'hono';
|
|
3
|
-
import { x402ResourceServer, x402HTTPResourceServer } from '@x402/hono';
|
|
4
|
-
import { registerExactEvmScheme } from '@x402/evm/exact/server';
|
|
5
|
-
import { HTTPFacilitatorClient } from '@x402/core/server';
|
|
6
|
-
import pMemoize from 'p-memoize';
|
|
7
|
-
import { HonoAdapter } from './adapter';
|
|
8
|
-
async function _fetchFoldsetAPI(endpoint, env) {
|
|
9
|
-
const res = await fetch(`https://api.foldset.com/v1/${endpoint}`, {
|
|
10
|
-
method: 'GET',
|
|
11
|
-
headers: {
|
|
12
|
-
Authorization: `Bearer ${env.FOLDSET_API_KEY}`,
|
|
13
|
-
Accept: 'application/json',
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
if (!res.ok) {
|
|
17
|
-
throw new Error(`Foldset API (${endpoint}) failed: ${res.status}`);
|
|
18
|
-
}
|
|
19
|
-
return (await res.json());
|
|
20
|
-
}
|
|
21
|
-
const cacheTTL = 60000; // 60 seconds
|
|
22
|
-
// This is an in memory cache. Cloudflare also has a cache but it is not
|
|
23
|
-
// in memory, so it would survive cold starts (this won't). I think in-memory
|
|
24
|
-
// makes more sense here for now.
|
|
25
|
-
// TODO rfradkin: Also make sure this caching works, didn't test it
|
|
26
|
-
const fetchFoldsetAPI = pMemoize(_fetchFoldsetAPI, {
|
|
27
|
-
maxAge: cacheTTL,
|
|
28
|
-
cacheKey: ([endpoint]) => endpoint,
|
|
29
|
-
});
|
|
30
|
-
async function getRestrictions(env) {
|
|
31
|
-
const restrictionList = await fetchFoldsetAPI('restrictions', env);
|
|
32
|
-
const restrictions = {};
|
|
33
|
-
for (const restriction of restrictionList) {
|
|
34
|
-
restrictions[restriction.pathname] = restriction.price;
|
|
35
|
-
}
|
|
36
|
-
return restrictions;
|
|
37
|
-
}
|
|
38
|
-
async function getEVMAddress(env) {
|
|
39
|
-
const data = await fetchFoldsetAPI('get-address', env);
|
|
40
|
-
return data.address;
|
|
41
|
-
}
|
|
42
|
-
// TODO rfradkin: Add metadata for bazaar finding
|
|
43
|
-
// https://x402.gitbook.io/x402/getting-started/quickstart-for-sellers
|
|
44
|
-
function buildRoutesConfig(restrictions, payTo) {
|
|
45
|
-
const routesConfig = {};
|
|
46
|
-
for (const [pathname, price] of Object.entries(restrictions)) {
|
|
47
|
-
routesConfig[pathname] = {
|
|
48
|
-
accepts: [
|
|
49
|
-
{
|
|
50
|
-
scheme: 'exact',
|
|
51
|
-
price: `$${price}`,
|
|
52
|
-
network: 'eip155:84532',
|
|
53
|
-
payTo,
|
|
54
|
-
},
|
|
55
|
-
],
|
|
56
|
-
description: 'Access to premium content',
|
|
57
|
-
mimeType: 'application/json',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return routesConfig;
|
|
61
|
-
}
|
|
62
|
-
let server = null;
|
|
63
|
-
// Right now, load times are like 800ms, which is pretty slow since
|
|
64
|
-
// static site is 200ms. Look into later, this call probably takes most of the time.
|
|
65
|
-
// This is like this cause can'd do async calls at module level in cloudflare
|
|
66
|
-
function getServer() {
|
|
67
|
-
if (!server) {
|
|
68
|
-
// TODO rfradkin: Hard coded for now but consider making it configurable
|
|
69
|
-
const facilitatorClient = new HTTPFacilitatorClient({
|
|
70
|
-
url: 'https://x402.org/facilitator',
|
|
71
|
-
});
|
|
72
|
-
server = new x402ResourceServer(facilitatorClient);
|
|
73
|
-
registerExactEvmScheme(server);
|
|
74
|
-
}
|
|
75
|
-
return server;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Hono payment middleware for x402 protocol (direct server instance).
|
|
79
|
-
*
|
|
80
|
-
* Use this when you want to pass a pre-configured x402ResourceServer instance.
|
|
81
|
-
* This provides more flexibility for testing, custom configuration, and reusing
|
|
82
|
-
* server instances across multiple middlewares.
|
|
83
|
-
*
|
|
84
|
-
* @param routes - Route configurations for protected endpoints
|
|
85
|
-
* @param server - Pre-configured x402ResourceServer instance
|
|
86
|
-
* @param paywallConfig - Optional configuration for the built-in paywall UI
|
|
87
|
-
* @param paywall - Optional custom paywall provider (overrides default)
|
|
88
|
-
* @param syncFacilitatorOnStart - Whether to sync with the facilitator on startup (defaults to true)
|
|
89
|
-
* @returns Hono middleware handler
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```typescript
|
|
93
|
-
* import { paymentMiddleware } from "@x402/hono";
|
|
94
|
-
* import { x402ResourceServer } from "@x402/core/server";
|
|
95
|
-
* import { registerExactEvmScheme } from "@x402/evm/exact/server";
|
|
96
|
-
*
|
|
97
|
-
* const server = new x402ResourceServer(myFacilitatorClient);
|
|
98
|
-
* registerExactEvmScheme(server, {});
|
|
99
|
-
*
|
|
100
|
-
* app.use(paymentMiddleware(routes, server, paywallConfig));
|
|
101
|
-
* ```
|
|
102
|
-
*/
|
|
103
|
-
export function paymentMiddleware(routes, server, paywallConfig, paywall, syncFacilitatorOnStart = true) {
|
|
104
|
-
// Create the x402 HTTP server instance with the resource server
|
|
105
|
-
const httpServer = new x402HTTPResourceServer(server, routes);
|
|
106
|
-
// Register custom paywall provider if provided
|
|
107
|
-
if (paywall) {
|
|
108
|
-
httpServer.registerPaywallProvider(paywall);
|
|
109
|
-
}
|
|
110
|
-
// Store initialization promise (not the result)
|
|
111
|
-
// httpServer.initialize() fetches facilitator support and validates routes
|
|
112
|
-
let initPromise = syncFacilitatorOnStart ? httpServer.initialize() : null;
|
|
113
|
-
// TODO rfradkin: Add bazaar extension
|
|
114
|
-
// // Dynamically register bazaar extension if routes declare it
|
|
115
|
-
// let bazaarPromise: Promise<void> | null = null;
|
|
116
|
-
// if (checkIfBazaarNeeded(routes)) {
|
|
117
|
-
// bazaarPromise = import("@x402/extensions/bazaar")
|
|
118
|
-
// .then(({ bazaarResourceServerExtension }) => {
|
|
119
|
-
// server.registerExtension(bazaarResourceServerExtension);
|
|
120
|
-
// })
|
|
121
|
-
// .catch(err => {
|
|
122
|
-
// console.error("Failed to load bazaar extension:", err);
|
|
123
|
-
// });
|
|
124
|
-
// }
|
|
125
|
-
return async (c, next) => {
|
|
126
|
-
// Create adapter and context
|
|
127
|
-
const adapter = new HonoAdapter(c);
|
|
128
|
-
const context = {
|
|
129
|
-
adapter,
|
|
130
|
-
path: c.req.path,
|
|
131
|
-
method: c.req.method,
|
|
132
|
-
paymentHeader: adapter.getHeader("payment-signature") || adapter.getHeader("x-payment"),
|
|
133
|
-
};
|
|
134
|
-
// Check if route requires payment before initializing facilitator
|
|
135
|
-
if (!httpServer.requiresPayment(context)) {
|
|
136
|
-
return next();
|
|
137
|
-
}
|
|
138
|
-
// Only initialize when processing a protected route
|
|
139
|
-
if (initPromise) {
|
|
140
|
-
await initPromise;
|
|
141
|
-
initPromise = null; // Clear after first await
|
|
142
|
-
}
|
|
143
|
-
// // Await bazaar extension loading if needed
|
|
144
|
-
// if (bazaarPromise) {
|
|
145
|
-
// await bazaarPromise;
|
|
146
|
-
// bazaarPromise = null;
|
|
147
|
-
// }
|
|
148
|
-
// Process payment requirement check
|
|
149
|
-
const result = await httpServer.processHTTPRequest(context, paywallConfig);
|
|
150
|
-
// Handle the different result types
|
|
151
|
-
switch (result.type) {
|
|
152
|
-
case "no-payment-required":
|
|
153
|
-
// No payment needed, proceed directly to the route handler
|
|
154
|
-
return next();
|
|
155
|
-
case "payment-error":
|
|
156
|
-
// Payment required but not provided or invalid
|
|
157
|
-
const { response } = result;
|
|
158
|
-
Object.entries(response.headers).forEach(([key, value]) => {
|
|
159
|
-
c.header(key, value);
|
|
160
|
-
});
|
|
161
|
-
if (response.isHtml) {
|
|
162
|
-
return c.html(response.body, response.status);
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
return c.json(response.body || {}, response.status);
|
|
166
|
-
}
|
|
167
|
-
case "payment-verified":
|
|
168
|
-
// Payment is valid, need to wrap response for settlement
|
|
169
|
-
const { paymentPayload, paymentRequirements } = result;
|
|
170
|
-
// Proceed to the next middleware or route handler
|
|
171
|
-
await next();
|
|
172
|
-
// Get the current response
|
|
173
|
-
let res = c.res;
|
|
174
|
-
// If the response from the protected route is >= 400, do not settle payment
|
|
175
|
-
if (res.status >= 400) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Clear the response so we can modify headers
|
|
179
|
-
c.res = undefined;
|
|
180
|
-
try {
|
|
181
|
-
const settleResult = await httpServer.processSettlement(paymentPayload, paymentRequirements);
|
|
182
|
-
if (!settleResult.success) {
|
|
183
|
-
// Settlement failed - do not return the protected resource
|
|
184
|
-
res = c.json({
|
|
185
|
-
error: "Settlement failed",
|
|
186
|
-
details: settleResult.errorReason,
|
|
187
|
-
}, 402);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
// Settlement succeeded - add headers to response
|
|
191
|
-
Object.entries(settleResult.headers).forEach(([key, value]) => {
|
|
192
|
-
res.headers.set(key, value);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
console.error(error);
|
|
198
|
-
// If settlement fails, return an error response
|
|
199
|
-
res = c.json({
|
|
200
|
-
error: "Settlement failed",
|
|
201
|
-
details: error instanceof Error ? error.message : "Unknown error",
|
|
202
|
-
}, 402);
|
|
203
|
-
}
|
|
204
|
-
// Restore the response (potentially modified with settlement headers)
|
|
205
|
-
c.res = res;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
const app = new Hono();
|
|
211
|
-
// TODO rfradkin: This is still potentially quite slow since it is getting
|
|
212
|
-
// each of these on each request regardless of whether the request is in
|
|
213
|
-
// the restrictions or not. Should find a better solution down the road
|
|
214
|
-
app.use('*', async (c, next) => {
|
|
215
|
-
const [restrictions, payTo, server] = await Promise.all([
|
|
216
|
-
getRestrictions(c.env),
|
|
217
|
-
getEVMAddress(c.env),
|
|
218
|
-
getServer(),
|
|
219
|
-
]);
|
|
220
|
-
const config = buildRoutesConfig(restrictions, payTo);
|
|
221
|
-
return paymentMiddleware(config, server)(c, next);
|
|
222
|
-
});
|
|
223
|
-
app.all('*', async (c) => {
|
|
224
|
-
const req = c.req.raw;
|
|
225
|
-
const resp = await fetch(req);
|
|
226
|
-
// This is cause the payment middleware needs to modify the headers to add the payment status
|
|
227
|
-
// (since it tags the data response after the request with settlement related headers)
|
|
228
|
-
// (ie success or failure)
|
|
229
|
-
// and the fetch headers returned by cloudlflare is immutable, so causes an issue
|
|
230
|
-
// TODO rfradkin: This could be cleaned up and made nicer, but works for now
|
|
231
|
-
return new Response(resp.body, {
|
|
232
|
-
status: resp.status,
|
|
233
|
-
statusText: resp.statusText,
|
|
234
|
-
headers: new Headers(resp.headers),
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
export default app;
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HTTPAdapter } from "@x402/core/server";
|
|
2
|
+
import { Context } from "hono";
|
|
3
|
+
export declare class HonoAdapter implements HTTPAdapter {
|
|
4
|
+
private c;
|
|
5
|
+
constructor(c: Context);
|
|
6
|
+
getHeader(name: string): string | undefined;
|
|
7
|
+
getMethod(): string;
|
|
8
|
+
getPath(): string;
|
|
9
|
+
getUrl(): string;
|
|
10
|
+
getAcceptHeader(): string;
|
|
11
|
+
getUserAgent(): string;
|
|
12
|
+
getQueryParams(): Record<string, string | string[]>;
|
|
13
|
+
getQueryParam(name: string): string | string[] | undefined;
|
|
14
|
+
getBody(): Promise<unknown>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/payment/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,qBAAa,WAAY,YAAW,WAAW;IACjC,OAAO,CAAC,CAAC;gBAAD,CAAC,EAAE,OAAO;IAE9B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3C,SAAS,IAAI,MAAM;IAInB,OAAO,IAAI,MAAM;IAIjB,MAAM,IAAI,MAAM;IAIhB,eAAe,IAAI,MAAM;IAIzB,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IASnD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAIpD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;CAOlC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class HonoAdapter {
|
|
2
|
+
c;
|
|
3
|
+
constructor(c) {
|
|
4
|
+
this.c = c;
|
|
5
|
+
}
|
|
6
|
+
getHeader(name) {
|
|
7
|
+
return this.c.req.header(name);
|
|
8
|
+
}
|
|
9
|
+
getMethod() {
|
|
10
|
+
return this.c.req.method;
|
|
11
|
+
}
|
|
12
|
+
getPath() {
|
|
13
|
+
return this.c.req.path;
|
|
14
|
+
}
|
|
15
|
+
getUrl() {
|
|
16
|
+
return this.c.req.url;
|
|
17
|
+
}
|
|
18
|
+
getAcceptHeader() {
|
|
19
|
+
return this.c.req.header("Accept") || "";
|
|
20
|
+
}
|
|
21
|
+
getUserAgent() {
|
|
22
|
+
return this.c.req.header("User-Agent") || "";
|
|
23
|
+
}
|
|
24
|
+
getQueryParams() {
|
|
25
|
+
const query = this.c.req.query();
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const [key, value] of Object.entries(query)) {
|
|
28
|
+
result[key] = value;
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
getQueryParam(name) {
|
|
33
|
+
return this.c.req.query(name);
|
|
34
|
+
}
|
|
35
|
+
async getBody() {
|
|
36
|
+
try {
|
|
37
|
+
return await this.c.req.json();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/payment/handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AA2GpC,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAQ/E"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/cloudflare";
|
|
2
|
+
import { handleWebhook } from "../webhooks";
|
|
3
|
+
import { logVisitEvent } from "../telemetry/logging";
|
|
4
|
+
import { wrappedPaymentHandler } from "../telemetry/sentry";
|
|
5
|
+
import { HonoAdapter } from "./adapter";
|
|
6
|
+
import { getHttpServer } from "./setup";
|
|
7
|
+
import { handleSettlement } from "./settlement";
|
|
8
|
+
import { isAiCrawler } from "../ai-crawlers";
|
|
9
|
+
// TODO rfradkin: Open a PR in x402 github to change the handlePaymentError to protected or allow for passing parameteres to specify
|
|
10
|
+
// this behavior
|
|
11
|
+
function createPaymentContext(c) {
|
|
12
|
+
const adapter = new HonoAdapter(c);
|
|
13
|
+
return {
|
|
14
|
+
adapter,
|
|
15
|
+
path: c.req.path,
|
|
16
|
+
method: c.req.method,
|
|
17
|
+
paymentHeader: adapter.getHeader("PAYMENT-SIGNATURE") || adapter.getHeader("X-PAYMENT"),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function handlePaymentError(c, response) {
|
|
21
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
22
|
+
c.header(key, value);
|
|
23
|
+
});
|
|
24
|
+
// We always return a 200 even though the status code is 402
|
|
25
|
+
// AI crawlers tend to view the raw html only if a 200 is returned
|
|
26
|
+
// For now, we will return 200 errors while blocking the content even though
|
|
27
|
+
// we should be returning response.status as 402 here
|
|
28
|
+
// We also provide the headers and html to everyone, regardless of web browser status
|
|
29
|
+
return c.html(response.body, response.status);
|
|
30
|
+
// if (response.isHtml) {
|
|
31
|
+
// return c.html(response.body as string, response.status as 402));
|
|
32
|
+
// }
|
|
33
|
+
// return c.json(response.body || {}, response.status as 402);
|
|
34
|
+
}
|
|
35
|
+
async function paymentHandlerHelper(c) {
|
|
36
|
+
if (!c.env.FOLDSET_API_KEY) {
|
|
37
|
+
throw new Error("Missing required environment variable: FOLDSET_API_KEY. See https://docs.foldset.com/setup");
|
|
38
|
+
}
|
|
39
|
+
if (!c.env.FOLDSET_CONFIG) {
|
|
40
|
+
throw new Error("Missing required KV namespace binding: FOLDSET_CONFIG. See https://docs.foldset.com/setup");
|
|
41
|
+
}
|
|
42
|
+
Sentry.setTag("url", c.req.url);
|
|
43
|
+
Sentry.setTag("path", c.req.path);
|
|
44
|
+
Sentry.setTag("method", c.req.method);
|
|
45
|
+
if (c.req.method === "POST" && c.req.path === "/foldset/webhooks") {
|
|
46
|
+
return handleWebhook(c);
|
|
47
|
+
}
|
|
48
|
+
if (!(await isAiCrawler(c))) {
|
|
49
|
+
return await fetch(c.req.raw);
|
|
50
|
+
}
|
|
51
|
+
const httpServer = await getHttpServer(c);
|
|
52
|
+
if (!httpServer) {
|
|
53
|
+
return await fetch(c.req.raw);
|
|
54
|
+
}
|
|
55
|
+
const context = createPaymentContext(c);
|
|
56
|
+
if (!httpServer.requiresPayment(context)) {
|
|
57
|
+
return await fetch(c.req.raw);
|
|
58
|
+
}
|
|
59
|
+
const result = await httpServer.processHTTPRequest(context);
|
|
60
|
+
if (result.type === "no-payment-required") {
|
|
61
|
+
const response = await fetch(c.req.raw);
|
|
62
|
+
logVisitEvent(c, response);
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
if (result.type === "payment-error") {
|
|
66
|
+
const response = handlePaymentError(c, result.response);
|
|
67
|
+
logVisitEvent(c, response);
|
|
68
|
+
return response;
|
|
69
|
+
}
|
|
70
|
+
const { paymentPayload, paymentRequirements } = result;
|
|
71
|
+
const upstreamResponse = await fetch(c.req.raw);
|
|
72
|
+
const response = new Response(upstreamResponse.body, {
|
|
73
|
+
status: upstreamResponse.status,
|
|
74
|
+
statusText: upstreamResponse.statusText,
|
|
75
|
+
headers: new Headers(upstreamResponse.headers),
|
|
76
|
+
});
|
|
77
|
+
const settledResponse = await handleSettlement(c, httpServer, paymentPayload, paymentRequirements, response);
|
|
78
|
+
logVisitEvent(c, settledResponse);
|
|
79
|
+
return settledResponse;
|
|
80
|
+
}
|
|
81
|
+
export function paymentHandler(c) {
|
|
82
|
+
return wrappedPaymentHandler({
|
|
83
|
+
request: c.req.raw,
|
|
84
|
+
context: c.executionCtx,
|
|
85
|
+
}, () => paymentHandlerHelper(c));
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../../src/payment/paywall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,YAAY,CAAC,eAAe,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAyG7F"}
|