@blamejs/blamejs-shop 0.0.129 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/admin.js +1 -2
  3. package/lib/affiliates.js +4 -3
  4. package/lib/analytics.js +3 -2
  5. package/lib/api-keys.js +1 -1
  6. package/lib/assembly-instructions.js +2 -1
  7. package/lib/auto-replenish.js +4 -3
  8. package/lib/backorder.js +2 -1
  9. package/lib/business-hours.js +8 -1
  10. package/lib/carrier-accounts.js +1 -1
  11. package/lib/carrier-rates.js +1 -1
  12. package/lib/cart-abandonment.js +3 -2
  13. package/lib/cart-bulk-ops.js +2 -1
  14. package/lib/cart-recovery.js +5 -4
  15. package/lib/cart.js +6 -2
  16. package/lib/catalog-drafts.js +1 -1
  17. package/lib/click-and-collect.js +3 -2
  18. package/lib/clickstream.js +4 -3
  19. package/lib/config.js +2 -1
  20. package/lib/cookie-consent.js +2 -1
  21. package/lib/credit-limits.js +2 -1
  22. package/lib/currency-display.js +2 -1
  23. package/lib/customer-activity.js +3 -2
  24. package/lib/customer-impersonation.js +3 -3
  25. package/lib/customer-merge.js +4 -3
  26. package/lib/customer-portal.js +4 -4
  27. package/lib/customer-risk-profile.js +2 -1
  28. package/lib/customer-segments.js +2 -1
  29. package/lib/customer-surveys.js +6 -3
  30. package/lib/delivery-estimate.js +2 -2
  31. package/lib/demand-forecast.js +2 -1
  32. package/lib/discount-analytics.js +2 -2
  33. package/lib/dunning.js +4 -1
  34. package/lib/email-warmup.js +6 -1
  35. package/lib/email.js +1 -8
  36. package/lib/error-log.js +3 -2
  37. package/lib/event-log.js +3 -2
  38. package/lib/fraud-screen.js +3 -1
  39. package/lib/fulfillment-sla.js +3 -1
  40. package/lib/index.js +11 -3
  41. package/lib/inventory-allocations.js +3 -0
  42. package/lib/inventory-snapshots.js +2 -1
  43. package/lib/invoice-renderer.js +2 -1
  44. package/lib/line-gift-wrap.js +6 -1
  45. package/lib/live-chat.js +2 -1
  46. package/lib/loyalty-redemption.js +2 -1
  47. package/lib/newsletter.js +6 -1
  48. package/lib/operator-activity-feed.js +4 -3
  49. package/lib/operator-sessions.js +7 -7
  50. package/lib/order-exchanges.js +1 -0
  51. package/lib/order-timeline.js +2 -1
  52. package/lib/payment-retries.js +2 -1
  53. package/lib/payment.js +5 -4
  54. package/lib/pixel-events.js +6 -5
  55. package/lib/preorder.js +2 -1
  56. package/lib/print-queue.js +2 -1
  57. package/lib/product-compare.js +2 -1
  58. package/lib/product-qa.js +2 -1
  59. package/lib/push-notifications.js +6 -5
  60. package/lib/recently-viewed.js +7 -2
  61. package/lib/recommendations.js +7 -2
  62. package/lib/referral-leaderboard.js +2 -1
  63. package/lib/refund-automation.js +1 -1
  64. package/lib/refund-policy.js +1 -1
  65. package/lib/reorder-reminders.js +2 -1
  66. package/lib/reorder-thresholds.js +2 -1
  67. package/lib/robots-config.js +1 -0
  68. package/lib/sales-reports.js +17 -14
  69. package/lib/sales-tax-filings.js +2 -1
  70. package/lib/save-for-later.js +2 -1
  71. package/lib/search-suggestions.js +1 -1
  72. package/lib/shipping-insurance.js +2 -1
  73. package/lib/shipping-labels.js +3 -2
  74. package/lib/shipping-zones.js +1 -0
  75. package/lib/shrinkage-report.js +9 -8
  76. package/lib/sms-dispatcher.js +6 -5
  77. package/lib/stock-alerts.js +1 -1
  78. package/lib/stock-receipts.js +2 -1
  79. package/lib/store-credit.js +2 -1
  80. package/lib/storefront-forms.js +1 -1
  81. package/lib/storefront.js +93 -112
  82. package/lib/subscription-analytics.js +7 -2
  83. package/lib/subscription-controls.js +9 -8
  84. package/lib/subscription-gifts.js +2 -1
  85. package/lib/subscriptions.js +2 -0
  86. package/lib/support-tickets.js +4 -4
  87. package/lib/tax-cert-renewals.js +2 -1
  88. package/lib/tax-remittance.js +2 -1
  89. package/lib/theme-assets.js +1 -1
  90. package/lib/vendor/MANIFEST.json +2 -2
  91. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  92. package/lib/vendor/blamejs/README.md +1 -0
  93. package/lib/vendor/blamejs/api-snapshot.json +92 -2
  94. package/lib/vendor/blamejs/index.js +1 -0
  95. package/lib/vendor/blamejs/lib/did.js +367 -0
  96. package/lib/vendor/blamejs/package.json +1 -1
  97. package/lib/vendor/blamejs/release-notes/v0.12.41.json +18 -0
  98. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9 -1
  99. package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +147 -0
  100. package/lib/vendor-invoices.js +1 -1
  101. package/lib/webhook-receiver.js +8 -2
  102. package/lib/webhook-subscriptions.js +1 -1
  103. package/lib/webhooks.js +6 -5
  104. package/lib/winback-campaigns.js +2 -1
  105. package/lib/wishlist-alerts.js +2 -1
  106. package/lib/wishlist-digest.js +2 -1
  107. package/package.json +1 -1
