payment-kit 1.27.2 → 1.28.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/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +10 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +32 -16
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +16 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +14 -2
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +1 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/models/customer.ts +23 -1
- package/api/src/store/models/payment-method.ts +4 -0
- package/api/src/store/models/price.ts +23 -14
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/run-build.js +390 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +585 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1157 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +293 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1553 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +69 -0
- package/cloudflare/wrangler.staging.json +66 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +26 -22
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/safe-did-address.tsx +75 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/customers/customers/detail.tsx +2 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- package/vite.config.ts +6 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { build } from 'esbuild';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const cfDir = __dirname;
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const result = await build({
|
|
8
|
+
entryPoints: [path.resolve(cfDir, 'worker.ts')],
|
|
9
|
+
bundle: true,
|
|
10
|
+
format: 'esm',
|
|
11
|
+
platform: 'node', // Use node platform — CF Workers nodejs_compat handles Node built-ins
|
|
12
|
+
target: 'esnext',
|
|
13
|
+
outdir: path.resolve(cfDir, 'dist'),
|
|
14
|
+
minify: true,
|
|
15
|
+
sourcemap: true,
|
|
16
|
+
metafile: true,
|
|
17
|
+
mainFields: ['module', 'main'], // Resolve ESM first, then CJS
|
|
18
|
+
conditions: ['import', 'module', 'default'],
|
|
19
|
+
// CF Workers runtime provides these
|
|
20
|
+
external: [
|
|
21
|
+
'cloudflare:*',
|
|
22
|
+
'__STATIC_CONTENT_MANIFEST',
|
|
23
|
+
],
|
|
24
|
+
alias: {
|
|
25
|
+
// === Sequelize → D1 shim ===
|
|
26
|
+
'sequelize': path.resolve(cfDir, 'shims/sequelize-d1/index.ts'),
|
|
27
|
+
'sqlite3': path.resolve(cfDir, 'shims/noop.ts'),
|
|
28
|
+
'cls-hooked': path.resolve(cfDir, 'shims/noop.ts'),
|
|
29
|
+
'express-async-errors': path.resolve(cfDir, 'shims/noop.ts'),
|
|
30
|
+
|
|
31
|
+
// === Express → shim ===
|
|
32
|
+
'express': path.resolve(cfDir, 'shims/express-compat/index.ts'),
|
|
33
|
+
'cors': path.resolve(cfDir, 'shims/cors.ts'),
|
|
34
|
+
'cookie-parser': path.resolve(cfDir, 'shims/cookie-parser.ts'),
|
|
35
|
+
|
|
36
|
+
// === @blocklet/sdk — each sub-path needs its own alias ===
|
|
37
|
+
// NOTE: more specific paths MUST come before less specific ones
|
|
38
|
+
'@blocklet/sdk/lib/middlewares/fallback': path.resolve(cfDir, 'shims/blocklet-sdk/fallback.ts'),
|
|
39
|
+
'@blocklet/sdk/lib/middlewares/cdn': path.resolve(cfDir, 'shims/blocklet-sdk/cdn.ts'),
|
|
40
|
+
'@blocklet/sdk/lib/middlewares/session': path.resolve(cfDir, 'shims/blocklet-sdk/session.ts'),
|
|
41
|
+
'@blocklet/sdk/lib/middlewares': path.resolve(cfDir, 'shims/blocklet-sdk/middlewares.ts'),
|
|
42
|
+
'@blocklet/sdk/lib/env': path.resolve(cfDir, 'shims/blocklet-sdk/env.ts'),
|
|
43
|
+
'@blocklet/sdk/lib/config': path.resolve(cfDir, 'shims/blocklet-sdk/config.ts'),
|
|
44
|
+
'@blocklet/sdk/lib/component': path.resolve(cfDir, 'shims/blocklet-sdk/component.ts'),
|
|
45
|
+
'@blocklet/sdk/lib/wallet': path.resolve(cfDir, 'shims/blocklet-sdk/wallet.ts'),
|
|
46
|
+
'@blocklet/sdk/lib/wallet-authenticator': path.resolve(cfDir, 'shims/blocklet-sdk/wallet-authenticator.ts'),
|
|
47
|
+
'@blocklet/sdk/lib/wallet-handler': path.resolve(cfDir, 'shims/blocklet-sdk/wallet-handler.ts'),
|
|
48
|
+
'@blocklet/sdk/lib/security': path.resolve(cfDir, 'shims/blocklet-sdk/security.ts'),
|
|
49
|
+
'@blocklet/sdk/lib/util/verify-sign': path.resolve(cfDir, 'shims/blocklet-sdk/verify-sign.ts'),
|
|
50
|
+
'@blocklet/sdk/lib/util/component-api': path.resolve(cfDir, 'shims/blocklet-sdk/component-api.ts'),
|
|
51
|
+
'@blocklet/sdk/lib/error-handler': path.resolve(cfDir, 'shims/noop.ts'),
|
|
52
|
+
'@blocklet/sdk/lib/did': path.resolve(cfDir, 'shims/blocklet-sdk/did.ts'),
|
|
53
|
+
'@blocklet/sdk/lib/types/notification': path.resolve(cfDir, 'shims/noop.ts'),
|
|
54
|
+
'@blocklet/sdk/service/notification': path.resolve(cfDir, 'shims/blocklet-sdk/notification.ts'),
|
|
55
|
+
'@blocklet/sdk/service/eventbus': path.resolve(cfDir, 'shims/blocklet-sdk/eventbus.ts'),
|
|
56
|
+
'@blocklet/sdk/service/auth': path.resolve(cfDir, 'shims/blocklet-sdk/auth-service.ts'),
|
|
57
|
+
'@blocklet/sdk/service/blocklet': path.resolve(cfDir, 'shims/blocklet-sdk/auth-service.ts'),
|
|
58
|
+
// Catch-all for any other @blocklet/sdk paths
|
|
59
|
+
'@blocklet/sdk': path.resolve(cfDir, 'shims/blocklet-sdk/index.ts'),
|
|
60
|
+
|
|
61
|
+
// === Other @blocklet packages ===
|
|
62
|
+
'@blocklet/xss': path.resolve(cfDir, 'shims/xss.ts'),
|
|
63
|
+
'@blocklet/error': path.resolve(cfDir, 'shims/error.ts'),
|
|
64
|
+
'@blocklet/logger': path.resolve(cfDir, 'shims/blocklet-sdk/logger.ts'),
|
|
65
|
+
'@blocklet/did-space-js': path.resolve(cfDir, 'shims/did-space-js.ts'),
|
|
66
|
+
'@blocklet/payment-vendor': path.resolve(cfDir, 'shims/payment-vendor.ts'),
|
|
67
|
+
|
|
68
|
+
// === ArcBlock / DID packages ===
|
|
69
|
+
'@arcblock/did-connect-storage-nedb': path.resolve(cfDir, 'shims/nedb-storage.ts'),
|
|
70
|
+
|
|
71
|
+
// === Other dependencies ===
|
|
72
|
+
'ws': path.resolve(cfDir, 'shims/ws-lite.ts'),
|
|
73
|
+
'pg': path.resolve(cfDir, 'shims/noop.ts'),
|
|
74
|
+
'follow-redirects': path.resolve(cfDir, 'shims/noop.ts'),
|
|
75
|
+
'crypto-js': path.resolve(cfDir, 'shims/crypto-js-warn.ts'),
|
|
76
|
+
'mime-types': path.resolve(cfDir, 'shims/mime-types.ts'),
|
|
77
|
+
'@abtnode/cron': path.resolve(cfDir, 'shims/cron.ts'),
|
|
78
|
+
'querystring': path.resolve(cfDir, 'shims/querystring.ts'),
|
|
79
|
+
'dotenv-flow': path.resolve(cfDir, 'shims/noop.ts'),
|
|
80
|
+
'fastq': path.resolve(cfDir, 'shims/fastq.ts'),
|
|
81
|
+
},
|
|
82
|
+
banner: {
|
|
83
|
+
js: [
|
|
84
|
+
// process polyfills
|
|
85
|
+
'if (typeof process !== "undefined") { if (!process.stderr) process.stderr = { fd: 2, write: function(){} }; else if (process.stderr.fd === undefined) process.stderr.fd = 2; if (!process.stdout) process.stdout = { fd: 1, write: function(){} }; else if (process.stdout.fd === undefined) process.stdout.fd = 1; }',
|
|
86
|
+
'if (typeof process !== "undefined" && !process.on) { process.on = function(){}; process.once = function(){}; process.off = function(){}; process.removeListener = function(){}; process.emit = function(){}; process.exit = function(){}; }',
|
|
87
|
+
// CF Workers: defer timers called during global scope init until first request
|
|
88
|
+
'var __deferredTimers = []; var __timersReady = false;',
|
|
89
|
+
'globalThis.__flushDeferredTimers = function() { if (__timersReady) return; __timersReady = true; var q = __deferredTimers; __deferredTimers = []; q.forEach(function(entry) { try { entry.fn.apply(null, entry.args); } catch(e) { console.error("deferred timer error:", e); } }); };',
|
|
90
|
+
'var _origSetTimeout = globalThis.setTimeout; globalThis.setTimeout = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetTimeout.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return 0; } }; } var id = _origSetTimeout.apply(this, arguments); if (typeof id === "number") { return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return id; } }; } if (id && !id.unref) id.unref = function(){}; return id; };',
|
|
91
|
+
'var _origSetInterval = globalThis.setInterval; globalThis.setInterval = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetInterval.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return 0; } }; } var id = _origSetInterval.apply(this, arguments); if (typeof id === "number") { return { unref: function(){}, ref: function(){}, [Symbol.toPrimitive]: function(){ return id; } }; } if (id && !id.unref) id.unref = function(){}; return id; };',
|
|
92
|
+
'var _origSetImmediate = globalThis.setImmediate; if (_origSetImmediate) { globalThis.setImmediate = function() { if (!__timersReady) { var args = arguments; __deferredTimers.push({fn: function(){ _origSetImmediate.apply(globalThis, args); }, args: []}); return { unref: function(){}, ref: function(){} }; } return _origSetImmediate.apply(this, arguments); }; } else { globalThis.setImmediate = function(fn) { if (!__timersReady) { __deferredTimers.push({fn: function(){ _origSetTimeout(fn, 0); }, args: []}); return { unref: function(){}, ref: function(){} }; } return _origSetTimeout(fn, 0); }; }',
|
|
93
|
+
'if (typeof process !== "undefined") { var _origNextTick = process.nextTick; if (_origNextTick) { process.nextTick = function() { if (!__timersReady) { var args = Array.prototype.slice.call(arguments); var fn = args.shift(); __deferredTimers.push({fn: function(){ _origNextTick.apply(process, [fn].concat(args)); }, args: []}); return; } return _origNextTick.apply(process, arguments); }; } else { process.nextTick = function(fn) { if (!__timersReady) { __deferredTimers.push({fn: function(){ _origSetTimeout(fn, 0); }, args: []}); return; } _origSetTimeout(fn, 0); }; } }',
|
|
94
|
+
// require polyfill for Node built-ins
|
|
95
|
+
'(function() {',
|
|
96
|
+
' var _origRequire = typeof globalThis.require === "function" ? globalThis.require : null;',
|
|
97
|
+
' var _qsShim = { stringify: function(obj) { return new URLSearchParams(obj).toString(); }, parse: function(str) { return Object.fromEntries(new URLSearchParams(str).entries()); } };',
|
|
98
|
+
' var _utilShim = { deprecate: function(fn) { return fn; }, inspect: function(obj) { return JSON.stringify(obj); }, inherits: function(ctor, superCtor) { if (superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor } }); } }, format: function() { return Array.prototype.join.call(arguments, " "); } };',
|
|
99
|
+
' var _ttyShim = { isatty: function() { return false; }, ReadStream: function(){}, WriteStream: function(){} };',
|
|
100
|
+
' var _emptyMod = {};',
|
|
101
|
+
' var _httpShim = (function() { var _noop = function(){}; _noop.prototype = {}; return { request: _noop, get: _noop, Agent: _noop, globalAgent: { options: {} }, STATUS_CODES: {}, METHODS: [], createServer: _noop }; })();',
|
|
102
|
+
' globalThis.require = function(mod) {',
|
|
103
|
+
' if (mod === "querystring") return _qsShim;',
|
|
104
|
+
' if (mod === "util") return _utilShim;',
|
|
105
|
+
' if (mod === "tty") return _ttyShim;',
|
|
106
|
+
' if (mod === "http" || mod === "https") return _httpShim;',
|
|
107
|
+
' if (mod === "crypto") return { randomBytes: function(n) { var b = new Uint8Array(n); crypto.getRandomValues(b); return Buffer.from(b); }, createHash: function() { return { update: function() { return this; }, digest: function() { return ""; } }; } };',
|
|
108
|
+
' if (mod === "stream" || mod === "fs" || mod === "net" || mod === "os" || mod === "path" || mod === "zlib" || mod === "tls" || mod === "child_process" || mod === "worker_threads") return _emptyMod;',
|
|
109
|
+
' if (_origRequire) return _origRequire(mod);',
|
|
110
|
+
' console.warn("Cannot require: " + mod);',
|
|
111
|
+
' return _emptyMod;',
|
|
112
|
+
' };',
|
|
113
|
+
'})();',
|
|
114
|
+
].join('\n'),
|
|
115
|
+
},
|
|
116
|
+
plugins: [
|
|
117
|
+
{
|
|
118
|
+
name: 'rolldown-runtime-shim',
|
|
119
|
+
setup(b: any) {
|
|
120
|
+
b.onResolve({ filter: /rolldown_runtime/ }, () => ({
|
|
121
|
+
path: path.resolve(cfDir, 'shims/rolldown-runtime.ts'),
|
|
122
|
+
}));
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
logLevel: 'warning',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const outputs = Object.keys(result.metafile!.outputs);
|
|
130
|
+
for (const o of outputs) {
|
|
131
|
+
const size = result.metafile!.outputs[o].bytes;
|
|
132
|
+
if (!o.endsWith('.map')) {
|
|
133
|
+
console.log(`${o}: ${(size / 1024).toFixed(1)}KB`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.log('BUILD SUCCESS');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main().catch((err) => {
|
|
140
|
+
console.error('BUILD FAILED');
|
|
141
|
+
console.error(err.message);
|
|
142
|
+
if (err.errors) {
|
|
143
|
+
for (const e of err.errors.slice(0, 30)) {
|
|
144
|
+
console.error(` ${e.text}`);
|
|
145
|
+
if (e.location) {
|
|
146
|
+
console.error(` at ${e.location.file}:${e.location.line}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
@@ -0,0 +1,527 @@
|
|
|
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
|
+
// Hono adapter – wraps Express-style (req, res, next) handlers for Hono
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
function parseCookieHeader(header: string): Record<string, string> {
|
|
176
|
+
const cookies: Record<string, string> = {};
|
|
177
|
+
if (!header) return cookies;
|
|
178
|
+
for (const pair of header.split(';')) {
|
|
179
|
+
const idx = pair.indexOf('=');
|
|
180
|
+
if (idx > 0) {
|
|
181
|
+
const key = pair.slice(0, idx).trim();
|
|
182
|
+
const val = pair.slice(idx + 1).trim();
|
|
183
|
+
cookies[key] = decodeURIComponent(val);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return cookies;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function negotiateLanguage(acceptHeader: string, ...langs: string[]): string | false {
|
|
190
|
+
if (!acceptHeader || langs.length === 0) return langs[0] || false;
|
|
191
|
+
const lower = acceptHeader.toLowerCase();
|
|
192
|
+
for (const lang of langs) {
|
|
193
|
+
const prefix = lang.toLowerCase().split('-')[0];
|
|
194
|
+
if (lower.includes(lang.toLowerCase()) || lower.includes(prefix)) {
|
|
195
|
+
return lang;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return langs[0] || false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createHonoRequest(c: any, bodyCache: any = {}): any {
|
|
202
|
+
const url = new URL(c.req.url);
|
|
203
|
+
const rawHeaders: Record<string, string> = {};
|
|
204
|
+
if (c.req.raw?.headers) {
|
|
205
|
+
c.req.raw.headers.forEach((value: string, key: string) => {
|
|
206
|
+
rawHeaders[key.toLowerCase()] = value;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
body: bodyCache,
|
|
212
|
+
query: Object.fromEntries(url.searchParams.entries()),
|
|
213
|
+
params: typeof c.req.param === 'function' ? c.req.param() : {},
|
|
214
|
+
headers: rawHeaders,
|
|
215
|
+
cookies: parseCookieHeader(rawHeaders.cookie || ''),
|
|
216
|
+
protocol: url.protocol.replace(':', ''),
|
|
217
|
+
originalUrl: url.pathname + url.search,
|
|
218
|
+
get(name: string) {
|
|
219
|
+
return rawHeaders[name.toLowerCase()];
|
|
220
|
+
},
|
|
221
|
+
header(name: string) {
|
|
222
|
+
return rawHeaders[name.toLowerCase()];
|
|
223
|
+
},
|
|
224
|
+
acceptsLanguages(...langs: string[]) {
|
|
225
|
+
return negotiateLanguage(rawHeaders['accept-language'] || '', ...langs);
|
|
226
|
+
},
|
|
227
|
+
raw: c,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function createHonoResponse(): any {
|
|
232
|
+
let result: { statusCode: number; body: any } | null = null;
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
jsonp(data: any) {
|
|
236
|
+
result = { statusCode: 200, body: data };
|
|
237
|
+
},
|
|
238
|
+
json(data: any) {
|
|
239
|
+
result = { statusCode: 200, body: data };
|
|
240
|
+
},
|
|
241
|
+
status(code: number) {
|
|
242
|
+
return {
|
|
243
|
+
json(data: any) {
|
|
244
|
+
result = { statusCode: code, body: data };
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
_getResult() {
|
|
249
|
+
return result;
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Wrap a chain of Express-style middlewares + handler into a single Hono handler.
|
|
256
|
+
*/
|
|
257
|
+
function wrapHandler(
|
|
258
|
+
middlewares: Array<(req: any, res: any, next: () => void) => any>,
|
|
259
|
+
handler: (req: any, res: any) => Promise<void>
|
|
260
|
+
) {
|
|
261
|
+
return async (c: any) => {
|
|
262
|
+
// Ensure __CF_ENV__ is available for D1Storage
|
|
263
|
+
if (!(globalThis as any).__CF_ENV__) {
|
|
264
|
+
(globalThis as any).__CF_ENV__ = c.env;
|
|
265
|
+
}
|
|
266
|
+
let body = {};
|
|
267
|
+
try {
|
|
268
|
+
const text = await c.req.text();
|
|
269
|
+
if (text) {
|
|
270
|
+
const contentType = c.req.header('content-type') || '';
|
|
271
|
+
if (contentType.includes('json')) {
|
|
272
|
+
body = JSON.parse(text);
|
|
273
|
+
} else if (contentType.includes('urlencoded')) {
|
|
274
|
+
body = Object.fromEntries(new URLSearchParams(text).entries());
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// Body parsing failed
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const req = createHonoRequest(c, body);
|
|
282
|
+
const res = createHonoResponse();
|
|
283
|
+
|
|
284
|
+
// Run middlewares
|
|
285
|
+
for (const mw of middlewares) {
|
|
286
|
+
let nextCalled = false;
|
|
287
|
+
await mw(req, res, () => {
|
|
288
|
+
nextCalled = true;
|
|
289
|
+
});
|
|
290
|
+
if (!nextCalled) {
|
|
291
|
+
const r = res._getResult();
|
|
292
|
+
if (r) return c.json(r.body, r.statusCode);
|
|
293
|
+
return c.json({ error: 'Unknown error' }, 500);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Run handler
|
|
298
|
+
try {
|
|
299
|
+
await handler(req, res);
|
|
300
|
+
} catch (err: any) {
|
|
301
|
+
console.error(
|
|
302
|
+
'[DID Connect] handler error:',
|
|
303
|
+
err?.message || err,
|
|
304
|
+
err?.stack?.split('\n').slice(0, 5).join('\n')
|
|
305
|
+
);
|
|
306
|
+
// Write error to D1 for debugging (console.error may not be visible in tail)
|
|
307
|
+
try {
|
|
308
|
+
const db = (globalThis as any).__CF_ENV__?.DB;
|
|
309
|
+
if (db) {
|
|
310
|
+
await db
|
|
311
|
+
.prepare('INSERT OR REPLACE INTO _locks (name, owner, expires_at) VALUES (?, ?, 0)')
|
|
312
|
+
.bind('debug-did-error', `${err?.message || err}`.substring(0, 200))
|
|
313
|
+
.run();
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
/* ignore */
|
|
317
|
+
}
|
|
318
|
+
return c.json({ error: err?.message || 'Internal server error' }, 500);
|
|
319
|
+
}
|
|
320
|
+
const r = res._getResult();
|
|
321
|
+
return c.json(r?.body ?? {}, r?.statusCode ?? 200);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Monkey-patch the WalletHandlers.attach method to support Hono apps.
|
|
327
|
+
*
|
|
328
|
+
* The published @arcblock/did-connect-js expects Express. We intercept attach()
|
|
329
|
+
* and register routes on the Hono app directly using wrapHandler().
|
|
330
|
+
*/
|
|
331
|
+
function createHonoCompatHandlers(opts: { tokenStorage: any; authenticator: any }): WalletHandlers {
|
|
332
|
+
const handlers = new WalletHandlers(opts);
|
|
333
|
+
const originalAttach = handlers.attach.bind(handlers);
|
|
334
|
+
|
|
335
|
+
// Override attach to use Hono-compatible route registration
|
|
336
|
+
(handlers as any).attach = function attachHono(config: any) {
|
|
337
|
+
const { app, action, ...rest } = config;
|
|
338
|
+
|
|
339
|
+
// Create a fake Express-like app that collects routes
|
|
340
|
+
const routes: Array<{
|
|
341
|
+
method: string;
|
|
342
|
+
path: string;
|
|
343
|
+
middlewares: Function[];
|
|
344
|
+
handler: Function;
|
|
345
|
+
}> = [];
|
|
346
|
+
|
|
347
|
+
const fakeApp: any = {
|
|
348
|
+
get(path: string, ...fns: Function[]) {
|
|
349
|
+
routes.push({ method: 'get', path, middlewares: fns.slice(0, -1), handler: fns[fns.length - 1] });
|
|
350
|
+
},
|
|
351
|
+
post(path: string, ...fns: Function[]) {
|
|
352
|
+
routes.push({ method: 'post', path, middlewares: fns.slice(0, -1), handler: fns[fns.length - 1] });
|
|
353
|
+
},
|
|
354
|
+
use(_path: string, _middleware: any) {
|
|
355
|
+
// CORS is handled by Hono's cors middleware already
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Call original attach with fake app to collect routes
|
|
360
|
+
originalAttach({ app: fakeApp, action, ...rest });
|
|
361
|
+
|
|
362
|
+
// Now register collected routes on the real Hono app
|
|
363
|
+
const prefix = (handlers as any).options?.prefix || '/api/did';
|
|
364
|
+
|
|
365
|
+
// Add CORS for DID Connect routes
|
|
366
|
+
app.use(`${prefix}/${action}/*`, async (c: any, next: () => Promise<void>) => {
|
|
367
|
+
c.header('Access-Control-Allow-Origin', '*');
|
|
368
|
+
c.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
369
|
+
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
370
|
+
if (c.req.method === 'OPTIONS') {
|
|
371
|
+
return c.text('', 204);
|
|
372
|
+
}
|
|
373
|
+
await next();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
for (const route of routes) {
|
|
377
|
+
const wrapped = wrapHandler(route.middlewares as any[], route.handler as any);
|
|
378
|
+
if (route.method === 'get') {
|
|
379
|
+
app.get(route.path, wrapped);
|
|
380
|
+
} else if (route.method === 'post') {
|
|
381
|
+
app.post(route.path, wrapped);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
return handlers;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Public API
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
|
|
393
|
+
export function initAuth(kv: KVNamespace, appSK: string) {
|
|
394
|
+
const wallet = fromSecretKey(appSK, walletType);
|
|
395
|
+
const tokenStorage = new CloudflareD1Storage({ ttl: 300 });
|
|
396
|
+
// Trailing slash is REQUIRED — without it the host 50% times out (verified 2026-04-20).
|
|
397
|
+
// Matches the format used by D1 payment_methods.settings.arcblock.api_host.
|
|
398
|
+
const chainHost = 'https://beta.abtnetwork.io/api/';
|
|
399
|
+
|
|
400
|
+
// Pre-warm @ocap/client/encode's module-level context cache. The first
|
|
401
|
+
// createTxEncoder()(...) call internally awaits fetchContext, which POSTs
|
|
402
|
+
// getChainInfo + getForgeState to beta — ~1-3s on warm isolates, more on
|
|
403
|
+
// cold. When `genRequestedClaims` is called by the merchant and triggers
|
|
404
|
+
// the encoder, merchant's did-connect-js aborts after 8s. Firing the fetch
|
|
405
|
+
// here at init (module load) lets the cache populate while the worker sets
|
|
406
|
+
// up the rest of the request pipeline.
|
|
407
|
+
fetchContext(chainHost).catch((err) => {
|
|
408
|
+
// Non-fatal: on failure the first real request will retry via the
|
|
409
|
+
// encoder's own fetchContext call.
|
|
410
|
+
console.warn('[initAuth] pre-warm chainInfo failed:', err?.message);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const authenticator = new WalletAuthenticator({
|
|
414
|
+
wallet: wallet.toJSON(),
|
|
415
|
+
appInfo: ({ baseUrl }: { baseUrl: string }) => ({
|
|
416
|
+
name: 'Payment Kit',
|
|
417
|
+
description: 'Payment Kit on Cloudflare Workers',
|
|
418
|
+
icon: 'https://www.arcblock.io/favicon.ico',
|
|
419
|
+
link: baseUrl,
|
|
420
|
+
}),
|
|
421
|
+
chainInfo: {
|
|
422
|
+
type: 'arcblock',
|
|
423
|
+
host: chainHost,
|
|
424
|
+
id: 'beta',
|
|
425
|
+
},
|
|
426
|
+
txEncoder: createTxEncoder(),
|
|
427
|
+
// CF Workers: chain RPC to beta.abtnetwork.io + getContext warm-up can exceed
|
|
428
|
+
// the default 8s. Extend to 30s to match Node.js payment-kit behavior.
|
|
429
|
+
timeout: 30000,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// did-connect-js 4.0.0 natively detects Hono apps via isHonoApp()
|
|
433
|
+
const handlers = new WalletHandlers({ tokenStorage, authenticator });
|
|
434
|
+
|
|
435
|
+
return { wallet, authenticator, handlers, tokenStorage };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Attach DID Connect routes to a Hono app.
|
|
440
|
+
*
|
|
441
|
+
* Registers the login action with full profile handling, and all payment-related
|
|
442
|
+
* DID Connect actions using the original handler implementations from
|
|
443
|
+
* api/src/routes/connect/*.ts. The handlers execute real on-chain operations
|
|
444
|
+
* (transfers, delegations, staking) via @ocap/client HTTP RPC calls, which
|
|
445
|
+
* work in CF Workers. If a handler fails to attach, it falls back to a stub.
|
|
446
|
+
*
|
|
447
|
+
* Payment actions registered:
|
|
448
|
+
* collect, collect-batch, payment, subscription, setup,
|
|
449
|
+
* change-payment, change-plan, change-payer,
|
|
450
|
+
* recharge, recharge-account,
|
|
451
|
+
* delegation, overdraft-protection, re-stake,
|
|
452
|
+
* auto-recharge-auth
|
|
453
|
+
*/
|
|
454
|
+
export function attachDIDConnectRoutes(app: any, kv: KVNamespace, appSK: string) {
|
|
455
|
+
const { handlers, tokenStorage } = initAuth(kv, appSK);
|
|
456
|
+
|
|
457
|
+
// ---- Login action (full implementation) ----
|
|
458
|
+
handlers.attach({
|
|
459
|
+
app,
|
|
460
|
+
action: 'login',
|
|
461
|
+
claims: {
|
|
462
|
+
profile: () => ({
|
|
463
|
+
description: 'Please provide your profile to login',
|
|
464
|
+
fields: ['fullName', 'email', 'avatar'],
|
|
465
|
+
}),
|
|
466
|
+
},
|
|
467
|
+
onAuth: async ({ userDid, userPk, claims, updateSession }: any) => {
|
|
468
|
+
const claim = claims.find((x: any) => x.type === 'profile');
|
|
469
|
+
const { type: _type, signature: _sig, ...rest } = claim || {};
|
|
470
|
+
await updateSession({
|
|
471
|
+
result: {
|
|
472
|
+
...rest,
|
|
473
|
+
did: userDid,
|
|
474
|
+
pk: userPk,
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// ---- Payment DID Connect actions (real implementations) ----
|
|
481
|
+
// These use the original handlers from api/src/routes/connect/*.ts.
|
|
482
|
+
// esbuild aliases handle all dependency replacements (@blocklet/sdk, sequelize, etc.)
|
|
483
|
+
// so that @ocap/client HTTP-based RPC calls work in CF Workers.
|
|
484
|
+
const paymentHandlerList = [
|
|
485
|
+
collectHandlers,
|
|
486
|
+
collectBatchHandlers,
|
|
487
|
+
payHandlers,
|
|
488
|
+
setupHandlers,
|
|
489
|
+
subscribeHandlers,
|
|
490
|
+
changePaymentHandlers,
|
|
491
|
+
changePlanHandlers,
|
|
492
|
+
changePayerHandlers,
|
|
493
|
+
rechargeHandlers,
|
|
494
|
+
rechargeAccountHandlers,
|
|
495
|
+
delegationHandlers,
|
|
496
|
+
overdraftProtectionHandlers,
|
|
497
|
+
reStakeHandlers,
|
|
498
|
+
autoRechargeAuthHandlers,
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
for (const handler of paymentHandlerList) {
|
|
502
|
+
try {
|
|
503
|
+
handlers.attach({ app, ...handler });
|
|
504
|
+
console.log(`[CF DID Connect] Attached real handler: ${handler.action}`);
|
|
505
|
+
} catch (err: any) {
|
|
506
|
+
console.error(`[CF DID Connect] Failed to attach handler ${handler.action}, using stub:`, err?.message);
|
|
507
|
+
// Fallback to stub if real handler fails to attach
|
|
508
|
+
handlers.attach({
|
|
509
|
+
app,
|
|
510
|
+
action: handler.action,
|
|
511
|
+
claims: {
|
|
512
|
+
profile: () => ({
|
|
513
|
+
description: `Please connect your wallet to proceed with ${handler.action}`,
|
|
514
|
+
fields: ['fullName', 'email'],
|
|
515
|
+
}),
|
|
516
|
+
},
|
|
517
|
+
onAuth: async ({ userDid, userPk, updateSession }: any) => {
|
|
518
|
+
await updateSession({
|
|
519
|
+
result: { did: userDid, pk: userPk },
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return { handlers, tokenStorage };
|
|
527
|
+
}
|