@tokenoftrust/storefront-runner 2.5.0 → 2.5.1
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.
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/** Operational browser client. All authority and state changes remain in the authenticated APIs. */
|
|
2
2
|
import type { FulfillmentView } from "../fulfillmentService.js";
|
|
3
|
+
import { parseCommerceDeskApiRoot } from "./commerceDeskRouting.js";
|
|
3
4
|
export function mountCommerceDesk(root: HTMLElement): void {
|
|
4
5
|
const status = root.querySelector<HTMLElement>("[data-desk-status]")!;
|
|
5
6
|
const rows = root.querySelector<HTMLElement>("[data-desk-orders]")!;
|
|
6
7
|
const next = root.querySelector<HTMLButtonElement>("[data-desk-next]")!;
|
|
8
|
+
const api = parseCommerceDeskApiRoot(root.dataset.commerceApi, root.dataset.targetTenant);
|
|
9
|
+
if (!api) {
|
|
10
|
+
rows.replaceChildren();
|
|
11
|
+
next.hidden = true;
|
|
12
|
+
root.querySelector<HTMLButtonElement>("[data-desk-refresh]")!.disabled = true;
|
|
13
|
+
status.textContent = "Open the selected store to view its fulfillment orders.";
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
7
16
|
let cursor: string | null = null;
|
|
8
17
|
async function request(url: string, body?: unknown): Promise<unknown> {
|
|
9
|
-
const response = await fetch(url
|
|
18
|
+
const response = await fetch(`${api}${url}`, {
|
|
10
19
|
credentials: "same-origin",
|
|
11
20
|
cache: "no-store",
|
|
12
21
|
...(body ? { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) } : {}),
|
|
@@ -45,8 +54,8 @@ export function mountCommerceDesk(root: HTMLElement): void {
|
|
|
45
54
|
try {
|
|
46
55
|
await request(
|
|
47
56
|
action === "claim"
|
|
48
|
-
? "/
|
|
49
|
-
: `/
|
|
57
|
+
? "/batches"
|
|
58
|
+
: `/orders/${encodeURIComponent(order.orderId)}`,
|
|
50
59
|
action === "claim"
|
|
51
60
|
? { orderIds: [order.orderId], expectedVersions: { [order.orderId]: order.eventVersion } }
|
|
52
61
|
: { action, expectedVersion: order.eventVersion, scope: "whole-order" },
|
|
@@ -86,10 +95,11 @@ export function mountCommerceDesk(root: HTMLElement): void {
|
|
|
86
95
|
status.textContent = "Loading orders…";
|
|
87
96
|
try {
|
|
88
97
|
const page = (await request(
|
|
89
|
-
`/
|
|
98
|
+
`/orders?limit=25${after ? `&cursor=${encodeURIComponent(after)}` : ""}`,
|
|
90
99
|
)) as { tenantId: string; orders: FulfillmentView[]; nextCursor: string | null };
|
|
91
100
|
if (page.tenantId !== root.dataset.targetTenant) {
|
|
92
101
|
rows.replaceChildren();
|
|
102
|
+
cursor = null;
|
|
93
103
|
next.hidden = true;
|
|
94
104
|
status.textContent = "Open the selected store to view its fulfillment orders.";
|
|
95
105
|
return;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Same-origin navigation only; fulfillment APIs independently authorize the routed tenant. */
|
|
2
|
+
import { withBase } from "../../publication/shared/basePath.js";
|
|
3
|
+
|
|
4
|
+
const API_ROOT = "/api/admin/fulfillment";
|
|
5
|
+
|
|
6
|
+
function canonicalHost(value: string): boolean {
|
|
7
|
+
if (!value || value.length > 253 || value.split(".").some((part) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(part))) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(`https://${value}/`);
|
|
12
|
+
return url.host === value && url.pathname === "/";
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Explicit server API metadata, never a preview/revision URL or request query. */
|
|
19
|
+
export function commerceDeskApiRoot(input: {
|
|
20
|
+
routedTenant: { appDomain: string; pathPrefixTenants?: boolean };
|
|
21
|
+
apiBasePath: string | undefined;
|
|
22
|
+
targetTenant: string;
|
|
23
|
+
registeredTarget: { appDomain: string } | null;
|
|
24
|
+
}): string | null {
|
|
25
|
+
const { routedTenant, apiBasePath, targetTenant, registeredTarget } = input;
|
|
26
|
+
if (
|
|
27
|
+
!canonicalHost(routedTenant.appDomain) || !canonicalHost(targetTenant) ||
|
|
28
|
+
registeredTarget?.appDomain !== targetTenant || typeof apiBasePath !== "string"
|
|
29
|
+
) return null;
|
|
30
|
+
if (apiBasePath && (
|
|
31
|
+
!apiBasePath.startsWith("/") || !apiBasePath.slice(1).includes(".") ||
|
|
32
|
+
!canonicalHost(apiBasePath.slice(1))
|
|
33
|
+
)) return null;
|
|
34
|
+
if (!apiBasePath && targetTenant === routedTenant.appDomain) return API_ROOT;
|
|
35
|
+
if (!apiBasePath && !routedTenant.pathPrefixTenants) return null;
|
|
36
|
+
if (!targetTenant.includes(".")) return null;
|
|
37
|
+
return withBase(`/${targetTenant}`, API_ROOT);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The SSR endpoint must agree with the displayed canonical tenant; malformed metadata stays inert. */
|
|
41
|
+
export function parseCommerceDeskApiRoot(value: string | undefined, targetTenant: string | undefined): string | null {
|
|
42
|
+
if (!targetTenant || !canonicalHost(targetTenant)) return null;
|
|
43
|
+
if (value === API_ROOT) return value;
|
|
44
|
+
return targetTenant.includes(".") && value === withBase(`/${targetTenant}`, API_ROOT) ? value : null;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
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",
|