package/lib/storefront.js CHANGED
@@ -1510,9 +1510,9 @@ var CART_LINE =
1510
1510
  " </span>\n" +
1511
1511
  " </a>\n" +
1512
1512
  " </td>\n" +
1513
- " <td>{{qty}}</td>\n" +
1514
- " <td class=\"price\">{{unit}}</td>\n" +
1515
- " <td class=\"price\">{{total}}</td>\n" +
1513
+ " <td data-label=\"Qty\">{{qty}}</td>\n" +
1514
+ " <td class=\"price\" data-label=\"Unit\">{{unit}}</td>\n" +
1515
+ " <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
1516
1516
  "</tr>\n";
1517
1517
 
1518
1518
  // Editable cart line — shown on the /cart page. Includes an inline
@@ -1536,14 +1536,14 @@ var CART_LINE_EDITABLE =
1536
1536
  " </span>\n" +
1537
1537
  " </a>\n" +
1538
1538
  " </td>\n" +
1539
- " <td class=\"cart-line__qty\">\n" +
1539
+ " <td class=\"cart-line__qty\" data-label=\"Qty\">\n" +
1540
1540
  " <form method=\"post\" action=\"/cart/lines/{{line_id}}/update\" class=\"cart-line__update\">\n" +
1541
- " <input type=\"number\" name=\"qty\" value=\"{{qty}}\" min=\"1\" max=\"99\" class=\"cart-line__qty-input\" aria-label=\"Quantity\">\n" +
1542
- " <button type=\"submit\" class=\"cart-line__btn\">Update</button>\n" +
1541
+ " <input type=\"number\" name=\"qty\" value=\"{{qty}}\" min=\"1\" max=\"99999\" class=\"cart-line__qty-input\" aria-label=\"Quantity\">\n" +
1542
+ " <button type=\"submit\" class=\"cart-line__btn cart-line__btn--update\">Update</button>\n" +
1543
1543
  " </form>\n" +
1544
1544
  " </td>\n" +
1545
- " <td class=\"price\">{{unit}}</td>\n" +
1546
- " <td class=\"price\">{{total}}</td>\n" +
1545
+ " <td class=\"price\" data-label=\"Price\">{{unit}}</td>\n" +
1546
+ " <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
1547
1547
  " <td class=\"cart-line__remove-cell\">\n" +
1548
1548
  " RAW_CART_LINE_SAVE" +
1549
1549
  " <form method=\"post\" action=\"/cart/lines/{{line_id}}/remove\">\n" +
@@ -1691,7 +1691,7 @@ var ORDER_PAGE =
1691
1691
  " <div class=\"order-page__items\">\n" +
1692
1692
  " <h2 class=\"pdp__variants-title\">Items</h2>\n" +
1693
1693
  " <div class=\"table-scroll\">\n" +
1694
- " <table>\n" +
1694
+ " <table class=\"cart-table\">\n" +
1695
1695
  " <thead><tr><th>Product</th><th>Qty</th><th>Unit</th><th>Total</th></tr></thead>\n" +
1696
1696
  " <tbody>{{line_rows}}</tbody>\n" +
1697
1697
  " </table>\n" +
