@blamejs/blamejs-shop 0.0.44
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/CHANGELOG.md +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module shop.storefront
|
|
4
|
+
* @title Storefront — server-rendered HTML for end customers
|
|
5
|
+
*
|
|
6
|
+
* @intro
|
|
7
|
+
* v1 ships a minimum viable storefront: read-only HTML routes
|
|
8
|
+
* for the home page (product list), the product detail page
|
|
9
|
+
* (PDP), and the cart view. Each renderer is a pure function
|
|
10
|
+
* returning an HTML string; `mount(router, deps)` wires the
|
|
11
|
+
* routes into a `b.router` instance and reads data via the
|
|
12
|
+
* provided catalog / cart primitives.
|
|
13
|
+
*
|
|
14
|
+
* Templates are inline string templates with the same strict
|
|
15
|
+
* `{{var}}` renderer the email primitive uses — HTML-escaped
|
|
16
|
+
* substitution, refusal of unknown / unused placeholders at
|
|
17
|
+
* composition time. The full theme primitive (with file-backed
|
|
18
|
+
* templates via `b.template`, asset fingerprinting via
|
|
19
|
+
* `b.objectStore`, theme inheritance + override resolution) lands
|
|
20
|
+
* in v1.x; the inline shape exists so the storefront is
|
|
21
|
+
* demonstrable today.
|
|
22
|
+
*
|
|
23
|
+
* POST routes (add-to-cart, checkout submit) land in the next
|
|
24
|
+
* patch alongside the Stripe Elements wiring — v0.0.8 is
|
|
25
|
+
* read-only HTML.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
var emailModule = require("./email");
|
|
29
|
+
var pricing = require("./pricing");
|
|
30
|
+
|
|
31
|
+
var bShop;
|
|
32
|
+
function _b() {
|
|
33
|
+
if (!bShop) bShop = require("./index");
|
|
34
|
+
return bShop.framework;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Re-use the strict renderer from the email primitive (same shape,
|
|
38
|
+
// same XSS guard, same unknown / unused refusal).
|
|
39
|
+
var _render = emailModule._render;
|
|
40
|
+
|
|
41
|
+
// ---- shared layout ------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
// Visual identity reference: the framework ships with two
|
|
44
|
+
// reference ecommerce templates (Lager + odor-buyer-file in
|
|
45
|
+
// .template/) — the layout below adopts odor's monochrome-plus-
|
|
46
|
+
// orange-accent palette (#191919 / #fa4f09 / #ffffff) and
|
|
47
|
+
// Montserrat headlines as the default theme. Customers fork the
|
|
48
|
+
// theme later by overriding LAYOUT + the per-page templates; the
|
|
49
|
+
// theme primitive (v1.x) makes that swap a per-directory drop-in.
|
|
50
|
+
//
|
|
51
|
+
// Brand assets live under R2 at `brand/<file>` — the layout
|
|
52
|
+
// references `/assets/brand/logo.png` which the Worker resolves to
|
|
53
|
+
// the bound R2 bucket. The 1536×1024 source PNG is committed
|
|
54
|
+
// only to .template/ (local-only) and uploaded once via
|
|
55
|
+
// `wrangler r2 object put`.
|
|
56
|
+
var LAYOUT =
|
|
57
|
+
"<!DOCTYPE html>\n" +
|
|
58
|
+
"<html lang=\"en\">\n" +
|
|
59
|
+
"<head>\n" +
|
|
60
|
+
" <meta charset=\"utf-8\">\n" +
|
|
61
|
+
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n" +
|
|
62
|
+
" <title>{{title}} — {{shop_name}}</title>\n" +
|
|
63
|
+
" <link rel=\"icon\" type=\"image/svg+xml\" href=\"/assets/brand/favicon.svg\">\n" +
|
|
64
|
+
" <link rel=\"stylesheet\" href=\"{{theme_css}}\">\n" +
|
|
65
|
+
"</head>\n" +
|
|
66
|
+
"<body>\n" +
|
|
67
|
+
" <a class=\"skip-link\" href=\"#main\">Skip to content</a>\n" +
|
|
68
|
+
"\n" +
|
|
69
|
+
" <div class=\"utility-bar\" role=\"complementary\">\n" +
|
|
70
|
+
" <div class=\"utility-bar__inner\">\n" +
|
|
71
|
+
" <span class=\"utility-bar__pill\"><span class=\"dot dot--live\" aria-hidden=\"true\"></span> Open source · Apache 2.0</span>\n" +
|
|
72
|
+
" <span class=\"utility-bar__msg\">Server-rendered HTML · post-quantum crypto on by default · zero npm runtime deps</span>\n" +
|
|
73
|
+
" <a class=\"utility-bar__link\" href=\"https://github.com/blamejs/blamejs.shop\" rel=\"noopener\">Star on GitHub →</a>\n" +
|
|
74
|
+
" </div>\n" +
|
|
75
|
+
" </div>\n" +
|
|
76
|
+
"\n" +
|
|
77
|
+
" <header class=\"site-header\">\n" +
|
|
78
|
+
" <div class=\"site-header__inner\">\n" +
|
|
79
|
+
" <a href=\"/\" class=\"brand\" aria-label=\"{{shop_name}}\"><img src=\"/assets/brand/logo.png\" alt=\"{{shop_name}}\"></a>\n" +
|
|
80
|
+
" <form class=\"site-search\" action=\"/search\" method=\"get\" role=\"search\">\n" +
|
|
81
|
+
" <div class=\"site-search__inner\">\n" +
|
|
82
|
+
" <label for=\"site-search-q\" class=\"skip-link\">Search products</label>\n" +
|
|
83
|
+
" <svg class=\"site-search__icon\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\"><path d=\"M21 21l-4.35-4.35M11 19a8 8 0 1 1 0-16 8 8 0 0 1 0 16Z\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\"/></svg>\n" +
|
|
84
|
+
" <input id=\"site-search-q\" type=\"search\" name=\"q\" value=\"{{search_q}}\" placeholder=\"Search the catalog\" autocomplete=\"off\" spellcheck=\"false\" maxlength=\"200\">\n" +
|
|
85
|
+
" <button type=\"submit\">Search</button>\n" +
|
|
86
|
+
" </div>\n" +
|
|
87
|
+
" </form>\n" +
|
|
88
|
+
" <nav class=\"site-nav\" aria-label=\"Primary\">\n" +
|
|
89
|
+
" <a class=\"site-nav__link\" href=\"/\">Shop</a>\n" +
|
|
90
|
+
" <a class=\"site-nav__link\" href=\"#framework\">Framework</a>\n" +
|
|
91
|
+
" <a class=\"site-nav__icon\" href=\"/account\" aria-label=\"Account\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" aria-hidden=\"true\"><path d=\"M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-7 9a7 7 0 0 1 14 0\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\"/></svg></a>\n" +
|
|
92
|
+
" <a class=\"cart-pill\" href=\"/cart\" aria-label=\"Cart, {{cart_count}} items\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\"><path d=\"M3 4h2l2.4 12.1a2 2 0 0 0 2 1.6h7.6a2 2 0 0 0 1.95-1.55L21 8H6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/><circle cx=\"10\" cy=\"21\" r=\"1.4\" fill=\"currentColor\"/><circle cx=\"17\" cy=\"21\" r=\"1.4\" fill=\"currentColor\"/></svg><span class=\"cart-pill__count\">{{cart_count}}</span></a>\n" +
|
|
93
|
+
" </nav>\n" +
|
|
94
|
+
" </div>\n" +
|
|
95
|
+
" </header>\n" +
|
|
96
|
+
"\n" +
|
|
97
|
+
" <main id=\"main\">{{body}}</main>\n" +
|
|
98
|
+
"\n" +
|
|
99
|
+
" <section class=\"newsletter-band\" aria-labelledby=\"newsletter-title\">\n" +
|
|
100
|
+
" <div class=\"newsletter-band__inner\">\n" +
|
|
101
|
+
" <div class=\"newsletter-band__copy\">\n" +
|
|
102
|
+
" <p class=\"eyebrow eyebrow--on-dark\">Stay in the loop</p>\n" +
|
|
103
|
+
" <h2 id=\"newsletter-title\">Get release notes the day they ship.</h2>\n" +
|
|
104
|
+
" <p class=\"newsletter-band__lede\">No marketing emails. A single short note when there's a new framework release, a security advisory, or a primitive worth knowing about.</p>\n" +
|
|
105
|
+
" </div>\n" +
|
|
106
|
+
" <form class=\"newsletter-band__form\" method=\"post\" action=\"/newsletter\">\n" +
|
|
107
|
+
" <label class=\"skip-link\" for=\"newsletter-email\">Email address</label>\n" +
|
|
108
|
+
" <input id=\"newsletter-email\" type=\"email\" name=\"email\" required placeholder=\"you@example.com\" autocomplete=\"email\">\n" +
|
|
109
|
+
" <button type=\"submit\">Subscribe</button>\n" +
|
|
110
|
+
" </form>\n" +
|
|
111
|
+
" </div>\n" +
|
|
112
|
+
" </section>\n" +
|
|
113
|
+
"\n" +
|
|
114
|
+
" <footer class=\"site-footer\">\n" +
|
|
115
|
+
" <div class=\"site-footer__inner\">\n" +
|
|
116
|
+
" <div class=\"site-footer__brand-col\">\n" +
|
|
117
|
+
" <img class=\"site-footer__logo\" src=\"/assets/brand/logo.png\" alt=\"{{shop_name}}\">\n" +
|
|
118
|
+
" <p class=\"site-footer__tagline\">An open-source shop framework — server-rendered HTML, zero npm runtime dependencies, security defaults on.</p>\n" +
|
|
119
|
+
" <ul class=\"site-footer__social\" aria-label=\"Project links\">\n" +
|
|
120
|
+
" <li><a href=\"https://github.com/blamejs/blamejs.shop\" rel=\"noopener\" aria-label=\"GitHub\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\"><path d=\"M12 .5a11.5 11.5 0 0 0-3.6 22.4c.6.1.8-.3.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.4-1.3-1.8-1.3-1.8-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.4-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11 11 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.6.2 2.8.1 3.1.8.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.3v3.4c0 .3.2.7.8.6A11.5 11.5 0 0 0 12 .5Z\" fill=\"currentColor\"/></svg></a></li>\n" +
|
|
121
|
+
" <li><a href=\"https://npmjs.com/package/blamejs\" rel=\"noopener\" aria-label=\"npm\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\"><path d=\"M2 7v10h6v-7h3v7h11V7H2Zm15 8h-2v-5h-3v5h-1V9h6v6Z\" fill=\"currentColor\"/></svg></a></li>\n" +
|
|
122
|
+
" <li><a href=\"/feed.xml\" aria-label=\"RSS feed\"><svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" aria-hidden=\"true\"><path d=\"M5 4v3a13 13 0 0 1 13 13h3A16 16 0 0 0 5 4Zm0 6v3a7 7 0 0 1 7 7h3a10 10 0 0 0-10-10Zm1 7a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z\" fill=\"currentColor\"/></svg></a></li>\n" +
|
|
123
|
+
" </ul>\n" +
|
|
124
|
+
" </div>\n" +
|
|
125
|
+
" <div class=\"site-footer__col\">\n" +
|
|
126
|
+
" <h4>Shop</h4>\n" +
|
|
127
|
+
" <ul>\n" +
|
|
128
|
+
" <li><a href=\"/\">All products</a></li>\n" +
|
|
129
|
+
" <li><a href=\"/?sort=new\">New arrivals</a></li>\n" +
|
|
130
|
+
" <li><a href=\"/?sort=sale\">On sale</a></li>\n" +
|
|
131
|
+
" <li><a href=\"/cart\">Cart</a></li>\n" +
|
|
132
|
+
" </ul>\n" +
|
|
133
|
+
" </div>\n" +
|
|
134
|
+
" <div class=\"site-footer__col\">\n" +
|
|
135
|
+
" <h4>Framework</h4>\n" +
|
|
136
|
+
" <ul>\n" +
|
|
137
|
+
" <li><a href=\"https://github.com/blamejs/blamejs.shop\" rel=\"noopener\">Source on GitHub</a></li>\n" +
|
|
138
|
+
" <li><a href=\"https://github.com/blamejs/blamejs\" rel=\"noopener\">blamejs core</a></li>\n" +
|
|
139
|
+
" <li><a href=\"/SECURITY.md\">Security policy</a></li>\n" +
|
|
140
|
+
" <li><a href=\"/CHANGELOG.md\">Changelog</a></li>\n" +
|
|
141
|
+
" </ul>\n" +
|
|
142
|
+
" </div>\n" +
|
|
143
|
+
" <div class=\"site-footer__col\">\n" +
|
|
144
|
+
" <h4>Operators</h4>\n" +
|
|
145
|
+
" <ul>\n" +
|
|
146
|
+
" <li><a href=\"/account\">Account</a></li>\n" +
|
|
147
|
+
" <li><a href=\"/orders\">Orders</a></li>\n" +
|
|
148
|
+
" <li><a href=\"/admin\">Admin</a></li>\n" +
|
|
149
|
+
" <li><a href=\"mailto:hello@blamejs.shop\">Contact</a></li>\n" +
|
|
150
|
+
" </ul>\n" +
|
|
151
|
+
" </div>\n" +
|
|
152
|
+
" </div>\n" +
|
|
153
|
+
" <div class=\"site-footer__copy\">\n" +
|
|
154
|
+
" <p>© {{year}} {{shop_name}} — built on blamejs · Apache 2.0 licensed.</p>\n" +
|
|
155
|
+
" <ul>\n" +
|
|
156
|
+
" <li><a href=\"/SECURITY.md\">Security</a></li>\n" +
|
|
157
|
+
" <li><a href=\"/privacy\">Privacy</a></li>\n" +
|
|
158
|
+
" <li><a href=\"/terms\">Terms</a></li>\n" +
|
|
159
|
+
" </ul>\n" +
|
|
160
|
+
" </div>\n" +
|
|
161
|
+
" </footer>\n" +
|
|
162
|
+
"</body>\n" +
|
|
163
|
+
"</html>\n";
|
|
164
|
+
|
|
165
|
+
// Default theme stylesheet URL. Operators pass `opts.theme_css` (or
|
|
166
|
+
// `opts.theme.assetUrl("css/main.css")` via the theme primitive) on
|
|
167
|
+
// each render call to override; absent that, every storefront page
|
|
168
|
+
// references the shipped default theme — so a fresh install renders
|
|
169
|
+
// styled out of the box without any wiring.
|
|
170
|
+
// The default theme stylesheet ships from R2 at this path. The
|
|
171
|
+
// `?v=` query is a build-time cache-buster — operator uploads to R2
|
|
172
|
+
// rewrite the bytes at the same path, and without the version
|
|
173
|
+
// param browsers happily serve the previous cached CSS for the
|
|
174
|
+
// five-minute default TTL. Bumping this constant on each release
|
|
175
|
+
// forces every active session to re-fetch.
|
|
176
|
+
var DEFAULT_THEME_CSS_URL = "/assets/themes/default/css/main.css?v=" + require("../package.json").version;
|
|
177
|
+
|
|
178
|
+
function _wrap(opts) {
|
|
179
|
+
var themeCss = (opts && typeof opts.theme_css === "string" && opts.theme_css.length)
|
|
180
|
+
? opts.theme_css
|
|
181
|
+
: DEFAULT_THEME_CSS_URL;
|
|
182
|
+
return _render(LAYOUT, {
|
|
183
|
+
title: opts.title,
|
|
184
|
+
shop_name: opts.shop_name,
|
|
185
|
+
cart_count: opts.cart_count == null ? 0 : opts.cart_count,
|
|
186
|
+
year: String(new Date().getUTCFullYear()),
|
|
187
|
+
search_q: opts.search_q == null ? "" : opts.search_q,
|
|
188
|
+
theme_css: themeCss,
|
|
189
|
+
body: "RAW_BODY_PLACEHOLDER",
|
|
190
|
+
}).replace("RAW_BODY_PLACEHOLDER", opts.body);
|
|
191
|
+
// The body is RAW HTML (already rendered + escaped at the
|
|
192
|
+
// per-fragment level). The placeholder swap is post-render so the
|
|
193
|
+
// outer renderer's HTML-escape doesn't double-escape the inner
|
|
194
|
+
// markup. `search_q` is HTML-escaped by the renderer like any
|
|
195
|
+
// other placeholder, so a customer-supplied query like
|
|
196
|
+
// `"><script>` lands as escaped text inside the input's `value`.
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---- home --------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
// PRODUCT_CARD has two flavors composed at render time —
|
|
202
|
+
// `_buildProductCard()` picks between them based on whether the
|
|
203
|
+
// product carries a media row. The image-bearing card uses an
|
|
204
|
+
// anchor-wrapper so the whole tile is clickable; the text-only
|
|
205
|
+
// fallback keeps the existing link inside the card body.
|
|
206
|
+
var PRODUCT_CARD_IMAGE =
|
|
207
|
+
"<a class=\"product-card\" href=\"/products/{{slug}}\">\n" +
|
|
208
|
+
" <figure class=\"product-card__media\">\n" +
|
|
209
|
+
" <img src=\"{{image_url}}\" alt=\"{{image_alt}}\" loading=\"lazy\">\n" +
|
|
210
|
+
" </figure>\n" +
|
|
211
|
+
" <div class=\"product-card__meta\">\n" +
|
|
212
|
+
" <h3 class=\"product-card__title\">{{title}}</h3>\n" +
|
|
213
|
+
" <p class=\"product-card__price\">{{price}}</p>\n" +
|
|
214
|
+
" </div>\n" +
|
|
215
|
+
"</a>\n";
|
|
216
|
+
|
|
217
|
+
var PRODUCT_CARD =
|
|
218
|
+
"<div class=\"card\">\n" +
|
|
219
|
+
" <h2>{{title}}</h2>\n" +
|
|
220
|
+
" <p class=\"price\">{{price}}</p>\n" +
|
|
221
|
+
" <a href=\"/products/{{slug}}\" class=\"card-link\">View product →</a>\n" +
|
|
222
|
+
"</div>\n";
|
|
223
|
+
|
|
224
|
+
// Render-time picker. Image-bearing cards become the dominant
|
|
225
|
+
// surface as soon as a product carries media; text-only cards
|
|
226
|
+
// remain the fallback so a freshly-listed product doesn't render
|
|
227
|
+
// an empty image slot.
|
|
228
|
+
function _buildProductCard(p) {
|
|
229
|
+
if (p.image_url) {
|
|
230
|
+
return _render(PRODUCT_CARD_IMAGE, {
|
|
231
|
+
title: p.title,
|
|
232
|
+
price: p.price,
|
|
233
|
+
slug: p.slug,
|
|
234
|
+
image_url: p.image_url,
|
|
235
|
+
image_alt: p.image_alt || p.title,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return _render(PRODUCT_CARD, {
|
|
239
|
+
title: p.title,
|
|
240
|
+
price: p.price,
|
|
241
|
+
slug: p.slug,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var HOME_HERO =
|
|
246
|
+
"<section class=\"hero hero--dark\">\n" +
|
|
247
|
+
" <div class=\"hero__bg\" aria-hidden=\"true\">\n" +
|
|
248
|
+
" <div class=\"hero__grid\"></div>\n" +
|
|
249
|
+
" <div class=\"hero__glow hero__glow--1\"></div>\n" +
|
|
250
|
+
" <div class=\"hero__glow hero__glow--2\"></div>\n" +
|
|
251
|
+
" </div>\n" +
|
|
252
|
+
" <div class=\"hero__inner\">\n" +
|
|
253
|
+
" <div class=\"hero__copy\">\n" +
|
|
254
|
+
" <p class=\"eyebrow eyebrow--on-dark\"><span class=\"dot dot--accent\" aria-hidden=\"true\"></span> Open-source ecommerce framework · v" + require("../package.json").version + "</p>\n" +
|
|
255
|
+
" <h1 class=\"hero__title\">Run a shop that owes <span class=\"accent\">nothing</span> to the dependency graph.</h1>\n" +
|
|
256
|
+
" <p class=\"hero__lede\">Server-rendered HTML. Post-quantum crypto on by default. Zero npm runtime dependencies. Every primitive is composed from a single vendored framework — no transitive supply chain to audit.</p>\n" +
|
|
257
|
+
" <div class=\"hero__cta\">\n" +
|
|
258
|
+
" <a href=\"#catalog\" class=\"btn-primary\">Browse the shop <span aria-hidden=\"true\">→</span></a>\n" +
|
|
259
|
+
" <a href=\"https://github.com/blamejs/blamejs.shop\" class=\"btn-ghost btn-ghost--on-dark\" rel=\"noopener\">View on GitHub</a>\n" +
|
|
260
|
+
" </div>\n" +
|
|
261
|
+
" <dl class=\"hero__stats\">\n" +
|
|
262
|
+
" <div><dt>Products live</dt><dd>{{product_count}}</dd></div>\n" +
|
|
263
|
+
" <div><dt>npm runtime deps</dt><dd>0</dd></div>\n" +
|
|
264
|
+
" <div><dt>Default crypto</dt><dd>PQC</dd></div>\n" +
|
|
265
|
+
" <div><dt>License</dt><dd>Apache 2.0</dd></div>\n" +
|
|
266
|
+
" </dl>\n" +
|
|
267
|
+
" </div>\n" +
|
|
268
|
+
" <aside class=\"hero__card\" aria-label=\"Storefront preview\">\n" +
|
|
269
|
+
" <div class=\"hero__card-bar\">\n" +
|
|
270
|
+
" <span class=\"hero__card-dot hero__card-dot--r\"></span>\n" +
|
|
271
|
+
" <span class=\"hero__card-dot hero__card-dot--y\"></span>\n" +
|
|
272
|
+
" <span class=\"hero__card-dot hero__card-dot--g\"></span>\n" +
|
|
273
|
+
" <span class=\"hero__card-url\">blamejs.shop / order / o-42</span>\n" +
|
|
274
|
+
" </div>\n" +
|
|
275
|
+
" <pre class=\"hero__card-body\"><code><span class=\"tk-c\">// Server-rendered order page</span>\n" +
|
|
276
|
+
"<span class=\"tk-k\">var</span> bShop = <span class=\"tk-f\">require</span>(<span class=\"tk-s\">\"./lib\"</span>);\n" +
|
|
277
|
+
"\n" +
|
|
278
|
+
"bShop.checkout.<span class=\"tk-f\">finalize</span>({\n" +
|
|
279
|
+
" cart_id: <span class=\"tk-s\">\"c_2024\"</span>,\n" +
|
|
280
|
+
" payment_intent: <span class=\"tk-s\">\"pi_3RtA…\"</span>,\n" +
|
|
281
|
+
" cursor_secret: <span class=\"tk-f\">b.crypto.namespaceHash</span>(\n" +
|
|
282
|
+
" <span class=\"tk-s\">\"order-cursor\"</span>, secret),\n" +
|
|
283
|
+
"}).<span class=\"tk-f\">then</span>(<span class=\"tk-k\">function</span> (o) {\n" +
|
|
284
|
+
" res.<span class=\"tk-f\">setHeader</span>(<span class=\"tk-s\">\"content-type\"</span>,\n" +
|
|
285
|
+
" <span class=\"tk-s\">\"text/html; charset=utf-8\"</span>);\n" +
|
|
286
|
+
" res.<span class=\"tk-f\">end</span>(storefront.<span class=\"tk-f\">renderOrder</span>(o));\n" +
|
|
287
|
+
"});</code></pre>\n" +
|
|
288
|
+
" </aside>\n" +
|
|
289
|
+
" </div>\n" +
|
|
290
|
+
"</section>\n" +
|
|
291
|
+
"\n" +
|
|
292
|
+
"<section class=\"marquee\" aria-hidden=\"true\">\n" +
|
|
293
|
+
" <div class=\"marquee__track\">\n" +
|
|
294
|
+
" <span>ML-KEM-1024 key agreement</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
295
|
+
" <span>ML-DSA-65 signatures</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
296
|
+
" <span>XChaCha20-Poly1305 sealed sessions</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
297
|
+
" <span>Argon2id passphrase hashing</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
298
|
+
" <span>SHAKE256 KDF</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
299
|
+
" <span>Stripe-first payments</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
300
|
+
" <span>WebAuthn passkeys</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
301
|
+
" <span>SLSA L3 provenance</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
302
|
+
" <span>Sigstore-keyless SBOM</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
303
|
+
" <span>Trusted Types enforced</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
304
|
+
" <span>Server-rendered HTML</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
305
|
+
" <span>Zero npm runtime deps</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
306
|
+
" <span aria-hidden=\"true\">ML-KEM-1024 key agreement</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
307
|
+
" <span aria-hidden=\"true\">ML-DSA-65 signatures</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
308
|
+
" <span aria-hidden=\"true\">XChaCha20-Poly1305 sealed sessions</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
309
|
+
" <span aria-hidden=\"true\">Argon2id passphrase hashing</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
310
|
+
" <span aria-hidden=\"true\">SHAKE256 KDF</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
311
|
+
" <span aria-hidden=\"true\">Stripe-first payments</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
312
|
+
" <span aria-hidden=\"true\">WebAuthn passkeys</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
313
|
+
" <span aria-hidden=\"true\">SLSA L3 provenance</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
314
|
+
" <span aria-hidden=\"true\">Sigstore-keyless SBOM</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
315
|
+
" <span aria-hidden=\"true\">Trusted Types enforced</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
316
|
+
" <span aria-hidden=\"true\">Server-rendered HTML</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
317
|
+
" <span aria-hidden=\"true\">Zero npm runtime deps</span><span class=\"marquee__sep\">◆</span>\n" +
|
|
318
|
+
" </div>\n" +
|
|
319
|
+
"</section>\n" +
|
|
320
|
+
"\n" +
|
|
321
|
+
"<section class=\"collections\" aria-labelledby=\"collections-title\">\n" +
|
|
322
|
+
" <header class=\"section-head\">\n" +
|
|
323
|
+
" <p class=\"eyebrow\">Featured collections</p>\n" +
|
|
324
|
+
" <h2 id=\"collections-title\" class=\"section-head__title\">Shaped for a storefront that ships from day one.</h2>\n" +
|
|
325
|
+
" <p class=\"section-head__lede\">Drop products into any of these starting categories — or define your own taxonomy through the catalog admin and the framework will server-render the grids, filters, and PDP routes for free.</p>\n" +
|
|
326
|
+
" </header>\n" +
|
|
327
|
+
" <div class=\"collections__grid\">\n" +
|
|
328
|
+
" <a class=\"collection-card\" href=\"/search?q=tee\">\n" +
|
|
329
|
+
" <div class=\"collection-card__art collection-card__art--1\" aria-hidden=\"true\"></div>\n" +
|
|
330
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
331
|
+
" <h3>Apparel</h3>\n" +
|
|
332
|
+
" <p>Sized, colored, inventoried.</p>\n" +
|
|
333
|
+
" </div>\n" +
|
|
334
|
+
" </a>\n" +
|
|
335
|
+
" <a class=\"collection-card\" href=\"/search?q=edge\">\n" +
|
|
336
|
+
" <div class=\"collection-card__art collection-card__art--2\" aria-hidden=\"true\"></div>\n" +
|
|
337
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
338
|
+
" <h3>Hardware</h3>\n" +
|
|
339
|
+
" <p>Serialized, warranty-tracked.</p>\n" +
|
|
340
|
+
" </div>\n" +
|
|
341
|
+
" </a>\n" +
|
|
342
|
+
" <a class=\"collection-card\" href=\"/search?q=license\">\n" +
|
|
343
|
+
" <div class=\"collection-card__art collection-card__art--3\" aria-hidden=\"true\"></div>\n" +
|
|
344
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
345
|
+
" <h3>Digital</h3>\n" +
|
|
346
|
+
" <p>License-key fulfillment.</p>\n" +
|
|
347
|
+
" </div>\n" +
|
|
348
|
+
" </a>\n" +
|
|
349
|
+
" <a class=\"collection-card\" href=\"/search?q=subscription\">\n" +
|
|
350
|
+
" <div class=\"collection-card__art collection-card__art--4\" aria-hidden=\"true\"></div>\n" +
|
|
351
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
352
|
+
" <h3>Subscriptions</h3>\n" +
|
|
353
|
+
" <p>Stripe-backed recurring.</p>\n" +
|
|
354
|
+
" </div>\n" +
|
|
355
|
+
" </a>\n" +
|
|
356
|
+
" <a class=\"collection-card\" href=\"/search?q=bundle\">\n" +
|
|
357
|
+
" <div class=\"collection-card__art collection-card__art--5\" aria-hidden=\"true\"></div>\n" +
|
|
358
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
359
|
+
" <h3>Bundles</h3>\n" +
|
|
360
|
+
" <p>Composite SKUs, atomic stock.</p>\n" +
|
|
361
|
+
" </div>\n" +
|
|
362
|
+
" </a>\n" +
|
|
363
|
+
" <a class=\"collection-card\" href=\"/search?q=gift\">\n" +
|
|
364
|
+
" <div class=\"collection-card__art collection-card__art--6\" aria-hidden=\"true\"></div>\n" +
|
|
365
|
+
" <div class=\"collection-card__meta\">\n" +
|
|
366
|
+
" <h3>Gift cards</h3>\n" +
|
|
367
|
+
" <p>PQC-signed redemption codes.</p>\n" +
|
|
368
|
+
" </div>\n" +
|
|
369
|
+
" </a>\n" +
|
|
370
|
+
" </div>\n" +
|
|
371
|
+
"</section>\n" +
|
|
372
|
+
"\n" +
|
|
373
|
+
"<section id=\"framework\" class=\"framework-band\" aria-labelledby=\"framework-title\">\n" +
|
|
374
|
+
" <div class=\"framework-band__inner\">\n" +
|
|
375
|
+
" <div class=\"framework-band__copy\">\n" +
|
|
376
|
+
" <p class=\"eyebrow\">Built on blamejs</p>\n" +
|
|
377
|
+
" <h2 id=\"framework-title\">Every behavior on this site is a composed primitive.</h2>\n" +
|
|
378
|
+
" <p class=\"framework-band__lede\">Twenty-plus shop primitives — cart, catalog, payment, order, subscriptions, customers, webhooks, audit log, sealed sessions, signed cursors, problem-details, money math, FSMs — sit on a vendored blamejs core that ships hundreds more. They compose. Replacing one doesn't fork the others.</p>\n" +
|
|
379
|
+
" <a class=\"link-arrow\" href=\"https://github.com/blamejs/blamejs.shop\" rel=\"noopener\">Read the source on GitHub <span aria-hidden=\"true\">→</span></a>\n" +
|
|
380
|
+
" </div>\n" +
|
|
381
|
+
" <ul class=\"framework-band__list\">\n" +
|
|
382
|
+
" <li>\n" +
|
|
383
|
+
" <span class=\"framework-band__num\">01</span>\n" +
|
|
384
|
+
" <h3>Server-rendered HTML</h3>\n" +
|
|
385
|
+
" <p>Every route renders a complete document at the origin. No client framework, no hydration spinner, no JavaScript required to read the page.</p>\n" +
|
|
386
|
+
" </li>\n" +
|
|
387
|
+
" <li>\n" +
|
|
388
|
+
" <span class=\"framework-band__num\">02</span>\n" +
|
|
389
|
+
" <h3>PQC-first crypto</h3>\n" +
|
|
390
|
+
" <p>ML-KEM-1024, ML-DSA-65, XChaCha20-Poly1305, SHAKE256, HKDF-SHA3-512, Argon2id. The application never reaches for AES-GCM, SHA-256, or classical ECDH on its own.</p>\n" +
|
|
391
|
+
" </li>\n" +
|
|
392
|
+
" <li>\n" +
|
|
393
|
+
" <span class=\"framework-band__num\">03</span>\n" +
|
|
394
|
+
" <h3>Zero runtime npm deps</h3>\n" +
|
|
395
|
+
" <p>One vendored framework, shipped byte-for-byte from a signed release tag. The tarball you install is the tarball that ran in CI.</p>\n" +
|
|
396
|
+
" </li>\n" +
|
|
397
|
+
" <li>\n" +
|
|
398
|
+
" <span class=\"framework-band__num\">04</span>\n" +
|
|
399
|
+
" <h3>Security defaults on</h3>\n" +
|
|
400
|
+
" <p>CSRF, fetch-metadata, origin, bot-guard, sealed cookies, Trusted Types, DoH, cookie-prefix policy — composed into the request lifecycle, not behind a feature flag.</p>\n" +
|
|
401
|
+
" </li>\n" +
|
|
402
|
+
" </ul>\n" +
|
|
403
|
+
" </div>\n" +
|
|
404
|
+
"</section>\n";
|
|
405
|
+
|
|
406
|
+
var CATALOG_EMPTY =
|
|
407
|
+
"<section id=\"catalog\" class=\"catalog-section\">\n" +
|
|
408
|
+
" <header class=\"section-head section-head--with-link\">\n" +
|
|
409
|
+
" <div>\n" +
|
|
410
|
+
" <p class=\"eyebrow\">Catalog</p>\n" +
|
|
411
|
+
" <h2 class=\"section-head__title\">The shop is open, waiting on its first listing.</h2>\n" +
|
|
412
|
+
" <p class=\"section-head__lede\">When you add a product through the admin API, it appears here in a server-rendered grid with filters, sorting, and a fully-routed PDP. Until then, this is the storefront shell.</p>\n" +
|
|
413
|
+
" </div>\n" +
|
|
414
|
+
" <a class=\"link-arrow\" href=\"/admin\">Open admin <span aria-hidden=\"true\">→</span></a>\n" +
|
|
415
|
+
" </header>\n" +
|
|
416
|
+
" <div class=\"catalog-empty\">\n" +
|
|
417
|
+
" <div class=\"catalog-empty__placeholder\" aria-hidden=\"true\">\n" +
|
|
418
|
+
" <span></span><span></span><span></span><span></span>\n" +
|
|
419
|
+
" </div>\n" +
|
|
420
|
+
" <div class=\"catalog-empty__copy\">\n" +
|
|
421
|
+
" <h3>Add the first product</h3>\n" +
|
|
422
|
+
" <p>The grid below renders as soon as a product is listed. Variants, prices, inventory levels, and tax categories are all wired up — you just supply the SKUs.</p>\n" +
|
|
423
|
+
" <pre class=\"catalog-empty__code\"><code>curl -X POST https://blamejs.shop/admin/products \\\n -H \"authorization: Bearer $ADMIN_API_KEY\" \\\n -d '{ \"title\": \"My first product\", \"slug\": \"first\" }'</code></pre>\n" +
|
|
424
|
+
" </div>\n" +
|
|
425
|
+
" </div>\n" +
|
|
426
|
+
"</section>\n";
|
|
427
|
+
|
|
428
|
+
var CATALOG_HEAD =
|
|
429
|
+
"<section id=\"catalog\" class=\"catalog-section\">\n" +
|
|
430
|
+
" <header class=\"section-head section-head--with-link\">\n" +
|
|
431
|
+
" <div>\n" +
|
|
432
|
+
" <p class=\"eyebrow\">Catalog</p>\n" +
|
|
433
|
+
" <h2 class=\"section-head__title\">Products in store</h2>\n" +
|
|
434
|
+
" <p class=\"section-head__lede\">Server-rendered listings — every card, price, and link arrived on the wire as complete HTML.</p>\n" +
|
|
435
|
+
" </div>\n" +
|
|
436
|
+
" <a class=\"link-arrow\" href=\"/?sort=new\">New arrivals <span aria-hidden=\"true\">→</span></a>\n" +
|
|
437
|
+
" </header>\n" +
|
|
438
|
+
" <div class=\"grid\">{{cards}}</div>\n" +
|
|
439
|
+
"</section>\n";
|
|
440
|
+
|
|
441
|
+
function renderHome(opts) {
|
|
442
|
+
if (!opts || !Array.isArray(opts.products)) throw new TypeError("storefront.renderHome: opts.products required");
|
|
443
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
444
|
+
var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
|
|
445
|
+
var title = opts.title || "Shop";
|
|
446
|
+
var assetPrefix = opts.asset_prefix || "/assets/";
|
|
447
|
+
var products = opts.products.map(function (p) {
|
|
448
|
+
var priceStr = p.starting_price_minor != null
|
|
449
|
+
? pricing.format(p.starting_price_minor, p.starting_price_currency || "USD")
|
|
450
|
+
: "—";
|
|
451
|
+
// Hero image — first media row attached to the product (the
|
|
452
|
+
// route handler bundles it in via `p.hero_media`). Image-less
|
|
453
|
+
// products render the text-only PRODUCT_CARD fallback below.
|
|
454
|
+
var imageUrl = p.hero_media ? assetPrefix + p.hero_media.r2_key : null;
|
|
455
|
+
var imageAlt = p.hero_media ? (p.hero_media.alt_text || p.title) : null;
|
|
456
|
+
return { title: p.title, price: priceStr, slug: p.slug, image_url: imageUrl, image_alt: imageAlt };
|
|
457
|
+
});
|
|
458
|
+
if (opts.theme) {
|
|
459
|
+
return opts.theme.render("home", {
|
|
460
|
+
title: title,
|
|
461
|
+
shop_name: shopName,
|
|
462
|
+
cart_count: cartCount,
|
|
463
|
+
products: products,
|
|
464
|
+
has_products: products.length > 0,
|
|
465
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
var cards = products.map(function (p) { return _buildProductCard(p); }).join("\n");
|
|
469
|
+
var catalog = products.length === 0
|
|
470
|
+
? CATALOG_EMPTY
|
|
471
|
+
: _render(CATALOG_HEAD, { cards: "RAW_CARDS_PLACEHOLDER" }).replace("RAW_CARDS_PLACEHOLDER", cards);
|
|
472
|
+
// Live stat in the hero — operator-facing visitors see the
|
|
473
|
+
// actual catalog size, not a stale hardcoded number. Falls back
|
|
474
|
+
// to a typographic em-dash when the catalog hasn't been seeded.
|
|
475
|
+
var heroProductCount = products.length === 0 ? "—" : String(products.length);
|
|
476
|
+
var hero = _render(HOME_HERO, { product_count: heroProductCount });
|
|
477
|
+
// The hero + value band + catalog section give the home page a
|
|
478
|
+
// designed surface even when no products are loaded yet —
|
|
479
|
+
// visitors land on the storefront shell, not a tech demo.
|
|
480
|
+
var body = hero + catalog;
|
|
481
|
+
return _wrap({
|
|
482
|
+
title: title,
|
|
483
|
+
shop_name: shopName,
|
|
484
|
+
cart_count: cartCount,
|
|
485
|
+
theme_css: opts.theme_css,
|
|
486
|
+
body: body,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ---- search results -----------------------------------------------------
|
|
491
|
+
|
|
492
|
+
var SEARCH_HEADER =
|
|
493
|
+
"<section class=\"search-page\">\n" +
|
|
494
|
+
" <header class=\"section-head section-head--with-link\">\n" +
|
|
495
|
+
" <div>\n" +
|
|
496
|
+
" <p class=\"eyebrow\">Search results</p>\n" +
|
|
497
|
+
" <h1 class=\"section-head__title\">{{title}}</h1>\n" +
|
|
498
|
+
" <p class=\"section-head__lede\">{{summary}}</p>\n" +
|
|
499
|
+
" </div>\n" +
|
|
500
|
+
" <a class=\"link-arrow\" href=\"/\">All products <span aria-hidden=\"true\">→</span></a>\n" +
|
|
501
|
+
" </header>\n" +
|
|
502
|
+
"</section>\n";
|
|
503
|
+
|
|
504
|
+
var SEARCH_EMPTY =
|
|
505
|
+
"<section class=\"search-empty\">\n" +
|
|
506
|
+
" <div class=\"search-empty__inner\">\n" +
|
|
507
|
+
" <p class=\"search-empty__icon\" aria-hidden=\"true\">⌕</p>\n" +
|
|
508
|
+
" <h2>{{heading}}</h2>\n" +
|
|
509
|
+
" <p>{{copy}}</p>\n" +
|
|
510
|
+
" <a href=\"/\" class=\"btn-ghost\">Browse the full catalog</a>\n" +
|
|
511
|
+
" </div>\n" +
|
|
512
|
+
"</section>\n";
|
|
513
|
+
|
|
514
|
+
function renderSearch(opts) {
|
|
515
|
+
if (!opts || typeof opts.q !== "string") throw new TypeError("storefront.renderSearch: opts.q (string) required");
|
|
516
|
+
var products = Array.isArray(opts.products) ? opts.products : [];
|
|
517
|
+
var qTrim = opts.q.trim();
|
|
518
|
+
var title, summary, emptyHeading, emptyCopy;
|
|
519
|
+
if (qTrim.length === 0) {
|
|
520
|
+
title = "Search the catalog";
|
|
521
|
+
summary = "Use the search box in the header to look for a product by title, SKU, or description.";
|
|
522
|
+
emptyHeading = "What are you looking for?";
|
|
523
|
+
emptyCopy = "Type a query in the header search to find products by title, SKU, or description.";
|
|
524
|
+
} else if (products.length === 0) {
|
|
525
|
+
title = "No matches";
|
|
526
|
+
summary = "Nothing in the catalog matched “" + qTrim + "”.";
|
|
527
|
+
emptyHeading = "We don't carry that yet";
|
|
528
|
+
emptyCopy = "Try a broader term, or browse every product on the home page.";
|
|
529
|
+
} else {
|
|
530
|
+
title = "“" + qTrim + "”";
|
|
531
|
+
summary = "Showing " + products.length + " match" + (products.length === 1 ? "" : "es") + " for your query.";
|
|
532
|
+
}
|
|
533
|
+
var header = _render(SEARCH_HEADER, { title: title, summary: summary });
|
|
534
|
+
var body;
|
|
535
|
+
if (products.length === 0) {
|
|
536
|
+
body = header + _render(SEARCH_EMPTY, { heading: emptyHeading, copy: emptyCopy });
|
|
537
|
+
} else {
|
|
538
|
+
var assetPrefix = opts.asset_prefix || "/assets/";
|
|
539
|
+
var cards = products.map(function (p) {
|
|
540
|
+
var priceStr = p.starting_price_minor != null
|
|
541
|
+
? pricing.format(p.starting_price_minor, p.starting_price_currency || "USD")
|
|
542
|
+
: "—";
|
|
543
|
+
var imageUrl = p.hero_media ? assetPrefix + p.hero_media.r2_key : null;
|
|
544
|
+
var imageAlt = p.hero_media ? (p.hero_media.alt_text || p.title) : null;
|
|
545
|
+
return _buildProductCard({ title: p.title, price: priceStr, slug: p.slug, image_url: imageUrl, image_alt: imageAlt });
|
|
546
|
+
}).join("\n");
|
|
547
|
+
body = header + "<section class=\"search-grid\"><div class=\"grid\">" + cards + "</div></section>";
|
|
548
|
+
}
|
|
549
|
+
return _wrap({
|
|
550
|
+
title: "Search",
|
|
551
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
552
|
+
cart_count: opts.cart_count,
|
|
553
|
+
search_q: opts.q,
|
|
554
|
+
theme_css: opts.theme_css,
|
|
555
|
+
body: body,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// ---- product detail -----------------------------------------------------
|
|
560
|
+
|
|
561
|
+
// Cart-add form. CSRF defense rests on the `shop_sid` session
|
|
562
|
+
// cookie's SameSite=Lax attribute — a cross-site form POST won't
|
|
563
|
+
// carry the cookie, so any cross-site "add to cart" lands in a
|
|
564
|
+
// fresh anonymous session that the victim never sees. Token-based
|
|
565
|
+
// CSRF as defense-in-depth is added alongside the Stripe Elements
|
|
566
|
+
// payment route in the next patch.
|
|
567
|
+
var VARIANT_ROW =
|
|
568
|
+
"<tr>\n" +
|
|
569
|
+
" <td class=\"variant-row__title\">{{title}}</td>\n" +
|
|
570
|
+
" <td class=\"variant-row__sku\"><code>{{sku}}</code></td>\n" +
|
|
571
|
+
" <td class=\"variant-row__price price\">{{price}}</td>\n" +
|
|
572
|
+
" <td class=\"variant-row__action\">\n" +
|
|
573
|
+
" <form method=\"post\" action=\"/cart/lines\">\n" +
|
|
574
|
+
" <input type=\"hidden\" name=\"variant_id\" value=\"{{variant_id}}\">\n" +
|
|
575
|
+
" <input type=\"number\" name=\"qty\" value=\"1\" min=\"1\" max=\"99\" class=\"variant-row__qty\" aria-label=\"Quantity\">\n" +
|
|
576
|
+
" <button type=\"submit\" class=\"btn-primary btn-primary--sm\">Add to cart</button>\n" +
|
|
577
|
+
" </form>\n" +
|
|
578
|
+
" </td>\n" +
|
|
579
|
+
"</tr>\n";
|
|
580
|
+
|
|
581
|
+
var PRODUCT_PAGE =
|
|
582
|
+
"<section class=\"pdp\">\n" +
|
|
583
|
+
" <nav class=\"breadcrumb\" aria-label=\"Breadcrumb\">\n" +
|
|
584
|
+
" <ol>\n" +
|
|
585
|
+
" <li><a href=\"/\">Shop</a></li>\n" +
|
|
586
|
+
" <li aria-current=\"page\">{{title}}</li>\n" +
|
|
587
|
+
" </ol>\n" +
|
|
588
|
+
" </nav>\n" +
|
|
589
|
+
" <div class=\"pdp__grid\">\n" +
|
|
590
|
+
" <div class=\"pdp__gallery\">RAW_GALLERY_PLACEHOLDER</div>\n" +
|
|
591
|
+
" <div class=\"pdp__info\">\n" +
|
|
592
|
+
" <p class=\"eyebrow\">Catalog product</p>\n" +
|
|
593
|
+
" <h1 class=\"pdp__title\">{{title}}</h1>\n" +
|
|
594
|
+
" <p class=\"pdp__description\">{{description}}</p>\n" +
|
|
595
|
+
" <div class=\"pdp__meta\">\n" +
|
|
596
|
+
" <span class=\"pdp__badge pdp__badge--ok\"><span class=\"dot dot--live\" aria-hidden=\"true\"></span> In stock</span>\n" +
|
|
597
|
+
" <span class=\"pdp__badge\">Ships from origin</span>\n" +
|
|
598
|
+
" <span class=\"pdp__badge\">Stripe-secured checkout</span>\n" +
|
|
599
|
+
" </div>\n" +
|
|
600
|
+
" <div class=\"pdp__variants\">\n" +
|
|
601
|
+
" <h2 class=\"pdp__variants-title\">Choose a variant</h2>\n" +
|
|
602
|
+
" <div class=\"table-scroll\">\n" +
|
|
603
|
+
" <table class=\"variant-table\">\n" +
|
|
604
|
+
" <thead><tr><th>Variant</th><th>SKU</th><th>Price</th><th class=\"variant-table__action-h\">Action</th></tr></thead>\n" +
|
|
605
|
+
" <tbody>{{variant_rows}}</tbody>\n" +
|
|
606
|
+
" </table>\n" +
|
|
607
|
+
" </div>\n" +
|
|
608
|
+
" </div>\n" +
|
|
609
|
+
" </div>\n" +
|
|
610
|
+
" </div>\n" +
|
|
611
|
+
"</section>\n";
|
|
612
|
+
|
|
613
|
+
// PDP gallery markup — composed once per render call from the
|
|
614
|
+
// product's media rows. When media is present, the first row drives
|
|
615
|
+
// the main figure (with `alt_text` for a11y) and up to three more
|
|
616
|
+
// rows feed the thumbnail strip below it. When media is absent the
|
|
617
|
+
// gallery falls back to the existing letter-mark placeholder so a
|
|
618
|
+
// freshly-seeded product never renders an empty square.
|
|
619
|
+
function _buildPdpGallery(product, media, assetPrefix) {
|
|
620
|
+
var prefix = assetPrefix || "/assets/";
|
|
621
|
+
function _escAttr(s) {
|
|
622
|
+
return String(s == null ? "" : s)
|
|
623
|
+
.replace(/&/g, "&")
|
|
624
|
+
.replace(/</g, "<")
|
|
625
|
+
.replace(/>/g, ">")
|
|
626
|
+
.replace(/"/g, """);
|
|
627
|
+
}
|
|
628
|
+
if (!media || media.length === 0) {
|
|
629
|
+
var initial = (product.title || "?").trim().charAt(0).toUpperCase() || "?";
|
|
630
|
+
return "<figure class=\"pdp__media\" aria-hidden=\"true\">" +
|
|
631
|
+
"<span class=\"pdp__media-mark\">" + _escAttr(initial) + "</span>" +
|
|
632
|
+
"</figure>" +
|
|
633
|
+
"<ul class=\"pdp__thumbs\" aria-hidden=\"true\">" +
|
|
634
|
+
"<li class=\"is-active\"></li><li></li><li></li><li></li>" +
|
|
635
|
+
"</ul>";
|
|
636
|
+
}
|
|
637
|
+
var hero = media[0];
|
|
638
|
+
var heroUrl = prefix + hero.r2_key;
|
|
639
|
+
var heroAlt = hero.alt_text || product.title || "Product image";
|
|
640
|
+
var heroImg = "<figure class=\"pdp__media pdp__media--image\">" +
|
|
641
|
+
"<img src=\"" + _escAttr(heroUrl) + "\" alt=\"" + _escAttr(heroAlt) + "\" loading=\"eager\">" +
|
|
642
|
+
"</figure>";
|
|
643
|
+
// Thumbnail strip — up to four slots, the active one is the hero.
|
|
644
|
+
// Real thumbnails come from additional media rows; missing slots
|
|
645
|
+
// render as dashed placeholders so the strip's grid doesn't
|
|
646
|
+
// collapse on a single-image product.
|
|
647
|
+
var thumbs = ["<li class=\"is-active\">" +
|
|
648
|
+
"<img src=\"" + _escAttr(heroUrl) + "\" alt=\"\">" +
|
|
649
|
+
"</li>"];
|
|
650
|
+
for (var i = 1; i < Math.min(media.length, 4); i += 1) {
|
|
651
|
+
var t = media[i];
|
|
652
|
+
var tUrl = prefix + t.r2_key;
|
|
653
|
+
thumbs.push("<li><img src=\"" + _escAttr(tUrl) + "\" alt=\"\"></li>");
|
|
654
|
+
}
|
|
655
|
+
while (thumbs.length < 4) thumbs.push("<li></li>");
|
|
656
|
+
return heroImg + "<ul class=\"pdp__thumbs\" aria-hidden=\"true\">" + thumbs.join("") + "</ul>";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function renderProduct(opts) {
|
|
660
|
+
if (!opts || !opts.product) throw new TypeError("storefront.renderProduct: opts.product required");
|
|
661
|
+
var variants = opts.variants || [];
|
|
662
|
+
var prices = opts.prices || {}; // { variant_id: { currency, amount_minor } }
|
|
663
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
664
|
+
var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
|
|
665
|
+
var description = opts.product.description || "";
|
|
666
|
+
var rendered = variants.map(function (v) {
|
|
667
|
+
var price = prices[v.id];
|
|
668
|
+
var priceStr = price ? pricing.format(price.amount_minor, price.currency) : "—";
|
|
669
|
+
var vTitle = v.title || (Object.keys(v.options || {}).map(function (k) { return v.options[k]; }).join(" / ") || "Default");
|
|
670
|
+
return { id: v.id, sku: v.sku, title: vTitle, price: priceStr };
|
|
671
|
+
});
|
|
672
|
+
if (opts.theme) {
|
|
673
|
+
return opts.theme.render("product", {
|
|
674
|
+
title: opts.product.title,
|
|
675
|
+
shop_name: shopName,
|
|
676
|
+
cart_count: cartCount,
|
|
677
|
+
product: { title: opts.product.title, description: description },
|
|
678
|
+
variants: rendered,
|
|
679
|
+
has_variants: rendered.length > 0,
|
|
680
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
var rows = rendered.map(function (v) {
|
|
684
|
+
return _render(VARIANT_ROW, { title: v.title, sku: v.sku, price: v.price, variant_id: v.id });
|
|
685
|
+
}).join("");
|
|
686
|
+
if (!rows) rows = "<tr><td colspan=\"4\" class=\"empty\">No variants available.</td></tr>";
|
|
687
|
+
var galleryHtml = _buildPdpGallery(opts.product, opts.media || [], opts.asset_prefix || "/assets/");
|
|
688
|
+
var body = _render(PRODUCT_PAGE, {
|
|
689
|
+
title: opts.product.title,
|
|
690
|
+
description: description,
|
|
691
|
+
variant_rows: "RAW_ROWS_PLACEHOLDER",
|
|
692
|
+
})
|
|
693
|
+
.replace("RAW_GALLERY_PLACEHOLDER", galleryHtml)
|
|
694
|
+
.replace("RAW_ROWS_PLACEHOLDER", rows);
|
|
695
|
+
return _wrap({
|
|
696
|
+
title: opts.product.title,
|
|
697
|
+
shop_name: shopName,
|
|
698
|
+
cart_count: cartCount,
|
|
699
|
+
theme_css: opts.theme_css,
|
|
700
|
+
body: body,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// ---- cart --------------------------------------------------------------
|
|
705
|
+
|
|
706
|
+
var CART_LINE =
|
|
707
|
+
"<tr><td>{{sku}}</td><td>{{qty}}</td><td class=\"price\">{{unit}}</td><td class=\"price\">{{total}}</td></tr>\n";
|
|
708
|
+
|
|
709
|
+
// Editable cart line — shown on the /cart page. Includes an inline
|
|
710
|
+
// qty form (POST /cart/lines/:id/update) and a remove form (POST
|
|
711
|
+
// /cart/lines/:id/remove). HTML forms don't natively support
|
|
712
|
+
// PATCH/DELETE so the framework routes use POST with verb-suffix
|
|
713
|
+
// paths.
|
|
714
|
+
var CART_LINE_EDITABLE =
|
|
715
|
+
"<tr>\n" +
|
|
716
|
+
" <td class=\"cart-line__sku\"><code>{{sku}}</code></td>\n" +
|
|
717
|
+
" <td class=\"cart-line__qty\">\n" +
|
|
718
|
+
" <form method=\"post\" action=\"/cart/lines/{{line_id}}/update\" class=\"cart-line__update\">\n" +
|
|
719
|
+
" <input type=\"number\" name=\"qty\" value=\"{{qty}}\" min=\"1\" max=\"99\" class=\"cart-line__qty-input\" aria-label=\"Quantity\">\n" +
|
|
720
|
+
" <button type=\"submit\" class=\"cart-line__btn\">Update</button>\n" +
|
|
721
|
+
" </form>\n" +
|
|
722
|
+
" </td>\n" +
|
|
723
|
+
" <td class=\"price\">{{unit}}</td>\n" +
|
|
724
|
+
" <td class=\"price\">{{total}}</td>\n" +
|
|
725
|
+
" <td class=\"cart-line__remove-cell\">\n" +
|
|
726
|
+
" <form method=\"post\" action=\"/cart/lines/{{line_id}}/remove\">\n" +
|
|
727
|
+
" <button type=\"submit\" class=\"cart-line__btn cart-line__btn--remove\" aria-label=\"Remove line\">Remove</button>\n" +
|
|
728
|
+
" </form>\n" +
|
|
729
|
+
" </td>\n" +
|
|
730
|
+
"</tr>\n";
|
|
731
|
+
|
|
732
|
+
// ---- checkout form + payment page + order confirmation -----------------
|
|
733
|
+
|
|
734
|
+
var CHECKOUT_PAGE =
|
|
735
|
+
"<section class=\"checkout-page\">\n" +
|
|
736
|
+
" <header class=\"section-head\">\n" +
|
|
737
|
+
" <p class=\"eyebrow\">Checkout</p>\n" +
|
|
738
|
+
" <h1 class=\"section-head__title\">Shipping details</h1>\n" +
|
|
739
|
+
" <p class=\"section-head__lede\">Enter where the order should ship. Payment runs through Stripe on the next step.</p>\n" +
|
|
740
|
+
" </header>\n" +
|
|
741
|
+
" <form method=\"post\" action=\"/checkout\" class=\"form-stack\">\n" +
|
|
742
|
+
" <div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Email</span><input type=\"email\" name=\"email\" required autocomplete=\"email\"></label></div>\n" +
|
|
743
|
+
" <div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Full name</span><input type=\"text\" name=\"name\" required autocomplete=\"name\"></label></div>\n" +
|
|
744
|
+
" <div class=\"form-row form-row--inline\">\n" +
|
|
745
|
+
" <label class=\"form-field\"><span class=\"form-field__label\">Country (ISO 3166-1)</span><input type=\"text\" name=\"country\" value=\"US\" maxlength=\"2\" pattern=\"[A-Z]{2}\" required autocomplete=\"country\" class=\"form-field__input--xs\"></label>\n" +
|
|
746
|
+
" <label class=\"form-field\"><span class=\"form-field__label\">State / Region</span><input type=\"text\" name=\"state\" maxlength=\"5\" autocomplete=\"address-level1\" class=\"form-field__input--xs\"></label>\n" +
|
|
747
|
+
" <label class=\"form-field\"><span class=\"form-field__label\">Postal code</span><input type=\"text\" name=\"postal\" maxlength=\"16\" autocomplete=\"postal-code\" class=\"form-field__input--sm\"></label>\n" +
|
|
748
|
+
" </div>\n" +
|
|
749
|
+
" <div class=\"checkout-summary\">\n" +
|
|
750
|
+
" <h3>Order summary</h3>\n" +
|
|
751
|
+
" <dl>\n" +
|
|
752
|
+
" <div><dt>Subtotal</dt><dd>{{subtotal}}</dd></div>\n" +
|
|
753
|
+
" <div class=\"checkout-summary__total\"><dt>Total <span class=\"small\">(plus tax + shipping)</span></dt><dd>{{subtotal}}</dd></div>\n" +
|
|
754
|
+
" </dl>\n" +
|
|
755
|
+
" </div>\n" +
|
|
756
|
+
" <div class=\"form-actions\"><button type=\"submit\" class=\"btn-primary\">Continue to payment <span aria-hidden=\"true\">→</span></button></div>\n" +
|
|
757
|
+
" </form>\n" +
|
|
758
|
+
"</section>\n";
|
|
759
|
+
|
|
760
|
+
function renderCheckoutForm(opts) {
|
|
761
|
+
if (!opts) throw new TypeError("storefront.renderCheckoutForm: opts required");
|
|
762
|
+
var lines = opts.lines || [];
|
|
763
|
+
var totals = opts.totals || { subtotal_minor: 0, currency: "USD" };
|
|
764
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
765
|
+
var subtotal = pricing.format(totals.subtotal_minor, totals.currency);
|
|
766
|
+
if (opts.theme) {
|
|
767
|
+
return opts.theme.render("checkout", {
|
|
768
|
+
title: "Checkout",
|
|
769
|
+
shop_name: shopName,
|
|
770
|
+
cart_count: lines.length,
|
|
771
|
+
subtotal: subtotal,
|
|
772
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
var body = _render(CHECKOUT_PAGE, { subtotal: subtotal });
|
|
776
|
+
return _wrap({
|
|
777
|
+
title: "Checkout",
|
|
778
|
+
shop_name: shopName,
|
|
779
|
+
cart_count: lines.length,
|
|
780
|
+
theme_css: opts.theme_css,
|
|
781
|
+
body: body,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Stripe Elements payment page — embeds Stripe.js + a minimal
|
|
786
|
+
// mount block. The publishable key is operator-supplied (env
|
|
787
|
+
// `STRIPE_PUBLISHABLE_KEY` → forwarded into the rendered HTML).
|
|
788
|
+
// The client_secret is per-order; never logged, never persisted.
|
|
789
|
+
var PAY_PAGE =
|
|
790
|
+
"<section class=\"pay-page\">\n" +
|
|
791
|
+
" <header class=\"section-head\">\n" +
|
|
792
|
+
" <p class=\"eyebrow\">Secure checkout · Stripe</p>\n" +
|
|
793
|
+
" <h1 class=\"section-head__title\">Pay {{grand_total}}</h1>\n" +
|
|
794
|
+
" <p class=\"section-head__lede\">Order <code class=\"inline-code\">{{order_id}}</code> · the Stripe Payment Element is mounted below in a same-origin form.</p>\n" +
|
|
795
|
+
" </header>\n" +
|
|
796
|
+
" <div class=\"pay-card\">\n" +
|
|
797
|
+
" <div id=\"payment-element\" class=\"pay-card__element\"></div>\n" +
|
|
798
|
+
" <button id=\"submit\" type=\"button\" class=\"btn-primary pay-card__submit\">Pay {{grand_total}}</button>\n" +
|
|
799
|
+
" <p id=\"payment-message\" class=\"pay-card__message\"></p>\n" +
|
|
800
|
+
" </div>\n" +
|
|
801
|
+
" <script src=\"https://js.stripe.com/v3/\"></script>\n" +
|
|
802
|
+
" <script>\n" +
|
|
803
|
+
" (function () {\n" +
|
|
804
|
+
" var stripe = Stripe({{pk_json}});\n" +
|
|
805
|
+
" var elements = stripe.elements({ clientSecret: {{client_secret_json}}, appearance: { theme: \"stripe\" } });\n" +
|
|
806
|
+
" var paymentElement = elements.create(\"payment\");\n" +
|
|
807
|
+
" paymentElement.mount(\"#payment-element\");\n" +
|
|
808
|
+
" document.getElementById(\"submit\").addEventListener(\"click\", function () {\n" +
|
|
809
|
+
" document.getElementById(\"payment-message\").textContent = \"Processing...\";\n" +
|
|
810
|
+
" stripe.confirmPayment({ elements: elements, confirmParams: { return_url: window.location.origin + \"/orders/{{order_id}}\" } }).then(function (result) {\n" +
|
|
811
|
+
" if (result.error) { document.getElementById(\"payment-message\").textContent = result.error.message || \"Payment failed.\"; }\n" +
|
|
812
|
+
" });\n" +
|
|
813
|
+
" });\n" +
|
|
814
|
+
" })();\n" +
|
|
815
|
+
" </script>\n" +
|
|
816
|
+
"</section>\n";
|
|
817
|
+
|
|
818
|
+
function renderPayPage(opts) {
|
|
819
|
+
if (!opts || !opts.order) throw new TypeError("storefront.renderPayPage: opts.order required");
|
|
820
|
+
if (!opts.client_secret) throw new TypeError("storefront.renderPayPage: opts.client_secret required");
|
|
821
|
+
if (!opts.publishable_key) throw new TypeError("storefront.renderPayPage: opts.publishable_key required");
|
|
822
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
823
|
+
var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
|
|
824
|
+
var grandTotal = pricing.format(opts.order.grand_total_minor, opts.order.currency);
|
|
825
|
+
// Stripe.js and client_secret values must be JSON-encoded so the
|
|
826
|
+
// template engine treats them as raw expressions (`{{{ }}}` /
|
|
827
|
+
// post-render replace) rather than HTML-escaping the quotes. The
|
|
828
|
+
// values are otherwise opaque to the renderer — no string
|
|
829
|
+
// concatenation possible at this layer.
|
|
830
|
+
var pkJson = JSON.stringify(opts.publishable_key);
|
|
831
|
+
var secretJson = JSON.stringify(opts.client_secret);
|
|
832
|
+
if (opts.theme) {
|
|
833
|
+
return opts.theme.render("pay", {
|
|
834
|
+
title: "Pay",
|
|
835
|
+
shop_name: shopName,
|
|
836
|
+
cart_count: cartCount,
|
|
837
|
+
order_id: opts.order.id,
|
|
838
|
+
grand_total: grandTotal,
|
|
839
|
+
pk_json: pkJson,
|
|
840
|
+
client_secret_json: secretJson,
|
|
841
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
var body = _render(PAY_PAGE, {
|
|
845
|
+
order_id: opts.order.id,
|
|
846
|
+
grand_total: grandTotal,
|
|
847
|
+
pk_json: "RAW_PK",
|
|
848
|
+
client_secret_json: "RAW_SECRET",
|
|
849
|
+
}).replace("RAW_PK", pkJson)
|
|
850
|
+
.replace("RAW_SECRET", secretJson);
|
|
851
|
+
return _wrap({
|
|
852
|
+
title: "Pay",
|
|
853
|
+
shop_name: shopName,
|
|
854
|
+
cart_count: cartCount,
|
|
855
|
+
theme_css: opts.theme_css,
|
|
856
|
+
body: body,
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
var ORDER_PAGE =
|
|
861
|
+
"<section class=\"order-page\">\n" +
|
|
862
|
+
" <header class=\"section-head\">\n" +
|
|
863
|
+
" <p class=\"eyebrow\">Order confirmed</p>\n" +
|
|
864
|
+
" <h1 class=\"section-head__title\">Order <code class=\"inline-code\">{{order_id}}</code></h1>\n" +
|
|
865
|
+
" <p class=\"section-head__lede\">Status: <span class=\"pdp__badge pdp__badge--ok\"><span class=\"dot dot--live\" aria-hidden=\"true\"></span> {{status}}</span></p>\n" +
|
|
866
|
+
" </header>\n" +
|
|
867
|
+
" <div class=\"order-page__grid\">\n" +
|
|
868
|
+
" <div class=\"order-page__items\">\n" +
|
|
869
|
+
" <h2 class=\"pdp__variants-title\">Items</h2>\n" +
|
|
870
|
+
" <div class=\"table-scroll\">\n" +
|
|
871
|
+
" <table>\n" +
|
|
872
|
+
" <thead><tr><th>SKU</th><th>Qty</th><th>Unit</th><th>Total</th></tr></thead>\n" +
|
|
873
|
+
" <tbody>{{line_rows}}</tbody>\n" +
|
|
874
|
+
" </table>\n" +
|
|
875
|
+
" </div>\n" +
|
|
876
|
+
" </div>\n" +
|
|
877
|
+
" <aside class=\"order-page__totals\">\n" +
|
|
878
|
+
" <h2 class=\"pdp__variants-title\">Totals</h2>\n" +
|
|
879
|
+
" <dl class=\"totals-list\">\n" +
|
|
880
|
+
" <div><dt>Subtotal</dt><dd>{{subtotal}}</dd></div>\n" +
|
|
881
|
+
" <div><dt>Tax</dt><dd>{{tax}}</dd></div>\n" +
|
|
882
|
+
" <div><dt>Shipping</dt><dd>{{shipping}}</dd></div>\n" +
|
|
883
|
+
" <div class=\"totals-list__grand\"><dt>Total</dt><dd>{{total}}</dd></div>\n" +
|
|
884
|
+
" </dl>\n" +
|
|
885
|
+
" </aside>\n" +
|
|
886
|
+
" </div>\n" +
|
|
887
|
+
"</section>\n";
|
|
888
|
+
|
|
889
|
+
function renderOrder(opts) {
|
|
890
|
+
if (!opts || !opts.order) throw new TypeError("storefront.renderOrder: opts.order required");
|
|
891
|
+
var o = opts.order;
|
|
892
|
+
var lines = o.lines || [];
|
|
893
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
894
|
+
var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
|
|
895
|
+
var rendered = lines.map(function (l) {
|
|
896
|
+
return {
|
|
897
|
+
sku: l.sku,
|
|
898
|
+
qty: String(l.qty),
|
|
899
|
+
unit: pricing.format(l.unit_amount_minor, l.unit_currency),
|
|
900
|
+
total: pricing.format(l.line_total_minor || (l.qty * l.unit_amount_minor), l.unit_currency),
|
|
901
|
+
};
|
|
902
|
+
});
|
|
903
|
+
var subtotal = pricing.format(o.subtotal_minor, o.currency);
|
|
904
|
+
var tax = pricing.format(o.tax_minor, o.currency);
|
|
905
|
+
var shipping = pricing.format(o.shipping_minor, o.currency);
|
|
906
|
+
var total = pricing.format(o.grand_total_minor, o.currency);
|
|
907
|
+
if (opts.theme) {
|
|
908
|
+
return opts.theme.render("order", {
|
|
909
|
+
title: "Order " + o.id,
|
|
910
|
+
shop_name: shopName,
|
|
911
|
+
cart_count: cartCount,
|
|
912
|
+
order_id: o.id,
|
|
913
|
+
status: o.status,
|
|
914
|
+
lines: rendered,
|
|
915
|
+
has_lines: rendered.length > 0,
|
|
916
|
+
subtotal: subtotal,
|
|
917
|
+
tax: tax,
|
|
918
|
+
shipping: shipping,
|
|
919
|
+
total: total,
|
|
920
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
var rows = rendered.map(function (l) {
|
|
924
|
+
return _render(CART_LINE, { sku: l.sku, qty: l.qty, unit: l.unit, total: l.total });
|
|
925
|
+
}).join("");
|
|
926
|
+
if (!rows) rows = "<tr><td colspan=\"4\" class=\"empty\">No items.</td></tr>";
|
|
927
|
+
var body = _render(ORDER_PAGE, {
|
|
928
|
+
order_id: o.id,
|
|
929
|
+
status: o.status,
|
|
930
|
+
line_rows: "RAW_LINES",
|
|
931
|
+
subtotal: subtotal,
|
|
932
|
+
tax: tax,
|
|
933
|
+
shipping: shipping,
|
|
934
|
+
total: total,
|
|
935
|
+
}).replace("RAW_LINES", rows);
|
|
936
|
+
return _wrap({
|
|
937
|
+
title: "Order " + o.id,
|
|
938
|
+
shop_name: shopName,
|
|
939
|
+
cart_count: cartCount,
|
|
940
|
+
theme_css: opts.theme_css,
|
|
941
|
+
body: body,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
var CART_PAGE =
|
|
946
|
+
"<section class=\"cart-page\">\n" +
|
|
947
|
+
" <header class=\"section-head\">\n" +
|
|
948
|
+
" <p class=\"eyebrow\">Your cart</p>\n" +
|
|
949
|
+
" <h1 class=\"section-head__title\">Review your items</h1>\n" +
|
|
950
|
+
" </header>\n" +
|
|
951
|
+
" <div class=\"cart-page__grid\">\n" +
|
|
952
|
+
" <div class=\"cart-page__items\">\n" +
|
|
953
|
+
" <div class=\"table-scroll\">\n" +
|
|
954
|
+
" <table class=\"cart-table\">\n" +
|
|
955
|
+
" <thead><tr><th>SKU</th><th>Quantity</th><th>Unit</th><th>Total</th><th class=\"variant-table__action-h\">Action</th></tr></thead>\n" +
|
|
956
|
+
" <tbody>{{line_rows}}</tbody>\n" +
|
|
957
|
+
" </table>\n" +
|
|
958
|
+
" </div>\n" +
|
|
959
|
+
" </div>\n" +
|
|
960
|
+
" <aside class=\"cart-page__summary\">\n" +
|
|
961
|
+
" <h2 class=\"pdp__variants-title\">Order summary</h2>\n" +
|
|
962
|
+
" <dl class=\"totals-list\">\n" +
|
|
963
|
+
" <div><dt>Subtotal</dt><dd>{{subtotal}}</dd></div>\n" +
|
|
964
|
+
" <div class=\"totals-list__grand\"><dt>Total</dt><dd>{{total}}</dd></div>\n" +
|
|
965
|
+
" </dl>\n" +
|
|
966
|
+
" <a href=\"/checkout\" class=\"btn-primary cart-page__checkout\">Continue to checkout <span aria-hidden=\"true\">→</span></a>\n" +
|
|
967
|
+
" <p class=\"cart-page__note\">Tax and shipping are calculated on the next step. Payment runs through Stripe.</p>\n" +
|
|
968
|
+
" </aside>\n" +
|
|
969
|
+
" </div>\n" +
|
|
970
|
+
"</section>\n";
|
|
971
|
+
|
|
972
|
+
function renderCart(opts) {
|
|
973
|
+
if (!opts) throw new TypeError("storefront.renderCart: opts required");
|
|
974
|
+
var lines = opts.lines || [];
|
|
975
|
+
var totals = opts.totals || { subtotal_minor: 0, grand_total_minor: 0, currency: "USD" };
|
|
976
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
977
|
+
var rendered = lines.map(function (l) {
|
|
978
|
+
return {
|
|
979
|
+
id: l.id,
|
|
980
|
+
sku: l.sku,
|
|
981
|
+
qty: String(l.qty),
|
|
982
|
+
unit: pricing.format(l.unit_amount_minor, l.unit_currency),
|
|
983
|
+
total: pricing.format(l.qty * l.unit_amount_minor, l.unit_currency),
|
|
984
|
+
};
|
|
985
|
+
});
|
|
986
|
+
var subtotal = pricing.format(totals.subtotal_minor, totals.currency);
|
|
987
|
+
var total = pricing.format(totals.grand_total_minor, totals.currency);
|
|
988
|
+
if (opts.theme) {
|
|
989
|
+
return opts.theme.render("cart", {
|
|
990
|
+
title: "Cart",
|
|
991
|
+
shop_name: shopName,
|
|
992
|
+
cart_count: lines.length,
|
|
993
|
+
lines: rendered,
|
|
994
|
+
has_lines: rendered.length > 0,
|
|
995
|
+
subtotal: subtotal,
|
|
996
|
+
total: total,
|
|
997
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
var rows = rendered.map(function (l) {
|
|
1001
|
+
return _render(CART_LINE_EDITABLE, {
|
|
1002
|
+
sku: l.sku, qty: l.qty, unit: l.unit, total: l.total, line_id: l.id,
|
|
1003
|
+
});
|
|
1004
|
+
}).join("");
|
|
1005
|
+
if (!rows) rows = "<tr><td colspan=\"5\" class=\"empty\">Your cart is empty.</td></tr>";
|
|
1006
|
+
var body = _render(CART_PAGE, {
|
|
1007
|
+
line_rows: "RAW_LINES",
|
|
1008
|
+
subtotal: subtotal,
|
|
1009
|
+
total: total,
|
|
1010
|
+
}).replace("RAW_LINES", rows);
|
|
1011
|
+
return _wrap({
|
|
1012
|
+
title: "Cart",
|
|
1013
|
+
shop_name: shopName,
|
|
1014
|
+
cart_count: lines.length,
|
|
1015
|
+
theme_css: opts.theme_css,
|
|
1016
|
+
body: body,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// ---- admin landing (HTML — the rest of /admin/* is JSON API) ----------
|
|
1021
|
+
|
|
1022
|
+
function renderAdminLanding(opts) {
|
|
1023
|
+
opts = opts || {};
|
|
1024
|
+
var body =
|
|
1025
|
+
"<section class=\"admin-landing\">" +
|
|
1026
|
+
"<header class=\"section-head\">" +
|
|
1027
|
+
"<p class=\"eyebrow\">Admin API</p>" +
|
|
1028
|
+
"<h1 class=\"section-head__title\">There's no admin GUI — operators talk to the framework over HTTP.</h1>" +
|
|
1029
|
+
"<p class=\"section-head__lede\">Every admin verb (create / update / archive / refund / restock) is a bearer-token-gated JSON endpoint under <code class=\"inline-code\">/admin/*</code>. Pair it with curl, an API client, or any internal ops console.</p>" +
|
|
1030
|
+
"</header>" +
|
|
1031
|
+
"<div class=\"admin-landing__grid\">" +
|
|
1032
|
+
"<article class=\"admin-card\">" +
|
|
1033
|
+
"<p class=\"admin-card__verb\">POST</p>" +
|
|
1034
|
+
"<h2>Create a product</h2>" +
|
|
1035
|
+
"<pre class=\"admin-card__code\"><code>curl -X POST $SHOP/admin/products \\\n -H \"authorization: Bearer $ADMIN_API_KEY\" \\\n -d '{ \"title\": \"Widget\", \"slug\": \"widget\" }'</code></pre>" +
|
|
1036
|
+
"</article>" +
|
|
1037
|
+
"<article class=\"admin-card\">" +
|
|
1038
|
+
"<p class=\"admin-card__verb\">GET</p>" +
|
|
1039
|
+
"<h2>Search products</h2>" +
|
|
1040
|
+
"<pre class=\"admin-card__code\"><code>curl $SHOP/admin/products/search?q=tee \\\n -H \"authorization: Bearer $ADMIN_API_KEY\"</code></pre>" +
|
|
1041
|
+
"</article>" +
|
|
1042
|
+
"<article class=\"admin-card\">" +
|
|
1043
|
+
"<p class=\"admin-card__verb\">POST</p>" +
|
|
1044
|
+
"<h2>Restock inventory</h2>" +
|
|
1045
|
+
"<pre class=\"admin-card__code\"><code>curl -X POST $SHOP/admin/inventory/OPR-TEE-BLK-L/restock \\\n -H \"authorization: Bearer $ADMIN_API_KEY\" \\\n -d '{ \"qty\": 25 }'</code></pre>" +
|
|
1046
|
+
"</article>" +
|
|
1047
|
+
"<article class=\"admin-card\">" +
|
|
1048
|
+
"<p class=\"admin-card__verb\">POST</p>" +
|
|
1049
|
+
"<h2>Issue a refund</h2>" +
|
|
1050
|
+
"<pre class=\"admin-card__code\"><code>curl -X POST $SHOP/admin/orders/$ID/refund \\\n -H \"authorization: Bearer $ADMIN_API_KEY\" \\\n -d '{ \"amount_minor\": 3200 }'</code></pre>" +
|
|
1051
|
+
"</article>" +
|
|
1052
|
+
"</div>" +
|
|
1053
|
+
"<aside class=\"admin-landing__note\">" +
|
|
1054
|
+
"<p><strong>Why no GUI?</strong> Treating the admin as an API keeps the surface area auditable — every state change lands as a request you can replay, log, and sign. Build the GUI your ops team needs; the framework stays out of the way.</p>" +
|
|
1055
|
+
"<p>Endpoint reference: <a href=\"https://github.com/blamejs/blamejs.shop/blob/main/lib/admin.js\" rel=\"noopener\" class=\"link-arrow\">lib/admin.js on GitHub <span aria-hidden=\"true\">→</span></a></p>" +
|
|
1056
|
+
"</aside>" +
|
|
1057
|
+
"</section>";
|
|
1058
|
+
return _wrap({
|
|
1059
|
+
title: "Admin",
|
|
1060
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1061
|
+
cart_count: opts.cart_count,
|
|
1062
|
+
theme_css: opts.theme_css,
|
|
1063
|
+
body: body,
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
// ---- newsletter thank-you ---------------------------------------------
|
|
1068
|
+
|
|
1069
|
+
function renderNewsletterThanks(opts) {
|
|
1070
|
+
opts = opts || {};
|
|
1071
|
+
// Two flavors: `new` (we just enrolled this address) and `dedup`
|
|
1072
|
+
// (the address was already on the list). Both render the same
|
|
1073
|
+
// shell with copy that tells the visitor what happened, so a
|
|
1074
|
+
// double-submit doesn't read as a quiet failure.
|
|
1075
|
+
var heading, lede;
|
|
1076
|
+
if (opts.status === "dedup") {
|
|
1077
|
+
heading = "You're already on the list.";
|
|
1078
|
+
lede = "We had this address from a previous signup. Nothing changed — you'll get release notes the day they ship.";
|
|
1079
|
+
} else {
|
|
1080
|
+
heading = "You're on the list.";
|
|
1081
|
+
lede = "We'll email you the day there's a new framework release, a security advisory, or a primitive worth knowing about. Nothing else.";
|
|
1082
|
+
}
|
|
1083
|
+
var body =
|
|
1084
|
+
"<section class=\"newsletter-thanks\">" +
|
|
1085
|
+
"<div class=\"newsletter-thanks__card\">" +
|
|
1086
|
+
"<p class=\"eyebrow\">Newsletter</p>" +
|
|
1087
|
+
"<h1 class=\"newsletter-thanks__title\">" + heading + "</h1>" +
|
|
1088
|
+
"<p class=\"newsletter-thanks__lede\">" + lede + "</p>" +
|
|
1089
|
+
"<div class=\"newsletter-thanks__cta\">" +
|
|
1090
|
+
"<a href=\"/\" class=\"btn-primary\">Back to the shop <span aria-hidden=\"true\">→</span></a>" +
|
|
1091
|
+
"<a href=\"https://github.com/blamejs/blamejs.shop\" class=\"btn-ghost\" rel=\"noopener\">Star on GitHub</a>" +
|
|
1092
|
+
"</div>" +
|
|
1093
|
+
"</div>" +
|
|
1094
|
+
"</section>";
|
|
1095
|
+
return _wrap({
|
|
1096
|
+
title: "Newsletter",
|
|
1097
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1098
|
+
cart_count: opts.cart_count,
|
|
1099
|
+
theme_css: opts.theme_css,
|
|
1100
|
+
body: body,
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
function renderNewsletterError(opts) {
|
|
1105
|
+
opts = opts || {};
|
|
1106
|
+
var body =
|
|
1107
|
+
"<section class=\"newsletter-thanks\">" +
|
|
1108
|
+
"<div class=\"newsletter-thanks__card\">" +
|
|
1109
|
+
"<p class=\"eyebrow\">Newsletter</p>" +
|
|
1110
|
+
"<h1 class=\"newsletter-thanks__title\">Couldn't enroll that address.</h1>" +
|
|
1111
|
+
"<p class=\"newsletter-thanks__lede\">" + (opts.message || "Check the address and try again — only RFC-shape email addresses are accepted.") + "</p>" +
|
|
1112
|
+
"<div class=\"newsletter-thanks__cta\">" +
|
|
1113
|
+
"<a href=\"/\" class=\"btn-primary\">Back to the shop <span aria-hidden=\"true\">→</span></a>" +
|
|
1114
|
+
"</div>" +
|
|
1115
|
+
"</div>" +
|
|
1116
|
+
"</section>";
|
|
1117
|
+
return _wrap({
|
|
1118
|
+
title: "Newsletter",
|
|
1119
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1120
|
+
cart_count: opts.cart_count,
|
|
1121
|
+
theme_css: opts.theme_css,
|
|
1122
|
+
body: body,
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// ---- 404 ---------------------------------------------------------------
|
|
1127
|
+
|
|
1128
|
+
function renderNotFound(opts) {
|
|
1129
|
+
opts = opts || {};
|
|
1130
|
+
var shopName = opts.shop_name || "blamejs.shop";
|
|
1131
|
+
var cartCount = opts.cart_count == null ? 0 : opts.cart_count;
|
|
1132
|
+
if (opts.theme) {
|
|
1133
|
+
return opts.theme.render("notfound", {
|
|
1134
|
+
title: "Not found",
|
|
1135
|
+
shop_name: shopName,
|
|
1136
|
+
cart_count: cartCount,
|
|
1137
|
+
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
var body =
|
|
1141
|
+
"<section class=\"not-found\">" +
|
|
1142
|
+
"<div class=\"not-found__inner\">" +
|
|
1143
|
+
"<p class=\"not-found__code\">404</p>" +
|
|
1144
|
+
"<h1 class=\"not-found__title\">That page slipped the catalog.</h1>" +
|
|
1145
|
+
"<p class=\"not-found__lede\">The URL didn't match any route on this storefront. The product might have moved, the slug might have changed, or the link might have been mistyped.</p>" +
|
|
1146
|
+
"<div class=\"not-found__cta\">" +
|
|
1147
|
+
"<a href=\"/\" class=\"btn-primary\">Back to the shop <span aria-hidden=\"true\">→</span></a>" +
|
|
1148
|
+
"<a href=\"/cart\" class=\"btn-ghost\">View your cart</a>" +
|
|
1149
|
+
"</div>" +
|
|
1150
|
+
"</div>" +
|
|
1151
|
+
"</section>";
|
|
1152
|
+
return _wrap({
|
|
1153
|
+
title: "Not found",
|
|
1154
|
+
shop_name: shopName,
|
|
1155
|
+
cart_count: cartCount,
|
|
1156
|
+
theme_css: opts.theme_css,
|
|
1157
|
+
body: body,
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// ---- route mount -------------------------------------------------------
|
|
1162
|
+
//
|
|
1163
|
+
// Caller (server.js) hands us a b.router instance + the data deps.
|
|
1164
|
+
// We mount the read-only HTML routes. POST routes for cart mutation
|
|
1165
|
+
// land alongside Stripe Elements wiring in the next patch.
|
|
1166
|
+
|
|
1167
|
+
// Session-id cookie binding — carries the cart's session_id across
|
|
1168
|
+
// requests. Plain HttpOnly + Secure + SameSite=Lax is sufficient here
|
|
1169
|
+
// because the value (a UUID) is unguessable and grants ZERO authority
|
|
1170
|
+
// — it's a routing key, not an authentication token. The cart itself
|
|
1171
|
+
// transitions to `customer_id` on login via cart.setCustomer.
|
|
1172
|
+
var SESSION_COOKIE_NAME = "shop_sid";
|
|
1173
|
+
var SESSION_COOKIE_MAX = 60 * 60 * 24 * 30; // 30 days
|
|
1174
|
+
|
|
1175
|
+
// Authenticated-customer cookie — carries an opaque sealed envelope
|
|
1176
|
+
// `{ customer_id, exp }`, AEAD-encrypted via b.vault.seal so the
|
|
1177
|
+
// cookie itself has no internal structure visible to a tampering
|
|
1178
|
+
// network attacker. Cookie name `shop_auth`; HttpOnly + Secure +
|
|
1179
|
+
// SameSite=Lax + Path=/. The vault key is the deployment's KEK; a
|
|
1180
|
+
// rotated vault invalidates every outstanding auth cookie (operator-
|
|
1181
|
+
// initiated logout-everywhere).
|
|
1182
|
+
var AUTH_COOKIE_NAME = "shop_auth";
|
|
1183
|
+
var AUTH_COOKIE_MAX = 60 * 60 * 24 * 14; // 14 days
|
|
1184
|
+
var AUTH_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
1185
|
+
|
|
1186
|
+
// WebAuthn ceremony state cookie — short-lived envelope holding the
|
|
1187
|
+
// random challenge + the ceremony-scoped metadata so register-finish /
|
|
1188
|
+
// login-finish can verify the same challenge the browser was sent.
|
|
1189
|
+
// Path-scoped to /account so it never leaks to other routes.
|
|
1190
|
+
var CHALLENGE_COOKIE_NAME = "shop_auth_chal";
|
|
1191
|
+
var CHALLENGE_COOKIE_MAX = 5 * 60; // 5 minutes
|
|
1192
|
+
|
|
1193
|
+
function _readSidCookie(req) {
|
|
1194
|
+
var raw = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
|
|
1195
|
+
if (!raw) return null;
|
|
1196
|
+
var parts = raw.split(";");
|
|
1197
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
1198
|
+
var p = parts[i].trim();
|
|
1199
|
+
var eq = p.indexOf("=");
|
|
1200
|
+
if (eq <= 0) continue;
|
|
1201
|
+
if (p.slice(0, eq) === SESSION_COOKIE_NAME) {
|
|
1202
|
+
var v = p.slice(eq + 1);
|
|
1203
|
+
// Cookie values are URL-encoded.
|
|
1204
|
+
try { return decodeURIComponent(v); } catch (_e) { return null; }
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
function _setSidCookie(res, sid) {
|
|
1211
|
+
var attrs = "Max-Age=" + SESSION_COOKIE_MAX + "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
1212
|
+
var header = SESSION_COOKIE_NAME + "=" + encodeURIComponent(sid) + "; " + attrs;
|
|
1213
|
+
if (typeof res.appendHeader === "function") res.appendHeader("Set-Cookie", header);
|
|
1214
|
+
else if (typeof res.setHeader === "function") res.setHeader("Set-Cookie", header);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function _readCookie(req, name) {
|
|
1218
|
+
var raw = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
|
|
1219
|
+
if (!raw) return null;
|
|
1220
|
+
var parts = raw.split(";");
|
|
1221
|
+
for (var i = 0; i < parts.length; i += 1) {
|
|
1222
|
+
var p = parts[i].trim();
|
|
1223
|
+
var eq = p.indexOf("=");
|
|
1224
|
+
if (eq <= 0) continue;
|
|
1225
|
+
if (p.slice(0, eq) === name) {
|
|
1226
|
+
try { return decodeURIComponent(p.slice(eq + 1)); }
|
|
1227
|
+
catch (_e) { return null; }
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function _appendCookie(res, header) {
|
|
1234
|
+
if (typeof res.appendHeader === "function") res.appendHeader("Set-Cookie", header);
|
|
1235
|
+
else if (typeof res.setHeader === "function") res.setHeader("Set-Cookie", header);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function _setAuthCookie(res, sealed) {
|
|
1239
|
+
var attrs = "Max-Age=" + AUTH_COOKIE_MAX + "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
1240
|
+
_appendCookie(res, AUTH_COOKIE_NAME + "=" + encodeURIComponent(sealed) + "; " + attrs);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function _clearAuthCookie(res) {
|
|
1244
|
+
_appendCookie(res,
|
|
1245
|
+
AUTH_COOKIE_NAME + "=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Lax");
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function _setChallengeCookie(res, sealed) {
|
|
1249
|
+
var attrs = "Max-Age=" + CHALLENGE_COOKIE_MAX + "; Path=/account; HttpOnly; Secure; SameSite=Lax";
|
|
1250
|
+
_appendCookie(res, CHALLENGE_COOKIE_NAME + "=" + encodeURIComponent(sealed) + "; " + attrs);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function _clearChallengeCookie(res) {
|
|
1254
|
+
_appendCookie(res,
|
|
1255
|
+
CHALLENGE_COOKIE_NAME + "=; Max-Age=0; Path=/account; HttpOnly; Secure; SameSite=Lax");
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// ---- account-page renderers --------------------------------------------
|
|
1259
|
+
|
|
1260
|
+
var ACCOUNT_LOGIN_PAGE =
|
|
1261
|
+
"<section class=\"auth-page\">\n" +
|
|
1262
|
+
" <div class=\"auth-card\">\n" +
|
|
1263
|
+
" <p class=\"eyebrow\">Sign in</p>\n" +
|
|
1264
|
+
" <h1 class=\"auth-card__title\">Welcome back</h1>\n" +
|
|
1265
|
+
" <p class=\"auth-card__lede\">Enter your email and authenticate with your passkey. No password to type, no recovery email to click.</p>\n" +
|
|
1266
|
+
" <form id=\"login-form\" method=\"post\" class=\"form-stack auth-form\">\n" +
|
|
1267
|
+
" <div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Email</span><input type=\"email\" name=\"email\" id=\"email\" required autocomplete=\"email\" autofocus></label></div>\n" +
|
|
1268
|
+
" <div class=\"form-actions\"><button type=\"submit\" class=\"btn-primary auth-form__submit\">Sign in with passkey</button></div>\n" +
|
|
1269
|
+
" <p id=\"login-message\" class=\"auth-form__message\"></p>\n" +
|
|
1270
|
+
" </form>\n" +
|
|
1271
|
+
" <p class=\"auth-card__alt\">New here? <a href=\"/account/register\">Create an account →</a></p>\n" +
|
|
1272
|
+
" </div>\n" +
|
|
1273
|
+
" <script>\n" +
|
|
1274
|
+
" (function () {\n" +
|
|
1275
|
+
" function _b64uToBuf(s){ s = s.replace(/-/g,'+').replace(/_/g,'/'); while(s.length%4)s+='='; var raw=atob(s); var arr=new Uint8Array(raw.length); for(var i=0;i<raw.length;i++)arr[i]=raw.charCodeAt(i); return arr.buffer; }\n" +
|
|
1276
|
+
" function _bufToB64u(buf){ var b=new Uint8Array(buf), s=''; for(var i=0;i<b.length;i++)s+=String.fromCharCode(b[i]); return btoa(s).replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,''); }\n" +
|
|
1277
|
+
" var form=document.getElementById('login-form');\n" +
|
|
1278
|
+
" var msg=document.getElementById('login-message');\n" +
|
|
1279
|
+
" form.addEventListener('submit', async function(ev){\n" +
|
|
1280
|
+
" ev.preventDefault(); msg.textContent='Requesting challenge...';\n" +
|
|
1281
|
+
" try {\n" +
|
|
1282
|
+
" var beginR = await fetch('/account/passkey/login-begin', { method:'POST', credentials:'same-origin', headers:{'content-type':'application/json'}, body: JSON.stringify({ email: document.getElementById('email').value }) });\n" +
|
|
1283
|
+
" if (!beginR.ok) { msg.textContent = 'Sign-in unavailable.'; return; }\n" +
|
|
1284
|
+
" var options = await beginR.json();\n" +
|
|
1285
|
+
" options.challenge = _b64uToBuf(options.challenge);\n" +
|
|
1286
|
+
" if (options.allowCredentials) options.allowCredentials = options.allowCredentials.map(function(c){ return Object.assign({}, c, { id: _b64uToBuf(c.id) }); });\n" +
|
|
1287
|
+
" var assertion = await navigator.credentials.get({ publicKey: options });\n" +
|
|
1288
|
+
" var payload = { id: assertion.id, rawId: _bufToB64u(assertion.rawId), type: assertion.type, response: { authenticatorData: _bufToB64u(assertion.response.authenticatorData), clientDataJSON: _bufToB64u(assertion.response.clientDataJSON), signature: _bufToB64u(assertion.response.signature), userHandle: assertion.response.userHandle ? _bufToB64u(assertion.response.userHandle) : null } };\n" +
|
|
1289
|
+
" var finishR = await fetch('/account/passkey/login-finish', { method:'POST', credentials:'same-origin', headers:{'content-type':'application/json'}, body: JSON.stringify(payload) });\n" +
|
|
1290
|
+
" if (finishR.ok) { window.location.href = '/account'; } else { msg.textContent = 'Sign-in failed.'; }\n" +
|
|
1291
|
+
" } catch (e) { msg.textContent = (e && e.message) || 'Sign-in error.'; }\n" +
|
|
1292
|
+
" });\n" +
|
|
1293
|
+
" })();\n" +
|
|
1294
|
+
" </script>\n" +
|
|
1295
|
+
"</section>\n";
|
|
1296
|
+
|
|
1297
|
+
function renderAccountLogin(opts) {
|
|
1298
|
+
opts = opts || {};
|
|
1299
|
+
return _wrap({
|
|
1300
|
+
title: "Sign in",
|
|
1301
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1302
|
+
cart_count: opts.cart_count,
|
|
1303
|
+
theme_css: opts.theme_css,
|
|
1304
|
+
body: ACCOUNT_LOGIN_PAGE,
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
var ACCOUNT_REGISTER_PAGE =
|
|
1309
|
+
"<section class=\"auth-page\">\n" +
|
|
1310
|
+
" <div class=\"auth-card\">\n" +
|
|
1311
|
+
" <p class=\"eyebrow\">Create an account</p>\n" +
|
|
1312
|
+
" <h1 class=\"auth-card__title\">Enroll a passkey</h1>\n" +
|
|
1313
|
+
" <p class=\"auth-card__lede\">Accounts use a passkey on your device — no password to remember, no shared secret to phish.</p>\n" +
|
|
1314
|
+
" <form id=\"reg-form\" method=\"post\" class=\"form-stack auth-form\">\n" +
|
|
1315
|
+
" <div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Email</span><input type=\"email\" name=\"email\" id=\"email\" required autocomplete=\"email\" autofocus></label></div>\n" +
|
|
1316
|
+
" <div class=\"form-row\"><label class=\"form-field\"><span class=\"form-field__label\">Display name</span><input type=\"text\" name=\"display_name\" id=\"display_name\" maxlength=\"128\" required autocomplete=\"name\"></label></div>\n" +
|
|
1317
|
+
" <div class=\"form-actions\"><button type=\"submit\" class=\"btn-primary auth-form__submit\">Create account & enroll passkey</button></div>\n" +
|
|
1318
|
+
" <p id=\"reg-message\" class=\"auth-form__message\"></p>\n" +
|
|
1319
|
+
" </form>\n" +
|
|
1320
|
+
" <p class=\"auth-card__alt\">Already have one? <a href=\"/account/login\">Sign in →</a></p>\n" +
|
|
1321
|
+
" </div>\n" +
|
|
1322
|
+
" <script>\n" +
|
|
1323
|
+
" (function () {\n" +
|
|
1324
|
+
" function _b64uToBuf(s){ s = s.replace(/-/g,'+').replace(/_/g,'/'); while(s.length%4)s+='='; var raw=atob(s); var arr=new Uint8Array(raw.length); for(var i=0;i<raw.length;i++)arr[i]=raw.charCodeAt(i); return arr.buffer; }\n" +
|
|
1325
|
+
" function _bufToB64u(buf){ var b=new Uint8Array(buf), s=''; for(var i=0;i<b.length;i++)s+=String.fromCharCode(b[i]); return btoa(s).replace(/\\+/g,'-').replace(/\\//g,'_').replace(/=+$/,''); }\n" +
|
|
1326
|
+
" var form=document.getElementById('reg-form');\n" +
|
|
1327
|
+
" var msg=document.getElementById('reg-message');\n" +
|
|
1328
|
+
" form.addEventListener('submit', async function(ev){\n" +
|
|
1329
|
+
" ev.preventDefault(); msg.textContent='Creating account...';\n" +
|
|
1330
|
+
" try {\n" +
|
|
1331
|
+
" var beginR = await fetch('/account/passkey/register-begin', { method:'POST', credentials:'same-origin', headers:{'content-type':'application/json'}, body: JSON.stringify({ email: document.getElementById('email').value, display_name: document.getElementById('display_name').value }) });\n" +
|
|
1332
|
+
" if (!beginR.ok) { msg.textContent = (await beginR.text()) || 'Registration unavailable.'; return; }\n" +
|
|
1333
|
+
" var options = await beginR.json();\n" +
|
|
1334
|
+
" options.challenge = _b64uToBuf(options.challenge);\n" +
|
|
1335
|
+
" options.user.id = _b64uToBuf(options.user.id);\n" +
|
|
1336
|
+
" if (options.excludeCredentials) options.excludeCredentials = options.excludeCredentials.map(function(c){ return Object.assign({}, c, { id: _b64uToBuf(c.id) }); });\n" +
|
|
1337
|
+
" var att = await navigator.credentials.create({ publicKey: options });\n" +
|
|
1338
|
+
" var payload = { id: att.id, rawId: _bufToB64u(att.rawId), type: att.type, response: { attestationObject: _bufToB64u(att.response.attestationObject), clientDataJSON: _bufToB64u(att.response.clientDataJSON), transports: att.response.getTransports ? att.response.getTransports() : [] } };\n" +
|
|
1339
|
+
" var finishR = await fetch('/account/passkey/register-finish', { method:'POST', credentials:'same-origin', headers:{'content-type':'application/json'}, body: JSON.stringify(payload) });\n" +
|
|
1340
|
+
" if (finishR.ok) { window.location.href = '/account'; } else { msg.textContent = (await finishR.text()) || 'Enrollment failed.'; }\n" +
|
|
1341
|
+
" } catch (e) { msg.textContent = (e && e.message) || 'Registration error.'; }\n" +
|
|
1342
|
+
" });\n" +
|
|
1343
|
+
" })();\n" +
|
|
1344
|
+
" </script>\n" +
|
|
1345
|
+
"</section>\n";
|
|
1346
|
+
|
|
1347
|
+
function renderAccountRegister(opts) {
|
|
1348
|
+
opts = opts || {};
|
|
1349
|
+
return _wrap({
|
|
1350
|
+
title: "Create account",
|
|
1351
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1352
|
+
cart_count: opts.cart_count,
|
|
1353
|
+
theme_css: opts.theme_css,
|
|
1354
|
+
body: ACCOUNT_REGISTER_PAGE,
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
var ACCOUNT_DASH_PAGE =
|
|
1359
|
+
"<section class=\"account-dash\">\n" +
|
|
1360
|
+
" <header class=\"account-dash__head\">\n" +
|
|
1361
|
+
" <div>\n" +
|
|
1362
|
+
" <p class=\"eyebrow\">Account</p>\n" +
|
|
1363
|
+
" <h1 class=\"section-head__title\">Hi, {{display_name}}</h1>\n" +
|
|
1364
|
+
" <p class=\"section-head__lede\">Your recent orders + account controls.</p>\n" +
|
|
1365
|
+
" </div>\n" +
|
|
1366
|
+
" <form method=\"post\" action=\"/account/logout\"><button type=\"submit\" class=\"btn-ghost\">Sign out</button></form>\n" +
|
|
1367
|
+
" </header>\n" +
|
|
1368
|
+
" <div class=\"account-dash__body\">\n" +
|
|
1369
|
+
" <h2 class=\"pdp__variants-title\">Recent orders</h2>\n" +
|
|
1370
|
+
" <div class=\"table-scroll\">\n" +
|
|
1371
|
+
" <table>\n" +
|
|
1372
|
+
" <thead><tr><th>Order</th><th>Status</th><th>Total</th></tr></thead>\n" +
|
|
1373
|
+
" <tbody>{{order_rows}}</tbody>\n" +
|
|
1374
|
+
" </table>\n" +
|
|
1375
|
+
" </div>\n" +
|
|
1376
|
+
" </div>\n" +
|
|
1377
|
+
"</section>\n";
|
|
1378
|
+
|
|
1379
|
+
var ACCOUNT_DASH_ORDER_ROW =
|
|
1380
|
+
"<tr><td><a href=\"/orders/{{order_id}}\">{{order_id_short}}</a></td><td>{{status}}</td><td class=\"price\">{{total}}</td></tr>\n";
|
|
1381
|
+
|
|
1382
|
+
function renderAccount(opts) {
|
|
1383
|
+
if (!opts || !opts.customer) throw new TypeError("storefront.renderAccount: opts.customer required");
|
|
1384
|
+
var rows = (opts.orders || []).map(function (o) {
|
|
1385
|
+
return _render(ACCOUNT_DASH_ORDER_ROW, {
|
|
1386
|
+
order_id: o.id,
|
|
1387
|
+
order_id_short: o.id.slice(0, 8),
|
|
1388
|
+
status: o.status,
|
|
1389
|
+
total: pricing.format(o.grand_total_minor, o.currency),
|
|
1390
|
+
});
|
|
1391
|
+
}).join("");
|
|
1392
|
+
if (!rows) rows = "<tr><td colspan=\"3\" class=\"empty\">No orders yet.</td></tr>";
|
|
1393
|
+
var body = _render(ACCOUNT_DASH_PAGE, {
|
|
1394
|
+
display_name: opts.customer.display_name,
|
|
1395
|
+
order_rows: "RAW_ORDER_ROWS",
|
|
1396
|
+
}).replace("RAW_ORDER_ROWS", rows);
|
|
1397
|
+
return _wrap({
|
|
1398
|
+
title: "Account",
|
|
1399
|
+
shop_name: opts.shop_name || "blamejs.shop",
|
|
1400
|
+
cart_count: opts.cart_count,
|
|
1401
|
+
theme_css: opts.theme_css,
|
|
1402
|
+
body: body,
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function mount(router, deps) {
|
|
1407
|
+
if (!router || typeof router.get !== "function") throw new TypeError("storefront.mount: router with .get() required");
|
|
1408
|
+
if (!deps || !deps.catalog || !deps.cart) throw new TypeError("storefront.mount: deps.catalog + deps.cart required");
|
|
1409
|
+
var shopName = (deps.config && deps.config.shop_name) || "blamejs.shop";
|
|
1410
|
+
// Optional theme — when supplied, every renderer below dispatches
|
|
1411
|
+
// to file-backed templates under <themesDir>/<name>/. When absent,
|
|
1412
|
+
// the inline-string templates above stay in force (operators on
|
|
1413
|
+
// older deploys keep their current look without a migration step).
|
|
1414
|
+
var theme = deps.theme || null;
|
|
1415
|
+
|
|
1416
|
+
function _send(res, status, html) {
|
|
1417
|
+
res.status(status);
|
|
1418
|
+
res.setHeader && res.setHeader("content-type", "text/html; charset=utf-8");
|
|
1419
|
+
res.end ? res.end(html) : res.send(html);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Cart-count read shared across every handler that wraps a page in
|
|
1423
|
+
// the layout — the header's cart pill renders the count. Returns 0
|
|
1424
|
+
// for visitors with no session cookie. Defined at the top of mount
|
|
1425
|
+
// so the /admin landing + the onNotFound 404 handler (mounted
|
|
1426
|
+
// outside the `if (deps.customers)` block below) can reach it.
|
|
1427
|
+
async function _cartCountForReq(req) {
|
|
1428
|
+
var sid = _readCookie(req, SESSION_COOKIE_NAME);
|
|
1429
|
+
if (!sid) return 0;
|
|
1430
|
+
var c = await deps.cart.bySession(sid);
|
|
1431
|
+
if (!c) return 0;
|
|
1432
|
+
var lines = await deps.cart.listLines(c.id);
|
|
1433
|
+
return lines.length;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// Resolve the cart for this request — read session_id from the
|
|
1437
|
+
// sealed cookie, create one (and the cart) if absent. Returns
|
|
1438
|
+
// the cart row OR null when the cart was just created (caller can
|
|
1439
|
+
// use { sid, cart: null } to skip lookup).
|
|
1440
|
+
async function _getOrCreateCart(req, res, currency) {
|
|
1441
|
+
var sid = _readSidCookie(req);
|
|
1442
|
+
if (!sid) {
|
|
1443
|
+
sid = _b().uuid.v7();
|
|
1444
|
+
_setSidCookie(res, sid);
|
|
1445
|
+
}
|
|
1446
|
+
var existing = await deps.cart.bySession(sid);
|
|
1447
|
+
if (existing) return { sid: sid, cart: existing };
|
|
1448
|
+
var created = await deps.cart.create(sid, { currency: currency || "USD" });
|
|
1449
|
+
return { sid: sid, cart: created };
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
router.get("/", async function (_req, res) {
|
|
1453
|
+
var page = await deps.catalog.products.list({ status: "active", limit: 24 });
|
|
1454
|
+
// Best-effort "starting price" + "hero media" lookup. Each
|
|
1455
|
+
// product on the home grid carries its first variant's USD
|
|
1456
|
+
// price (when one exists) and its first attached media row
|
|
1457
|
+
// (when one exists). Both are best-effort — products without
|
|
1458
|
+
// a price render `—`; products without media render the
|
|
1459
|
+
// text-only PRODUCT_CARD fallback.
|
|
1460
|
+
var products = [];
|
|
1461
|
+
for (var i = 0; i < page.rows.length; i += 1) {
|
|
1462
|
+
var p = page.rows[i];
|
|
1463
|
+
var variants = await deps.catalog.variants.listForProduct(p.id);
|
|
1464
|
+
var startingPrice = null;
|
|
1465
|
+
if (variants.length) {
|
|
1466
|
+
var price = await deps.catalog.prices.current(variants[0].id, "USD");
|
|
1467
|
+
if (price) startingPrice = price;
|
|
1468
|
+
}
|
|
1469
|
+
var media = await deps.catalog.media.listForProduct(p.id);
|
|
1470
|
+
var heroMedia = media.length ? media[0] : null;
|
|
1471
|
+
products.push(Object.assign({}, p, {
|
|
1472
|
+
starting_price_minor: startingPrice ? startingPrice.amount_minor : null,
|
|
1473
|
+
starting_price_currency: startingPrice ? startingPrice.currency : "USD",
|
|
1474
|
+
hero_media: heroMedia,
|
|
1475
|
+
}));
|
|
1476
|
+
}
|
|
1477
|
+
var html = renderHome({ products: products, shop_name: shopName, theme: theme });
|
|
1478
|
+
_send(res, 200, html);
|
|
1479
|
+
});
|
|
1480
|
+
|
|
1481
|
+
router.get("/search", async function (req, res) {
|
|
1482
|
+
var url = req.url ? new URL(req.url, "http://localhost") : null;
|
|
1483
|
+
var qRaw = url && url.searchParams.get("q");
|
|
1484
|
+
var q = typeof qRaw === "string" ? qRaw : "";
|
|
1485
|
+
// Cap at the validator's max length before handing to the
|
|
1486
|
+
// primitive — defends against a 10 MiB `?q=...` mass that would
|
|
1487
|
+
// otherwise round-trip through the LIKE escape function.
|
|
1488
|
+
if (q.length > 200) q = q.slice(0, 200);
|
|
1489
|
+
var products = [];
|
|
1490
|
+
if (q.trim().length > 0) {
|
|
1491
|
+
var page = await deps.catalog.products.search({ q: q, status: "active", limit: 24 });
|
|
1492
|
+
for (var i = 0; i < page.rows.length; i += 1) {
|
|
1493
|
+
var p = page.rows[i];
|
|
1494
|
+
var variants = await deps.catalog.variants.listForProduct(p.id);
|
|
1495
|
+
var startingPrice = null;
|
|
1496
|
+
if (variants.length) {
|
|
1497
|
+
var price = await deps.catalog.prices.current(variants[0].id, "USD");
|
|
1498
|
+
if (price) startingPrice = price;
|
|
1499
|
+
}
|
|
1500
|
+
var media = await deps.catalog.media.listForProduct(p.id);
|
|
1501
|
+
var heroMedia = media.length ? media[0] : null;
|
|
1502
|
+
products.push(Object.assign({}, p, {
|
|
1503
|
+
starting_price_minor: startingPrice ? startingPrice.amount_minor : null,
|
|
1504
|
+
starting_price_currency: startingPrice ? startingPrice.currency : "USD",
|
|
1505
|
+
hero_media: heroMedia,
|
|
1506
|
+
}));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
var sid = _readSidCookie(req);
|
|
1510
|
+
var cartCount = 0;
|
|
1511
|
+
if (sid) {
|
|
1512
|
+
var c = await deps.cart.bySession(sid);
|
|
1513
|
+
if (c) {
|
|
1514
|
+
var lines = await deps.cart.listLines(c.id);
|
|
1515
|
+
cartCount = lines.length;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
_send(res, 200, renderSearch({
|
|
1519
|
+
q: q,
|
|
1520
|
+
products: products,
|
|
1521
|
+
shop_name: shopName,
|
|
1522
|
+
cart_count: cartCount,
|
|
1523
|
+
}));
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
router.get("/products/:slug", async function (req, res) {
|
|
1527
|
+
var slug = req.params && req.params.slug;
|
|
1528
|
+
if (!slug) return _send(res, 400, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1529
|
+
var product = await deps.catalog.products.bySlug(slug);
|
|
1530
|
+
if (!product) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1531
|
+
var variants = await deps.catalog.variants.listForProduct(product.id);
|
|
1532
|
+
var prices = {};
|
|
1533
|
+
for (var i = 0; i < variants.length; i += 1) {
|
|
1534
|
+
var p = await deps.catalog.prices.current(variants[i].id, "USD");
|
|
1535
|
+
if (p) prices[variants[i].id] = p;
|
|
1536
|
+
}
|
|
1537
|
+
// Media — first row drives the hero image, the next three feed
|
|
1538
|
+
// the thumbnail strip. `listForProduct` is product-level only;
|
|
1539
|
+
// variant-level media (`listForVariant`) would feed a swap-on-
|
|
1540
|
+
// variant-select interaction we don't ship yet.
|
|
1541
|
+
var media = await deps.catalog.media.listForProduct(product.id);
|
|
1542
|
+
// Render cart count from the current session's cart, if any.
|
|
1543
|
+
var sid = _readSidCookie(req);
|
|
1544
|
+
var cartCount = 0;
|
|
1545
|
+
if (sid) {
|
|
1546
|
+
var c = await deps.cart.bySession(sid);
|
|
1547
|
+
if (c) {
|
|
1548
|
+
var lines = await deps.cart.listLines(c.id);
|
|
1549
|
+
cartCount = lines.length;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
var html = renderProduct({
|
|
1553
|
+
product: product,
|
|
1554
|
+
variants: variants,
|
|
1555
|
+
prices: prices,
|
|
1556
|
+
media: media,
|
|
1557
|
+
shop_name: shopName,
|
|
1558
|
+
cart_count: cartCount,
|
|
1559
|
+
theme: theme,
|
|
1560
|
+
});
|
|
1561
|
+
_send(res, 200, html);
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
router.get("/cart", async function (req, res) {
|
|
1565
|
+
var sid = _readSidCookie(req);
|
|
1566
|
+
if (!sid) {
|
|
1567
|
+
return _send(res, 200, renderCart({
|
|
1568
|
+
lines: [], totals: { subtotal_minor: 0, grand_total_minor: 0, currency: "USD" },
|
|
1569
|
+
shop_name: shopName, theme: theme,
|
|
1570
|
+
}));
|
|
1571
|
+
}
|
|
1572
|
+
var c = await deps.cart.bySession(sid);
|
|
1573
|
+
if (!c) {
|
|
1574
|
+
return _send(res, 200, renderCart({
|
|
1575
|
+
lines: [], totals: { subtotal_minor: 0, grand_total_minor: 0, currency: "USD" },
|
|
1576
|
+
shop_name: shopName, theme: theme,
|
|
1577
|
+
}));
|
|
1578
|
+
}
|
|
1579
|
+
var lines = await deps.cart.listLines(c.id);
|
|
1580
|
+
var totals = pricing.totals(c, lines, {});
|
|
1581
|
+
_send(res, 200, renderCart({ lines: lines, totals: totals, shop_name: shopName, theme: theme }));
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
// ---- checkout flow -------------------------------------------------
|
|
1585
|
+
//
|
|
1586
|
+
// GET /checkout — renders the shipping form
|
|
1587
|
+
// POST /checkout — calls checkout.confirm; redirects to /pay/:order_id
|
|
1588
|
+
// GET /pay/:order_id — Stripe Elements payment page
|
|
1589
|
+
// GET /orders/:order_id — order confirmation (post-purchase landing)
|
|
1590
|
+
//
|
|
1591
|
+
// The checkout / payment / order deps are optional in mount(); the
|
|
1592
|
+
// routes only register when supplied. This lets the framework boot
|
|
1593
|
+
// in pure-storefront mode (catalog + cart only) for stores that
|
|
1594
|
+
// are still configuring payment.
|
|
1595
|
+
if (deps.checkout && deps.order) {
|
|
1596
|
+
router.get("/checkout", async function (req, res) {
|
|
1597
|
+
var sid = _readSidCookie(req);
|
|
1598
|
+
if (!sid) return _send(res, 303, "<a href=\"/cart\">Cart is empty</a>"), res.setHeader && res.setHeader("location", "/cart");
|
|
1599
|
+
var c = await deps.cart.bySession(sid);
|
|
1600
|
+
if (!c) {
|
|
1601
|
+
res.status(303); res.setHeader && res.setHeader("location", "/cart");
|
|
1602
|
+
return res.end ? res.end() : res.send("");
|
|
1603
|
+
}
|
|
1604
|
+
var lines = await deps.cart.listLines(c.id);
|
|
1605
|
+
if (!lines.length) {
|
|
1606
|
+
res.status(303); res.setHeader && res.setHeader("location", "/cart");
|
|
1607
|
+
return res.end ? res.end() : res.send("");
|
|
1608
|
+
}
|
|
1609
|
+
var totals = pricing.totals(c, lines, {});
|
|
1610
|
+
_send(res, 200, renderCheckoutForm({ lines: lines, totals: totals, shop_name: shopName, theme: theme }));
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
router.post("/checkout", async function (req, res) {
|
|
1614
|
+
var body = req.body || {};
|
|
1615
|
+
var sid = _readSidCookie(req);
|
|
1616
|
+
if (!sid) {
|
|
1617
|
+
res.status(400); return res.end ? res.end("No session") : res.send("No session");
|
|
1618
|
+
}
|
|
1619
|
+
var c = await deps.cart.bySession(sid);
|
|
1620
|
+
if (!c) {
|
|
1621
|
+
res.status(400); return res.end ? res.end("No cart") : res.send("No cart");
|
|
1622
|
+
}
|
|
1623
|
+
// Defensive cart-state guard — if the cart has already been
|
|
1624
|
+
// converted (e.g. duplicate-submit on POST refresh), redirect
|
|
1625
|
+
// to the most recent order for this session.
|
|
1626
|
+
if (c.status !== "active") {
|
|
1627
|
+
res.status(303); res.setHeader && res.setHeader("location", "/cart");
|
|
1628
|
+
return res.end ? res.end() : res.send("");
|
|
1629
|
+
}
|
|
1630
|
+
var shipTo = {
|
|
1631
|
+
country: (body.country || "").toUpperCase(),
|
|
1632
|
+
state: body.state ? String(body.state).toUpperCase() : undefined,
|
|
1633
|
+
postal: body.postal || undefined,
|
|
1634
|
+
};
|
|
1635
|
+
try {
|
|
1636
|
+
// default_shipping_id may be a literal string or an
|
|
1637
|
+
// operator-supplied async resolver (e.g. backed by the
|
|
1638
|
+
// config primitive) so re-reads happen per request without
|
|
1639
|
+
// a container restart.
|
|
1640
|
+
var defaultShipId;
|
|
1641
|
+
if (typeof deps.default_shipping_id === "function") {
|
|
1642
|
+
defaultShipId = await deps.default_shipping_id();
|
|
1643
|
+
} else {
|
|
1644
|
+
defaultShipId = deps.default_shipping_id;
|
|
1645
|
+
}
|
|
1646
|
+
var result = await deps.checkout.confirm({
|
|
1647
|
+
cart_id: c.id,
|
|
1648
|
+
ship_to: shipTo,
|
|
1649
|
+
selected_shipping_id: defaultShipId || "std",
|
|
1650
|
+
customer: { email: body.email, name: body.name },
|
|
1651
|
+
idempotency_key: "checkout:" + c.id + ":" + _b().uuid.v7(),
|
|
1652
|
+
});
|
|
1653
|
+
// Set a short-lived pay cookie so /pay/:order_id can serve the
|
|
1654
|
+
// client_secret without re-running confirm.
|
|
1655
|
+
var payCookie = "shop_pay=" + encodeURIComponent(result.payment_intent.client_secret) +
|
|
1656
|
+
"; Max-Age=900; Path=/pay/; HttpOnly; Secure; SameSite=Strict";
|
|
1657
|
+
if (res.appendHeader) res.appendHeader("Set-Cookie", payCookie);
|
|
1658
|
+
else if (res.setHeader) res.setHeader("Set-Cookie", payCookie);
|
|
1659
|
+
res.status(303);
|
|
1660
|
+
res.setHeader && res.setHeader("location", "/pay/" + result.order.id);
|
|
1661
|
+
return res.end ? res.end() : res.send("");
|
|
1662
|
+
} catch (e) {
|
|
1663
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
1664
|
+
var msg = (e && e.message) || "checkout failed";
|
|
1665
|
+
return res.end ? res.end(msg) : res.send(msg);
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
router.get("/pay/:order_id", async function (req, res) {
|
|
1670
|
+
var orderId = req.params && req.params.order_id;
|
|
1671
|
+
if (!orderId) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1672
|
+
var o = await deps.order.get(orderId);
|
|
1673
|
+
if (!o) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1674
|
+
// Read the client_secret from the shop_pay cookie set on POST
|
|
1675
|
+
// /checkout. The cookie is scoped Path=/pay/ + SameSite=Strict
|
|
1676
|
+
// so it's only sent to the pay route and never cross-origin.
|
|
1677
|
+
var rawCookies = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
|
|
1678
|
+
var clientSecret = null;
|
|
1679
|
+
rawCookies.split(";").forEach(function (p) {
|
|
1680
|
+
var t = p.trim();
|
|
1681
|
+
if (t.indexOf("shop_pay=") === 0) {
|
|
1682
|
+
try { clientSecret = decodeURIComponent(t.slice("shop_pay=".length)); } catch (_e) { /* drop */ }
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
if (!clientSecret) {
|
|
1686
|
+
res.status(303); res.setHeader && res.setHeader("location", "/cart");
|
|
1687
|
+
return res.end ? res.end() : res.send("");
|
|
1688
|
+
}
|
|
1689
|
+
var pk = deps.stripe_publishable_key || "";
|
|
1690
|
+
if (!pk) {
|
|
1691
|
+
res.status(503);
|
|
1692
|
+
return res.end ? res.end("Stripe publishable key not configured") : res.send("Stripe publishable key not configured");
|
|
1693
|
+
}
|
|
1694
|
+
_send(res, 200, renderPayPage({
|
|
1695
|
+
order: o,
|
|
1696
|
+
client_secret: clientSecret,
|
|
1697
|
+
publishable_key: pk,
|
|
1698
|
+
shop_name: shopName,
|
|
1699
|
+
theme: theme,
|
|
1700
|
+
}));
|
|
1701
|
+
});
|
|
1702
|
+
|
|
1703
|
+
router.get("/orders/:order_id", async function (req, res) {
|
|
1704
|
+
var orderId = req.params && req.params.order_id;
|
|
1705
|
+
if (!orderId) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1706
|
+
var o = await deps.order.get(orderId);
|
|
1707
|
+
if (!o) return _send(res, 404, renderNotFound({ shop_name: shopName, theme: theme }));
|
|
1708
|
+
_send(res, 200, renderOrder({ order: o, shop_name: shopName, theme: theme }));
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// ---- customer accounts (passkey-only) ------------------------------
|
|
1713
|
+
//
|
|
1714
|
+
// Mount only when deps.customers is supplied (operator opts in by
|
|
1715
|
+
// wiring the customers primitive in server.js). The account routes
|
|
1716
|
+
// also depend on b.vault for sealed-cookie envelopes — the seal /
|
|
1717
|
+
// unseal calls throw `vault/not-initialized` at request time when
|
|
1718
|
+
// the operator hasn't supplied VAULT_PASSPHRASE; routes surface
|
|
1719
|
+
// that as 503 so the rest of the storefront stays up.
|
|
1720
|
+
if (deps.customers) {
|
|
1721
|
+
var rpName = shopName;
|
|
1722
|
+
var rpId = deps.rpId || (deps.shop_origin ? new URL(deps.shop_origin).hostname : "localhost");
|
|
1723
|
+
var expectedOrigin = deps.shop_origin || ("https://" + rpId);
|
|
1724
|
+
|
|
1725
|
+
function _b64u(buf) {
|
|
1726
|
+
// Node Buffer / Uint8Array -> base64url string
|
|
1727
|
+
var b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
1728
|
+
return b.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function _sealEnvelope(obj) {
|
|
1732
|
+
return _b().vault.seal(JSON.stringify(obj));
|
|
1733
|
+
}
|
|
1734
|
+
function _unsealEnvelope(s) {
|
|
1735
|
+
try {
|
|
1736
|
+
var raw = _b().vault.unseal(s);
|
|
1737
|
+
return JSON.parse(raw);
|
|
1738
|
+
} catch (_e) { return null; }
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function _currentCustomer(req) {
|
|
1742
|
+
var raw = _readCookie(req, AUTH_COOKIE_NAME);
|
|
1743
|
+
if (!raw) return null;
|
|
1744
|
+
var env = _unsealEnvelope(raw);
|
|
1745
|
+
if (!env || !env.customer_id || !env.exp) return null;
|
|
1746
|
+
if (env.exp < Date.now()) return null;
|
|
1747
|
+
return env;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function _serviceUnavailable(res, msg) {
|
|
1751
|
+
res.status(503);
|
|
1752
|
+
return res.end ? res.end(msg) : res.send(msg);
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
function _readJsonBody(req) {
|
|
1756
|
+
// b.middleware.bodyParser leaves JSON in req.body when the
|
|
1757
|
+
// request's content-type is application/json. Some test
|
|
1758
|
+
// harnesses POST a string body — fall back to JSON.parse.
|
|
1759
|
+
if (req.body && typeof req.body === "object") return req.body;
|
|
1760
|
+
if (typeof req.body === "string") {
|
|
1761
|
+
try { return JSON.parse(req.body); } catch (_e) { return {}; }
|
|
1762
|
+
}
|
|
1763
|
+
return {};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
router.get("/account/login", async function (req, res) {
|
|
1767
|
+
var cartCount = await _cartCountForReq(req);
|
|
1768
|
+
_send(res, 200, renderAccountLogin({ shop_name: shopName, cart_count: cartCount }));
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
router.get("/account/register", async function (req, res) {
|
|
1772
|
+
var cartCount = await _cartCountForReq(req);
|
|
1773
|
+
_send(res, 200, renderAccountRegister({ shop_name: shopName, cart_count: cartCount }));
|
|
1774
|
+
});
|
|
1775
|
+
|
|
1776
|
+
router.post("/account/passkey/register-begin", async function (req, res) {
|
|
1777
|
+
try {
|
|
1778
|
+
var body = _readJsonBody(req);
|
|
1779
|
+
// Persist the customer row up-front. The address is the
|
|
1780
|
+
// registration's natural identifier — if enrollment fails
|
|
1781
|
+
// the customer can re-attempt with the same email; the
|
|
1782
|
+
// primitive's duplicate-refusal surfaces as a typed code.
|
|
1783
|
+
var existing = await deps.customers.byEmailHash(
|
|
1784
|
+
deps.customers.hashEmail(body.email),
|
|
1785
|
+
);
|
|
1786
|
+
var customer;
|
|
1787
|
+
if (existing) {
|
|
1788
|
+
customer = existing;
|
|
1789
|
+
} else {
|
|
1790
|
+
customer = await deps.customers.register({
|
|
1791
|
+
email: body.email,
|
|
1792
|
+
display_name: body.display_name,
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
var startOpts = await _b().auth.passkey.startRegistration({
|
|
1796
|
+
rpName: rpName,
|
|
1797
|
+
rpId: rpId,
|
|
1798
|
+
userName: customer.email_hash.slice(0, 16),
|
|
1799
|
+
userDisplayName: customer.display_name,
|
|
1800
|
+
attestationType: "none",
|
|
1801
|
+
});
|
|
1802
|
+
// Seal the ceremony state (challenge + customer_id) into the
|
|
1803
|
+
// shop_auth_chal cookie so register-finish verifies against
|
|
1804
|
+
// the same challenge without server-side state.
|
|
1805
|
+
var sealed = _sealEnvelope({
|
|
1806
|
+
kind: "register",
|
|
1807
|
+
customer_id: customer.id,
|
|
1808
|
+
challenge: startOpts.challenge,
|
|
1809
|
+
created_at: Date.now(),
|
|
1810
|
+
});
|
|
1811
|
+
_setChallengeCookie(res, sealed);
|
|
1812
|
+
res.status(200);
|
|
1813
|
+
res.setHeader && res.setHeader("content-type", "application/json");
|
|
1814
|
+
return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
|
|
1815
|
+
} catch (e) {
|
|
1816
|
+
if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
|
|
1817
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
1818
|
+
return res.end ? res.end((e && e.message) || "register-begin failed") : res.send((e && e.message) || "register-begin failed");
|
|
1819
|
+
}
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
router.post("/account/passkey/register-finish", async function (req, res) {
|
|
1823
|
+
try {
|
|
1824
|
+
var rawCookie = _readCookie(req, CHALLENGE_COOKIE_NAME);
|
|
1825
|
+
if (!rawCookie) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
|
|
1826
|
+
var env = _unsealEnvelope(rawCookie);
|
|
1827
|
+
if (!env || env.kind !== "register") {
|
|
1828
|
+
res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge");
|
|
1829
|
+
}
|
|
1830
|
+
var att = _readJsonBody(req);
|
|
1831
|
+
var rv = await _b().auth.passkey.verifyRegistration({
|
|
1832
|
+
response: att,
|
|
1833
|
+
expectedChallenge: env.challenge,
|
|
1834
|
+
expectedOrigin: expectedOrigin,
|
|
1835
|
+
expectedRPID: rpId,
|
|
1836
|
+
});
|
|
1837
|
+
if (!rv || !rv.verified) {
|
|
1838
|
+
res.status(400); return res.end ? res.end("attestation refused") : res.send("attestation refused");
|
|
1839
|
+
}
|
|
1840
|
+
var info = rv.registrationInfo || {};
|
|
1841
|
+
var credentialId = info.credentialID || att.rawId || att.id;
|
|
1842
|
+
var publicKey = info.credentialPublicKey;
|
|
1843
|
+
if (credentialId && typeof credentialId !== "string") credentialId = _b64u(credentialId);
|
|
1844
|
+
if (publicKey && typeof publicKey !== "string") publicKey = _b64u(publicKey);
|
|
1845
|
+
var transports = "";
|
|
1846
|
+
if (att.response && Array.isArray(att.response.transports)) {
|
|
1847
|
+
transports = att.response.transports.filter(function (t) { return /^[a-z]+$/.test(t); }).join(",");
|
|
1848
|
+
}
|
|
1849
|
+
await deps.customers.addPasskey(env.customer_id, {
|
|
1850
|
+
credential_id: credentialId,
|
|
1851
|
+
public_key: publicKey,
|
|
1852
|
+
counter: info.counter || 0,
|
|
1853
|
+
transports: transports,
|
|
1854
|
+
});
|
|
1855
|
+
_clearChallengeCookie(res);
|
|
1856
|
+
_setAuthCookie(res, _sealEnvelope({
|
|
1857
|
+
customer_id: env.customer_id,
|
|
1858
|
+
exp: Date.now() + AUTH_TTL_MS,
|
|
1859
|
+
}));
|
|
1860
|
+
res.status(200);
|
|
1861
|
+
return res.end ? res.end("ok") : res.send("ok");
|
|
1862
|
+
} catch (e) {
|
|
1863
|
+
if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
|
|
1864
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
1865
|
+
return res.end ? res.end((e && e.message) || "register-finish failed") : res.send((e && e.message) || "register-finish failed");
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
router.post("/account/passkey/login-begin", async function (req, res) {
|
|
1870
|
+
try {
|
|
1871
|
+
var body = _readJsonBody(req);
|
|
1872
|
+
var hash = deps.customers.hashEmail(body.email);
|
|
1873
|
+
var customer = await deps.customers.byEmailHash(hash);
|
|
1874
|
+
// Even when the customer doesn't exist, return a valid-shaped
|
|
1875
|
+
// challenge with an empty allowCredentials list — the client
|
|
1876
|
+
// can't distinguish "no such address" from "wrong passkey",
|
|
1877
|
+
// protecting against email-enumeration.
|
|
1878
|
+
var allow = [];
|
|
1879
|
+
if (customer) {
|
|
1880
|
+
var pks = await deps.customers.listPasskeys(customer.id);
|
|
1881
|
+
allow = pks.map(function (p) {
|
|
1882
|
+
return {
|
|
1883
|
+
id: p.credential_id,
|
|
1884
|
+
type: "public-key",
|
|
1885
|
+
transports: p.transports ? p.transports.split(",") : undefined,
|
|
1886
|
+
};
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
var startOpts = await _b().auth.passkey.startAuthentication({
|
|
1890
|
+
rpId: rpId,
|
|
1891
|
+
allowCredentials: allow,
|
|
1892
|
+
userVerification: "preferred",
|
|
1893
|
+
});
|
|
1894
|
+
var sealed = _sealEnvelope({
|
|
1895
|
+
kind: "login",
|
|
1896
|
+
email_hash: hash,
|
|
1897
|
+
challenge: startOpts.challenge,
|
|
1898
|
+
created_at: Date.now(),
|
|
1899
|
+
});
|
|
1900
|
+
_setChallengeCookie(res, sealed);
|
|
1901
|
+
res.status(200);
|
|
1902
|
+
res.setHeader && res.setHeader("content-type", "application/json");
|
|
1903
|
+
return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
|
|
1904
|
+
} catch (e) {
|
|
1905
|
+
if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
|
|
1906
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
1907
|
+
return res.end ? res.end((e && e.message) || "login-begin failed") : res.send((e && e.message) || "login-begin failed");
|
|
1908
|
+
}
|
|
1909
|
+
});
|
|
1910
|
+
|
|
1911
|
+
router.post("/account/passkey/login-finish", async function (req, res) {
|
|
1912
|
+
try {
|
|
1913
|
+
var rawCookie = _readCookie(req, CHALLENGE_COOKIE_NAME);
|
|
1914
|
+
if (!rawCookie) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
|
|
1915
|
+
var env = _unsealEnvelope(rawCookie);
|
|
1916
|
+
if (!env || env.kind !== "login") { res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge"); }
|
|
1917
|
+
var assertion = _readJsonBody(req);
|
|
1918
|
+
var credentialId = assertion.id || assertion.rawId;
|
|
1919
|
+
if (!credentialId) { res.status(400); return res.end ? res.end("missing credential id") : res.send("missing credential id"); }
|
|
1920
|
+
var passkey = await deps.customers.getPasskeyByCredentialId(credentialId);
|
|
1921
|
+
if (!passkey) { res.status(401); return res.end ? res.end("unknown credential") : res.send("unknown credential"); }
|
|
1922
|
+
var customer = await deps.customers.get(passkey.customer_id);
|
|
1923
|
+
if (!customer) { res.status(401); return res.end ? res.end("unknown customer") : res.send("unknown customer"); }
|
|
1924
|
+
if (customer.email_hash !== env.email_hash) {
|
|
1925
|
+
// The login-begin email and the credential's customer
|
|
1926
|
+
// must agree — refuse cross-account credential reuse.
|
|
1927
|
+
res.status(401); return res.end ? res.end("credential / account mismatch") : res.send("credential / account mismatch");
|
|
1928
|
+
}
|
|
1929
|
+
var rv = await _b().auth.passkey.verifyAuthentication({
|
|
1930
|
+
response: assertion,
|
|
1931
|
+
expectedChallenge: env.challenge,
|
|
1932
|
+
expectedOrigin: expectedOrigin,
|
|
1933
|
+
expectedRPID: rpId,
|
|
1934
|
+
authenticator: {
|
|
1935
|
+
credentialID: passkey.credential_id,
|
|
1936
|
+
credentialPublicKey: passkey.public_key,
|
|
1937
|
+
counter: passkey.counter,
|
|
1938
|
+
},
|
|
1939
|
+
});
|
|
1940
|
+
if (!rv || !rv.verified) {
|
|
1941
|
+
res.status(401); return res.end ? res.end("assertion refused") : res.send("assertion refused");
|
|
1942
|
+
}
|
|
1943
|
+
// Persist the new counter value (clone-detection).
|
|
1944
|
+
var newCounter = (rv.authenticationInfo && rv.authenticationInfo.newCounter) || 0;
|
|
1945
|
+
try {
|
|
1946
|
+
await deps.customers.updatePasskeyCounter(passkey.id, newCounter);
|
|
1947
|
+
} catch (e) {
|
|
1948
|
+
if (e && e.code === "PASSKEY_COUNTER_REGRESSION") {
|
|
1949
|
+
res.status(401); return res.end ? res.end("counter regression — possible clone") : res.send("counter regression — possible clone");
|
|
1950
|
+
}
|
|
1951
|
+
throw e;
|
|
1952
|
+
}
|
|
1953
|
+
// Merge the anonymous cart into a customer-owned cart so
|
|
1954
|
+
// the shopper doesn't lose items on sign-in.
|
|
1955
|
+
var sid = _readCookie(req, SESSION_COOKIE_NAME);
|
|
1956
|
+
if (sid) {
|
|
1957
|
+
try {
|
|
1958
|
+
var anonCart = await deps.cart.bySession(sid);
|
|
1959
|
+
if (anonCart) await deps.cart.setCustomer(anonCart.id, customer.id);
|
|
1960
|
+
} catch (_e) { /* best-effort merge; sign-in itself succeeds */ }
|
|
1961
|
+
}
|
|
1962
|
+
_clearChallengeCookie(res);
|
|
1963
|
+
_setAuthCookie(res, _sealEnvelope({
|
|
1964
|
+
customer_id: customer.id,
|
|
1965
|
+
exp: Date.now() + AUTH_TTL_MS,
|
|
1966
|
+
}));
|
|
1967
|
+
res.status(200);
|
|
1968
|
+
return res.end ? res.end("ok") : res.send("ok");
|
|
1969
|
+
} catch (e) {
|
|
1970
|
+
if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
|
|
1971
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
1972
|
+
return res.end ? res.end((e && e.message) || "login-finish failed") : res.send((e && e.message) || "login-finish failed");
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
router.get("/account", async function (req, res) {
|
|
1977
|
+
var auth;
|
|
1978
|
+
try { auth = _currentCustomer(req); }
|
|
1979
|
+
catch (e) {
|
|
1980
|
+
if (e && e.code === "vault/not-initialized") return _serviceUnavailable(res, "auth not configured");
|
|
1981
|
+
throw e;
|
|
1982
|
+
}
|
|
1983
|
+
if (!auth) {
|
|
1984
|
+
res.status(303); res.setHeader && res.setHeader("location", "/account/login");
|
|
1985
|
+
return res.end ? res.end() : res.send("");
|
|
1986
|
+
}
|
|
1987
|
+
var customer = await deps.customers.get(auth.customer_id);
|
|
1988
|
+
if (!customer) {
|
|
1989
|
+
_clearAuthCookie(res);
|
|
1990
|
+
res.status(303); res.setHeader && res.setHeader("location", "/account/login");
|
|
1991
|
+
return res.end ? res.end() : res.send("");
|
|
1992
|
+
}
|
|
1993
|
+
var orders = [];
|
|
1994
|
+
if (deps.order) {
|
|
1995
|
+
var page = await deps.order.listForCustomer(customer.id, { limit: 10 });
|
|
1996
|
+
orders = page.rows;
|
|
1997
|
+
}
|
|
1998
|
+
var cartCount = await _cartCountForReq(req);
|
|
1999
|
+
_send(res, 200, renderAccount({
|
|
2000
|
+
customer: customer, orders: orders, shop_name: shopName, cart_count: cartCount,
|
|
2001
|
+
}));
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
router.post("/account/logout", function (_req, res) {
|
|
2005
|
+
_clearAuthCookie(res);
|
|
2006
|
+
res.status(303); res.setHeader && res.setHeader("location", "/");
|
|
2007
|
+
return res.end ? res.end() : res.send("");
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// POST /cart/lines — add a line. Reads variant_id + qty from the
|
|
2012
|
+
// form body (b.middleware.bodyParser parses it into req.body).
|
|
2013
|
+
// CSRF token validation is the responsibility of the csrfProtect
|
|
2014
|
+
// middleware mounted at the app level (server.js). Redirects to
|
|
2015
|
+
// /cart on success so a refresh doesn't re-submit the form.
|
|
2016
|
+
router.post("/cart/lines", async function (req, res) {
|
|
2017
|
+
var body = req.body || {};
|
|
2018
|
+
var variantId = body.variant_id;
|
|
2019
|
+
var qtyRaw = body.qty;
|
|
2020
|
+
var qty = parseInt(qtyRaw, 10);
|
|
2021
|
+
if (!variantId || !Number.isFinite(qty) || qty < 1 || qty > 99) {
|
|
2022
|
+
res.status(400);
|
|
2023
|
+
return res.end ? res.end("Invalid request") : res.send("Invalid request");
|
|
2024
|
+
}
|
|
2025
|
+
var resolved = await _getOrCreateCart(req, res, "USD");
|
|
2026
|
+
try {
|
|
2027
|
+
await deps.cart.addLine(resolved.cart.id, { variant_id: variantId, qty: qty });
|
|
2028
|
+
} catch (e) {
|
|
2029
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
2030
|
+
return res.end ? res.end((e && e.message) || "Error") : res.send((e && e.message) || "Error");
|
|
2031
|
+
}
|
|
2032
|
+
res.status(303);
|
|
2033
|
+
res.setHeader && res.setHeader("location", "/cart");
|
|
2034
|
+
res.end ? res.end() : res.send("");
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
// POST /cart/lines/:line_id/update — change qty on an existing
|
|
2038
|
+
// line. Form value `qty` is the new quantity (1..99). HTML forms
|
|
2039
|
+
// only support GET/POST so the verb is in the path.
|
|
2040
|
+
router.post("/cart/lines/:line_id/update", async function (req, res) {
|
|
2041
|
+
var lineId = req.params && req.params.line_id;
|
|
2042
|
+
var qty = parseInt((req.body || {}).qty, 10);
|
|
2043
|
+
if (!lineId || !Number.isFinite(qty) || qty < 1 || qty > 99) {
|
|
2044
|
+
res.status(400);
|
|
2045
|
+
return res.end ? res.end("Invalid request") : res.send("Invalid request");
|
|
2046
|
+
}
|
|
2047
|
+
try {
|
|
2048
|
+
var updated = await deps.cart.updateLine(lineId, { qty: qty });
|
|
2049
|
+
if (!updated) {
|
|
2050
|
+
res.status(404);
|
|
2051
|
+
return res.end ? res.end("Line not found") : res.send("Line not found");
|
|
2052
|
+
}
|
|
2053
|
+
} catch (e) {
|
|
2054
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
2055
|
+
return res.end ? res.end((e && e.message) || "Error") : res.send((e && e.message) || "Error");
|
|
2056
|
+
}
|
|
2057
|
+
res.status(303);
|
|
2058
|
+
res.setHeader && res.setHeader("location", "/cart");
|
|
2059
|
+
res.end ? res.end() : res.send("");
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2062
|
+
// POST /cart/lines/:line_id/remove — delete the line outright.
|
|
2063
|
+
router.post("/cart/lines/:line_id/remove", async function (req, res) {
|
|
2064
|
+
var lineId = req.params && req.params.line_id;
|
|
2065
|
+
if (!lineId) {
|
|
2066
|
+
res.status(400);
|
|
2067
|
+
return res.end ? res.end("Invalid request") : res.send("Invalid request");
|
|
2068
|
+
}
|
|
2069
|
+
try {
|
|
2070
|
+
await deps.cart.removeLine(lineId);
|
|
2071
|
+
} catch (e) {
|
|
2072
|
+
res.status(e instanceof TypeError ? 400 : 500);
|
|
2073
|
+
return res.end ? res.end((e && e.message) || "Error") : res.send((e && e.message) || "Error");
|
|
2074
|
+
}
|
|
2075
|
+
res.status(303);
|
|
2076
|
+
res.setHeader && res.setHeader("location", "/cart");
|
|
2077
|
+
res.end ? res.end() : res.send("");
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
// Newsletter signup — POST /newsletter from the footer band.
|
|
2081
|
+
// Validates the address through `b.guardEmail`, idempotently
|
|
2082
|
+
// enrolls via the newsletter primitive (when wired), and renders
|
|
2083
|
+
// a designed thank-you page. Mount only when `deps.newsletter`
|
|
2084
|
+
// is present so operators that haven't wired the primitive get
|
|
2085
|
+
// a clean 404 instead of a misleading "thanks" response.
|
|
2086
|
+
if (deps.newsletter) {
|
|
2087
|
+
router.post("/newsletter", async function (req, res) {
|
|
2088
|
+
var body = req.body || {};
|
|
2089
|
+
var cartCount = 0;
|
|
2090
|
+
try { cartCount = await _cartCountForReq(req); } catch (_e) { /* drop-silent — empty cart fallback */ }
|
|
2091
|
+
try {
|
|
2092
|
+
var result = await deps.newsletter.signup({
|
|
2093
|
+
email: body.email,
|
|
2094
|
+
source: "storefront-footer",
|
|
2095
|
+
});
|
|
2096
|
+
return _send(res, 200, renderNewsletterThanks({
|
|
2097
|
+
shop_name: shopName,
|
|
2098
|
+
cart_count: cartCount,
|
|
2099
|
+
status: result.status,
|
|
2100
|
+
}));
|
|
2101
|
+
} catch (e) {
|
|
2102
|
+
// TypeError == operator-fault validation refusal; everything
|
|
2103
|
+
// else (D1 unreachable, vault hiccup) hits the 500 branch.
|
|
2104
|
+
var isInputError = e instanceof TypeError;
|
|
2105
|
+
return _send(res, isInputError ? 400 : 500, renderNewsletterError({
|
|
2106
|
+
shop_name: shopName,
|
|
2107
|
+
cart_count: cartCount,
|
|
2108
|
+
message: isInputError ? "That doesn't look like a valid email address. Check the format and try again." : null,
|
|
2109
|
+
}));
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
// Designed admin landing — the rest of /admin/* is JSON. This
|
|
2115
|
+
// single GET gives footer links + curious visitors a designed
|
|
2116
|
+
// page explaining the API-only posture instead of a 404.
|
|
2117
|
+
router.get("/admin", async function (req, res) {
|
|
2118
|
+
var cartCount = 0;
|
|
2119
|
+
try { cartCount = await _cartCountForReq(req); } catch (_e) { /* drop-silent — empty cart fallback */ }
|
|
2120
|
+
_send(res, 200, renderAdminLanding({
|
|
2121
|
+
shop_name: shopName,
|
|
2122
|
+
cart_count: cartCount,
|
|
2123
|
+
}));
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
// Catch-all 404 — every unmatched route lands on the designed
|
|
2127
|
+
// not-found page (gradient 404 + back-to-shop CTA) inside the
|
|
2128
|
+
// standard layout, instead of the framework's default
|
|
2129
|
+
// `<h1>404 Not Found</h1>` text body. Wired via the router's
|
|
2130
|
+
// onNotFound hook so it covers GET/POST/HEAD uniformly.
|
|
2131
|
+
if (typeof router.onNotFound === "function") {
|
|
2132
|
+
router.onNotFound(async function (req, res) {
|
|
2133
|
+
var cartCount = 0;
|
|
2134
|
+
try { cartCount = await _cartCountForReq(req); } catch (_e) { /* drop-silent — empty cart fallback */ }
|
|
2135
|
+
_send(res, 404, renderNotFound({
|
|
2136
|
+
shop_name: shopName,
|
|
2137
|
+
cart_count: cartCount,
|
|
2138
|
+
theme: theme,
|
|
2139
|
+
}));
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
module.exports = {
|
|
2145
|
+
mount: mount,
|
|
2146
|
+
renderHome: renderHome,
|
|
2147
|
+
renderSearch: renderSearch,
|
|
2148
|
+
renderProduct: renderProduct,
|
|
2149
|
+
renderCart: renderCart,
|
|
2150
|
+
renderCheckoutForm: renderCheckoutForm,
|
|
2151
|
+
renderPayPage: renderPayPage,
|
|
2152
|
+
renderOrder: renderOrder,
|
|
2153
|
+
renderAccountLogin: renderAccountLogin,
|
|
2154
|
+
renderAccountRegister: renderAccountRegister,
|
|
2155
|
+
renderAccount: renderAccount,
|
|
2156
|
+
renderNotFound: renderNotFound,
|
|
2157
|
+
// Layout exposed so operators forking the framework can override.
|
|
2158
|
+
_wrap: _wrap,
|
|
2159
|
+
LAYOUT: LAYOUT,
|
|
2160
|
+
};
|