payment-kit 1.29.0 → 1.29.2
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 +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- 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/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 +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- 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 +81 -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 +50 -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 +259 -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 +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -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 +214 -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 +17 -12
- 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 +12 -4
- 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 +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- 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} +193 -223
- 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} +96 -80
- 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 +667 -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 +66 -22
- 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/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/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/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 +236 -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/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/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -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 +31 -56
- 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/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- 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/node-fetch.ts +35 -0
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- 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/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- 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/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/cloudflare/worker.ts
CHANGED
|
@@ -14,15 +14,30 @@ import { setDB } from './shims/sequelize-d1/model';
|
|
|
14
14
|
import { initialize } from '../api/src/store/models';
|
|
15
15
|
import { Sequelize } from './shims/sequelize-d1/sequelize-class';
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
import
|
|
19
|
-
import
|
|
17
|
+
// Phase 12b: pin the core queue engine to 'workerd' mode BEFORE any business
|
|
18
|
+
// queue module loads (createQueue reads it at import to disable the node poll
|
|
19
|
+
// loop). Side-effect import — keep it ahead of service/crons/queues below.
|
|
20
|
+
import './queue-runtime-mode';
|
|
21
|
+
|
|
22
|
+
// Phase 12a (Option 3 seam): the worker's /api business surface now comes from
|
|
23
|
+
// the embedded payment service factory instead of a direct routes import. The
|
|
24
|
+
// factory performs assembly (config slot authoritative via setCoreConfig + model
|
|
25
|
+
// initialize), and exposes `http.resourceRoutes` — the resource routers only,
|
|
26
|
+
// no node app shell, no DID-Connect handlers (the worker registers those through
|
|
27
|
+
// its own Hono `attachDIDConnectRoutes`). The factory's lazy `handler` getter is
|
|
28
|
+
// never touched here, so the node-only app shell never runs under the shim.
|
|
29
|
+
import { createEmbeddedPaymentService } from '../api/src/service';
|
|
30
|
+
import type { PaymentCoreService } from '../api/src/service';
|
|
20
31
|
|
|
21
32
|
// Import cron instance for scheduled handler
|
|
22
33
|
import { cronInstance } from './shims/cron';
|
|
23
34
|
|
|
24
|
-
//
|
|
25
|
-
|
|
35
|
+
// Phase 12b: drive the SAME core queue engine (api/src/libs/queue) through its
|
|
36
|
+
// host-facing runtime surface instead of the legacy cloudflare/shims/queue.ts
|
|
37
|
+
// duplicate engine (dead under the canonical build — its registry is never
|
|
38
|
+
// populated). scheduled() → dispatchDueJobs(); queue() → getQueueHandler();
|
|
39
|
+
// HTTP/scheduled/queue flush → flushQueueWork().
|
|
40
|
+
import { dispatchDueJobs, getQueueHandler, getAllQueueNames, flushQueueWork } from '../api/src/libs/queue/runtime';
|
|
26
41
|
|
|
27
42
|
// Import crons init to register all cron jobs
|
|
28
43
|
import crons from '../api/src/crons/index';
|
|
@@ -53,6 +68,10 @@ import { withD1Retry } from './shims/sequelize-d1/retry';
|
|
|
53
68
|
// DID Connect: login routes proxied to blocklet-service, business actions (pay/subscribe) handled locally
|
|
54
69
|
import { attachDIDConnectRoutes } from './did-connect-auth';
|
|
55
70
|
|
|
71
|
+
// Phase 7: tenant-context middleware — resolves Host -> tenant (single point)
|
|
72
|
+
// and wraps the request chain in context.withTenant (multi-mode fail-closed).
|
|
73
|
+
import { tenantMiddleware } from './tenant-middleware';
|
|
74
|
+
|
|
56
75
|
FetchRequest.registerGetUrl(async (req: FetchRequest) => {
|
|
57
76
|
const resp = await fetch(req.url, {
|
|
58
77
|
method: req.method || 'GET',
|
|
@@ -204,6 +223,93 @@ function ensureModelsInit() {
|
|
|
204
223
|
}
|
|
205
224
|
}
|
|
206
225
|
|
|
226
|
+
// Phase 12a/12c: build the explicit PaymentCoreConfig from CF env. These keys are
|
|
227
|
+
// read only by the core (via the libs/env.ts readConfig boundary), never by a
|
|
228
|
+
// worker shim, so the factory config slot carries them. Phase 12c removed the
|
|
229
|
+
// last `process.env` mirror: the HTTP path (buildApp) and the scheduled()/queue()
|
|
230
|
+
// path (setupEnv) both call ensurePaymentService(env) -> setCoreConfig, so the
|
|
231
|
+
// config slot is authoritative on every path and no process.env fallback remains.
|
|
232
|
+
function envToPaymentCoreConfig(env: Env): Record<string, any> {
|
|
233
|
+
const config: Record<string, any> = { BLOCKLET_MODE: 'production' };
|
|
234
|
+
if (env.APP_PID) {
|
|
235
|
+
config.BLOCKLET_APP_PID = env.APP_PID;
|
|
236
|
+
config.BLOCKLET_APP_ID = env.APP_PID;
|
|
237
|
+
}
|
|
238
|
+
if (env.APP_URL) {
|
|
239
|
+
config.APP_URL = env.APP_URL;
|
|
240
|
+
config.BLOCKLET_APP_URL = env.APP_URL;
|
|
241
|
+
}
|
|
242
|
+
if (env.APP_NAME) config.BLOCKLET_APP_NAME = env.APP_NAME;
|
|
243
|
+
// Stripe webhook secret: env override for signature verification (DB value may
|
|
244
|
+
// be empty if not configured in the original Blocklet Server).
|
|
245
|
+
if (env.STRIPE_WEBHOOK_SECRET) config.STRIPE_WEBHOOK_SECRET = env.STRIPE_WEBHOOK_SECRET;
|
|
246
|
+
if (env.PAYMENT_CHANGE_LOCKED_PRICE) config.PAYMENT_CHANGE_LOCKED_PRICE = env.PAYMENT_CHANGE_LOCKED_PRICE;
|
|
247
|
+
if (env.SHORT_URL_DOMAIN) config.SHORT_URL_DOMAIN = env.SHORT_URL_DOMAIN;
|
|
248
|
+
// Audience for the Pub/Sub OIDC JWT wrapping Google Play RTDN webhooks; without
|
|
249
|
+
// it googlePlayEndpoint() falls back to the workers.dev origin -> audience mismatch.
|
|
250
|
+
if (env.GOOGLE_PLAY_WEBHOOK_URL) config.GOOGLE_PLAY_WEBHOOK_URL = env.GOOGLE_PLAY_WEBHOOK_URL;
|
|
251
|
+
// Pub/Sub sender binding — without it the google_play webhook can't enforce the
|
|
252
|
+
// push service account on CF and would fail open (PR #1381 P1).
|
|
253
|
+
if (env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT)
|
|
254
|
+
config.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT = env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT;
|
|
255
|
+
if (env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER)
|
|
256
|
+
config.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER = env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER;
|
|
257
|
+
return config;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Phase 12a: assemble the embedded payment service once per isolate. The factory
|
|
261
|
+
// performs config/db assembly (setCoreConfig + initialize), so models are bound
|
|
262
|
+
// here and ensureModelsInit() becomes a no-op on the HTTP path. Slots beyond
|
|
263
|
+
// config/db are intentionally omitted in 12a (defaults preserve current worker
|
|
264
|
+
// behavior: single-mode default tenant resolved from BLOCKLET_APP_PID); richer
|
|
265
|
+
// slot bridging is 12b/12c.
|
|
266
|
+
let paymentService: PaymentCoreService | null = null;
|
|
267
|
+
function ensurePaymentService(env: Env): PaymentCoreService {
|
|
268
|
+
if (paymentService) return paymentService;
|
|
269
|
+
const sequelize = new Sequelize();
|
|
270
|
+
const svc = createEmbeddedPaymentService({
|
|
271
|
+
config: envToPaymentCoreConfig(env),
|
|
272
|
+
db: { sequelize },
|
|
273
|
+
});
|
|
274
|
+
// Phase 12c HARD GATE: the CF worker is a host adapter. It mounts only
|
|
275
|
+
// svc.http.resourceRoutes and registers DID-Connect through its own Hono
|
|
276
|
+
// shell — it must NEVER touch svc.handler, whose lazy getter builds the
|
|
277
|
+
// node-only Express app shell (app.set / static / connect attach) that does
|
|
278
|
+
// not belong under workerd. Trap the access so a future regression fails loud
|
|
279
|
+
// at runtime instead of silently constructing the node app in the isolate.
|
|
280
|
+
paymentService = new Proxy(svc, {
|
|
281
|
+
get(target, prop, receiver) {
|
|
282
|
+
if (prop === 'handler') {
|
|
283
|
+
throw new Error(
|
|
284
|
+
'[worker hard-gate] svc.handler is forbidden in the CF worker — mount svc.http.resourceRoutes instead'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return Reflect.get(target, prop, receiver);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
modelsInitialized = true; // the factory called initialize(sequelize)
|
|
291
|
+
return paymentService;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Phase 2 (W1-1b): static D1 migration SQL cannot know the deployment app
|
|
295
|
+
// DID, so the instance_did backfill + unique-key rebuilds run through the
|
|
296
|
+
// shared runtime routine. Idempotent (NULL-only updates, DDL checks), so a
|
|
297
|
+
// once-per-isolate guard just avoids 38 no-op UPDATEs on every cron tick.
|
|
298
|
+
let tenantBackfillDone = false;
|
|
299
|
+
async function ensureTenantBackfill() {
|
|
300
|
+
if (tenantBackfillDone) return;
|
|
301
|
+
try {
|
|
302
|
+
const { runTenantBackfill } = await import('../api/src/store/tenant-backfill');
|
|
303
|
+
const result = await runTenantBackfill(new Sequelize() as any);
|
|
304
|
+
const touched = Object.entries(result.backfilled).filter(([, n]) => n > 0);
|
|
305
|
+
if (touched.length) console.log('[tenant-backfill] backfilled:', JSON.stringify(Object.fromEntries(touched)));
|
|
306
|
+
tenantBackfillDone = true;
|
|
307
|
+
} catch (e: any) {
|
|
308
|
+
// fail-loud but non-fatal: crons still run; next tick retries
|
|
309
|
+
console.error('[tenant-backfill] failed:', e?.message || e);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
207
313
|
function ensureCronsInit() {
|
|
208
314
|
if (!cronsInitialized) {
|
|
209
315
|
try {
|
|
@@ -234,6 +340,12 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
234
340
|
return cachedApp;
|
|
235
341
|
}
|
|
236
342
|
|
|
343
|
+
// Phase 12a: assemble the embedded payment service (config/db/slots) and take
|
|
344
|
+
// its resource routes for mounting. Only `http.resourceRoutes` is read — the
|
|
345
|
+
// node-only `handler` getter is never touched, so the express app shell + the
|
|
346
|
+
// DID-Connect handlers it carries never run under the workerd shim.
|
|
347
|
+
const service = ensurePaymentService(env);
|
|
348
|
+
|
|
237
349
|
const app = new Hono<HonoEnv>();
|
|
238
350
|
|
|
239
351
|
// CORS
|
|
@@ -255,43 +367,17 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
255
367
|
// In queue consumer/cron, this flag is absent — createEvent uses __cfPendingJobs__ (blocking).
|
|
256
368
|
(globalThis as any).__cfHttpContext__ = true;
|
|
257
369
|
setDB(withD1Retry(c.env.DB.withSession('first-primary')));
|
|
258
|
-
if (c.env.JOB_QUEUE) setCFQueue(c.env.JOB_QUEUE);
|
|
259
370
|
ensureModelsInit();
|
|
260
371
|
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (c.env.APP_PID) {
|
|
271
|
-
process.env.BLOCKLET_APP_PID = c.env.APP_PID;
|
|
272
|
-
process.env.BLOCKLET_APP_ID = c.env.APP_PID;
|
|
273
|
-
}
|
|
274
|
-
if (c.env.APP_NAME) process.env.BLOCKLET_APP_NAME = c.env.APP_NAME;
|
|
275
|
-
if (c.env.PAYMENT_CHANGE_LOCKED_PRICE) process.env.PAYMENT_CHANGE_LOCKED_PRICE = c.env.PAYMENT_CHANGE_LOCKED_PRICE;
|
|
276
|
-
if (c.env.SHORT_URL_DOMAIN) process.env.SHORT_URL_DOMAIN = c.env.SHORT_URL_DOMAIN;
|
|
277
|
-
// Audience for the Pub/Sub OIDC JWT that wraps Google Play RTDN webhooks.
|
|
278
|
-
// Without this mirror, libs/util.ts googlePlayEndpoint() falls back to
|
|
279
|
-
// getUrl('/api/integrations/google-play/webhook') — which resolves to
|
|
280
|
-
// the *.workers.dev origin, not the custom domain Pub/Sub actually
|
|
281
|
-
// POSTs to. Every webhook then dies with "audience mismatch".
|
|
282
|
-
if (c.env.GOOGLE_PLAY_WEBHOOK_URL) {
|
|
283
|
-
process.env.GOOGLE_PLAY_WEBHOOK_URL = c.env.GOOGLE_PLAY_WEBHOOK_URL;
|
|
284
|
-
}
|
|
285
|
-
// Pub/Sub sender binding — without mirroring these, the google_play webhook
|
|
286
|
-
// can't enforce the push service account on CF and would fail open (PR #1381 P1).
|
|
287
|
-
if (c.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT) {
|
|
288
|
-
process.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT = c.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT;
|
|
289
|
-
}
|
|
290
|
-
if (c.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER) {
|
|
291
|
-
process.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER = c.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER;
|
|
292
|
-
}
|
|
293
|
-
process.env.BLOCKLET_MODE = 'production';
|
|
294
|
-
}
|
|
372
|
+
// Phase 12a: CF env now flows through the factory config slot (assembled in
|
|
373
|
+
// ensurePaymentService -> setCoreConfig, made authoritative via the
|
|
374
|
+
// libs/env.ts readConfig boundary). The per-request process.env mirror that
|
|
375
|
+
// lived here is gone. This defensive idempotent call guarantees the config is
|
|
376
|
+
// wired for this isolate regardless of Hono app-cache state; it is a no-op
|
|
377
|
+
// after the first call. (Phase 12c: scheduled()/queue() call the same
|
|
378
|
+
// ensurePaymentService via setupEnv, so the config slot is authoritative
|
|
379
|
+
// there too — no process.env mirror on any path.)
|
|
380
|
+
ensurePaymentService(c.env);
|
|
295
381
|
|
|
296
382
|
// Register Stripe key decrypt overrides from env vars
|
|
297
383
|
// Fetch EK from AUTH_SERVICE and initialize decrypt capability (first request only)
|
|
@@ -351,10 +437,7 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
351
437
|
// --- Append Server-Timing header ---
|
|
352
438
|
const totalDur = Math.round(performance.now() - t0);
|
|
353
439
|
const d1 = getD1Timing();
|
|
354
|
-
const timings = [
|
|
355
|
-
`total;dur=${totalDur}`,
|
|
356
|
-
`auth;dur=${authDur};desc="${authSource}"`,
|
|
357
|
-
];
|
|
440
|
+
const timings = [`total;dur=${totalDur}`, `auth;dur=${authDur};desc="${authSource}"`];
|
|
358
441
|
if (d1.queries > 0) {
|
|
359
442
|
timings.push(`db;dur=${Math.round(d1.wallMs)};desc="${d1.queries}q ${d1.rowsRead}r"`);
|
|
360
443
|
if (d1.sqlMs > 0) timings.push(`db_sql;dur=${Math.round(d1.sqlMs)}`);
|
|
@@ -366,6 +449,12 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
366
449
|
c.res.headers.append('Server-Timing', timings.join(', '));
|
|
367
450
|
});
|
|
368
451
|
|
|
452
|
+
// Tenant context: resolve Host -> tenant (single point) and wrap the request
|
|
453
|
+
// chain in context.withTenant so every TenantModel query is scoped. Scoped to
|
|
454
|
+
// /api/* so /health and static assets never require a tenant. multi mode:
|
|
455
|
+
// unknown/missing Host -> 400 fail-closed (no default-tenant fallback).
|
|
456
|
+
app.use('/api/*', tenantMiddleware());
|
|
457
|
+
|
|
369
458
|
// Health check
|
|
370
459
|
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
371
460
|
|
|
@@ -608,10 +697,11 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
608
697
|
// Notification unread count
|
|
609
698
|
app.get('/api/notifications/unread-count', (c) => c.json({ unReadCount: 0 }));
|
|
610
699
|
|
|
611
|
-
// Manually trigger job dispatch (same as cron's
|
|
700
|
+
// Manually trigger job dispatch (same as cron's scheduled() due-dispatch)
|
|
612
701
|
app.post('/api/__dev__/dispatch-jobs', async (c) => {
|
|
613
|
-
const names =
|
|
614
|
-
const result = await
|
|
702
|
+
const names = getAllQueueNames();
|
|
703
|
+
const result = await dispatchDueJobs();
|
|
704
|
+
await flushQueueWork();
|
|
615
705
|
return c.json({ handlers: names, ...result });
|
|
616
706
|
});
|
|
617
707
|
|
|
@@ -640,8 +730,11 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
640
730
|
// for creating PaymentMethods is owner-gated, and staging has no owner yet
|
|
641
731
|
// (Pengfei's blocklet-service role is `member`). Gated by PAYMENT_LIVEMODE
|
|
642
732
|
// === 'false' so this only ever touches testmode data.
|
|
643
|
-
//
|
|
644
|
-
|
|
733
|
+
// Phase 4 (express→hono): the resource routes are a native hono app
|
|
734
|
+
// (service.http.resourceRoutes) mounted by the /api/* dispatcher registered
|
|
735
|
+
// AFTER the worker's own /api/__dev__ routes (see below, replacing the old
|
|
736
|
+
// "not implemented" catch-all). The old express-compat mountExpressRoutes shim
|
|
737
|
+
// is gone.
|
|
645
738
|
|
|
646
739
|
// Dev endpoint: D1 admin operations
|
|
647
740
|
// Test CF Queue send directly
|
|
@@ -986,9 +1079,33 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
986
1079
|
}
|
|
987
1080
|
});
|
|
988
1081
|
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
1082
|
+
// Phase 4 (express→hono): native hono resource routes. Registered AFTER the
|
|
1083
|
+
// worker's own /api/__dev__ routes so those match first; this dispatcher then
|
|
1084
|
+
// handles every other /api/* path (returning resourceRoutes' own 404 for an
|
|
1085
|
+
// unknown route). The RPC-resolved caller identity is injected as x-user-*
|
|
1086
|
+
// request headers — the native authenticate() reads those — and any
|
|
1087
|
+
// client-supplied x-user-* is overwritten/stripped so it can never be forged
|
|
1088
|
+
// (component auth via x-component-sig is left intact: it is verified
|
|
1089
|
+
// cryptographically downstream). The worker already provides cors + tenant, so
|
|
1090
|
+
// resourceRoutes uses a LITE app-shell (xss only); flushQueueWork() preserves
|
|
1091
|
+
// the workerd flush-before-response the old shim ran after each handler.
|
|
1092
|
+
const resourceRoutes = service.http.resourceRoutes as Hono;
|
|
1093
|
+
const USER_HEADERS = ['x-user-did', 'x-user-role', 'x-user-provider', 'x-user-fullname', 'x-user-wallet-os'];
|
|
1094
|
+
app.all('/api/*', async (c) => {
|
|
1095
|
+
const headers = new Headers(c.req.raw.headers);
|
|
1096
|
+
for (const h of USER_HEADERS) headers.delete(h); // never trust a client-supplied identity header
|
|
1097
|
+
const caller: CallerIdentityDTO | null = c.get('caller');
|
|
1098
|
+
if (caller) {
|
|
1099
|
+
const canonicalDid = caller.did?.startsWith('did:abt:') ? caller.did : `did:abt:${caller.did}`;
|
|
1100
|
+
headers.set('x-user-did', canonicalDid);
|
|
1101
|
+
headers.set('x-user-role', `blocklet-${caller.role || 'guest'}`);
|
|
1102
|
+
headers.set('x-user-provider', caller.authMethod === 'access-key' ? 'access-key' : caller.authMethod || 'wallet');
|
|
1103
|
+
headers.set('x-user-fullname', encodeURIComponent(caller.displayName || ''));
|
|
1104
|
+
headers.set('x-user-wallet-os', '');
|
|
1105
|
+
}
|
|
1106
|
+
const res = await resourceRoutes.fetch(new Request(c.req.raw, { headers }), c.env, c.executionCtx);
|
|
1107
|
+
await flushQueueWork(); // drain workerd deferred queue work before responding
|
|
1108
|
+
return res;
|
|
992
1109
|
});
|
|
993
1110
|
|
|
994
1111
|
// === Media Kit Proxy ===
|
|
@@ -1158,347 +1275,6 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
1158
1275
|
return app;
|
|
1159
1276
|
}
|
|
1160
1277
|
|
|
1161
|
-
// === Express-to-Hono Route Adapter ===
|
|
1162
|
-
|
|
1163
|
-
function normalizeRoutePath(prefix: string, routePath: string): string {
|
|
1164
|
-
let full = (prefix + routePath).replace(/\/+/g, '/');
|
|
1165
|
-
if (!full.startsWith('/')) full = `/${full}`;
|
|
1166
|
-
if (full.length > 1 && full.endsWith('/')) full = full.slice(0, -1);
|
|
1167
|
-
return full;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
function createExpressReq(c: any, routeParams: Record<string, string>): any {
|
|
1171
|
-
const url = new URL(c.req.url);
|
|
1172
|
-
const query: Record<string, any> = {};
|
|
1173
|
-
url.searchParams.forEach((v, k) => {
|
|
1174
|
-
query[k] = v;
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
const headers: Record<string, string> = {};
|
|
1178
|
-
c.req.raw.headers.forEach((v: string, k: string) => {
|
|
1179
|
-
headers[k.toLowerCase()] = v;
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
const req: any = {
|
|
1183
|
-
method: c.req.method,
|
|
1184
|
-
url: url.pathname + url.search,
|
|
1185
|
-
path: url.pathname,
|
|
1186
|
-
originalUrl: url.pathname + url.search,
|
|
1187
|
-
query,
|
|
1188
|
-
params: { ...routeParams },
|
|
1189
|
-
body: null,
|
|
1190
|
-
headers,
|
|
1191
|
-
user: null,
|
|
1192
|
-
// Worker-wide livemode is driven by the PAYMENT_LIVEMODE env var (set on
|
|
1193
|
-
// each deployment). Routes that filter PaymentMethod by livemode rely on
|
|
1194
|
-
// this value matching what we stored when bootstrapping the methods.
|
|
1195
|
-
livemode: c.env?.PAYMENT_LIVEMODE !== 'false',
|
|
1196
|
-
baseCurrency: null,
|
|
1197
|
-
ip: headers['cf-connecting-ip'] || headers['x-forwarded-for'] || '127.0.0.1',
|
|
1198
|
-
get(name: string) {
|
|
1199
|
-
return headers[name.toLowerCase()];
|
|
1200
|
-
},
|
|
1201
|
-
header(name: string) {
|
|
1202
|
-
return headers[name.toLowerCase()];
|
|
1203
|
-
},
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
return req;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
function createExpressRes(): any {
|
|
1210
|
-
const res: any = {
|
|
1211
|
-
_statusCode: 200,
|
|
1212
|
-
_headers: {} as Record<string, string>,
|
|
1213
|
-
_body: null as any,
|
|
1214
|
-
_sent: false,
|
|
1215
|
-
_redirectUrl: null as string | null,
|
|
1216
|
-
headersSent: false,
|
|
1217
|
-
|
|
1218
|
-
status(code: number) {
|
|
1219
|
-
res._statusCode = code;
|
|
1220
|
-
return res;
|
|
1221
|
-
},
|
|
1222
|
-
json(data: any) {
|
|
1223
|
-
if (res._sent) return res;
|
|
1224
|
-
res._sent = true;
|
|
1225
|
-
res.headersSent = true;
|
|
1226
|
-
res._body = data;
|
|
1227
|
-
res._headers['content-type'] = 'application/json';
|
|
1228
|
-
return res;
|
|
1229
|
-
},
|
|
1230
|
-
send(data: any) {
|
|
1231
|
-
if (res._sent) return res;
|
|
1232
|
-
res._sent = true;
|
|
1233
|
-
res.headersSent = true;
|
|
1234
|
-
res._body = data;
|
|
1235
|
-
return res;
|
|
1236
|
-
},
|
|
1237
|
-
redirect(urlOrStatus: any, url?: string) {
|
|
1238
|
-
res._sent = true;
|
|
1239
|
-
res.headersSent = true;
|
|
1240
|
-
if (typeof urlOrStatus === 'number') {
|
|
1241
|
-
res._statusCode = urlOrStatus;
|
|
1242
|
-
res._redirectUrl = url;
|
|
1243
|
-
} else {
|
|
1244
|
-
res._statusCode = 302;
|
|
1245
|
-
res._redirectUrl = urlOrStatus;
|
|
1246
|
-
}
|
|
1247
|
-
return res;
|
|
1248
|
-
},
|
|
1249
|
-
set(key: string, value: string) {
|
|
1250
|
-
res._headers[key.toLowerCase()] = value;
|
|
1251
|
-
return res;
|
|
1252
|
-
},
|
|
1253
|
-
setHeader(key: string, value: string) {
|
|
1254
|
-
res._headers[key.toLowerCase()] = value;
|
|
1255
|
-
return res;
|
|
1256
|
-
},
|
|
1257
|
-
cookie(_name: string, _value: string, _options?: any) {
|
|
1258
|
-
return res;
|
|
1259
|
-
},
|
|
1260
|
-
end() {
|
|
1261
|
-
if (!res._sent) {
|
|
1262
|
-
res._sent = true;
|
|
1263
|
-
res.headersSent = true;
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
type(t: string) {
|
|
1267
|
-
res._headers['content-type'] = t;
|
|
1268
|
-
return res;
|
|
1269
|
-
},
|
|
1270
|
-
};
|
|
1271
|
-
|
|
1272
|
-
Object.defineProperty(res, 'statusCode', {
|
|
1273
|
-
get() {
|
|
1274
|
-
return res._statusCode;
|
|
1275
|
-
},
|
|
1276
|
-
set(v: number) {
|
|
1277
|
-
res._statusCode = v;
|
|
1278
|
-
},
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
return res;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function expressResToResponse(res: any): Response {
|
|
1285
|
-
if (res._redirectUrl) {
|
|
1286
|
-
return Response.redirect(res._redirectUrl, res._statusCode || 302);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
const headers = new Headers(res._headers);
|
|
1290
|
-
|
|
1291
|
-
if (res._body === null || res._body === undefined) {
|
|
1292
|
-
return new Response(null, { status: res._statusCode, headers });
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
if (typeof res._body === 'string') {
|
|
1296
|
-
return new Response(res._body, { status: res._statusCode, headers });
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
if (res._body instanceof ArrayBuffer || res._body instanceof Uint8Array) {
|
|
1300
|
-
return new Response(res._body, { status: res._statusCode, headers });
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (!headers.has('content-type')) {
|
|
1304
|
-
headers.set('content-type', 'application/json');
|
|
1305
|
-
}
|
|
1306
|
-
let jsonStr = JSON.stringify(res._body);
|
|
1307
|
-
// Rewrite legacy blocklet server URLs to CF Workers domain
|
|
1308
|
-
// Old format: https://old-domain/payment/methods/x.png -> https://cf-domain/methods/x.png
|
|
1309
|
-
const cfAppUrl = ((globalThis as any).__CF_ENV__?.APP_URL || '').replace(/\/$/, '');
|
|
1310
|
-
if (cfAppUrl) {
|
|
1311
|
-
jsonStr = jsonStr
|
|
1312
|
-
.split('https://bbqa7swuuaze4l2y5salvngyjyohlhq5fs5j42eokni.did.abtnet.io/payment/')
|
|
1313
|
-
.join(`${cfAppUrl}/`);
|
|
1314
|
-
jsonStr = jsonStr.split('https://bbqa7swuuaze4l2y5salvngyjyohlhq5fs5j42eokni.did.abtnet.io').join(cfAppUrl);
|
|
1315
|
-
}
|
|
1316
|
-
return new Response(jsonStr, { status: res._statusCode, headers });
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
async function runExpressHandlers(handlers: Function[], req: any, res: any): Promise<void> {
|
|
1320
|
-
let idx = 0;
|
|
1321
|
-
|
|
1322
|
-
async function runNext(err?: any): Promise<void> {
|
|
1323
|
-
if (err) {
|
|
1324
|
-
console.error('[CF Worker] Express middleware error:', err?.message || err);
|
|
1325
|
-
if (!res._sent) {
|
|
1326
|
-
res.status(500).json({ error: err?.message || 'Internal Server Error' });
|
|
1327
|
-
}
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
if (idx >= handlers.length || res._sent) return;
|
|
1331
|
-
|
|
1332
|
-
const handler = handlers[idx++];
|
|
1333
|
-
if (!handler) return runNext();
|
|
1334
|
-
|
|
1335
|
-
if (handler.length === 4) {
|
|
1336
|
-
return runNext();
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
return new Promise<void>((resolve) => {
|
|
1340
|
-
let nextCalled = false;
|
|
1341
|
-
|
|
1342
|
-
try {
|
|
1343
|
-
const result = handler(req, res, (nextErr?: any) => {
|
|
1344
|
-
nextCalled = true;
|
|
1345
|
-
runNext(nextErr)
|
|
1346
|
-
.then(resolve)
|
|
1347
|
-
.catch((e: any) => {
|
|
1348
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1349
|
-
resolve();
|
|
1350
|
-
});
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
if (result && typeof result.then === 'function') {
|
|
1354
|
-
result
|
|
1355
|
-
.then(() => {
|
|
1356
|
-
if (!nextCalled) {
|
|
1357
|
-
resolve();
|
|
1358
|
-
}
|
|
1359
|
-
})
|
|
1360
|
-
.catch((e: any) => {
|
|
1361
|
-
console.error('[CF Worker] Async handler error:', e?.message || e);
|
|
1362
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1363
|
-
resolve();
|
|
1364
|
-
});
|
|
1365
|
-
} else if (!nextCalled) {
|
|
1366
|
-
resolve();
|
|
1367
|
-
}
|
|
1368
|
-
} catch (e: any) {
|
|
1369
|
-
console.error('[CF Worker] Sync handler error:', e?.message || e);
|
|
1370
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1371
|
-
resolve();
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
await runNext();
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
function mountExpressRoutes(honoApp: Hono<HonoEnv>, prefix: string, expressRouter: any) {
|
|
1380
|
-
const routes: RouteEntry[] = expressRouter._routes || [];
|
|
1381
|
-
|
|
1382
|
-
console.log(`[CF Worker] Mounting ${routes.length} Express routes under ${prefix}`);
|
|
1383
|
-
|
|
1384
|
-
for (const route of routes) {
|
|
1385
|
-
const fullPath = normalizeRoutePath(prefix, route.path);
|
|
1386
|
-
const method = route.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
1387
|
-
|
|
1388
|
-
if (!['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
|
|
1389
|
-
console.warn(`[CF Worker] Skipping unsupported method: ${route.method} ${fullPath}`);
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
honoApp[method](fullPath, async (c) => {
|
|
1394
|
-
const req = createExpressReq(c, c.req.param());
|
|
1395
|
-
const res = createExpressRes();
|
|
1396
|
-
|
|
1397
|
-
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(c.req.method)) {
|
|
1398
|
-
try {
|
|
1399
|
-
const contentType = c.req.header('content-type') || '';
|
|
1400
|
-
const isStripeWebhook = fullPath.includes('/integrations/stripe/webhook');
|
|
1401
|
-
|
|
1402
|
-
if (isStripeWebhook) {
|
|
1403
|
-
const rawBody = await c.req.arrayBuffer();
|
|
1404
|
-
req.body = Buffer.from(rawBody);
|
|
1405
|
-
req.rawBody = req.body;
|
|
1406
|
-
} else if (contentType.includes('application/json')) {
|
|
1407
|
-
req.body = await c.req.json();
|
|
1408
|
-
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
1409
|
-
const text = await c.req.text();
|
|
1410
|
-
req.body = Object.fromEntries(new URLSearchParams(text));
|
|
1411
|
-
} else if (contentType.includes('text/')) {
|
|
1412
|
-
req.body = await c.req.text();
|
|
1413
|
-
} else {
|
|
1414
|
-
try {
|
|
1415
|
-
req.body = await c.req.json();
|
|
1416
|
-
} catch {
|
|
1417
|
-
try {
|
|
1418
|
-
req.body = await c.req.text();
|
|
1419
|
-
} catch {
|
|
1420
|
-
req.body = null;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
} catch {
|
|
1425
|
-
req.body = {};
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// Debug logging for webhook
|
|
1430
|
-
if (fullPath.includes('stripe/webhook')) {
|
|
1431
|
-
console.log('[CF Worker] Stripe webhook request received:', {
|
|
1432
|
-
method: c.req.method,
|
|
1433
|
-
path: fullPath,
|
|
1434
|
-
hasSignature: !!req.headers['stripe-signature'],
|
|
1435
|
-
bodyType: typeof req.body,
|
|
1436
|
-
bodyLength: req.body?.length || 0,
|
|
1437
|
-
isBuffer: Buffer.isBuffer(req.body),
|
|
1438
|
-
handlersCount: route.handlers.length,
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// Inject caller identity resolved by AUTH_SERVICE RPC (or mock fallback).
|
|
1443
|
-
// AUTH_SERVICE returns the bare base58 address; Customer/Subscription queries
|
|
1444
|
-
// and entitlement lookups expect the canonical `did:abt:…` form, so we
|
|
1445
|
-
// normalize once here at the boundary instead of in every downstream call site.
|
|
1446
|
-
const caller: CallerIdentityDTO | null = c.get('caller');
|
|
1447
|
-
if (caller) {
|
|
1448
|
-
const canonicalDid = caller.did?.startsWith('did:abt:') ? caller.did : `did:abt:${caller.did}`;
|
|
1449
|
-
req.user = {
|
|
1450
|
-
did: canonicalDid,
|
|
1451
|
-
role: caller.role || 'guest',
|
|
1452
|
-
provider: caller.authMethod === 'access-key' ? 'access-key' : 'wallet',
|
|
1453
|
-
fullName: caller.displayName || '',
|
|
1454
|
-
walletOS: '',
|
|
1455
|
-
via: 'dashboard',
|
|
1456
|
-
};
|
|
1457
|
-
req.headers['x-user-did'] = canonicalDid;
|
|
1458
|
-
req.headers['x-user-role'] = `blocklet-${caller.role || 'guest'}`;
|
|
1459
|
-
req.headers['x-user-provider'] = caller.authMethod || 'wallet';
|
|
1460
|
-
req.headers['x-user-fullname'] = encodeURIComponent(caller.displayName || '');
|
|
1461
|
-
req.headers['x-user-wallet-os'] = '';
|
|
1462
|
-
} else {
|
|
1463
|
-
req.user = { did: '', role: 'guest', provider: '', fullName: '', walletOS: '', via: '' };
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
try {
|
|
1467
|
-
await runExpressHandlers(route.handlers, req, res);
|
|
1468
|
-
} catch (e: any) {
|
|
1469
|
-
console.error(
|
|
1470
|
-
`[CF Worker] Unhandled error in ${route.method} ${fullPath}:`,
|
|
1471
|
-
e?.message || e,
|
|
1472
|
-
'\n',
|
|
1473
|
-
e?.stack?.split('\n').slice(0, 8).join('\n')
|
|
1474
|
-
);
|
|
1475
|
-
if (!res._sent) {
|
|
1476
|
-
res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
// Debug logging for webhook response
|
|
1481
|
-
if (fullPath.includes('stripe/webhook')) {
|
|
1482
|
-
console.log('[CF Worker] Stripe webhook handler result:', {
|
|
1483
|
-
sent: res._sent,
|
|
1484
|
-
statusCode: res._statusCode,
|
|
1485
|
-
body:
|
|
1486
|
-
typeof res._body === 'string' ? res._body.substring(0, 200) : JSON.stringify(res._body)?.substring(0, 200),
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
// Ensure all async push() jobs complete before returning
|
|
1491
|
-
await flushPendingJobs();
|
|
1492
|
-
|
|
1493
|
-
if (!res._sent) {
|
|
1494
|
-
console.warn(`[CF Worker] No response sent for ${route.method} ${fullPath}`);
|
|
1495
|
-
return c.json({ error: 'No response from handler' }, 500);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
return expressResToResponse(res);
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
1278
|
|
|
1503
1279
|
// === Shared env setup for scheduled/queue handlers ===
|
|
1504
1280
|
function setupEnv(env: Env) {
|
|
@@ -1509,21 +1285,13 @@ function setupEnv(env: Env) {
|
|
|
1509
1285
|
// Queue consumer and cron: NOT HTTP context — createEvent must block (listeners complete before ack/return)
|
|
1510
1286
|
(globalThis as any).__cfHttpContext__ = false;
|
|
1511
1287
|
setDB(env.DB.withSession('first-primary'));
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
if (env.APP_PID) {
|
|
1521
|
-
process.env.BLOCKLET_APP_PID = env.APP_PID;
|
|
1522
|
-
process.env.BLOCKLET_APP_ID = env.APP_PID;
|
|
1523
|
-
}
|
|
1524
|
-
if (env.APP_NAME) process.env.BLOCKLET_APP_NAME = env.APP_NAME;
|
|
1525
|
-
process.env.BLOCKLET_MODE = 'production';
|
|
1526
|
-
}
|
|
1288
|
+
// Phase 12c: assemble the service so the CONFIG SLOT (setCoreConfig) is
|
|
1289
|
+
// authoritative on the scheduled()/queue() paths too. envToPaymentCoreConfig
|
|
1290
|
+
// carries BLOCKLET_MODE + every key the old setupEnv process.env mirror set,
|
|
1291
|
+
// and the core reads them only through libs/env.ts readConfig — so the worker
|
|
1292
|
+
// env mirror is deleted (no more process.env fallback). Idempotent + cached;
|
|
1293
|
+
// also binds models, so ensureModelsInit() is subsumed.
|
|
1294
|
+
ensurePaymentService(env);
|
|
1527
1295
|
|
|
1528
1296
|
// Security init is handled in the per-request middleware (first request only)
|
|
1529
1297
|
}
|
|
@@ -1531,7 +1299,6 @@ function setupEnv(env: Env) {
|
|
|
1531
1299
|
// === Export ===
|
|
1532
1300
|
export default {
|
|
1533
1301
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
1534
|
-
setWaitUntil((p) => ctx.waitUntil(p));
|
|
1535
1302
|
// Expose waitUntil globally for createEvent to use in HTTP context
|
|
1536
1303
|
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1537
1304
|
|
|
@@ -1542,7 +1309,8 @@ export default {
|
|
|
1542
1309
|
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
|
|
1543
1310
|
setupEnv(env);
|
|
1544
1311
|
ensureCronsInit();
|
|
1545
|
-
|
|
1312
|
+
await ensureTenantBackfill();
|
|
1313
|
+
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1546
1314
|
|
|
1547
1315
|
console.log('Scheduled event:', event.cron, 'scheduledTime:', event.scheduledTime);
|
|
1548
1316
|
|
|
@@ -1555,54 +1323,57 @@ export default {
|
|
|
1555
1323
|
// execution crosses a minute boundary, matching on wall-clock would miss
|
|
1556
1324
|
// exact-minute crons like "0 1 * * * *".
|
|
1557
1325
|
await cronInstance.runAll(new Date(event.scheduledTime));
|
|
1558
|
-
|
|
1559
|
-
|
|
1326
|
+
// Phase 12b: the workerd trigger for the node queue engine's due-job
|
|
1327
|
+
// re-dispatch. dispatchDueJobs() runs each scheduled queue's redispatchDue()
|
|
1328
|
+
// (the same cancel-then-replay-from-store body the node loop() runs on a
|
|
1329
|
+
// timer), then flushQueueWork() drains the re-pushed executions before the
|
|
1330
|
+
// isolate freezes.
|
|
1331
|
+
await dispatchDueJobs();
|
|
1332
|
+
await flushQueueWork();
|
|
1560
1333
|
},
|
|
1561
1334
|
|
|
1562
|
-
// CF Queue consumer —
|
|
1335
|
+
// CF Queue consumer — resolves the SAME core queue handle by name and runs
|
|
1336
|
+
// the job through the engine's runJobWithTenant/onJob/retry/cancel path. Under
|
|
1337
|
+
// the canonical node-engine model immediate jobs execute in-isolate via fastq
|
|
1338
|
+
// (nothing is sent to CF Queue), so this consumer is the back-compat path for
|
|
1339
|
+
// any message still on JOB_QUEUE — it must never fork the engine.
|
|
1563
1340
|
async queue(
|
|
1564
1341
|
batch: MessageBatch<{ queueName: string; jobId: string; job: any; persist?: boolean }>,
|
|
1565
1342
|
env: Env,
|
|
1566
|
-
ctx: ExecutionContext
|
|
1343
|
+
ctx: ExecutionContext
|
|
1567
1344
|
) {
|
|
1568
1345
|
setupEnv(env);
|
|
1569
|
-
|
|
1346
|
+
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1570
1347
|
|
|
1571
1348
|
console.log(`[queue:consumer] Received batch of ${batch.messages.length} messages`);
|
|
1572
1349
|
|
|
1573
1350
|
for (const msg of batch.messages) {
|
|
1574
|
-
const { queueName, jobId, job
|
|
1351
|
+
const { queueName, jobId, job } = msg.body;
|
|
1575
1352
|
|
|
1576
|
-
const
|
|
1353
|
+
const handle = getQueueHandler(queueName);
|
|
1577
1354
|
|
|
1578
|
-
if (!
|
|
1579
|
-
console.error(`[queue:consumer] No
|
|
1355
|
+
if (!handle) {
|
|
1356
|
+
console.error(`[queue:consumer] No core queue registered for "${queueName}", acking message`);
|
|
1580
1357
|
msg.ack();
|
|
1581
1358
|
continue;
|
|
1582
1359
|
}
|
|
1583
1360
|
|
|
1584
|
-
// persist defaults to true for backward compatibility and for direct
|
|
1585
|
-
// push() immediate jobs (where addJob wrote the row, so we must delete
|
|
1586
|
-
// it after onJob succeeds).
|
|
1587
|
-
//
|
|
1588
|
-
// Scheduled dispatches from runAllScheduledJobs set persist=false —
|
|
1589
|
-
// the dispatcher already deleted the D1 row before sending, and
|
|
1590
|
-
// onJob may have re-pushed a fresh row with the same id; deleting
|
|
1591
|
-
// again would wipe out that new row.
|
|
1592
|
-
const shouldPersist = persist !== false;
|
|
1593
|
-
|
|
1594
1361
|
try {
|
|
1595
|
-
console.log(`[queue:consumer] Processing ${queueName}:${jobId}
|
|
1596
|
-
|
|
1362
|
+
console.log(`[queue:consumer] Processing ${queueName}:${jobId}`);
|
|
1363
|
+
// persist:false — the row already exists in D1 (the original push wrote
|
|
1364
|
+
// it); re-running inline through pushAndWait executes onJob and clears
|
|
1365
|
+
// the row on success without a duplicate-addJob that would hang.
|
|
1366
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1367
|
+
await handle.pushAndWait({ job, id: jobId, persist: false });
|
|
1597
1368
|
console.log(`[queue:consumer] Completed ${queueName}:${jobId}`);
|
|
1598
1369
|
msg.ack();
|
|
1599
1370
|
} catch (err: any) {
|
|
1600
1371
|
console.error(`[queue:consumer] Failed ${queueName}:${jobId}:`, err?.message || err);
|
|
1601
|
-
// Don't retry via CF Queue — job is in D1,
|
|
1372
|
+
// Don't retry via CF Queue — job is in D1, scheduled() will re-dispatch
|
|
1602
1373
|
msg.ack();
|
|
1603
1374
|
}
|
|
1604
1375
|
}
|
|
1605
1376
|
|
|
1606
|
-
await
|
|
1377
|
+
await flushQueueWork();
|
|
1607
1378
|
},
|
|
1608
1379
|
};
|