@tokenoftrust/storefront-runner 4.0.0 → 4.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.
- package/apps/storefront/src/lib/checkout/commerce/checkoutHandoffRoute.ts +3 -1
- package/apps/storefront/src/lib/checkout/shared/lazyCheckoutLoader.ts +49 -1
- package/apps/storefront/src/lib/content/seo/faqItems.ts +18 -4
- package/package.json +1 -1
- package/packages/public-runtime/src/checkout.ts +16 -0
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
withShippingPolicy,
|
|
33
33
|
withReturnUrl,
|
|
34
34
|
trustedCartReturnUrl,
|
|
35
|
+
CHECKOUT_NAVIGATION_REFERRER_POLICY,
|
|
35
36
|
SHIPPING_UNAVAILABLE_CART_MESSAGE,
|
|
36
37
|
ShippingPolicyUnavailableError,
|
|
37
38
|
type CartDenialCode,
|
|
@@ -183,6 +184,7 @@ async function cartFormPost(context: APIContext): Promise<Response> {
|
|
|
183
184
|
headers: {
|
|
184
185
|
location,
|
|
185
186
|
"cache-control": "no-store",
|
|
187
|
+
"referrer-policy": CHECKOUT_NAVIGATION_REFERRER_POLICY,
|
|
186
188
|
"x-tot-payment-authorized": "false",
|
|
187
189
|
},
|
|
188
190
|
});
|
|
@@ -233,7 +235,7 @@ function sampleResponse(body: string | null, status: number, location?: string):
|
|
|
233
235
|
return new Response(body, { status, headers: {
|
|
234
236
|
"content-type": "application/json; charset=utf-8",
|
|
235
237
|
"cache-control": "private, no-store",
|
|
236
|
-
"referrer-policy": "no-referrer",
|
|
238
|
+
"referrer-policy": location ? CHECKOUT_NAVIGATION_REFERRER_POLICY : "no-referrer",
|
|
237
239
|
"x-robots-tag": "noindex, nofollow",
|
|
238
240
|
"x-tot-payment-authorized": "false",
|
|
239
241
|
...(location ? { location } : {}),
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
* published (CloudFront/S3) output — both render Layout.astro. The session id
|
|
39
39
|
* is not part of that HTML.
|
|
40
40
|
*/
|
|
41
|
+
import { CHECKOUT_NAVIGATION_REFERRER_POLICY } from "@tot/public-runtime";
|
|
41
42
|
import { CART_UNAVAILABLE_MESSAGE } from "@/lib/checkout/commerce/cartSession";
|
|
42
43
|
|
|
43
44
|
export const LAZY_CHECKOUT_LOADER_IDLE_MS = 8000;
|
|
@@ -77,6 +78,7 @@ export function lazyCheckoutLoaderJs(options: LazyCheckoutLoaderOptions): string
|
|
|
77
78
|
var cartOrigin = ${literal(options.cartOrigin)};
|
|
78
79
|
var sessionHref = ${literal(options.sessionHref)};
|
|
79
80
|
var cookiePath = ${literal(options.cookiePath)};
|
|
81
|
+
var referrerPolicy = ${literal(CHECKOUT_NAVIGATION_REFERRER_POLICY)};
|
|
80
82
|
var storeNotPublic = ${options.storeNotPublic ? "true" : "false"};
|
|
81
83
|
var events = ${literal(LAZY_CHECKOUT_LOADER_EVENTS)};
|
|
82
84
|
var state = 'idle';
|
|
@@ -87,6 +89,44 @@ export function lazyCheckoutLoaderJs(options: LazyCheckoutLoaderOptions): string
|
|
|
87
89
|
if (!url) return false;
|
|
88
90
|
try { return new URL(url, document.baseURI).origin === cartOrigin; } catch (e) { return false; }
|
|
89
91
|
}
|
|
92
|
+
// Foxy copies the Referer onto cart_cancel_and_continue_link and a later
|
|
93
|
+
// origin-only Referer overwrites it. The document policy stays strict-origin.
|
|
94
|
+
// Checkout requests opt into the full store-home URL, then the address is put back
|
|
95
|
+
// when the request snapshots it synchronously (a cart script). A navigation
|
|
96
|
+
// snapshots after this turn, so that path leaves the store-home URL in place.
|
|
97
|
+
function storeHome() {
|
|
98
|
+
return location.origin + cookiePath;
|
|
99
|
+
}
|
|
100
|
+
function revealStoreHome() {
|
|
101
|
+
var home = storeHome();
|
|
102
|
+
if (location.href === home) return;
|
|
103
|
+
window.history.replaceState(window.history.state, "", home);
|
|
104
|
+
}
|
|
105
|
+
function restoreHref(current) {
|
|
106
|
+
if (location.href !== current) window.history.replaceState(window.history.state, "", current);
|
|
107
|
+
}
|
|
108
|
+
var origCreate = document.createElement;
|
|
109
|
+
document.createElement = function (name) {
|
|
110
|
+
var el = origCreate.apply(document, arguments);
|
|
111
|
+
if (String(name).toLowerCase() !== "script") return el;
|
|
112
|
+
var proto = window.HTMLScriptElement && window.HTMLScriptElement.prototype;
|
|
113
|
+
var srcDesc = proto && Object.getOwnPropertyDescriptor(proto, "src");
|
|
114
|
+
if (!srcDesc || !srcDesc.get || !srcDesc.set) return el;
|
|
115
|
+
Object.defineProperty(el, "src", {
|
|
116
|
+
configurable: true,
|
|
117
|
+
enumerable: true,
|
|
118
|
+
get: function () { return srcDesc.get.call(this); },
|
|
119
|
+
set: function (value) {
|
|
120
|
+
if (!targetsCart(value)) return srcDesc.set.call(this, value);
|
|
121
|
+
this.referrerPolicy = referrerPolicy;
|
|
122
|
+
var current = location.href;
|
|
123
|
+
revealStoreHome();
|
|
124
|
+
try { return srcDesc.set.call(this, value); }
|
|
125
|
+
finally { restoreHref(current); }
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return el;
|
|
129
|
+
};
|
|
90
130
|
function commerceReachable() {
|
|
91
131
|
var nodes = document.querySelectorAll(${managedCart} + ', a[href], form[action]');
|
|
92
132
|
for (var i = 0; i < nodes.length; i++) {
|
|
@@ -209,8 +249,10 @@ export function lazyCheckoutLoaderJs(options: LazyCheckoutLoaderOptions): string
|
|
|
209
249
|
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
210
250
|
var anchor = event.target && event.target.closest ? event.target.closest('a[href]') : null;
|
|
211
251
|
if (!anchor || anchor.target === '_blank' || !targetsCart(anchor.href)) return;
|
|
252
|
+
anchor.referrerPolicy = referrerPolicy;
|
|
212
253
|
if (state === 'ready') {
|
|
213
254
|
stampAnchor(anchor);
|
|
255
|
+
if (!event.defaultPrevented) revealStoreHome();
|
|
214
256
|
return;
|
|
215
257
|
}
|
|
216
258
|
if (event.defaultPrevented) return;
|
|
@@ -268,7 +310,13 @@ export function lazyCheckoutLoaderJs(options: LazyCheckoutLoaderOptions): string
|
|
|
268
310
|
var cartForm = targetsCart(form.getAttribute('action'));
|
|
269
311
|
if (!managed && !cartForm) return;
|
|
270
312
|
if (form.dataset.cartSession === '1') {
|
|
271
|
-
if (!cartForm || state === 'ready')
|
|
313
|
+
if (!cartForm || state === 'ready') {
|
|
314
|
+
if (!event.defaultPrevented && state === 'ready') {
|
|
315
|
+
form.referrerPolicy = referrerPolicy;
|
|
316
|
+
revealStoreHome();
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
272
320
|
event.preventDefault();
|
|
273
321
|
whenReady(function () { stampForm(form); resubmit(form); });
|
|
274
322
|
return;
|
|
@@ -40,14 +40,28 @@ function detailsPair(block: string): FaqItem | null {
|
|
|
40
40
|
return { q, a: textOf(rest) };
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
43
|
+
/** A heading inside details is that pair's question, not a second heading pair. */
|
|
44
|
+
function insideDetails(index: number, ranges: Array<{ start: number; end: number }>): boolean {
|
|
45
|
+
return ranges.some((range) => index >= range.start && index < range.end);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** FAQ pairs from sanitized body HTML: question-phrased h2–h4 outside details, plus every details/summary pair. */
|
|
44
49
|
export function faqFromHtml(html: string | undefined): FaqItem[] {
|
|
45
50
|
if (!html) return [];
|
|
51
|
+
const ranges = [...html.matchAll(/<details\b[^>]*>[\s\S]*?<\/details>/gi)].map((match) => ({
|
|
52
|
+
start: match.index ?? 0,
|
|
53
|
+
end: (match.index ?? 0) + match[0].length,
|
|
54
|
+
}));
|
|
46
55
|
const parts = html.split(/(<h[2-4][^>]*>[\s\S]*?<\/h[2-4]>)/i);
|
|
47
56
|
const items: FaqItem[] = [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (let i = 0; i < parts.length; i++) {
|
|
59
|
+
const part = parts[i] ?? "";
|
|
60
|
+
if (i % 2 === 1) {
|
|
61
|
+
const q = textOf(part);
|
|
62
|
+
if (isQuestion(q) && !insideDetails(offset, ranges)) items.push({ q, a: textOf(parts[i + 1] ?? "") });
|
|
63
|
+
}
|
|
64
|
+
offset += part.length;
|
|
51
65
|
}
|
|
52
66
|
for (const block of html.matchAll(/<details\b[^>]*>[\s\S]*?<\/details>/gi)) {
|
|
53
67
|
const pair = detailsPair(block[0]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
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",
|
|
@@ -212,6 +212,22 @@ export const INTERNAL_CART_FIELDS = ["currency", "tenant", "verification", "ship
|
|
|
212
212
|
/** The signed cart option the checkout store's Continue Shopping link reads (`withReturnUrl`). */
|
|
213
213
|
export const RETURN_URL_CART_FIELD = "return_url";
|
|
214
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Referrer policy for a request the browser makes to the managed checkout (a cart
|
|
217
|
+
* script, a cart navigation, or the hop that follows a 303 onto the cart).
|
|
218
|
+
*
|
|
219
|
+
* Measured 2026-10-24 against tot-preview: Foxy sets `cart_cancel_and_continue_link`
|
|
220
|
+
* from the Referer and ignores a `continue` parameter. An origin-only Referer
|
|
221
|
+
* overwrites a fuller one; the first request with no Referer falls back to the
|
|
222
|
+
* store's configured store URL (the host root). `strict-origin-when-cross-origin`
|
|
223
|
+
* sends only that origin on a cross-origin checkout request, so empty-cart and
|
|
224
|
+
* receipt links drop the tenant path. This value sends the full URL on https and
|
|
225
|
+
* nothing on a downgrade. It is not the document policy: a redirect response's
|
|
226
|
+
* Referrer-Policy replaces the element's, and `no-referrer` on that response
|
|
227
|
+
* clears the path. The site-wide header stays strict-origin.
|
|
228
|
+
*/
|
|
229
|
+
export const CHECKOUT_NAVIGATION_REFERRER_POLICY = "no-referrer-when-downgrade";
|
|
230
|
+
|
|
215
231
|
/** The signed cart option the checkout store reads the shipping charge from. */
|
|
216
232
|
export const SHIPPING_POLICY_CART_FIELD = "shipping_policy";
|
|
217
233
|
|