payment-kit 1.29.2 → 1.29.4
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/api/src/bootstrap.ts +11 -0
- package/api/src/crons/index.ts +14 -13
- package/api/src/crons/tenant-fanout.ts +82 -0
- package/api/src/host-node/did-connect-runtime-node.ts +33 -0
- package/api/src/host-node/serve-static-arc.ts +68 -0
- package/api/src/host-node/serve-static.ts +41 -0
- package/api/src/libs/auth.ts +166 -27
- package/api/src/libs/context.ts +11 -0
- package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
- package/api/src/libs/did-connect/tenant-identity.ts +221 -0
- package/api/src/libs/drivers/identity.ts +61 -0
- package/api/src/libs/drivers/index.ts +1 -1
- package/api/src/libs/http-fetch-adapter.ts +11 -1
- package/api/src/libs/queue/index.ts +14 -2
- package/api/src/middlewares/hono/context.ts +7 -0
- package/api/src/middlewares/hono/csrf.ts +13 -2
- package/api/src/middlewares/hono/security.ts +6 -11
- package/api/src/queues/checkout-session.ts +21 -9
- package/api/src/queues/event.ts +29 -7
- package/api/src/queues/payment.ts +23 -9
- package/api/src/queues/payout.ts +28 -16
- package/api/src/queues/refund.ts +18 -6
- package/api/src/routes/hono/customers.ts +6 -1
- package/api/src/routes/hono/refunds.ts +2 -3
- package/api/src/service.ts +178 -31
- package/api/src/store/sequelize.ts +16 -1
- package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -0
- package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
- package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
- package/api/tests/libs/service-host.spec.ts +37 -0
- package/api/tests/queues/event-tenant.spec.ts +60 -4
- package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
- package/api/tests/service/fail-closed-http.spec.ts +79 -0
- package/api/tests/service/static-arc-handler.spec.ts +101 -0
- package/api/tests/service/static-externalized.spec.ts +48 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +8 -21
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +10 -5
- package/cloudflare/cf-adapter.ts +419 -0
- package/cloudflare/did-connect-runtime.ts +96 -0
- package/cloudflare/did-connect-token-storage.ts +151 -0
- package/cloudflare/esbuild-cf-config.cjs +407 -0
- package/cloudflare/run-build.js +33 -357
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- package/cloudflare/tests/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +35 -10
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +98 -56
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +0 -1
- package/cloudflare/wrangler.staging.json +0 -6
- package/package.json +7 -7
- package/scripts/bootstrap-inject.ts +166 -0
- package/src/app.tsx +2 -1
- package/src/libs/service-host.ts +13 -0
- package/vite.arc.config.ts +159 -0
- package/cloudflare/did-connect-auth.ts +0 -310
- package/cloudflare/shims/blocklet-sdk/util-csrf.ts +0 -13
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +0 -8
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
+
// P1 (README D1 / F1) — arc-targeted SPA build.
|
|
3
|
+
//
|
|
4
|
+
// The standalone `vite.config.ts` injects its base via createBlockletPlugin
|
|
5
|
+
// (vite.config.ts:56), whose base is CLI-immutable — so a `--base` override
|
|
6
|
+
// cannot retarget it. arc needs a DETERMINISTIC base of '/.well-known/payment/',
|
|
7
|
+
// so this config (like cloudflare/vite.config.ts) drops createBlockletPlugin and
|
|
8
|
+
// sets `base` + `outDir` explicitly. Output lands directly in
|
|
9
|
+
// packages/payment-core/web/ so the @arcblock/payment-service tarball ships the
|
|
10
|
+
// frontend (T1.3 appends web/** to files). There is no blocklet-server doing
|
|
11
|
+
// runtime window.blocklet injection here, so the P2 bootstrap helper injects it
|
|
12
|
+
// at build time (single source — same helper the CF build uses).
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
import react from '@vitejs/plugin-react';
|
|
16
|
+
import { defineConfig } from 'vite';
|
|
17
|
+
import svgr from 'vite-plugin-svgr';
|
|
18
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
19
|
+
|
|
20
|
+
import { PAYMENT_KIT_DID, buildBootstrapScript } from './scripts/bootstrap-inject';
|
|
21
|
+
|
|
22
|
+
const coreDir = __dirname; // blocklets/core — where index.html and src/ live
|
|
23
|
+
const UI_PREFIX = '/.well-known/payment';
|
|
24
|
+
// Physical isolation (README D1 / data-leak): arc artifacts live in payment-core's
|
|
25
|
+
// web/, never mixed with the standalone did-pay worker output (cloudflare/public).
|
|
26
|
+
const webOutDir = path.resolve(coreDir, '../../packages/payment-core/web');
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
root: coreDir,
|
|
30
|
+
base: `${UI_PREFIX}/`,
|
|
31
|
+
plugins: [
|
|
32
|
+
tsconfigPaths({ root: coreDir }),
|
|
33
|
+
react(),
|
|
34
|
+
// Build-time window.blocklet bootstrap (P2 helper, single source). The
|
|
35
|
+
// remoteBlockletUrl is root-exact (the helper throws otherwise, T2.3), prefix
|
|
36
|
+
// is local-only (G3), componentId = PAYMENT_KIT_DID so getPrefix resolves to
|
|
37
|
+
// the arc UI prefix (packages/react/src/libs/util.ts:87).
|
|
38
|
+
{
|
|
39
|
+
name: 'arc-inject-blocklet',
|
|
40
|
+
transformIndexHtml(html: string) {
|
|
41
|
+
const injection = buildBootstrapScript({
|
|
42
|
+
uiPrefix: UI_PREFIX,
|
|
43
|
+
componentId: PAYMENT_KIT_DID,
|
|
44
|
+
remoteBlockletUrl: '/__blocklet__.js?type=json',
|
|
45
|
+
// serviceHost = ORIGIN ROOT, not '/.well-known/service'. The DID-Connect
|
|
46
|
+
// SessionProvider treats serviceHost as the API base and appends its OWN
|
|
47
|
+
// auth-service prefix (groupPrefix + servicePrefix + '/api/did' =
|
|
48
|
+
// '/.well-known/service/api/did') — so the session/csrf URLs resolve to
|
|
49
|
+
// <origin>/.well-known/service/api/did/*. Passing '/.well-known/service'
|
|
50
|
+
// double-counts it (=> /.well-known/service/.well-known/service/api/did/session).
|
|
51
|
+
// Root '/' lets the lib's appended prefix land at origin root, where arc
|
|
52
|
+
// serves the auth service (a sibling of /.well-known/payment, not under it).
|
|
53
|
+
serviceHost: '/',
|
|
54
|
+
// protect serviceHost too: the remote __blocklet__.js carries arc's own
|
|
55
|
+
// serviceHost/servicePrefix — must not clobber this build-time root base.
|
|
56
|
+
localOnly: ['prefix', 'serviceHost', 'navigation', 'componentMountPoints'],
|
|
57
|
+
// the payment blocklet's own nav + mount point — AUTH_SERVICE's
|
|
58
|
+
// __blocklet__.js doesn't carry them, so @blocklet/ui-react's dashboard
|
|
59
|
+
// would crash on `navigation.forEach` without these (localOnly-protected).
|
|
60
|
+
extra: {
|
|
61
|
+
componentMountPoints: [
|
|
62
|
+
{
|
|
63
|
+
did: PAYMENT_KIT_DID,
|
|
64
|
+
name: 'Payment Kit',
|
|
65
|
+
mountPoint: UI_PREFIX,
|
|
66
|
+
appId: PAYMENT_KIT_DID,
|
|
67
|
+
status: 'running',
|
|
68
|
+
capabilities: { component: true },
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
navigation: [
|
|
72
|
+
{
|
|
73
|
+
id: 'payments',
|
|
74
|
+
title: { en: 'Payments', zh: '支付管理' },
|
|
75
|
+
icon: 'ion:card-outline',
|
|
76
|
+
link: '/admin',
|
|
77
|
+
section: ['dashboard', 'sessionManager'],
|
|
78
|
+
role: ['admin', 'owner'],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'integrations',
|
|
82
|
+
title: { en: 'Integrations', zh: '快速集成' },
|
|
83
|
+
icon: 'ion:flash-outline',
|
|
84
|
+
link: '/integrations',
|
|
85
|
+
section: ['dashboard', 'sessionManager'],
|
|
86
|
+
role: ['admin', 'owner'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'billing',
|
|
90
|
+
title: { en: 'Billing', zh: '我的账单' },
|
|
91
|
+
icon: 'ion:receipt-outline',
|
|
92
|
+
link: '/customer',
|
|
93
|
+
private: true,
|
|
94
|
+
section: ['userCenter', 'sessionManager'],
|
|
95
|
+
role: ['owner', 'admin', 'member', 'guest'],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return html.replace('<head>', `<head>${injection}`);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
svgr(),
|
|
104
|
+
],
|
|
105
|
+
resolve: {
|
|
106
|
+
alias: {
|
|
107
|
+
// Point workspace packages at source (same as the standalone dev/CF builds).
|
|
108
|
+
'@blocklet/payment-react': path.resolve(coreDir, '../../packages/react/src'),
|
|
109
|
+
'@blocklet/payment-react-headless': path.resolve(coreDir, '../../packages/payment-react-headless/src'),
|
|
110
|
+
'@blocklet/payment-js': path.resolve(coreDir, '../../packages/client/src'),
|
|
111
|
+
'lodash.assign': 'lodash/assign',
|
|
112
|
+
'lodash.clonedeep': 'lodash/cloneDeep',
|
|
113
|
+
'lodash.isequal': 'lodash/isEqual',
|
|
114
|
+
'lodash.merge': 'lodash/merge',
|
|
115
|
+
'lodash.find': 'lodash/find',
|
|
116
|
+
},
|
|
117
|
+
dedupe: [
|
|
118
|
+
'@blocklet/ui-react',
|
|
119
|
+
'@arcblock/ux',
|
|
120
|
+
'@arcblock/did-connect-react',
|
|
121
|
+
'@mui/material',
|
|
122
|
+
'@mui/icons-material',
|
|
123
|
+
'react',
|
|
124
|
+
'react-dom',
|
|
125
|
+
'lodash',
|
|
126
|
+
'bn.js',
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
build: {
|
|
130
|
+
outDir: webOutDir,
|
|
131
|
+
emptyOutDir: true,
|
|
132
|
+
cssCodeSplit: false,
|
|
133
|
+
modulePreload: false,
|
|
134
|
+
// Align with build.mjs sourcemap:false — the shipped web/ carries no source
|
|
135
|
+
// maps (no source-layout leak, Security spec).
|
|
136
|
+
sourcemap: false,
|
|
137
|
+
reportCompressedSize: false,
|
|
138
|
+
chunkSizeWarningLimit: 2000,
|
|
139
|
+
commonjsOptions: { include: [/node_modules/], transformMixedEsModules: true },
|
|
140
|
+
rollupOptions: {
|
|
141
|
+
output: {
|
|
142
|
+
manualChunks: {
|
|
143
|
+
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
|
|
144
|
+
'vendor-mui': ['@mui/material', '@mui/icons-material', '@mui/system'],
|
|
145
|
+
'vendor-arcblock': ['@arcblock/did-connect-react', '@arcblock/ux'],
|
|
146
|
+
'vendor-blocklet': ['@blocklet/ui-react'],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
define: {
|
|
152
|
+
global: 'globalThis',
|
|
153
|
+
'process.env': {},
|
|
154
|
+
},
|
|
155
|
+
optimizeDeps: {
|
|
156
|
+
include: ['react', 'react-dom', 'react/jsx-runtime'],
|
|
157
|
+
esbuildOptions: { mainFields: ['module', 'main'], resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'] },
|
|
158
|
+
},
|
|
159
|
+
});
|
|
@@ -1,310 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DID Connect authentication for Cloudflare Workers.
|
|
3
|
-
*
|
|
4
|
-
* Since the published @arcblock/did-connect-js@1.28.0 does not include the Hono adapter,
|
|
5
|
-
* we port the adapter logic here. When a newer version with native Hono support ships,
|
|
6
|
-
* this file can be replaced by a direct import.
|
|
7
|
-
*
|
|
8
|
-
* Reference: blockchain/did/did-connect/src/adapters/hono.ts
|
|
9
|
-
*/
|
|
10
|
-
import { WalletAuthenticator, WalletHandlers } from '@arcblock/did-connect-js';
|
|
11
|
-
// abt-wallet 4.20+ is dual-decode (both CBOR + protobuf), so we can go back
|
|
12
|
-
// to @ocap/client/encode's CBOR-only txEncoder. Drops ~300KB google-protobuf
|
|
13
|
-
// runtime from the worker bundle. Requires abt-wallet >= 4.20.
|
|
14
|
-
// fetchContext is also exported so we can pre-warm the module-level cache
|
|
15
|
-
// at isolate init time — avoids the first-request 8s merchant timeout that
|
|
16
|
-
// comes from fetchContext hitting beta chain (2 GraphQL queries in parallel).
|
|
17
|
-
import { createTxEncoder, fetchContext } from '@ocap/client/encode';
|
|
18
|
-
import * as Mcrypto from '@ocap/mcrypto';
|
|
19
|
-
import { fromSecretKey } from '@ocap/wallet';
|
|
20
|
-
import { EventEmitter } from 'events';
|
|
21
|
-
|
|
22
|
-
// Import original connect handlers — esbuild aliases handle all dependency replacements
|
|
23
|
-
import collectHandlers from '../api/src/routes/connect/collect';
|
|
24
|
-
import collectBatchHandlers from '../api/src/routes/connect/collect-batch';
|
|
25
|
-
import payHandlers from '../api/src/routes/connect/pay';
|
|
26
|
-
import setupHandlers from '../api/src/routes/connect/setup';
|
|
27
|
-
import subscribeHandlers from '../api/src/routes/connect/subscribe';
|
|
28
|
-
import changePaymentHandlers from '../api/src/routes/connect/change-payment';
|
|
29
|
-
import changePlanHandlers from '../api/src/routes/connect/change-plan';
|
|
30
|
-
import changePayerHandlers from '../api/src/routes/connect/change-payer';
|
|
31
|
-
import rechargeHandlers from '../api/src/routes/connect/recharge';
|
|
32
|
-
import rechargeAccountHandlers from '../api/src/routes/connect/recharge-account';
|
|
33
|
-
import delegationHandlers from '../api/src/routes/connect/delegation';
|
|
34
|
-
import overdraftProtectionHandlers from '../api/src/routes/connect/overdraft-protection';
|
|
35
|
-
import reStakeHandlers from '../api/src/routes/connect/re-stake';
|
|
36
|
-
import autoRechargeAuthHandlers from '../api/src/routes/connect/auto-recharge-auth';
|
|
37
|
-
|
|
38
|
-
const walletType = {
|
|
39
|
-
role: Mcrypto.types.RoleType.ROLE_APPLICATION,
|
|
40
|
-
pk: Mcrypto.types.KeyType.ED25519,
|
|
41
|
-
hash: Mcrypto.types.HashType.SHA3,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// CloudflareKVStorage – ported from blockchain/did/did-connect/src/storage/kv.ts
|
|
46
|
-
// Not yet published in @arcblock/did-connect-js@1.28.0
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
// D1-based storage for DID Connect tokens.
|
|
50
|
-
// Replaces KV which is eventually consistent across datacenters (up to 60s delay).
|
|
51
|
-
// D1 is strongly consistent — writes are immediately visible to all readers.
|
|
52
|
-
// This fixes mobile wallet scanning: wallet POST auth writes to datacenter A,
|
|
53
|
-
// browser status polling reads from datacenter B, and sees the update immediately.
|
|
54
|
-
class CloudflareD1Storage extends EventEmitter {
|
|
55
|
-
private ttl: number;
|
|
56
|
-
|
|
57
|
-
constructor(options: { ttl?: number } = {}) {
|
|
58
|
-
super();
|
|
59
|
-
this.ttl = options.ttl ?? 300;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private _getDB() {
|
|
63
|
-
const env = (globalThis as any).__CF_ENV__;
|
|
64
|
-
const db = env?.DB;
|
|
65
|
-
if (!db) {
|
|
66
|
-
console.error('[D1Storage] DB not available - __CF_ENV__ missing or no DB binding');
|
|
67
|
-
return db;
|
|
68
|
-
}
|
|
69
|
-
return db.withSession('first-primary');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async create(token: string, status = 'created') {
|
|
73
|
-
try {
|
|
74
|
-
const db = this._getDB();
|
|
75
|
-
if (!db) throw new Error('DB not available');
|
|
76
|
-
const record = { token, status };
|
|
77
|
-
const expiresAt = Math.floor(Date.now() / 1000) + this.ttl;
|
|
78
|
-
await db
|
|
79
|
-
.prepare('INSERT OR REPLACE INTO _did_connect_tokens (token, data, expires_at) VALUES (?, ?, ?)')
|
|
80
|
-
.bind(token, JSON.stringify(record), expiresAt)
|
|
81
|
-
.run();
|
|
82
|
-
this.emit('create', record);
|
|
83
|
-
return record;
|
|
84
|
-
} catch (err: any) {
|
|
85
|
-
console.error('[D1Storage] create failed:', token, err?.message || err);
|
|
86
|
-
throw err;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async read(token: string) {
|
|
91
|
-
try {
|
|
92
|
-
const db = this._getDB();
|
|
93
|
-
if (!db) return null;
|
|
94
|
-
const now = Math.floor(Date.now() / 1000);
|
|
95
|
-
const row = await db
|
|
96
|
-
.prepare('SELECT data FROM _did_connect_tokens WHERE token = ? AND expires_at > ?')
|
|
97
|
-
.bind(token, now)
|
|
98
|
-
.first();
|
|
99
|
-
if (!row) return null;
|
|
100
|
-
return JSON.parse(row.data as string);
|
|
101
|
-
} catch (err: any) {
|
|
102
|
-
console.error('[D1Storage] read failed:', token, err?.message || err);
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
update(token: string, updates: Record<string, any>) {
|
|
108
|
-
// did-connect-js's handlers/util.ts onProcessError calls tokenStorage.update
|
|
109
|
-
// without awaiting it (fire-and-forget). On CF Workers a non-awaited Promise
|
|
110
|
-
// is terminated when the request handler returns, so the UPDATE never hits
|
|
111
|
-
// D1 and the token stays at "scanned" forever — the wallet UI then loops on
|
|
112
|
-
// `status: scanned` and shows "validating..." indefinitely.
|
|
113
|
-
//
|
|
114
|
-
// We wrap the real write in a promise, and register it via the global
|
|
115
|
-
// ctx.waitUntil (exposed as __cfWaitUntil__ in worker.ts) so the runtime
|
|
116
|
-
// keeps the isolate alive until the D1 write finishes, regardless of
|
|
117
|
-
// whether the caller awaits us.
|
|
118
|
-
const promise = (async () => {
|
|
119
|
-
try {
|
|
120
|
-
const existing = await this.read(token);
|
|
121
|
-
if (!existing) return null;
|
|
122
|
-
|
|
123
|
-
delete updates.token;
|
|
124
|
-
const merged = { ...existing, ...updates };
|
|
125
|
-
const expiresAt = Math.floor(Date.now() / 1000) + this.ttl;
|
|
126
|
-
const db = this._getDB();
|
|
127
|
-
if (!db) throw new Error('DB not available');
|
|
128
|
-
await db
|
|
129
|
-
.prepare('UPDATE _did_connect_tokens SET data = ?, expires_at = ? WHERE token = ?')
|
|
130
|
-
.bind(JSON.stringify(merged), expiresAt, token)
|
|
131
|
-
.run();
|
|
132
|
-
this.emit('update', merged);
|
|
133
|
-
return merged;
|
|
134
|
-
} catch (err: any) {
|
|
135
|
-
console.error('[D1Storage] update failed:', token, err?.message || err);
|
|
136
|
-
throw err;
|
|
137
|
-
}
|
|
138
|
-
})();
|
|
139
|
-
|
|
140
|
-
const waitUntil = (globalThis as any).__cfWaitUntil__ as ((p: Promise<any>) => void) | undefined;
|
|
141
|
-
if (typeof waitUntil === 'function') {
|
|
142
|
-
// Swallow rejection in waitUntil — the inner promise will still reject
|
|
143
|
-
// for any caller that does await us.
|
|
144
|
-
waitUntil(promise.catch(() => {}));
|
|
145
|
-
}
|
|
146
|
-
return promise;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async delete(token: string) {
|
|
150
|
-
try {
|
|
151
|
-
const existing = await this.read(token);
|
|
152
|
-
if (existing) {
|
|
153
|
-
this.emit('destroy', existing);
|
|
154
|
-
}
|
|
155
|
-
const db = this._getDB();
|
|
156
|
-
if (!db) return;
|
|
157
|
-
await db.prepare('DELETE FROM _did_connect_tokens WHERE token = ?').bind(token).run();
|
|
158
|
-
} catch (err: any) {
|
|
159
|
-
console.error('[D1Storage] delete failed:', token, err?.message || err);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async exist(token: string, did?: string) {
|
|
164
|
-
const record = await this.read(token);
|
|
165
|
-
if (!record) return false;
|
|
166
|
-
if (did) return record.did === did;
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
// Public API
|
|
174
|
-
// ---------------------------------------------------------------------------
|
|
175
|
-
|
|
176
|
-
export function initAuth(kv: KVNamespace, appSK: string) {
|
|
177
|
-
const wallet = fromSecretKey(appSK, walletType);
|
|
178
|
-
const tokenStorage = new CloudflareD1Storage({ ttl: 300 });
|
|
179
|
-
// Trailing slash is REQUIRED — without it the host 50% times out (verified 2026-04-20).
|
|
180
|
-
// Matches the format used by D1 payment_methods.settings.arcblock.api_host.
|
|
181
|
-
const chainHost = 'https://beta.abtnetwork.io/api/';
|
|
182
|
-
|
|
183
|
-
// Pre-warm @ocap/client/encode's module-level context cache. The first
|
|
184
|
-
// createTxEncoder()(...) call internally awaits fetchContext, which POSTs
|
|
185
|
-
// getChainInfo + getForgeState to beta — ~1-3s on warm isolates, more on
|
|
186
|
-
// cold. When `genRequestedClaims` is called by the merchant and triggers
|
|
187
|
-
// the encoder, merchant's did-connect-js aborts after 8s. Firing the fetch
|
|
188
|
-
// here at init (module load) lets the cache populate while the worker sets
|
|
189
|
-
// up the rest of the request pipeline.
|
|
190
|
-
fetchContext(chainHost).catch((err) => {
|
|
191
|
-
// Non-fatal: on failure the first real request will retry via the
|
|
192
|
-
// encoder's own fetchContext call.
|
|
193
|
-
console.warn('[initAuth] pre-warm chainInfo failed:', err?.message);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const authenticator = new WalletAuthenticator({
|
|
197
|
-
wallet: wallet.toJSON(),
|
|
198
|
-
appInfo: ({ baseUrl }: { baseUrl: string }) => ({
|
|
199
|
-
name: 'Payment Kit',
|
|
200
|
-
description: 'Payment Kit on Cloudflare Workers',
|
|
201
|
-
icon: 'https://www.arcblock.io/favicon.ico',
|
|
202
|
-
link: baseUrl,
|
|
203
|
-
}),
|
|
204
|
-
chainInfo: {
|
|
205
|
-
type: 'arcblock',
|
|
206
|
-
host: chainHost,
|
|
207
|
-
id: 'beta',
|
|
208
|
-
},
|
|
209
|
-
txEncoder: createTxEncoder(),
|
|
210
|
-
// CF Workers: chain RPC to beta.abtnetwork.io + getContext warm-up can exceed
|
|
211
|
-
// the default 8s. Extend to 30s to match Node.js payment-kit behavior.
|
|
212
|
-
timeout: 30000,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// did-connect-js 4.0.0 natively detects Hono apps via isHonoApp()
|
|
216
|
-
const handlers = new WalletHandlers({ tokenStorage, authenticator });
|
|
217
|
-
|
|
218
|
-
return { wallet, authenticator, handlers, tokenStorage };
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Attach DID Connect routes to a Hono app.
|
|
223
|
-
*
|
|
224
|
-
* Registers the login action with full profile handling, and all payment-related
|
|
225
|
-
* DID Connect actions using the original handler implementations from
|
|
226
|
-
* api/src/routes/connect/*.ts. The handlers execute real on-chain operations
|
|
227
|
-
* (transfers, delegations, staking) via @ocap/client HTTP RPC calls, which
|
|
228
|
-
* work in CF Workers. If a handler fails to attach, it falls back to a stub.
|
|
229
|
-
*
|
|
230
|
-
* Payment actions registered:
|
|
231
|
-
* collect, collect-batch, payment, subscription, setup,
|
|
232
|
-
* change-payment, change-plan, change-payer,
|
|
233
|
-
* recharge, recharge-account,
|
|
234
|
-
* delegation, overdraft-protection, re-stake,
|
|
235
|
-
* auto-recharge-auth
|
|
236
|
-
*/
|
|
237
|
-
export function attachDIDConnectRoutes(app: any, kv: KVNamespace, appSK: string) {
|
|
238
|
-
const { handlers, tokenStorage } = initAuth(kv, appSK);
|
|
239
|
-
|
|
240
|
-
// ---- Login action (full implementation) ----
|
|
241
|
-
handlers.attach({
|
|
242
|
-
app,
|
|
243
|
-
action: 'login',
|
|
244
|
-
claims: {
|
|
245
|
-
profile: () => ({
|
|
246
|
-
description: 'Please provide your profile to login',
|
|
247
|
-
fields: ['fullName', 'email', 'avatar'],
|
|
248
|
-
}),
|
|
249
|
-
},
|
|
250
|
-
onAuth: async ({ userDid, userPk, claims, updateSession }: any) => {
|
|
251
|
-
const claim = claims.find((x: any) => x.type === 'profile');
|
|
252
|
-
const { type: _type, signature: _sig, ...rest } = claim || {};
|
|
253
|
-
await updateSession({
|
|
254
|
-
result: {
|
|
255
|
-
...rest,
|
|
256
|
-
did: userDid,
|
|
257
|
-
pk: userPk,
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// ---- Payment DID Connect actions (real implementations) ----
|
|
264
|
-
// These use the original handlers from api/src/routes/connect/*.ts.
|
|
265
|
-
// esbuild aliases handle all dependency replacements (@blocklet/sdk, sequelize, etc.)
|
|
266
|
-
// so that @ocap/client HTTP-based RPC calls work in CF Workers.
|
|
267
|
-
const paymentHandlerList = [
|
|
268
|
-
collectHandlers,
|
|
269
|
-
collectBatchHandlers,
|
|
270
|
-
payHandlers,
|
|
271
|
-
setupHandlers,
|
|
272
|
-
subscribeHandlers,
|
|
273
|
-
changePaymentHandlers,
|
|
274
|
-
changePlanHandlers,
|
|
275
|
-
changePayerHandlers,
|
|
276
|
-
rechargeHandlers,
|
|
277
|
-
rechargeAccountHandlers,
|
|
278
|
-
delegationHandlers,
|
|
279
|
-
overdraftProtectionHandlers,
|
|
280
|
-
reStakeHandlers,
|
|
281
|
-
autoRechargeAuthHandlers,
|
|
282
|
-
];
|
|
283
|
-
|
|
284
|
-
for (const handler of paymentHandlerList) {
|
|
285
|
-
try {
|
|
286
|
-
handlers.attach({ app, ...handler });
|
|
287
|
-
console.log(`[CF DID Connect] Attached real handler: ${handler.action}`);
|
|
288
|
-
} catch (err: any) {
|
|
289
|
-
console.error(`[CF DID Connect] Failed to attach handler ${handler.action}, using stub:`, err?.message);
|
|
290
|
-
// Fallback to stub if real handler fails to attach
|
|
291
|
-
handlers.attach({
|
|
292
|
-
app,
|
|
293
|
-
action: handler.action,
|
|
294
|
-
claims: {
|
|
295
|
-
profile: () => ({
|
|
296
|
-
description: `Please connect your wallet to proceed with ${handler.action}`,
|
|
297
|
-
fields: ['fullName', 'email'],
|
|
298
|
-
}),
|
|
299
|
-
},
|
|
300
|
-
onAuth: async ({ userDid, userPk, updateSession }: any) => {
|
|
301
|
-
await updateSession({
|
|
302
|
-
result: { did: userDid, pk: userPk },
|
|
303
|
-
});
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return { handlers, tokenStorage };
|
|
310
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// CF shim for @blocklet/sdk/lib/util/csrf.
|
|
2
|
-
//
|
|
3
|
-
// DEAD on the CF path: the csrf middleware (middlewares/hono/csrf) lives only on
|
|
4
|
-
// the NODE app-shell pipeline (service.ts buildHonoApp / configureNativePipeline,
|
|
5
|
-
// reached via getHonoApp). The CF worker mounts a LITE app-shell (xss only) and
|
|
6
|
-
// owns its own cors, so csrf never executes on the worker. These stubs exist only
|
|
7
|
-
// so esbuild can resolve the import in the bundled-but-unreachable node code.
|
|
8
|
-
export const getCsrfSecret = (): string => '';
|
|
9
|
-
export const sign = (_secret: string, _value: string): string => '';
|
|
10
|
-
export const verify = (_secret: string, _value: string, _token: string): boolean => false;
|
|
11
|
-
export const hmac = (_secret: string, _value: string): string => '';
|
|
12
|
-
|
|
13
|
-
export default { getCsrfSecret, sign, verify, hmac };
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// CF shim for @blocklet/sdk/lib/util/wallet.
|
|
2
|
-
//
|
|
3
|
-
// DEAD on the CF path: the only importer is the csrf middleware (node-shell only;
|
|
4
|
-
// see util-csrf.ts), which reads isDidWalletConnect to skip csrf for DID-wallet
|
|
5
|
-
// requests. Never executes on the worker — a resolving stub is enough.
|
|
6
|
-
export const isDidWalletConnect = (_userAgent?: string): boolean => false;
|
|
7
|
-
|
|
8
|
-
export default { isDidWalletConnect };
|