@@ -2082,7 +2082,6 @@ function renderNotFound(opts) {
2082
2082
  // — it's a routing key, not an authentication token. The cart itself
2083
2083
  // transitions to `customer_id` on login via cart.setCustomer.
2084
2084
  var SESSION_COOKIE_NAME = "shop_sid";
2085
- var SESSION_COOKIE_MAX = 60 * 60 * 24 * 30; // 30 days
2086
2085
 
2087
2086
  // Authenticated-customer cookie — carries an opaque sealed envelope
2088
2087
  // `{ customer_id, exp }`, AEAD-encrypted via b.vault.seal so the
@@ -2092,79 +2091,85 @@ var SESSION_COOKIE_MAX = 60 * 60 * 24 * 30; // 30 days
2092
2091
  // rotated vault invalidates every outstanding auth cookie (operator-
2093
2092
  // initiated logout-everywhere).
2094
2093
  var AUTH_COOKIE_NAME = "shop_auth";
2095
- var AUTH_COOKIE_MAX = 60 * 60 * 24 * 14; // 14 days
2096
- var AUTH_TTL_MS = 14 * 24 * 60 * 60 * 1000;
2097
2094
 
2098
2095
  // WebAuthn ceremony state cookie — short-lived envelope holding the
2099
2096
  // random challenge + the ceremony-scoped metadata so register-finish /
2100
2097
  // login-finish can verify the same challenge the browser was sent.
2101
2098
  // Path-scoped to /account so it never leaks to other routes.
2102
2099
  var CHALLENGE_COOKIE_NAME = "shop_auth_chal";
2103
- var CHALLENGE_COOKIE_MAX = 5 * 60; // 5 minutes
2104
2100
 
2105
- function _readSidCookie(req) {
2106
- var raw = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
2107
- if (!raw) return null;
2108
- var parts = raw.split(";");
2109
- for (var i = 0; i < parts.length; i += 1) {
2110
- var p = parts[i].trim();
2111
- var eq = p.indexOf("=");
2112
- if (eq <= 0) continue;
2113
- if (p.slice(0, eq) === SESSION_COOKIE_NAME) {
2114
- var v = p.slice(eq + 1);
2115
- // Cookie values are URL-encoded.
2116
- try { return decodeURIComponent(v); } catch (_e) { return null; }
2117
- }
2101
+ // Short-lived cookie carrying the Stripe PaymentIntent client_secret
2102
+ // from POST /checkout to GET /pay/:order_id. Path-scoped to /pay/ +
2103
+ // SameSite=Strict so it's only ever sent to the pay route.
2104
+ var PAY_COOKIE_NAME = "shop_pay";
2105
+
2106
+ // Shape of a valid session id — mirrors cart.js's SESSION_ID_RE.
2107
+ var SID_SHAPE_RE = /^[A-Za-z0-9_-]{16,64}$/;
2108
+
2109
+ // All cookie transport composes the framework's cookie primitive
2110
+ // (`b.cookies`) RFC 6265 parse/serialize, prefix invariants, and the
2111
+ // vault-sealed read/write helpers — rather than hand-built Set-Cookie
2112
+ // strings and manual header splitting. The jar is memoized; it only
2113
+ // captures the vault reference (seal/unseal run lazily per call), so
2114
+ // building it before vault.init() has completed is safe.
2115
+ var _jar = null;
2116
+ function _cookieJar() {
2117
+ if (!_jar) {
2118
+ _jar = _b().cookies.create({
2119
+ vault: _b().vault,
2120
+ defaults: { httpOnly: true, secure: true, sameSite: "Lax", path: "/" },
2121
+ });
2118
2122
  }
2119
- return null;
2120
- }
2121
-
2122
- function _setSidCookie(res, sid) {
2123
- var attrs = "Max-Age=" + SESSION_COOKIE_MAX + "; Path=/; HttpOnly; Secure; SameSite=Lax";
2124
- var header = SESSION_COOKIE_NAME + "=" + encodeURIComponent(sid) + "; " + attrs;
2125
- if (typeof res.appendHeader === "function") res.appendHeader("Set-Cookie", header);
2126
- else if (typeof res.setHeader === "function") res.setHeader("Set-Cookie", header);
2123
+ return _jar;
2127
2124
  }
2128
2125
 
2129
2126
  function _readCookie(req, name) {
2130
- var raw = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
2131
- if (!raw) return null;
2132
- var parts = raw.split(";");
2133
- for (var i = 0; i < parts.length; i += 1) {
2134
- var p = parts[i].trim();
2135
- var eq = p.indexOf("=");
2136
- if (eq <= 0) continue;
2137
- if (p.slice(0, eq) === name) {
2138
- try { return decodeURIComponent(p.slice(eq + 1)); }
2139
- catch (_e) { return null; }
2140
- }
2141
- }
2142
- return null;
2127
+ return _cookieJar().read(req, name);
2143
2128
  }
2144
2129
 
2145
- function _appendCookie(res, header) {
2146
- if (typeof res.appendHeader === "function") res.appendHeader("Set-Cookie", header);
2147
- else if (typeof res.setHeader === "function") res.setHeader("Set-Cookie", header);
2130
+ function _readSidCookie(req) {
2131
+ // A cookie carrying anything but a well-shaped session id (a stale
2132
+ // value from an old deploy, a tampered cookie, garbage) reads as "no
2133
+ // session" rather than reaching cart.bySession — which throws on a
2134
+ // malformed id and would turn every page that renders the cart count
2135
+ // into a 500. The cookie grants zero authority, so dropping a
2136
+ // malformed one silently is safe.
2137
+ var v = _cookieJar().read(req, SESSION_COOKIE_NAME);
2138
+ return v && SID_SHAPE_RE.test(v) ? v : null;
2148
2139
  }
2149
2140
 
2150
- function _setAuthCookie(res, sealed) {
2151
- var attrs = "Max-Age=" + AUTH_COOKIE_MAX + "; Path=/; HttpOnly; Secure; SameSite=Lax";
2152
- _appendCookie(res, AUTH_COOKIE_NAME + "=" + encodeURIComponent(sealed) + "; " + attrs);
2141
+ function _setSidCookie(res, sid) {
2142
+ var T = _b().constants.TIME;
2143
+ _cookieJar().write(res, SESSION_COOKIE_NAME, sid, { expires: new Date(Date.now() + T.days(30)) });
2153
2144
  }
2154
2145
 
2146
+ // Auth + WebAuthn-challenge cookies carry a vault-sealed JSON envelope.
2147
+ // writeSealed/readSealed handle the seal + the on-wire prefix; the
2148
+ // caller works in plain objects.
2149
+ function _setAuthCookie(res, env) {
2150
+ var T = _b().constants.TIME;
2151
+ _cookieJar().writeSealed(res, AUTH_COOKIE_NAME, JSON.stringify(env), { expires: new Date(Date.now() + T.days(14)) });
2152
+ }
2155
2153
  function _clearAuthCookie(res) {
2156
- _appendCookie(res,
2157
- AUTH_COOKIE_NAME + "=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Lax");
2154
+ _cookieJar().clear(res, AUTH_COOKIE_NAME);
2158
2155
  }
2159
-
2160
- function _setChallengeCookie(res, sealed) {
2161
- var attrs = "Max-Age=" + CHALLENGE_COOKIE_MAX + "; Path=/account; HttpOnly; Secure; SameSite=Lax";
2162
- _appendCookie(res, CHALLENGE_COOKIE_NAME + "=" + encodeURIComponent(sealed) + "; " + attrs);
2156
+ function _readAuthEnv(req) {
2157
+ var raw = _cookieJar().readSealed(req, AUTH_COOKIE_NAME);
2158
+ if (raw === null) return null;
2159
+ try { return JSON.parse(raw); } catch (_e) { return null; }
2163
2160
  }
2164
2161
 
2162
+ function _setChallengeCookie(res, env) {
2163
+ var T = _b().constants.TIME;
2164
+ _cookieJar().writeSealed(res, CHALLENGE_COOKIE_NAME, JSON.stringify(env), { expires: new Date(Date.now() + T.minutes(5)), path: "/account" });
2165
+ }
2165
2166
  function _clearChallengeCookie(res) {
2166
- _appendCookie(res,
2167
- CHALLENGE_COOKIE_NAME + "=; Max-Age=0; Path=/account; HttpOnly; Secure; SameSite=Lax");
2167
+ _cookieJar().clear(res, CHALLENGE_COOKIE_NAME, { path: "/account" });
2168
+ }
2169
+ function _readChallengeEnv(req) {
2170
+ var raw = _cookieJar().readSealed(req, CHALLENGE_COOKIE_NAME);
2171
+ if (raw === null) return null;
2172
+ try { return JSON.parse(raw); } catch (_e) { return null; }
2168
2173
  }
2169
2174
 
2170
2175
  // ---- account-page renderers --------------------------------------------
@@ -2303,10 +2308,10 @@ var ACCOUNT_DASH_PAGE =
2303
2308
 
2304
2309
  var ACCOUNT_DASH_ORDER_ROW =
2305
2310
  "<tr>\n" +
2306
- " <td><a href=\"/orders/{{order_id}}\" class=\"account-order__id\"><code>{{order_id_short}}</code></a></td>\n" +
2307
- " <td class=\"account-order__items\">RAW_ACCOUNT_ORDER_THUMBS</td>\n" +
2308
- " <td><span class=\"pdp__badge {{status_class}}\">{{status}}</span></td>\n" +
2309
- " <td class=\"price\">{{total}}</td>\n" +
2311
+ " <td data-label=\"Order\"><a href=\"/orders/{{order_id}}\" class=\"account-order__id\"><code>{{order_id_short}}</code></a></td>\n" +
2312
+ " <td class=\"account-order__items\" data-label=\"Items\">RAW_ACCOUNT_ORDER_THUMBS</td>\n" +
2313
+ " <td data-label=\"Status\"><span class=\"pdp__badge {{status_class}}\">{{status}}</span></td>\n" +
2314
+ " <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
2310
2315
  "</tr>\n";
2311
2316
 
2312
2317
  function renderAccount(opts) {
@@ -2407,7 +2412,7 @@ function mount(router, deps) {
2407
2412
  // so the /admin landing + the onNotFound 404 handler (mounted
2408
2413
  // outside the `if (deps.customers)` block below) can reach it.
2409
2414
  async function _cartCountForReq(req) {
2410
- var sid = _readCookie(req, SESSION_COOKIE_NAME);
2415
+ var sid = _readSidCookie(req);
2411
2416
  if (!sid) return 0;
2412
2417
  var c = await deps.cart.bySession(sid);
2413
2418
  if (!c) return 0;
@@ -2421,10 +2426,7 @@ function mount(router, deps) {
2421
2426
  // reader rather than a copy per call site. A missing / malformed /
2422
2427
  // expired cookie returns null — never throws.
2423
2428
  function _currentCustomerEnv(req) {
2424
- var raw = _readCookie(req, AUTH_COOKIE_NAME);
2425
- if (!raw) return null;
2426
- var env;
2427
- try { env = JSON.parse(_b().vault.unseal(raw)); } catch (_e) { return null; }
2429
+ var env = _readAuthEnv(req);
2428
2430
  if (!env || !env.customer_id || !env.exp || env.exp < Date.now()) return null;
2429
2431
  return env;
2430
2432
  }
@@ -2775,11 +2777,11 @@ function mount(router, deps) {
2775
2777
  idempotency_key: "checkout:" + c.id + ":" + _b().uuid.v7(),
2776
2778
  });
2777
2779
  // Set a short-lived pay cookie so /pay/:order_id can serve the
2778
- // client_secret without re-running confirm.
2779
- var payCookie = "shop_pay=" + encodeURIComponent(result.payment_intent.client_secret) +
2780
- "; Max-Age=900; Path=/pay/; HttpOnly; Secure; SameSite=Strict";
2781
- if (res.appendHeader) res.appendHeader("Set-Cookie", payCookie);
2782
- else if (res.setHeader) res.setHeader("Set-Cookie", payCookie);
2780
+ // client_secret without re-running confirm. Scoped to /pay/ +
2781
+ // SameSite=Strict so it's only ever sent to the pay route.
2782
+ _cookieJar().write(res, PAY_COOKIE_NAME, result.payment_intent.client_secret, {
2783
+ expires: new Date(Date.now() + _b().constants.TIME.minutes(15)), path: "/pay/", sameSite: "Strict",
2784
+ });
2783
2785
  res.status(303);
2784
2786
  res.setHeader && res.setHeader("location", "/pay/" + result.order.id);
2785
2787
  return res.end ? res.end() : res.send("");
@@ -2798,14 +2800,7 @@ function mount(router, deps) {
2798
2800
  // Read the client_secret from the shop_pay cookie set on POST
2799
2801
  // /checkout. The cookie is scoped Path=/pay/ + SameSite=Strict
2800
2802
  // so it's only sent to the pay route and never cross-origin.
2801
- var rawCookies = (req.headers && (req.headers.cookie || req.headers.Cookie)) || "";
2802
- var clientSecret = null;
2803
- rawCookies.split(";").forEach(function (p) {
2804
- var t = p.trim();
2805
- if (t.indexOf("shop_pay=") === 0) {
2806
- try { clientSecret = decodeURIComponent(t.slice("shop_pay=".length)); } catch (_e) { /* drop */ }
2807
- }
2808
- });
2803
+ var clientSecret = _readCookie(req, PAY_COOKIE_NAME);
2809
2804
  if (!clientSecret) {
2810
2805
  res.status(303); res.setHeader && res.setHeader("location", "/cart");
2811
2806
  return res.end ? res.end() : res.send("");
@@ -2871,16 +2866,6 @@ function mount(router, deps) {
2871
2866
  return _b().crypto.toBase64Url(buf);
2872
2867
  }
2873
2868
 
2874
- function _sealEnvelope(obj) {
2875
- return _b().vault.seal(JSON.stringify(obj));
2876
- }
2877
- function _unsealEnvelope(s) {
2878
- try {
2879
- var raw = _b().vault.unseal(s);
2880
- return JSON.parse(raw);
2881
- } catch (_e) { return null; }
2882
- }
2883
-
2884
2869
  function _currentCustomer(req) {
2885
2870
  return _currentCustomerEnv(req);
2886
2871
  }
@@ -2940,13 +2925,12 @@ function mount(router, deps) {
2940
2925
  // Seal the ceremony state (challenge + customer_id) into the
2941
2926
  // shop_auth_chal cookie so register-finish verifies against
2942
2927
  // the same challenge without server-side state.
2943
- var sealed = _sealEnvelope({
2928
+ _setChallengeCookie(res, {
2944
2929
  kind: "register",
2945
2930
  customer_id: customer.id,
2946
2931
  challenge: startOpts.challenge,
2947
2932
  created_at: Date.now(),
2948
2933
  });
2949
- _setChallengeCookie(res, sealed);
2950
2934
  res.status(200);
2951
2935
  res.setHeader && res.setHeader("content-type", "application/json");
2952
2936
  return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
@@ -2959,10 +2943,9 @@ function mount(router, deps) {
2959
2943
 
2960
2944
  router.post("/account/passkey/register-finish", async function (req, res) {
2961
2945
  try {
2962
- var rawCookie = _readCookie(req, CHALLENGE_COOKIE_NAME);
2963
- if (!rawCookie) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
2964
- var env = _unsealEnvelope(rawCookie);
2965
- if (!env || env.kind !== "register") {
2946
+ var env = _readChallengeEnv(req);
2947
+ if (!env) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
2948
+ if (env.kind !== "register") {
2966
2949
  res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge");
2967
2950
  }
2968
2951
  var att = _readJsonBody(req);
@@ -2991,10 +2974,10 @@ function mount(router, deps) {
2991
2974
  transports: transports,
2992
2975
  });
2993
2976
  _clearChallengeCookie(res);
2994
- _setAuthCookie(res, _sealEnvelope({
2977
+ _setAuthCookie(res, {
2995
2978
  customer_id: env.customer_id,
2996
- exp: Date.now() + AUTH_TTL_MS,
2997
- }));
2979
+ exp: Date.now() + _b().constants.TIME.days(14),
2980
+ });
2998
2981
  res.status(200);
2999
2982
  return res.end ? res.end("ok") : res.send("ok");
3000
2983
  } catch (e) {
@@ -3029,13 +3012,12 @@ function mount(router, deps) {
3029
3012
  allowCredentials: allow,
3030
3013
  userVerification: "preferred",
3031
3014
  });
3032
- var sealed = _sealEnvelope({
3015
+ _setChallengeCookie(res, {
3033
3016
  kind: "login",
3034
3017
  email_hash: hash,
3035
3018
  challenge: startOpts.challenge,
3036
3019
  created_at: Date.now(),
3037
3020
  });
3038
- _setChallengeCookie(res, sealed);
3039
3021
  res.status(200);
3040
3022
  res.setHeader && res.setHeader("content-type", "application/json");
3041
3023
  return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
@@ -3048,10 +3030,9 @@ function mount(router, deps) {
3048
3030
 
3049
3031
  router.post("/account/passkey/login-finish", async function (req, res) {
3050
3032
  try {
3051
- var rawCookie = _readCookie(req, CHALLENGE_COOKIE_NAME);
3052
- if (!rawCookie) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
3053
- var env = _unsealEnvelope(rawCookie);
3054
- if (!env || env.kind !== "login") { res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge"); }
3033
+ var env = _readChallengeEnv(req);
3034
+ if (!env) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
3035
+ if (env.kind !== "login") { res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge"); }
3055
3036
  var assertion = _readJsonBody(req);
3056
3037
  var credentialId = assertion.id || assertion.rawId;
3057
3038
  if (!credentialId) { res.status(400); return res.end ? res.end("missing credential id") : res.send("missing credential id"); }
@@ -3090,7 +3071,7 @@ function mount(router, deps) {
3090
3071
  }
3091
3072
  // Merge the anonymous cart into a customer-owned cart so
3092
3073
  // the shopper doesn't lose items on sign-in.
3093
- var sid = _readCookie(req, SESSION_COOKIE_NAME);
3074
+ var sid = _readSidCookie(req);
3094
3075
  if (sid) {
3095
3076
  try {
3096
3077
  var anonCart = await deps.cart.bySession(sid);
@@ -3098,10 +3079,10 @@ function mount(router, deps) {
3098
3079
  } catch (_e) { /* best-effort merge; sign-in itself succeeds */ }
3099
3080
  }
3100
3081
  _clearChallengeCookie(res);
3101
- _setAuthCookie(res, _sealEnvelope({
3082
+ _setAuthCookie(res, {
3102
3083
  customer_id: customer.id,
3103
- exp: Date.now() + AUTH_TTL_MS,
3104
- }));
3084
+ exp: Date.now() + _b().constants.TIME.days(14),
3085
+ });
3105
3086
  res.status(200);
3106
3087
  return res.end ? res.end("ok") : res.send("ok");
3107
3088
  } catch (e) {
@@ -162,10 +162,15 @@
162
162
 
163
163
  // ---- constants ----------------------------------------------------------
164
164
 
165
+ // `_b()` is a hoisted function declaration (defined below); resolving the
166
+ // framework constants here at module-eval is safe — the index entry point
167
+ // exposes `framework` before the require cascade.
168
+ var C = _b().constants;
169
+
165
170
  var CACHE_NAMESPACE = "subscription-analytics-cache";
166
171
 
167
- var DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes
168
- var ONE_DAY_MS = 24 * 60 * 60 * 1000;
172
+ var DEFAULT_TTL_MS = C.TIME.minutes(5); // 5 minutes
173
+ var ONE_DAY_MS = C.TIME.days(1);
169
174
  var ONE_YEAR_MS = 365 * ONE_DAY_MS;
170
175
  var DEFAULT_LTV_WINDOW_MS = 90 * ONE_DAY_MS;
171
176
 
@@ -55,6 +55,7 @@ function _b() {
55
55
  if (!bShop) bShop = require("./index");
56
56
  return bShop.framework;
57
57
  }
58
+ var C = _b().constants;
58
59
 
59
60
  // ---- constants ----------------------------------------------------------
60
61
 
@@ -89,12 +90,12 @@ var FREQUENCIES = [
89
90
  // step skipNext / changeFrequency surface; a calendar-accurate
90
91
  // scheduler is out of scope for v1.
91
92
  var PERIOD_MS = Object.freeze({
92
- weekly: 7 * 24 * 60 * 60 * 1000,
93
- biweekly: 14 * 24 * 60 * 60 * 1000,
94
- monthly: 30 * 24 * 60 * 60 * 1000,
95
- quarterly: 90 * 24 * 60 * 60 * 1000,
96
- semiannual: 182 * 24 * 60 * 60 * 1000,
97
- annual: 365 * 24 * 60 * 60 * 1000,
93
+ weekly: C.TIME.days(7),
94
+ biweekly: C.TIME.days(14),
95
+ monthly: C.TIME.days(30),
96
+ quarterly: C.TIME.days(90),
97
+ semiannual: C.TIME.days(182),
98
+ annual: C.TIME.days(365),
98
99
  });
99
100
 
100
101
  // Plan-interval → frequency fallback. When the row has no `frequency`
@@ -115,7 +116,7 @@ function _frequencyFromPlan(plan) {
115
116
  return "monthly";
116
117
  }
117
118
 
118
- var REACTIVATE_GRACE_MS = 90 * 24 * 60 * 60 * 1000;
119
+ var REACTIVATE_GRACE_MS = C.TIME.days(90);
119
120
  var MAX_REASON_LEN = 280;
120
121
  var MAX_SKIP_COUNT = 12;
121
122
  var MAX_QUANTITY = 1000000;
@@ -599,7 +600,7 @@ function create(opts) {
599
600
  if (now - row.cancelled_at > REACTIVATE_GRACE_MS) {
600
601
  var gErr = new Error(
601
602
  "subscriptionControls.reactivate: refused — cancellation is older than the " +
602
- (REACTIVATE_GRACE_MS / (24 * 60 * 60 * 1000)) + "-day grace window"
603
+ (REACTIVATE_GRACE_MS / C.TIME.days(1)) + "-day grace window"
603
604
  );
604
605
  gErr.code = "SUBSCRIPTION_REACTIVATE_GRACE_EXPIRED";
605
606
  throw gErr;
@@ -72,6 +72,7 @@ function _b() {
72
72
  if (!bShop) bShop = require("./index");
73
73
  return bShop.framework;
74
74
  }
75
+ var C = _b().constants;
75
76
 
76
77
  // ---- constants ----------------------------------------------------------
77
78
 
@@ -94,7 +95,7 @@ var MAX_LIST_LIMIT = 200;
94
95
  var DEFAULT_LIST_LIMIT = 50;
95
96
  // Default redemption window: 365 days. Operators wanting a different
96
97
  // horizon pass `expires_at_ms` (absolute) at purchase time.
97
- var DEFAULT_EXPIRY_MS = 365 * 86400 * 1000;
98
+ var DEFAULT_EXPIRY_MS = C.TIME.days(365);
98
99
 
99
100
  // ---- validators ---------------------------------------------------------
100
101
 
@@ -214,7 +214,9 @@ function _extractFromStripeObject(obj) {
214
214
  // Stripe nests current_period_start/_end at the top level when the
215
215
  // event is `customer.subscription.*`. Both are unix seconds; we
216
216
  // store epoch ms.
217
+ // allow:raw-time-literal — Stripe sends unix SECONDS; the * 1000 converts a runtime field to epoch ms, not a fixed duration
217
218
  var periodStart = obj && obj.current_period_start != null ? obj.current_period_start * 1000 : null;
219
+ // allow:raw-time-literal — Stripe sends unix SECONDS; the * 1000 converts a runtime field to epoch ms, not a fixed duration
218
220
  var periodEnd = obj && obj.current_period_end != null ? obj.current_period_end * 1000 : null;
219
221
  return {
220
222
  stripe_subscription_id: obj && obj.id,
@@ -75,10 +75,10 @@ var ALLOWED_STATUSES = [
75
75
  // ticket is considered breached. Closed / resolved tickets never
76
76
  // breach. Drives `slaCheck()` for the scheduler.
77
77
  var SLA_MS = {
78
- urgent: 1 * 3600 * 1000,
79
- high: 4 * 3600 * 1000,
80
- normal: 24 * 3600 * 1000,
81
- low: 72 * 3600 * 1000,
78
+ urgent: _b().constants.TIME.hours(1),
79
+ high: _b().constants.TIME.hours(4),
80
+ normal: _b().constants.TIME.hours(24),
81
+ low: _b().constants.TIME.hours(72),
82
82
  };
83
83
 
84
84
  // FSM. Encoded as an explicit allow-list keyed by from-state. Every
@@ -71,6 +71,7 @@ function _b() {
71
71
  if (!bShop) bShop = require("./index");
72
72
  return bShop.framework;
73
73
  }
74
+ var C = _b().constants;
74
75
 
75
76
  var JURISDICTION_RE = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
76
77
  var SLUG_RE = /^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$/;
@@ -81,7 +82,7 @@ var MAX_ESCALATE_DAYS = 365;
81
82
  var MAX_ESCALATED_TO_LEN = 256;
82
83
  var DEFAULT_CHANNELS = Object.freeze(["email"]);
83
84
 
84
- var DAY_MS = 86400000;
85
+ var DAY_MS = C.TIME.days(1);
85
86
 
86
87
  var STATUSES = Object.freeze(["queued", "sent", "escalated", "renewed", "expired"]);
87
88
 
@@ -90,6 +90,7 @@ function _b() {
90
90
  if (!bShop) bShop = require("./index");
91
91
  return bShop.framework;
92
92
  }
93
+ var C = _b().constants;
93
94
 
94
95
  // ---- constants ----------------------------------------------------------
95
96
 
@@ -107,7 +108,7 @@ var MAX_PENALTY_REASON_LEN = 1000;
107
108
 
108
109
  var DEFAULT_DAYS_LATE_MIN = 1;
109
110
  var MAX_DAYS_LATE_MIN = 3650; // ~10 years
110
- var MS_PER_DAY = 24 * 60 * 60 * 1000;
111
+ var MS_PER_DAY = C.TIME.days(1);
111
112
 
112
113
  var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
113
114
 
@@ -137,7 +137,7 @@ var ZERO_WIDTH_RE = new RegExp(
137
137
  "[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u061C]"
138
138
  );
139
139
 
140
- var MS_PER_DAY = 86400000;
140
+ var MS_PER_DAY = _b().constants.TIME.days(1);
141
141
 
142
142
  var bShop;
143
143
  function _b() {
@@ -3,8 +3,8 @@
3
3
  "_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
4
4
  "packages": {
5
5
  "blamejs": {
6
- "version": "0.12.40",
7
- "tag": "v0.12.40",
6
+ "version": "0.12.41",
7
+ "tag": "v0.12.41",
8
8
  "license": "Apache-2.0",
9
9
  "author": "blamejs contributors",
10
10
  "source": "https://github.com/blamejs/blamejs",
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.12.x
10
10
 
11
+ - v0.12.41 (2026-05-24) — **`b.did` — W3C DID resolution (did:key + did:web) feeding the credential verifiers.** Resolve W3C Decentralized Identifiers (DID Core 1.0) to verification keys — the link that lets a credential's issuer be named by a DID rather than a raw key. Resolve the issuer DID of a b.vc / b.mdoc / b.scitt credential to a node:crypto KeyObject and hand it to the verifier. did:key encodes the public key in the identifier (multicodec + base58btc), so resolution is deterministic and offline — Ed25519, P-256, P-384, and secp256k1 round-trip; did:web places the DID document at an HTTPS URL derived from the identifier, with the network fetch left to the operator (the framework parses the operator-fetched document and extracts its verification methods, as publicKeyMultibase or publicKeyJwk). b.did.keyToDid encodes a KeyObject as a did:key (an issuer naming itself), b.did.parse splits the identifier (and returns the did:web URL to fetch), and b.did.resolve returns the document and verification keys. DID Core 1.0 is a W3C Recommendation; the method specs (did:key W3C CCG report, did:web DID method registry — EUDI-mandated) are deployed-stable. Composes node:crypto; no new runtime dependency. **Added:** *`b.did.resolve(did, opts?)` / `b.did.keyToDid(publicKey)` / `b.did.parse(did)`* — `resolve` returns `{ didDocument, verificationMethods: [{ id, controller, type, publicKey }] }` with each `publicKey` a `node:crypto` KeyObject ready for `b.vc.verify` / `b.mdoc.verifyIssuerSigned` / `b.scitt.verifyStatement`. did:key resolves deterministically and offline (base58btc + multicodec → Ed25519 raw key or EC compressed point, rebuilt via SPKI); did:web requires the operator to pass the fetched DID document as `opts.document` (the URL to GET is on `b.did.parse(did).url`) and the document `id` must match the requested DID. A publicKeyJwk in a DID document is imported only after its `kty`/`crv` is allowlisted (Ed25519 / P-256 / P-384 / secp256k1) — an unexpected key type from an untrusted document is refused, not blindly imported. `keyToDid` encodes an Ed25519 / P-256 / P-384 / secp256k1 KeyObject as a did:key; `parse` derives the did:web HTTPS URL (`host[:port][:path]` → `https://host/path/did.json`, or `/.well-known/did.json`). Unknown methods, malformed base58, unsupported multicodec codes, and unsupported key types are each refused.
12
+
11
13
  - v0.12.40 (2026-05-24) — **`b.mdoc` — ISO 18013-5 mdoc / mDL issuer-data verification.** Verify the issuer-signed data of an ISO/IEC 18013-5 mdoc — the credential format behind mobile driving licences (mDL) and the ISO track of the EU Digital Identity Wallet. This is the relying-party side: confirm that the data elements a holder presents were signed by the issuer and have not been altered. An mdoc's IssuerSigned carries the disclosed data elements and an issuerAuth that is a COSE_Sign1 (b.cose) over a Mobile Security Object (MSO) holding a per-element digest. b.mdoc.verifyIssuerSigned verifies the COSE signature with the issuer certificate from the COSE x5chain header, parses the MSO, enforces its validityInfo window, and recomputes each disclosed element's digest (the full Tag-24 IssuerSignedItemBytes) to match it against the MSO constant-time — the integrity check that makes selective disclosure trustworthy. An absent or mismatched digest is refused. Signing algorithms follow b.cose verification (the classical ES256/384/512 + EdDSA that real mDL issuers use; the caller names the allowlist); opts.trustAnchorsPem additionally verifies the issuer certificate chain. This completes the credential trio alongside W3C VCDM (b.vc) and IETF SD-JWT VC (b.auth.sdJwtVc). Composes b.cose + b.cbor; no new runtime dependency. **Added:** *`b.mdoc.verifyIssuerSigned(issuerSigned, opts)`* — Takes the CBOR `IssuerSigned` map (the operator extracts it from the device response / QR) and returns `{ docType, version, digestAlgorithm, validityInfo, namespaces, signerCert, alg }`. Verifies the COSE_Sign1 `issuerAuth` against the mandatory `opts.algorithms` allowlist using the issuer certificate from its `x5chain` (label 33) header; parses the Tag-24 Mobile Security Object; enforces the MSO `validityInfo` window against `opts.at` (default now; must be a valid Date; malformed dates fail closed); and recomputes the digest of every disclosed `IssuerSignedItem` (over the full Tag-24 bytes, with the MSO `digestAlgorithm` — SHA-256/384/512) to match the MSO `valueDigests` constant-time — an absent or mismatched digest is refused with `mdoc/digest-mismatch`. `opts.expectedDocType` pins the document type; `opts.trustAnchorsPem` (a PEM string or array) additionally verifies the issuer certificate chain and validity at the asserted time. A malformed `x5chain` certificate is refused with a clean `mdoc/bad-cert`. The mdoc device-authentication half (the SessionTranscript-bound holder-binding proof) is a presentation-protocol concern and is not part of issuer-data verification.
12
14
 
13
15
  - v0.12.39 (2026-05-24) — **`b.vc` — W3C Verifiable Credentials 2.0 (issue / verify, JOSE + COSE securing).** Issue and verify W3C Verifiable Credentials (VC Data Model 2.0, a W3C Recommendation) secured per Securing Verifiable Credentials using JOSE and COSE (VC-JOSE-COSE, also a W3C Recommendation, May 2025). A verifiable credential is a tamper-evident, signed set of claims an issuer makes about a subject — a diploma, a membership, a license, an age assertion. Two securing mechanisms are supported, both signing the credential itself (no JWT/CWT claims wrapper): JOSE produces a compact JWS with the vc+jwt media type, signed with ES256/384/512 or EdDSA; COSE produces a COSE_Sign1 (application/vc+cose) over b.cose, which also accepts ML-DSA-87 for PQC-forward deployments. b.vc.verify auto-detects the form from the input, requires an algorithm allowlist, always refuses the JOSE none algorithm, re-checks the VCDM 2.0 structural rules, and enforces the validFrom / validUntil window. This is the W3C credential model, distinct from the IETF SD-JWT VC already at b.auth.sdJwtVc. Composes b.cose; no new runtime dependency. **Added:** *`b.vc.issue(credential, opts)` / `b.vc.verify(secured, opts)`* — `issue` validates the credential against the VCDM 2.0 structural rules (the `credentials/v2` context first, a `VerifiableCredential` type, an issuer, a credential subject) and signs it: `securing: "jose"` returns a compact JWS string (`typ` header `vc+jwt`), `securing: "cose"` returns COSE_Sign1 bytes (`typ` header `application/vc+cose`, content type `application/vc`) via `b.cose`. The credential is the exact signed payload — no JWT/CWT claims are injected. `verify` auto-detects the securing form from the input (compact-JWS string vs. COSE_Sign1 bytes), verifies the signature against the mandatory `opts.algorithms` allowlist (the JOSE `none` algorithm is always refused), re-checks the structural rules, enforces the `validFrom` / `validUntil` window against `opts.at` (default now; must be a valid Date), and optionally matches `opts.expectedIssuer` against the credential issuer id. Returns `{ credential, securing, alg, issuer }`.
@@ -133,6 +133,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
133
133
  - **Trusted timestamping** — `b.tsa` RFC 3161 timestamp client: `buildRequest` a TimeStampReq, `parseResponse`, and `verifyToken` against your data — the message imprint, sent nonce, critical/sole `id-kp-timeStamping` EKU, and CMS signature are all checked, with optional certificate-chain verification. Timestamp a release artifact, audit checkpoint, or signed statement against any RFC 3161 TSA. Composes `b.cms` + the in-tree ASN.1 DER codec
134
134
  - **Verifiable Credentials** — `b.vc` W3C Verifiable Credentials Data Model 2.0 (VC-JOSE-COSE): `issue` / `verify` a signed credential as a compact JWS (`vc+jwt`, ES256/384/512 + EdDSA) or a COSE_Sign1 (`vc+cose`, + ML-DSA-87) over `b.cose`. VCDM structural + `validFrom`/`validUntil` checks; the JOSE `none` algorithm is always refused. The W3C model, distinct from the IETF SD-JWT VC at `b.auth.sdJwtVc`
135
135
  - **Mobile credentials (mDL)** — `b.mdoc` ISO/IEC 18013-5 issuer-data verification: `verifyIssuerSigned` checks the COSE_Sign1 IssuerAuth (issuer cert from the `x5chain` header), the Mobile Security Object validity window, and every disclosed element's digest against the MSO `valueDigests` (the selective-disclosure integrity check), with optional issuer-chain verification. The ISO credential ecosystem alongside `b.vc` and `b.auth.sdJwtVc`. Composes `b.cose` + `b.cbor`
136
+ - **Decentralized Identifiers** — `b.did` W3C DID resolution (DID Core 1.0): `resolve` a `did:key` (deterministic, offline — Ed25519 / P-256 / P-384 / secp256k1) or `did:web` (operator-fetched document) to `node:crypto` verification keys, so a credential's issuer DID resolves to the key that verifies it (`b.vc` / `b.mdoc` / `b.scitt`). `keyToDid` names a key as a `did:key`; document JWKs are kty/crv-allowlisted before import
136
137
  - **Document parsers** — `b.parsers` (XML / TOML / YAML / .env); `b.config` (schema-validated env)
137
138
  - **File-type detection** — `b.fileType` magic-byte content classification with deny-on-upload categories (image / document / archive / executable / etc.)
138
139
  ### Content-safety gates