@tokenoftrust/storefront-runner 3.9.0 → 3.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -113,7 +113,30 @@ function fullCartProduct(value: unknown, handle: string, scope: string): value i
|
|
|
113
113
|
Object.values(variant.option_values).every(option => typeof option === "string") &&
|
|
114
114
|
variant.inventory && typeof variant.inventory === "object" && !Array.isArray(variant.inventory) && typeof variant.inventory.available === "boolean");
|
|
115
115
|
}
|
|
116
|
+
/** The caller's per-request id (its token's `jti`), echoed so the caller's decision log joins this one. */
|
|
117
|
+
const CORRELATION_ID = /^cart-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
118
|
+
/**
|
|
119
|
+
* One `commerce-final-cart-decision` line per request: the caller's correlation id, the finding, the
|
|
120
|
+
* HTTP status and the elapsed time. Closed values only — never cart, tenant-config or shopper data.
|
|
121
|
+
* A throw answers JSON `authority-error` so the caller maps it to a shopper sentence.
|
|
122
|
+
*/
|
|
116
123
|
export async function POST(context: APIContext): Promise<Response> {
|
|
124
|
+
const started = Date.now();
|
|
125
|
+
const header = context.request.headers.get("x-commerce-correlation-id") ?? "";
|
|
126
|
+
const correlationId = CORRELATION_ID.test(header) ? header : "absent";
|
|
127
|
+
let response: Response;
|
|
128
|
+
let threw = false;
|
|
129
|
+
try { response = await authorize(context); } catch { threw = true; response = deny("authority-error", 500); }
|
|
130
|
+
let finding = "approved";
|
|
131
|
+
if (response.status !== 200 || threw) {
|
|
132
|
+
try { finding = String((await response.clone().json() as { finding?: unknown }).finding ?? "unreadable").slice(0, 80); } catch { finding = "unreadable"; }
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
console.info("commerce-final-cart-decision", JSON.stringify({ correlationId, ok: response.status === 200, finding, status: response.status, threw, ms: Date.now() - started }));
|
|
136
|
+
} catch { /* Diagnostic delivery cannot alter the decision. */ }
|
|
137
|
+
return response;
|
|
138
|
+
}
|
|
139
|
+
async function authorize(context: APIContext): Promise<Response> {
|
|
117
140
|
if (Number(context.request.headers.get("content-length") ?? 0) > 65536) return deny("malformed-request");
|
|
118
141
|
const raw = await context.request.text();
|
|
119
142
|
if (raw.length > 65536) return deny("malformed-request");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.2",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -331,12 +331,15 @@ export type CartFieldVisibility = "native" | "internal" | "visible" | "variant-o
|
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
333
|
* Characters the checkout store does not keep in a field name, each mapped to a look-alike it
|
|
334
|
-
* keeps. The store
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* non-
|
|
334
|
+
* keeps. The store hashes `code + name + value` with the name as it received it
|
|
335
|
+
* (https://wiki.foxycart.com/v/2.0/hmac_validation), and it receives request names through PHP,
|
|
336
|
+
* which converts "dots and spaces in variable names ... to underscores"
|
|
337
|
+
* (https://www.php.net/manual/en/language.variables.external.php, "HTML Forms (GET and POST)").
|
|
338
|
+
* Not documented, measured 2026-10-23 against tot-preview.foxycart.com by submitting each name
|
|
339
|
+
* with a non-matching hash and reading it back from the "Cart Validation Error: <name>" refusal:
|
|
340
|
+
* `[` also becomes `_` (a `[…]` pair turns the field into an array), leading spaces are dropped,
|
|
341
|
+
* `||` ends the name, and a name holding `&` or `"` is never validated. Every other character
|
|
342
|
+
* (NBSP, `/`, `'`, `+`, `-`, `#`, `=`, `%`, `,`, `:`, a single `|`, non-ASCII) came back unchanged.
|
|
340
343
|
*/
|
|
341
344
|
const CHECKOUT_NAME_LOOKALIKES: Readonly<Record<string, string>> = {
|
|
342
345
|
" ": " ",
|