payment-kit 1.29.1 → 1.29.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/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +47 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +41 -37
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- 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/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +247 -47
- package/api/src/libs/context.ts +89 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- 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/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +142 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +60 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +271 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +80 -0
- package/api/src/middlewares/hono/csrf.ts +83 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +209 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +38 -21
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +41 -11
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +64 -37
- package/api/src/queues/payout.ts +37 -21
- package/api/src/queues/refund.ts +36 -18
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +814 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +82 -23
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -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/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/service-host.spec.ts +37 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +292 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- 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/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +34 -27
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +33 -13
- 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/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +33 -403
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +261 -448
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +25 -0
- package/cloudflare/wrangler.staging.json +0 -6
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/bootstrap-inject.ts +166 -0
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/app.tsx +2 -1
- package/src/env.d.ts +13 -1
- package/src/libs/service-host.ts +13 -0
- package/tsconfig.json +1 -1
- package/vite.arc.config.ts +159 -0
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/did-connect-auth.ts +0 -527
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
package/api/dev.ts
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
|
+
// Dev shell — express→hono Phase 4. Dev-only (NOT bundled into production:
|
|
2
|
+
// tsconfig.api excludes api/dev.ts; the production entry is ./src → index.ts).
|
|
3
|
+
//
|
|
4
|
+
// Hosts the Vite client + HMR on a `connect` app and routes backend paths to the
|
|
5
|
+
// hono service. `vite-plugin-blocklet`'s setupClient only knows `app.use(vite
|
|
6
|
+
// .middlewares)`, so the dev shell hosts Vite on connect (NOT express — core is
|
|
7
|
+
// express-free). Backend paths are routed to service.fetch EXPLICITLY by prefix:
|
|
8
|
+
// a Web-Fetch 404 cannot call connect next(), so hono must never be the trailing
|
|
9
|
+
// catch-all (that would swallow client routes the SPA fallback should serve).
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
|
|
12
|
+
import { getRequestListener } from '@hono/node-server';
|
|
13
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
14
|
+
import connect from 'connect';
|
|
1
15
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
16
|
import { setupClient } from 'vite-plugin-blocklet';
|
|
3
17
|
|
|
4
|
-
import {
|
|
18
|
+
import { buildService, onListening } from './src/bootstrap';
|
|
19
|
+
|
|
20
|
+
const { service, port } = buildService();
|
|
21
|
+
|
|
22
|
+
// Hand backend paths to the hono app; everything else falls through to the Vite
|
|
23
|
+
// middleware setupClient() mounts after this router.
|
|
24
|
+
const honoListener = getRequestListener(service.fetch);
|
|
25
|
+
const isBackendPath = (url: string): boolean => {
|
|
26
|
+
const pathname = url.split('?')[0] || '';
|
|
27
|
+
return pathname === '/api' || pathname.startsWith('/api/') || pathname.startsWith('/.well-known/');
|
|
28
|
+
};
|
|
5
29
|
|
|
30
|
+
const app = connect();
|
|
31
|
+
app.use((req, res, next) => {
|
|
32
|
+
if (isBackendPath(req.url || '')) {
|
|
33
|
+
honoListener(req, res);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
next();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const server = createServer(app);
|
|
40
|
+
|
|
41
|
+
// setupClient appends the Vite connect middleware (client assets + HMR + SPA
|
|
42
|
+
// fallback) AFTER our backend router, and wires the HMR ws upgrade onto `server`.
|
|
6
43
|
setupClient(app, {
|
|
7
44
|
server,
|
|
8
|
-
// @ts-ignore
|
|
45
|
+
// @ts-ignore — import.meta.hot is injected by tsx/vite-node at runtime
|
|
9
46
|
importMetaHot: import.meta.hot,
|
|
10
47
|
});
|
|
48
|
+
|
|
49
|
+
server.listen(port, () => onListening(service, port));
|
package/api/hono.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ContextVariableMap skeleton (Phase 0, express→hono migration).
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the express `Request` augmentation in api/third.d.ts so that
|
|
4
|
+
// `c.set(...)` / `c.get(...)` are typed everywhere across the migration. This
|
|
5
|
+
// file only declares the key→type contract; the actual injection lands later:
|
|
6
|
+
// - user / customer / doc / customer_id → Phase 1 (libs/security.ts)
|
|
7
|
+
// - locale / t → Phase 1 (libs/middleware.ts)
|
|
8
|
+
// - sanitizedBody → Phase 1 (forked xss middleware)
|
|
9
|
+
// - livemode / baseCurrency → Phase 3 (routes/index.ts middleware)
|
|
10
|
+
// - stripeEvent / stripeClient → Phase 3e (stripe verifyWebhookSig)
|
|
11
|
+
//
|
|
12
|
+
// Two keys exist only on the hono side (no express equivalent), because hono's
|
|
13
|
+
// Request is immutable and cannot be rewritten in place the way express was:
|
|
14
|
+
// - sanitizedBody : xss is the single body read-point; routes read this, never
|
|
15
|
+
// c.req.json() (a re-read would return the UN-sanitized
|
|
16
|
+
// original — see design §7 security note).
|
|
17
|
+
// - customer_id : security "mine" mode injects the verified customer id here
|
|
18
|
+
// (express wrote req.query.customer_id =, impossible on hono).
|
|
19
|
+
import 'hono';
|
|
20
|
+
|
|
21
|
+
declare module 'hono' {
|
|
22
|
+
interface ContextVariableMap {
|
|
23
|
+
user: {
|
|
24
|
+
did: string;
|
|
25
|
+
role: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
fullName: string;
|
|
28
|
+
walletOS: string;
|
|
29
|
+
via?: string;
|
|
30
|
+
};
|
|
31
|
+
livemode: boolean;
|
|
32
|
+
locale: string;
|
|
33
|
+
t: (key: string, ...args: any[]) => string;
|
|
34
|
+
doc: any;
|
|
35
|
+
customer: any;
|
|
36
|
+
baseCurrency: any;
|
|
37
|
+
customer_id: string;
|
|
38
|
+
sanitizedBody: any;
|
|
39
|
+
stripeEvent: any;
|
|
40
|
+
stripeClient: any;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Ambient declaration for node:sqlite (experimental, Node 22+). It exists at
|
|
2
|
+
// runtime (the tests + schema-drift-guard run on a node that ships it) but
|
|
3
|
+
// @types/node does not yet declare it, so tsc --noEmit fails to resolve the import.
|
|
4
|
+
// Declared as a class so it works both as a value (`new DatabaseSync()`) and as a
|
|
5
|
+
// type (`let db: DatabaseSync`). Typed loosely — test/tooling-only consumers.
|
|
6
|
+
declare module 'node:sqlite' {
|
|
7
|
+
export class DatabaseSync {
|
|
8
|
+
constructor(path?: string, options?: any);
|
|
9
|
+
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Blocklet server service bootstrap — shared by the production entry
|
|
2
|
+
// (./index.ts → api/dist/index.js) and the dev shell (../dev.ts).
|
|
3
|
+
//
|
|
4
|
+
// Phase 4 (express→hono): the listened surface is the hono app (service.fetch).
|
|
5
|
+
// Production serves it directly via @hono/node-server. Dev hosts the Vite client
|
|
6
|
+
// + HMR on a `connect` shell (../dev.ts) and routes backend paths to the same
|
|
7
|
+
// service.fetch — so both entries build the service identically here and only
|
|
8
|
+
// differ in HOW they put it behind a socket.
|
|
9
|
+
|
|
10
|
+
import logger from './libs/logger';
|
|
11
|
+
import { getDefaultInstanceDid, getTenantMode } from './libs/tenant';
|
|
12
|
+
import { createBlockletServerDidConnectRuntime } from './libs/auth';
|
|
13
|
+
import { createEmbeddedPaymentService, type EmbeddedPaymentService } from './service';
|
|
14
|
+
import { attachNodeStatic } from './host-node/serve-static';
|
|
15
|
+
import { sequelize } from './store/sequelize';
|
|
16
|
+
import { blockletPort } from './libs/env';
|
|
17
|
+
|
|
18
|
+
export function buildService(): { service: EmbeddedPaymentService; port: number } {
|
|
19
|
+
const tenancy =
|
|
20
|
+
getTenantMode() === 'multi'
|
|
21
|
+
? ({ mode: 'multi' } as const)
|
|
22
|
+
: ({ mode: 'single', instanceDid: getDefaultInstanceDid() } as const);
|
|
23
|
+
|
|
24
|
+
const service = createEmbeddedPaymentService({
|
|
25
|
+
config: { ...process.env }, // Phase 12 narrows this to an explicit schema
|
|
26
|
+
db: { sequelize },
|
|
27
|
+
tenancy,
|
|
28
|
+
// S3-CF Phase 1 ①: the blocklet-server host owns static/SPA serving (moved out
|
|
29
|
+
// of the runtime-neutral buildHonoApp). attachNodeStatic is production-gated, so
|
|
30
|
+
// dev (this same bootstrap) is unaffected — identical to the prior behavior.
|
|
31
|
+
staticHandler: attachNodeStatic,
|
|
32
|
+
// S3-CF (DID convergence): blocklet-server is the ONE host that uses the
|
|
33
|
+
// @blocklet/sdk DID-Connect wrapper (autoConnect / notification relay /
|
|
34
|
+
// federated login). Injected explicitly via the same host-injection entry as
|
|
35
|
+
// CF/arc-node — not relied on as an implicit libs/auth default.
|
|
36
|
+
didConnectRuntime: createBlockletServerDidConnectRuntime(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const port = parseInt(blockletPort()!, 10);
|
|
40
|
+
return { service, port };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Start background services once the socket is bound (the listen callback). */
|
|
44
|
+
export function onListening(service: EmbeddedPaymentService, port: number): void {
|
|
45
|
+
logger.info(`> payment-kit ready on ${port}`);
|
|
46
|
+
service.lifecycle.start().catch((startErr) => logger.error('failed to start background services', startErr));
|
|
47
|
+
}
|
package/api/src/crons/base.ts
CHANGED
|
@@ -175,7 +175,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
175
175
|
await pAll(
|
|
176
176
|
deleteTasks.map((task) => () => task()),
|
|
177
177
|
{
|
|
178
|
-
concurrency: notificationCronConcurrency,
|
|
178
|
+
concurrency: notificationCronConcurrency(),
|
|
179
179
|
stopOnError: false,
|
|
180
180
|
}
|
|
181
181
|
);
|
|
@@ -192,7 +192,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
192
192
|
};
|
|
193
193
|
}),
|
|
194
194
|
{
|
|
195
|
-
concurrency: notificationCronConcurrency,
|
|
195
|
+
concurrency: notificationCronConcurrency(),
|
|
196
196
|
stopOnError: false,
|
|
197
197
|
}
|
|
198
198
|
);
|
|
@@ -231,7 +231,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
231
231
|
);
|
|
232
232
|
|
|
233
233
|
await pAll(deletePromises, {
|
|
234
|
-
concurrency: notificationCronConcurrency,
|
|
234
|
+
concurrency: notificationCronConcurrency(),
|
|
235
235
|
stopOnError: false,
|
|
236
236
|
});
|
|
237
237
|
}
|
package/api/src/crons/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getCronDriver } from '../libs/drivers/cron';
|
|
2
2
|
|
|
3
3
|
import { checkStakeRevokeTx } from '../integrations/arcblock/stake';
|
|
4
4
|
import { runIapReconcile } from '../integrations/iap-reconcile';
|
|
@@ -34,6 +34,7 @@ import { CheckoutSession } from '../store/models';
|
|
|
34
34
|
import { createOverdueDetection } from './overdue-detection';
|
|
35
35
|
import { createPaymentStat } from './payment-stat';
|
|
36
36
|
import { retryPendingEvents } from './retry-pending-events';
|
|
37
|
+
import { perTenant } from './tenant-fanout';
|
|
37
38
|
import { SubscriptionTrialWillEndSchedule } from './subscription-trial-will-end';
|
|
38
39
|
import { SubscriptionWillCanceledSchedule } from './subscription-will-canceled';
|
|
39
40
|
import { SubscriptionWillRenewSchedule } from './subscription-will-renew';
|
|
@@ -63,30 +64,33 @@ function init() {
|
|
|
63
64
|
},
|
|
64
65
|
]
|
|
65
66
|
: [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
// D2: register through the injected cron driver (no bare @abtnode/cron). On
|
|
68
|
+
// the node host the driver self-schedules via @abtnode/cron; on CF the host
|
|
69
|
+
// drives runDue() from scheduled(). register() is idempotent (clears + re-adds
|
|
70
|
+
// + restarts the scheduler), so a stop()/start() cycle never double-registers.
|
|
71
|
+
getCronDriver().register(
|
|
72
|
+
[
|
|
69
73
|
{
|
|
70
74
|
name: 'subscription.will.renew',
|
|
71
|
-
time: notificationCronTime,
|
|
72
|
-
fn: () => new SubscriptionWillRenewSchedule().run(),
|
|
75
|
+
time: notificationCronTime(),
|
|
76
|
+
fn: perTenant('subscription.will.renew', () => new SubscriptionWillRenewSchedule().run()),
|
|
73
77
|
options: { runOnInit: true },
|
|
74
78
|
},
|
|
75
79
|
{
|
|
76
80
|
name: 'subscription.trial.will.end',
|
|
77
|
-
time: notificationCronTime,
|
|
78
|
-
fn: () => new SubscriptionTrialWillEndSchedule().run(),
|
|
81
|
+
time: notificationCronTime(),
|
|
82
|
+
fn: perTenant('subscription.trial.will.end', () => new SubscriptionTrialWillEndSchedule().run()),
|
|
79
83
|
options: { runOnInit: true },
|
|
80
84
|
},
|
|
81
85
|
{
|
|
82
86
|
name: 'customer.subscription.will_canceled',
|
|
83
|
-
time: notificationCronTime,
|
|
84
|
-
fn: () => new SubscriptionWillCanceledSchedule().run(),
|
|
87
|
+
time: notificationCronTime(),
|
|
88
|
+
fn: perTenant('customer.subscription.will_canceled', () => new SubscriptionWillCanceledSchedule().run()),
|
|
85
89
|
options: { runOnInit: true },
|
|
86
90
|
},
|
|
87
91
|
{
|
|
88
92
|
name: 'subscription.schedule.retry',
|
|
89
|
-
time: subscriptionCronTime,
|
|
93
|
+
time: subscriptionCronTime(),
|
|
90
94
|
fn: startSubscriptionQueue,
|
|
91
95
|
options: { runOnInit: false },
|
|
92
96
|
},
|
|
@@ -101,35 +105,35 @@ function init() {
|
|
|
101
105
|
},
|
|
102
106
|
{
|
|
103
107
|
name: 'checkoutSession.cleanup.expired',
|
|
104
|
-
time: expiredSessionCleanupCronTime,
|
|
105
|
-
fn: async () => {
|
|
108
|
+
time: expiredSessionCleanupCronTime(),
|
|
109
|
+
fn: perTenant('checkoutSession.cleanup.expired', async () => {
|
|
106
110
|
const removedCount = await CheckoutSession.cleanupExpiredSessions();
|
|
107
111
|
logger.info('CheckoutSession.cleanupExpiredSessions', { removedCount });
|
|
108
|
-
},
|
|
112
|
+
}),
|
|
109
113
|
options: { runOnInit: true },
|
|
110
114
|
},
|
|
111
115
|
{
|
|
112
116
|
name: 'stripe.invoice.sync',
|
|
113
|
-
time: stripeInvoiceCronTime,
|
|
114
|
-
fn: batchHandleStripeInvoices,
|
|
117
|
+
time: stripeInvoiceCronTime(),
|
|
118
|
+
fn: perTenant('stripe.invoice.sync', batchHandleStripeInvoices),
|
|
115
119
|
options: { runOnInit: false },
|
|
116
120
|
},
|
|
117
121
|
{
|
|
118
122
|
name: 'stripe.payment.sync',
|
|
119
|
-
time: stripePaymentCronTime,
|
|
120
|
-
fn: batchHandleStripePayments,
|
|
123
|
+
time: stripePaymentCronTime(),
|
|
124
|
+
fn: perTenant('stripe.payment.sync', batchHandleStripePayments),
|
|
121
125
|
options: { runOnInit: false },
|
|
122
126
|
},
|
|
123
127
|
{
|
|
124
128
|
name: 'stripe.subscription.sync',
|
|
125
|
-
time: stripeSubscriptionCronTime,
|
|
126
|
-
fn: batchHandleStripeSubscriptions,
|
|
129
|
+
time: stripeSubscriptionCronTime(),
|
|
130
|
+
fn: perTenant('stripe.subscription.sync', batchHandleStripeSubscriptions),
|
|
127
131
|
options: { runOnInit: false },
|
|
128
132
|
},
|
|
129
133
|
{
|
|
130
134
|
name: 'customer.stake.revoked',
|
|
131
|
-
time: revokeStakeCronTime,
|
|
132
|
-
fn: checkStakeRevokeTx,
|
|
135
|
+
time: revokeStakeCronTime(),
|
|
136
|
+
fn: perTenant('customer.stake.revoked', checkStakeRevokeTx),
|
|
133
137
|
options: { runOnInit: false },
|
|
134
138
|
},
|
|
135
139
|
{
|
|
@@ -137,8 +141,8 @@ function init() {
|
|
|
137
141
|
// subscription state and patches local drift (refunds/renewals the
|
|
138
142
|
// webhook missed). See blocklets/core/api/src/integrations/iap-reconcile.ts.
|
|
139
143
|
name: 'iap.reconcile',
|
|
140
|
-
time: iapReconcileCronTime,
|
|
141
|
-
fn: () => runIapReconcile(),
|
|
144
|
+
time: iapReconcileCronTime(),
|
|
145
|
+
fn: perTenant('iap.reconcile', () => runIapReconcile()),
|
|
142
146
|
options: { runOnInit: false },
|
|
143
147
|
},
|
|
144
148
|
{
|
|
@@ -146,52 +150,52 @@ function init() {
|
|
|
146
150
|
// with pending_webhooks>0 older than 60s and re-invokes handleEvent.
|
|
147
151
|
// See blocklets/core/api/src/crons/retry-pending-events.ts.
|
|
148
152
|
name: 'event.retry',
|
|
149
|
-
time: eventRetryCronTime,
|
|
150
|
-
fn: () => retryPendingEvents(),
|
|
153
|
+
time: eventRetryCronTime(),
|
|
154
|
+
fn: perTenant('event.retry', () => retryPendingEvents()),
|
|
151
155
|
options: { runOnInit: false },
|
|
152
156
|
},
|
|
153
157
|
{
|
|
154
158
|
name: 'payment.stat',
|
|
155
|
-
time: paymentStatCronTime,
|
|
156
|
-
fn: () => createPaymentStat(),
|
|
159
|
+
time: paymentStatCronTime(),
|
|
160
|
+
fn: perTenant('payment.stat', () => createPaymentStat()),
|
|
157
161
|
options: { runOnInit: false },
|
|
158
162
|
},
|
|
159
163
|
{
|
|
160
164
|
name: 'payment.daily.report',
|
|
161
|
-
time: overdueDetectionCronTime,
|
|
162
|
-
fn: () => createOverdueDetection(),
|
|
165
|
+
time: overdueDetectionCronTime(),
|
|
166
|
+
fn: perTenant('payment.daily.report', () => createOverdueDetection()),
|
|
163
167
|
options: { runOnInit: false },
|
|
164
168
|
},
|
|
165
169
|
{
|
|
166
170
|
name: 'deposit.vault',
|
|
167
|
-
time: depositVaultCronTime,
|
|
171
|
+
time: depositVaultCronTime(),
|
|
168
172
|
fn: () => startDepositVaultQueue(),
|
|
169
173
|
options: { runOnInit: true },
|
|
170
174
|
},
|
|
171
175
|
{
|
|
172
176
|
name: 'credit.consumption',
|
|
173
|
-
time: creditConsumptionCronTime,
|
|
177
|
+
time: creditConsumptionCronTime(),
|
|
174
178
|
fn: () => startCreditConsumeQueue(),
|
|
175
179
|
options: { runOnInit: true },
|
|
176
180
|
},
|
|
177
181
|
{
|
|
178
182
|
name: 'vendor.status.check',
|
|
179
|
-
time: vendorStatusCheckCronTime,
|
|
183
|
+
time: vendorStatusCheckCronTime(),
|
|
180
184
|
fn: () => startVendorStatusCheckSchedule(),
|
|
181
185
|
options: { runOnInit: false },
|
|
182
186
|
},
|
|
183
187
|
{
|
|
184
188
|
name: 'vendor.return.scan',
|
|
185
|
-
time: vendorReturnScanCronTime,
|
|
189
|
+
time: vendorReturnScanCronTime(),
|
|
186
190
|
fn: () => scheduleVendorReturnScan(),
|
|
187
191
|
options: { runOnInit: false },
|
|
188
192
|
},
|
|
189
193
|
...archiveJobs,
|
|
190
194
|
],
|
|
191
|
-
|
|
195
|
+
(error: Error, name: string) => {
|
|
192
196
|
logger.error('run job failed', { name, error });
|
|
193
|
-
}
|
|
194
|
-
|
|
197
|
+
}
|
|
198
|
+
);
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
export default {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Notification from '@blocklet/sdk/service/notification';
|
|
2
2
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
3
3
|
import { env } from '@blocklet/sdk/lib/config';
|
|
4
4
|
import { fromUnitToToken, BN } from '@ocap/util';
|
|
@@ -175,7 +175,7 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
175
175
|
currencyTotals.set(currencyId, currentTotal.add(group.total));
|
|
176
176
|
|
|
177
177
|
// Check if this user's pending exceeds threshold for this currency
|
|
178
|
-
const thresholdInUnit = new BN(overdueThreshold.toString()).mul(new BN(10).pow(new BN(currency.decimal)));
|
|
178
|
+
const thresholdInUnit = new BN(overdueThreshold().toString()).mul(new BN(10).pow(new BN(currency.decimal)));
|
|
179
179
|
if (group.total.gt(thresholdInUnit)) {
|
|
180
180
|
exceedsThreshold = true;
|
|
181
181
|
}
|
|
@@ -33,6 +33,12 @@ const REALTIME_GRACE_SECONDS = 60;
|
|
|
33
33
|
// generous headroom and we still drain the backlog over a few minutes.
|
|
34
34
|
const BATCH_LIMIT = 5;
|
|
35
35
|
|
|
36
|
+
// Phase 4 (W1-3): this scan is deliberately tenant-agnostic — it only
|
|
37
|
+
// re-enqueues event IDs. Tenant enforcement happens downstream in
|
|
38
|
+
// handleEvent's fanout (endpoints filtered by the event's instance_did) and
|
|
39
|
+
// handleWebhook's event/endpoint tenant invariant. Events whose creation was
|
|
40
|
+
// tenant-rejected never produced a row, so this backstop cannot "rescue"
|
|
41
|
+
// them — permanent loss of rejected events is the intended semantic.
|
|
36
42
|
export async function retryPendingEvents(): Promise<void> {
|
|
37
43
|
const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
|
|
38
44
|
const docs = await Event.findAll({
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Multi-tenant cron fan-out (S3 arc-payment-embed — Phase 7).
|
|
2
|
+
//
|
|
3
|
+
// Background crons that issue a top-level tenant-scoped model query
|
|
4
|
+
// (subscription schedules, reconciliation, payment stat, overdue detection,
|
|
5
|
+
// stake-revoke, expired-session cleanup, stripe sync, event retry) have NO
|
|
6
|
+
// request to carry a tenant. In multi mode `getInstanceDid()` throws
|
|
7
|
+
// TENANT_CONTEXT_MISSING and the whole pass does nothing. Single mode is
|
|
8
|
+
// unaffected — the default tenant auto-resolves.
|
|
9
|
+
//
|
|
10
|
+
// Tenant enumeration is SELF-CONTAINED (decision 2026-06-13): payment-core owns
|
|
11
|
+
// its own scope, so we iterate the tenants that have payment configuration in
|
|
12
|
+
// OUR OWN store (`provisionTenant` seeds a PaymentMethod row per tenant) rather
|
|
13
|
+
// than asking the host for a registry. A tenant with no payment rows has
|
|
14
|
+
// nothing for these crons to do, and this fits a lazily-provisioned host
|
|
15
|
+
// (arc-node) where a tenant appears in payment.db only after its first request.
|
|
16
|
+
//
|
|
17
|
+
// Queue-starter crons (startXQueue) are NOT wrapped: each persisted job already
|
|
18
|
+
// carries its own instance_did and runs inside withTenant(job.tenant) via the
|
|
19
|
+
// queue runtime (libs/queue runJobWithTenant). Wrapping them would re-scan and
|
|
20
|
+
// double-process.
|
|
21
|
+
|
|
22
|
+
import { withTenant } from '../libs/context';
|
|
23
|
+
import logger from '../libs/logger';
|
|
24
|
+
import { getTenantMode } from '../libs/tenant';
|
|
25
|
+
import { systemFindAll } from '../store/scoped';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tenants that have payment configuration in payment-core's own store. Reads
|
|
29
|
+
* across tenants (systemFindAll runs inside runAsSystem so the TenantModel base
|
|
30
|
+
* does not re-scope to a — here absent — active tenant). DISTINCT is done in JS
|
|
31
|
+
* to stay compatible with both real Sequelize and the worker's sequelize-d1
|
|
32
|
+
* shim. PaymentMethod is the canonical "provisioned tenant" marker; a tenant
|
|
33
|
+
* that has any payment data necessarily has a payment method.
|
|
34
|
+
*/
|
|
35
|
+
export async function listProvisionedTenants(): Promise<string[]> {
|
|
36
|
+
// eslint-disable-next-line global-require
|
|
37
|
+
const { PaymentMethod } = require('../store/models');
|
|
38
|
+
// raw:true returns plain rows, not Model instances — cast through unknown.
|
|
39
|
+
const rows = (await systemFindAll(PaymentMethod, {
|
|
40
|
+
attributes: ['instance_did'],
|
|
41
|
+
raw: true,
|
|
42
|
+
})) as unknown as Array<{ instance_did?: string | null }>;
|
|
43
|
+
const dids = new Set<string>();
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
if (row?.instance_did) dids.add(row.instance_did);
|
|
46
|
+
}
|
|
47
|
+
return [...dids];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap a tenant-scoped cron fn so it runs once per provisioned tenant inside
|
|
52
|
+
* `withTenant`. Single mode runs the fn as-is (the default tenant auto-resolves
|
|
53
|
+
* in getInstanceDid). Multi mode enumerates provisioned tenants and runs the fn
|
|
54
|
+
* per tenant; per-tenant errors are ISOLATED (caught + logged) so one tenant's
|
|
55
|
+
* failure never aborts the rest of the pass.
|
|
56
|
+
*
|
|
57
|
+
* @param listTenants enumeration seam (defaults to listProvisionedTenants;
|
|
58
|
+
* injected in tests so the fan-out logic is exercised without a DB).
|
|
59
|
+
*/
|
|
60
|
+
export function perTenant(
|
|
61
|
+
name: string,
|
|
62
|
+
fn: () => Promise<unknown> | unknown,
|
|
63
|
+
listTenants: () => Promise<string[]> = listProvisionedTenants
|
|
64
|
+
): () => Promise<void> {
|
|
65
|
+
return async () => {
|
|
66
|
+
if (getTenantMode() === 'single') {
|
|
67
|
+
await fn();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const dids = await listTenants();
|
|
71
|
+
if (dids.length === 0) return;
|
|
72
|
+
logger.info('cron.tenant.fanout', { cron: name, tenantCount: dids.length });
|
|
73
|
+
for (const instanceDid of dids) {
|
|
74
|
+
// async callback so a SYNCHRONOUS throw from fn surfaces as a rejection
|
|
75
|
+
// (and stays inside the tenant's ALS scope) — then .catch isolates it.
|
|
76
|
+
// eslint-disable-next-line no-await-in-loop, require-await -- sequential per-tenant pass; async wraps sync throws
|
|
77
|
+
await withTenant(instanceDid, async () => fn()).catch((error: unknown) =>
|
|
78
|
+
logger.error('cron tenant pass failed', { cron: name, instanceDid, error })
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Node host adapter — the AUTH_SERVICE-backed DID-Connect runtime for the
|
|
2
|
+
// arc-node embedded host. The node analog of cloudflare/did-connect-runtime.ts:
|
|
3
|
+
// the shared @arcblock/did-connect-js runtime whose signing wallet/appInfo are
|
|
4
|
+
// resolved per-tenant via resolveTenantIdentity (getInstanceAppIdentity), NOT a
|
|
5
|
+
// fixed isolate key.
|
|
6
|
+
//
|
|
7
|
+
// Without this, a bare host falls back to createBlockletServerDidConnectRuntime,
|
|
8
|
+
// whose WalletAuthenticator is constructed with `makeWallet()` (env BLOCKLET_APP_SK)
|
|
9
|
+
// — which arc never sets — so the first DID-Connect sign (e.g. /api/did/payment/token
|
|
10
|
+
// → generateSession) throws "Missing public key for SK wallet: BLOCKLET_APP_PK".
|
|
11
|
+
//
|
|
12
|
+
// Differences from the CF runtime are node-only:
|
|
13
|
+
// - txEncoder = the @ocap/client/encode CBOR encoder (same one the
|
|
14
|
+
// blocklet-server runtime passes), resolved from the host's node_modules.
|
|
15
|
+
// - tokenStorage omitted → buildTokenStorage's file-backed nedb default
|
|
16
|
+
// (os.tmpdir on a bare host; DID-Connect session tokens are ephemeral).
|
|
17
|
+
// - chainInfo omitted → the 14 payment DID handlers resolve it per-payment in
|
|
18
|
+
// onConnect, exactly as the blocklet-server runtime (which passes none).
|
|
19
|
+
import type { DidConnectRuntime, DidConnectTokenStorage } from '../libs/auth';
|
|
20
|
+
import { createDidConnectJsRuntime } from '../libs/did-connect/runtime-did-connect-js';
|
|
21
|
+
|
|
22
|
+
export function createNodeDidConnectRuntime(opts?: {
|
|
23
|
+
tokenStorage?: DidConnectTokenStorage;
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}): DidConnectRuntime {
|
|
26
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
27
|
+
const { createTxEncoder } = require('@ocap/client/encode');
|
|
28
|
+
return createDidConnectJsRuntime({
|
|
29
|
+
tokenStorage: opts?.tokenStorage,
|
|
30
|
+
txEncoder: createTxEncoder(),
|
|
31
|
+
timeout: opts?.timeout ?? 30000,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// P3b (README D3 / F3) — clean node static + SPA handler for embedded hosts (arc).
|
|
2
|
+
//
|
|
3
|
+
// Unlike `attachNodeStatic` (serve-static.ts), this is NOT blocklet-server bound:
|
|
4
|
+
// - no isProduction gate, no blockletAppDir (host passes an explicit webRoot)
|
|
5
|
+
// - does NOT reuse the @blocklet/sdk-bound `fallback` middleware, which injects
|
|
6
|
+
// getBlockletJs / theme server-side. This handler serves index.html VERBATIM
|
|
7
|
+
// — the window.blocklet bootstrap is already baked in at BUILD time (P2), so
|
|
8
|
+
// there is ZERO server-side injection (T3b.3).
|
|
9
|
+
//
|
|
10
|
+
// Wired via the host `staticHandler` slot (P3a / service.ts:81), AFTER the
|
|
11
|
+
// api/connect routes. Order (T3b.1): SPA fallback FIRST (html GET/HEAD that is
|
|
12
|
+
// not a RESOURCE_PATTERN asset → index.html), serveStatic AFTER (real asset
|
|
13
|
+
// files; a miss falls through to 404, never index.html — T3b.2).
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
|
|
17
|
+
import type { Hono } from 'hono';
|
|
18
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
19
|
+
import { RESOURCE_PATTERN } from '@blocklet/constant';
|
|
20
|
+
|
|
21
|
+
function acceptsHtml(accept: string): boolean {
|
|
22
|
+
if (!accept) return true;
|
|
23
|
+
return accept.includes('text/html') || accept.includes('application/xhtml+xml') || accept.includes('*/*');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Build a host-injectable static/SPA handler over `webRoot` (the arc webRoot =
|
|
28
|
+
* `@arcblock/payment-service/web`). Returns a `(app) => void` for the
|
|
29
|
+
* staticHandler slot. Throws at construction if webRoot has no index.html
|
|
30
|
+
* (fail fast — a misconfigured webRoot must not silently 404 every navigation).
|
|
31
|
+
*/
|
|
32
|
+
export function createNodeStaticHandler(webRoot: string): (app: Hono) => void {
|
|
33
|
+
const indexPath = path.join(webRoot, 'index.html');
|
|
34
|
+
if (!fs.existsSync(indexPath)) {
|
|
35
|
+
throw new Error(`createNodeStaticHandler: webRoot has no index.html: ${indexPath}`);
|
|
36
|
+
}
|
|
37
|
+
// index.html is an immutable build artifact for the process lifetime — read once.
|
|
38
|
+
let cachedIndex: string | null = null;
|
|
39
|
+
|
|
40
|
+
return (app: Hono) => {
|
|
41
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
42
|
+
const { serveStatic } = require('@hono/node-server/serve-static');
|
|
43
|
+
|
|
44
|
+
// SPA fallback FIRST — html navigations (not assets) get index.html VERBATIM.
|
|
45
|
+
app.use('*', async (c, next) => {
|
|
46
|
+
const method = c.req.method.toUpperCase();
|
|
47
|
+
if (
|
|
48
|
+
(method !== 'GET' && method !== 'HEAD') ||
|
|
49
|
+
!acceptsHtml(c.req.header('accept') || '') ||
|
|
50
|
+
RESOURCE_PATTERN.test(c.req.path)
|
|
51
|
+
) {
|
|
52
|
+
return next();
|
|
53
|
+
}
|
|
54
|
+
if (cachedIndex == null) cachedIndex = fs.readFileSync(indexPath, 'utf8');
|
|
55
|
+
return c.html(cachedIndex);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Real asset files. Pass the ABSOLUTE webRoot: serveStatic does
|
|
59
|
+
// `join(root, filename)` + existsSync, so an absolute root resolves the file
|
|
60
|
+
// regardless of process.cwd(). (attachNodeStatic mapped to a cwd-relative
|
|
61
|
+
// path; that breaks in an embedded host whose cwd is not the app dir — e.g.
|
|
62
|
+
// the arc daemon, whose cwd ≠ the node_modules webRoot.) A miss falls
|
|
63
|
+
// through (→ 404).
|
|
64
|
+
app.use('*', serveStatic({ root: webRoot }));
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default createNodeStaticHandler;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Node host (blocklet-server) static + SPA serving.
|
|
2
|
+
//
|
|
3
|
+
// S3-CF Phase 1 inversion ①: this is the node-only static/SPA shell that used to
|
|
4
|
+
// live INSIDE buildHonoApp (service.ts). It is moved here so the runtime-neutral
|
|
5
|
+
// hono app carries ZERO node:fs / @hono/node-server — that is what let `http.fetch`
|
|
6
|
+
// converge to a single surface shared by node, CF, and the standalone worker.
|
|
7
|
+
//
|
|
8
|
+
// The blocklet-server entry (bootstrap.ts) injects this as the `staticHandler`
|
|
9
|
+
// slot. It is a verbatim move of the old block (serveStatic + the node:fs
|
|
10
|
+
// fallback), still production-gated, so blocklet-server behavior is unchanged. The
|
|
11
|
+
// CF/standalone worker serves assets via env.ASSETS and injects nothing here, so
|
|
12
|
+
// this module is never reached by the worker bundle.
|
|
13
|
+
import path from 'path';
|
|
14
|
+
|
|
15
|
+
import type { Hono } from 'hono';
|
|
16
|
+
|
|
17
|
+
import { isProduction, blockletAppDir } from '../libs/env';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Wire production static + SPA fallback onto the full node hono app. No-op outside
|
|
21
|
+
* production (identical to the old `if (isProduction())` gate in buildHonoApp).
|
|
22
|
+
*
|
|
23
|
+
* `fallback` runs first and skips asset paths (RESOURCE_PATTERN) + non-html, so
|
|
24
|
+
* real files fall through to serveStatic and html navigations get the injected
|
|
25
|
+
* index.html. Both modules are required lazily so this file's import stays cheap.
|
|
26
|
+
*/
|
|
27
|
+
export function attachNodeStatic(app: Hono): void {
|
|
28
|
+
if (!isProduction()) return;
|
|
29
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
30
|
+
const { serveStatic } = require('@hono/node-server/serve-static');
|
|
31
|
+
// eslint-disable-next-line global-require
|
|
32
|
+
const { fallback } = require('../middlewares/hono/fallback');
|
|
33
|
+
const staticDir = path.resolve(blockletAppDir()!, 'dist');
|
|
34
|
+
// serveStatic resolves `root` relative to process.cwd(); map the absolute app
|
|
35
|
+
// dist dir back to a cwd-relative path so it resolves to the same place.
|
|
36
|
+
const staticRoot = path.relative(process.cwd(), staticDir) || '.';
|
|
37
|
+
app.use('*', fallback('index.html', { root: staticDir })); // injected index.html for html GET (skips assets)
|
|
38
|
+
app.use('*', serveStatic({ root: staticRoot })); // real asset files
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default attachNodeStatic;
|