@doswiftly/storefront-sdk 19.0.0 → 19.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/CHANGELOG.md +64 -0
- package/dist/core/client/create-client.d.ts.map +1 -1
- package/dist/core/client/create-client.js +8 -1
- package/dist/core/client/remote-debug-transport.d.ts +48 -0
- package/dist/core/client/remote-debug-transport.d.ts.map +1 -0
- package/dist/core/client/remote-debug-transport.js +198 -0
- package/dist/core/client/types.d.ts +51 -1
- package/dist/core/client/types.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/react/cookies.d.ts +11 -1
- package/dist/react/cookies.d.ts.map +1 -1
- package/dist/react/cookies.js +11 -4
- package/dist/react/hooks/use-cart-manager.d.ts +7 -0
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
- package/dist/react/hooks/use-cart-manager.js +5 -2
- package/dist/react/providers/cart-manager-provider.d.ts +8 -1
- package/dist/react/providers/cart-manager-provider.d.ts.map +1 -1
- package/dist/react/providers/cart-manager-provider.js +2 -1
- package/dist/react/providers/storefront-provider.d.ts +12 -1
- package/dist/react/providers/storefront-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-provider.js +4 -3
- package/dist/react/stores/currency.store.d.ts +6 -1
- package/dist/react/stores/currency.store.d.ts.map +1 -1
- package/dist/react/stores/currency.store.js +49 -41
- package/dist/react/stores/language.store.d.ts +6 -1
- package/dist/react/stores/language.store.d.ts.map +1 -1
- package/dist/react/stores/language.store.js +46 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 19.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 868894e: Add remote debug shipping for both GraphQL **and** cookie-lifecycle events, so you can trace a real shopper session end-to-end instead of reproducing it with DevTools open.
|
|
8
|
+
|
|
9
|
+
**Why**: the most useful checkout signal is often "when was the `cart-id` / currency / language cookie set or cleared?" (e.g. `cart-id` cleared by `complete()`), but debug output previously covered only GraphQL request/response. Now both flow through one channel keyed by a single `sessionId`, so one filter in your logs gives the full interleaved timeline.
|
|
10
|
+
|
|
11
|
+
**Additive (backward-compatible)** — everything is opt-in and off by default:
|
|
12
|
+
1. `DebugOptions.remote?: boolean | RemoteDebugOptions | RemoteDebugSink` — ship debug events to a backend ingest endpoint. Pass a pre-built transport (a `RemoteDebugSink`) to share **one** channel/`sessionId` across the client and the cookie stores.
|
|
13
|
+
2. New `createRemoteDebugTransport(...)` factory (+ `RemoteDebugTransport` / `RemoteDebugSink` / `RemoteDebugOptions` types).
|
|
14
|
+
3. Cookie stores emit a new `DebugEvent` with `phase: 'cookie'` (`{ name, action: 'set' | 'clear', value?, maxAge? }`) when wired with a sink:
|
|
15
|
+
- `createBrowserCartCookieStore({ onDebug })` — `cart-id` (when you own the cookie store).
|
|
16
|
+
- `useCartManager({ cookieDebug })` / `<CartManagerProvider cookieDebug={...}>` — `cart-id` when the cart manager owns the cookie store.
|
|
17
|
+
- `createCurrencyStore({ onDebug })` / `createLanguageStore(initial, { onDebug })` — currency / language.
|
|
18
|
+
- `<StorefrontProvider cookieDebug={...}>` forwards the sink to the currency + language stores.
|
|
19
|
+
4. `DebugOptions`, `DebugEvent`, `RemoteDebugOptions`, `RemoteDebugSink` are exported for typing.
|
|
20
|
+
|
|
21
|
+
**Usage example** (one shared channel across GraphQL + cookies):
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import {
|
|
25
|
+
createStorefrontClient,
|
|
26
|
+
createRemoteDebugTransport,
|
|
27
|
+
createBrowserCartCookieStore,
|
|
28
|
+
} from "@doswiftly/storefront-sdk";
|
|
29
|
+
|
|
30
|
+
const debugOn = process.env.NEXT_PUBLIC_SDK_DEBUG_REMOTE === "true";
|
|
31
|
+
const transport = debugOn
|
|
32
|
+
? createRemoteDebugTransport({
|
|
33
|
+
endpoint: `${apiUrl}/storefront/debug-logs`,
|
|
34
|
+
shopSlug,
|
|
35
|
+
fetch: globalThis.fetch,
|
|
36
|
+
})
|
|
37
|
+
: null;
|
|
38
|
+
|
|
39
|
+
const client = createStorefrontClient({
|
|
40
|
+
apiUrl,
|
|
41
|
+
shopSlug,
|
|
42
|
+
debug: transport
|
|
43
|
+
? { userErrors: true, response: true, timing: true, remote: transport }
|
|
44
|
+
: false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// cart-id set/clear on the same channel:
|
|
48
|
+
const cartCookieStore = createBrowserCartCookieStore(
|
|
49
|
+
transport ? { onDebug: (e) => transport.capture(e) } : undefined,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// currency + language via the provider:
|
|
53
|
+
// <StorefrontProvider cookieDebug={transport ? (e) => transport.capture(e) : undefined} … />
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Behaviour:
|
|
57
|
+
- **Batched** (default 10 events / 5s); `keepalive` is used on page-hide/unload flushes so trailing events survive a navigation or payment-gateway redirect.
|
|
58
|
+
- **Fire-and-forget**: a failed send is swallowed — telemetry can never break the request that produced it.
|
|
59
|
+
- Cookies are **not** sent (`credentials: 'omit'`); the shop is identified by the `X-Shop-Slug` header.
|
|
60
|
+
- **Redaction**: the SDK masks `Authorization` / `customerAccessToken` in _headers_. It does **not** redact request `variables` or response bodies — the backend ingest endpoint masks emails + credential-named fields. Enable `response` / `headers` only for operations whose payload you're comfortable sending to your backend.
|
|
61
|
+
- Default endpoint is `${apiUrl}/storefront/debug-logs`; override via `RemoteDebugOptions.endpoint`.
|
|
62
|
+
|
|
63
|
+
**Migration checklist for existing storefronts**:
|
|
64
|
+
- [ ] No action required — all of the above is opt-in and defaults to off.
|
|
65
|
+
- [ ] To adopt: build one transport (gated behind an env flag), pass it to `createStorefrontClient`, the cart cookie store, and `<StorefrontProvider cookieDebug>`, then confirm requests to `/storefront/debug-logs`.
|
|
66
|
+
|
|
3
67
|
## 19.0.0
|
|
4
68
|
|
|
5
69
|
### Major Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../../../src/core/client/create-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAKjB,MAAM,SAAS,CAAC;AAQjB,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAgHvF"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 0 runtime dependencies.
|
|
12
12
|
*/
|
|
13
13
|
import { createExecute, resolveDebugOptions } from './execute';
|
|
14
|
+
import { maybeAttachRemoteTransport } from './remote-debug-transport';
|
|
14
15
|
import { compose } from './compose';
|
|
15
16
|
import { dedupe } from './dedupe';
|
|
16
17
|
import { hashQuery } from './hash';
|
|
@@ -24,7 +25,13 @@ export function createStorefrontClient(config) {
|
|
|
24
25
|
// Normalise the public `boolean | 'verbose' | DebugOptions` union into the
|
|
25
26
|
// canonical ResolvedDebugOptions shape (or null = no logging). Env var
|
|
26
27
|
// fallback (`DOSWIFTLY_SDK_DEBUG`) applies only when `debug` is undefined.
|
|
27
|
-
|
|
28
|
+
// When `debug.remote` is set, the resolved sink is wrapped so every event is
|
|
29
|
+
// also shipped to the backend ingest endpoint (default `${apiUrl}/storefront/debug-logs`).
|
|
30
|
+
const resolvedDebug = maybeAttachRemoteTransport(resolveDebugOptions(debug), debug, {
|
|
31
|
+
apiUrl,
|
|
32
|
+
shopSlug,
|
|
33
|
+
fetch: customFetch,
|
|
34
|
+
});
|
|
28
35
|
// Create the innermost execute function (native fetch)
|
|
29
36
|
const innerExecute = createExecute({ endpoint, fetch: customFetch, debug: resolvedDebug });
|
|
30
37
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote debug transport — batches debug events and ships them to a backend ingest
|
|
3
|
+
* endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
|
|
4
|
+
*
|
|
5
|
+
* Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
|
|
6
|
+
* storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
|
|
7
|
+
* after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
|
|
8
|
+
* matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
|
|
9
|
+
* otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
|
|
10
|
+
*
|
|
11
|
+
* Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
|
|
12
|
+
* call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
|
|
13
|
+
*/
|
|
14
|
+
import type { DebugEvent, DebugOptions } from './types';
|
|
15
|
+
import type { ResolvedDebugOptions } from './execute';
|
|
16
|
+
export interface RemoteDebugTransportConfig {
|
|
17
|
+
/** Fully-resolved ingest endpoint URL. */
|
|
18
|
+
endpoint: string;
|
|
19
|
+
/** Shop slug sent as `X-Shop-Slug` so the backend can resolve the tenant. */
|
|
20
|
+
shopSlug: string;
|
|
21
|
+
/** Fetch implementation (the same one the client transport uses). */
|
|
22
|
+
fetch: typeof globalThis.fetch;
|
|
23
|
+
batchSize?: number;
|
|
24
|
+
flushIntervalMs?: number;
|
|
25
|
+
sessionId?: string;
|
|
26
|
+
sdkVersion?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RemoteDebugTransport {
|
|
29
|
+
/** Buffer one debug event; flushes automatically per the batching policy. */
|
|
30
|
+
capture(event: DebugEvent): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createRemoteDebugTransport(config: RemoteDebugTransportConfig): RemoteDebugTransport;
|
|
33
|
+
/**
|
|
34
|
+
* Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
|
|
35
|
+
* the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function cookieDebugEvent(name: string, action: 'set' | 'clear', value?: string, maxAge?: number): DebugEvent;
|
|
38
|
+
/**
|
|
39
|
+
* If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
|
|
40
|
+
* remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
|
|
41
|
+
* local `log` behaviour is preserved exactly.
|
|
42
|
+
*/
|
|
43
|
+
export declare function maybeAttachRemoteTransport(resolved: ResolvedDebugOptions | null, debugConfig: boolean | 'verbose' | DebugOptions | undefined, context: {
|
|
44
|
+
apiUrl: string;
|
|
45
|
+
shopSlug: string;
|
|
46
|
+
fetch: typeof globalThis.fetch;
|
|
47
|
+
}): ResolvedDebugOptions | null;
|
|
48
|
+
//# sourceMappingURL=remote-debug-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-debug-transport.d.ts","sourceRoot":"","sources":["../../../src/core/client/remote-debug-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAuC,MAAM,SAAS,CAAC;AAC7F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMtD,MAAM,WAAW,0BAA0B;IACzC,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAClC;AAuDD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,oBAAoB,CAmFnG;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,KAAK,GAAG,OAAO,EACvB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAKZ;AAOD;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EACrC,WAAW,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,EAC3D,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,GAC5E,oBAAoB,GAAG,IAAI,CA0C7B"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote debug transport — batches debug events and ships them to a backend ingest
|
|
3
|
+
* endpoint over `fetch`. 0 runtime dependencies (only `fetch`, `Date`, `globalThis.crypto`).
|
|
4
|
+
*
|
|
5
|
+
* Fire-and-forget by contract: a failed flush is swallowed so telemetry can never break the
|
|
6
|
+
* storefront request that produced the event. The buffer flushes when it reaches `batchSize`,
|
|
7
|
+
* after `flushIntervalMs`, or when the page is hidden / unloading (browser only) — the last one
|
|
8
|
+
* matters for redirect-heavy flows (e.g. payment gateway hand-off) where trailing events would
|
|
9
|
+
* otherwise be lost. `fetch(..., { keepalive: true })` lets that final send complete during unload.
|
|
10
|
+
*
|
|
11
|
+
* Credentials are never sent (`credentials: 'omit'`) — this is telemetry, not an authenticated
|
|
12
|
+
* call. The shop is identified by the `X-Shop-Slug` header; the backend validates it.
|
|
13
|
+
*/
|
|
14
|
+
import { CART_COOKIE_NAME } from '../cart/cookie-config';
|
|
15
|
+
const DEFAULT_BATCH_SIZE = 10;
|
|
16
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
17
|
+
/**
|
|
18
|
+
* Pull identifying ids out of operation variables so the backend log is filterable by cart /
|
|
19
|
+
* order without parsing the whole payload. `cartId` comes from an explicit `cartId` variable, or
|
|
20
|
+
* from the `id` variable on cart operations (cart mutations pass the cart as `id: cartId`). The
|
|
21
|
+
* operation-name check is anchored (`^cart`) so only cart operations (`CartAddLines`, …) qualify —
|
|
22
|
+
* an unrelated op that merely contains "cart" (e.g. `ProductInCartBadge`) won't mislabel its `id`.
|
|
23
|
+
*/
|
|
24
|
+
function extractIdentifiers(event) {
|
|
25
|
+
// Cookie events carry the value directly — surface the cart-id so its set/clear correlates with
|
|
26
|
+
// the cart's GraphQL operations under one `cartId` filter.
|
|
27
|
+
if (event.phase === 'cookie') {
|
|
28
|
+
const cookie = event.data;
|
|
29
|
+
return cookie.name === CART_COOKIE_NAME && typeof cookie.value === 'string'
|
|
30
|
+
? { cartId: cookie.value }
|
|
31
|
+
: {};
|
|
32
|
+
}
|
|
33
|
+
const variables = event.data.variables;
|
|
34
|
+
if (!variables || typeof variables !== 'object')
|
|
35
|
+
return {};
|
|
36
|
+
const v = variables;
|
|
37
|
+
const op = event.operationName ?? '';
|
|
38
|
+
const out = {};
|
|
39
|
+
const cartId = typeof v.cartId === 'string'
|
|
40
|
+
? v.cartId
|
|
41
|
+
: /^cart/i.test(op) && typeof v.id === 'string'
|
|
42
|
+
? v.id
|
|
43
|
+
: undefined;
|
|
44
|
+
if (cartId)
|
|
45
|
+
out.cartId = cartId;
|
|
46
|
+
if (typeof v.orderId === 'string')
|
|
47
|
+
out.orderId = v.orderId;
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generate a session correlation id. Prefers `crypto.randomUUID`; falls back to a
|
|
52
|
+
* timestamp + random suffix when unavailable (uniqueness within one client is all that is
|
|
53
|
+
* required here — there is no security property to preserve).
|
|
54
|
+
*/
|
|
55
|
+
function generateSessionId() {
|
|
56
|
+
const c = globalThis.crypto;
|
|
57
|
+
if (c && typeof c.randomUUID === 'function')
|
|
58
|
+
return c.randomUUID();
|
|
59
|
+
return `sdk-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
60
|
+
}
|
|
61
|
+
export function createRemoteDebugTransport(config) {
|
|
62
|
+
const { endpoint, shopSlug, fetch: fetchFn, batchSize = DEFAULT_BATCH_SIZE, flushIntervalMs = DEFAULT_FLUSH_INTERVAL_MS, sdkVersion, } = config;
|
|
63
|
+
const sessionId = config.sessionId ?? generateSessionId();
|
|
64
|
+
let buffer = [];
|
|
65
|
+
let timer = null;
|
|
66
|
+
function clearFlushTimer() {
|
|
67
|
+
if (timer !== null) {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
timer = null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function flush(useKeepalive = false) {
|
|
73
|
+
if (buffer.length === 0)
|
|
74
|
+
return;
|
|
75
|
+
const events = buffer;
|
|
76
|
+
buffer = [];
|
|
77
|
+
clearFlushTimer();
|
|
78
|
+
// Fire-and-forget — a transport failure must never surface to the caller.
|
|
79
|
+
void Promise.resolve()
|
|
80
|
+
.then(() => fetchFn(endpoint, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'X-Shop-Slug': shopSlug,
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify({ sessionId, sdkVersion, events }),
|
|
87
|
+
// `keepalive` only for unload flushes — on a live page a large batch can exceed the
|
|
88
|
+
// browser's keepalive body quota (~64KB) and be rejected (then silently swallowed),
|
|
89
|
+
// whereas an ordinary fetch has no such cap. Unload flushes need it to survive navigation.
|
|
90
|
+
keepalive: useKeepalive,
|
|
91
|
+
credentials: 'omit',
|
|
92
|
+
}))
|
|
93
|
+
.catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
// Flush on page hide / unload so trailing events survive a navigation or gateway redirect.
|
|
96
|
+
// Browser-only — guarded so the core stays usable in Node / Edge / Deno. These listeners live for
|
|
97
|
+
// the transport's lifetime (no teardown): the storefront client is expected to be created once per
|
|
98
|
+
// app (the SDK's singleton/Context convention), so they are registered once. Do not build a new
|
|
99
|
+
// client with `debug.remote` on every render — that would accumulate listeners.
|
|
100
|
+
const g = globalThis;
|
|
101
|
+
if (typeof g.addEventListener === 'function') {
|
|
102
|
+
g.addEventListener('pagehide', () => flush(true));
|
|
103
|
+
g.addEventListener('visibilitychange', () => {
|
|
104
|
+
if (g.document?.visibilityState === 'hidden')
|
|
105
|
+
flush(true);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
capture(event) {
|
|
110
|
+
buffer.push({
|
|
111
|
+
timestamp: new Date().toISOString(),
|
|
112
|
+
phase: event.phase,
|
|
113
|
+
operationName: event.operationName ?? null,
|
|
114
|
+
...extractIdentifiers(event),
|
|
115
|
+
data: event.data,
|
|
116
|
+
});
|
|
117
|
+
if (buffer.length >= batchSize) {
|
|
118
|
+
flush();
|
|
119
|
+
}
|
|
120
|
+
else if (timer === null) {
|
|
121
|
+
timer = setTimeout(() => flush(), flushIntervalMs);
|
|
122
|
+
// Don't keep a Node/Edge event loop alive for a pending flush (browser timers are numbers
|
|
123
|
+
// with no `unref`; Node/Edge timers are objects that have it).
|
|
124
|
+
if (timer && typeof timer === 'object' && 'unref' in timer) {
|
|
125
|
+
timer.unref();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Build a `phase: 'cookie'` {@link DebugEvent} for a cookie set/clear. Single construction point so
|
|
133
|
+
* the cart / currency / language stores emit an identical shape (`{ name, action, value?, maxAge? }`).
|
|
134
|
+
*/
|
|
135
|
+
export function cookieDebugEvent(name, action, value, maxAge) {
|
|
136
|
+
const data = { name, action };
|
|
137
|
+
if (value !== undefined)
|
|
138
|
+
data.value = value;
|
|
139
|
+
if (maxAge !== undefined)
|
|
140
|
+
data.maxAge = maxAge;
|
|
141
|
+
return { phase: 'cookie', operationName: undefined, data };
|
|
142
|
+
}
|
|
143
|
+
/** True when `remote` is a pre-built sink (has a callable `capture`) rather than options/boolean. */
|
|
144
|
+
function isRemoteSink(remote) {
|
|
145
|
+
return typeof remote === 'object' && typeof remote.capture === 'function';
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* If `debug.remote` is enabled, wrap the resolved debug sink so every event is also handed to a
|
|
149
|
+
* remote transport. Returns the input untouched when remote is off (or debug is disabled), so the
|
|
150
|
+
* local `log` behaviour is preserved exactly.
|
|
151
|
+
*/
|
|
152
|
+
export function maybeAttachRemoteTransport(resolved, debugConfig, context) {
|
|
153
|
+
if (!resolved)
|
|
154
|
+
return resolved;
|
|
155
|
+
// `remote` only exists on the object form of `debug` (`DebugOptions`).
|
|
156
|
+
if (typeof debugConfig !== 'object' || !debugConfig.remote) {
|
|
157
|
+
return resolved;
|
|
158
|
+
}
|
|
159
|
+
const remote = debugConfig.remote;
|
|
160
|
+
// A pre-built sink (e.g. a transport shared with the cookie stores) is used as-is, so the whole
|
|
161
|
+
// app reports through one channel + sessionId. Otherwise build a transport from the options.
|
|
162
|
+
let transport;
|
|
163
|
+
if (isRemoteSink(remote)) {
|
|
164
|
+
transport = remote;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const remoteOpts = remote === true ? {} : remote;
|
|
168
|
+
transport = createRemoteDebugTransport({
|
|
169
|
+
endpoint: remoteOpts.endpoint ?? `${context.apiUrl.replace(/\/$/, '')}/storefront/debug-logs`,
|
|
170
|
+
shopSlug: context.shopSlug,
|
|
171
|
+
fetch: context.fetch,
|
|
172
|
+
batchSize: remoteOpts.batchSize,
|
|
173
|
+
flushIntervalMs: remoteOpts.flushIntervalMs,
|
|
174
|
+
sessionId: remoteOpts.sessionId,
|
|
175
|
+
sdkVersion: remoteOpts.sdkVersion,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const originalLog = resolved.log;
|
|
179
|
+
return {
|
|
180
|
+
...resolved,
|
|
181
|
+
// Telemetry must never break the request: `execute` calls `debug.log` synchronously outside any
|
|
182
|
+
// try/catch, so a throwing local sink or remote `capture` is swallowed here.
|
|
183
|
+
log: (event) => {
|
|
184
|
+
try {
|
|
185
|
+
originalLog(event);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* local sink threw — ignore */
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
transport.capture(event);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
/* remote sink threw — ignore */
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -110,14 +110,64 @@ export interface DebugOptions {
|
|
|
110
110
|
userErrors?: boolean;
|
|
111
111
|
/** Custom logger sink. Default `console.log('[StorefrontSDK]', event.phase, event.operationName, event.data)`. Use for routing into `pino` / `winston`. */
|
|
112
112
|
log?: (event: DebugEvent) => void;
|
|
113
|
+
/**
|
|
114
|
+
* Ship debug events to a remote ingest endpoint, in addition to the local `log` sink.
|
|
115
|
+
*
|
|
116
|
+
* Pass `true` to use defaults, or a `RemoteDebugOptions` object to customise the endpoint and
|
|
117
|
+
* batching. Events are buffered and sent with `fetch` (with `keepalive` on page-unload flushes so
|
|
118
|
+
* trailing events survive a navigation); a failed send is swallowed so telemetry can never break
|
|
119
|
+
* the request that produced it. Cookies are not sent (`credentials: 'omit'`). Default `false`.
|
|
120
|
+
*
|
|
121
|
+
* Redaction note: the SDK masks `Authorization` / `customerAccessToken` in *headers*. It does NOT
|
|
122
|
+
* redact the request `variables` or response body it ships — the backend ingest endpoint is the
|
|
123
|
+
* safety net there (it masks emails and credential-named fields). Enable `response` / `headers`
|
|
124
|
+
* only for operations whose payload you are comfortable sending to your backend.
|
|
125
|
+
*
|
|
126
|
+
* Which events are shipped is governed by the other flags above — `remote: true` alone ships the
|
|
127
|
+
* minimal set (request variables + response status + `userErrors`); enable `response` / `headers`
|
|
128
|
+
* / `timing` to ship more.
|
|
129
|
+
*
|
|
130
|
+
* Pass a pre-built transport (a `RemoteDebugSink`, e.g. from `createRemoteDebugTransport`) to
|
|
131
|
+
* share ONE channel + sessionId across the GraphQL client and the cookie stores, so a single
|
|
132
|
+
* `sessionId` in your logs gives the full interleaved timeline (GraphQL + cookie set/clear).
|
|
133
|
+
*/
|
|
134
|
+
remote?: boolean | RemoteDebugOptions | RemoteDebugSink;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Configuration for the remote debug transport (`DebugOptions.remote`). All fields optional.
|
|
138
|
+
*/
|
|
139
|
+
export interface RemoteDebugOptions {
|
|
140
|
+
/** Ingest endpoint URL. Default `${apiUrl}/storefront/debug-logs`. */
|
|
141
|
+
endpoint?: string;
|
|
142
|
+
/** Flush the buffer once this many events accumulate. Default `10`. */
|
|
143
|
+
batchSize?: number;
|
|
144
|
+
/** Flush the buffer after this many milliseconds even if `batchSize` is not reached. Default `5000`. */
|
|
145
|
+
flushIntervalMs?: number;
|
|
146
|
+
/** Correlation id grouping every event from one client instance. Default a generated id. */
|
|
147
|
+
sessionId?: string;
|
|
148
|
+
/** Optional client/build version label attached to every batch. */
|
|
149
|
+
sdkVersion?: string;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Minimal surface of a debug transport — accepts already-built `DebugEvent`s. A `RemoteDebugSink`
|
|
153
|
+
* created once (via `createRemoteDebugTransport`) and shared between the GraphQL client and the
|
|
154
|
+
* cookie stores forms a single debug channel with one `sessionId`.
|
|
155
|
+
*/
|
|
156
|
+
export interface RemoteDebugSink {
|
|
157
|
+
capture(event: DebugEvent): void;
|
|
113
158
|
}
|
|
114
159
|
/**
|
|
115
160
|
* Structured event handed to a custom `DebugOptions.log` sink. Stable contract:
|
|
116
161
|
* `phase` and `operationName` are always present; `data` carries the merged
|
|
117
162
|
* payload selected by `DebugOptions` flags.
|
|
163
|
+
*
|
|
164
|
+
* `phase: 'cookie'` events come from the SDK cookie layer (cart / currency / language) rather than
|
|
165
|
+
* the GraphQL transport — `operationName` is `undefined` and `data` is
|
|
166
|
+
* `{ name, action: 'set' | 'clear', value?, maxAge? }`. They let you see exactly when a cookie was
|
|
167
|
+
* written or removed (e.g. `cart-id` cleared by `complete()`) alongside the GraphQL timeline.
|
|
118
168
|
*/
|
|
119
169
|
export interface DebugEvent {
|
|
120
|
-
phase: 'request' | 'response';
|
|
170
|
+
phase: 'request' | 'response' | 'cookie';
|
|
121
171
|
operationName: string | undefined;
|
|
122
172
|
data: Record<string, unknown>;
|
|
123
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/client/types.ts"],"names":[],"mappings":"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"}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
export { createStorefrontClient } from './client/create-client';
|
|
37
|
-
export type { StorefrontClient, StorefrontClientConfig, Middleware, ExecuteFn, GraphQLRequest, GraphQLResponse, GraphQLErrorInfo, UserError, CacheStrategy, CacheOptions, TypedDocumentString, } from './client/types';
|
|
37
|
+
export type { StorefrontClient, StorefrontClientConfig, Middleware, ExecuteFn, GraphQLRequest, GraphQLResponse, GraphQLErrorInfo, UserError, CacheStrategy, CacheOptions, TypedDocumentString, DebugOptions, DebugEvent, RemoteDebugOptions, RemoteDebugSink, } from './client/types';
|
|
38
|
+
export { createRemoteDebugTransport } from './client/remote-debug-transport';
|
|
39
|
+
export type { RemoteDebugTransport, RemoteDebugTransportConfig } from './client/remote-debug-transport';
|
|
38
40
|
export { authMiddleware } from './middleware/auth';
|
|
39
41
|
export { currencyMiddleware } from './middleware/currency';
|
|
40
42
|
export { languageMiddleware } from './middleware/language';
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,YAAY,EACZ,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"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"}
|
package/dist/core/index.js
CHANGED
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
*/
|
|
36
36
|
// Client factory
|
|
37
37
|
export { createStorefrontClient } from './client/create-client';
|
|
38
|
+
// Remote debug transport — build one and share it across the client + cookie stores
|
|
39
|
+
// (`debug: { remote: transport }`) for a single debug channel / sessionId.
|
|
40
|
+
export { createRemoteDebugTransport } from './client/remote-debug-transport';
|
|
38
41
|
// Middleware
|
|
39
42
|
export { authMiddleware } from './middleware/auth';
|
|
40
43
|
export { currencyMiddleware } from './middleware/currency';
|
package/dist/react/cookies.d.ts
CHANGED
|
@@ -26,6 +26,16 @@ export declare function setCookie(name: string, value: string, options?: {
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function deleteCookie(name: string, path?: string): void;
|
|
28
28
|
import type { CartCookieStore } from '../core/cart/cart-recovery';
|
|
29
|
+
import type { DebugEvent } from '../core/client/types';
|
|
30
|
+
export interface BrowserCartCookieStoreOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Optional debug sink. When provided, every `set` / `clear` of the `cart-id` cookie emits a
|
|
33
|
+
* `phase: 'cookie'` {@link DebugEvent}. Wire it to the same transport the GraphQL client uses
|
|
34
|
+
* (one shared sessionId) to see exactly when the cart cookie is written or removed — e.g. the
|
|
35
|
+
* `clear()` that `complete()` performs after checkout.
|
|
36
|
+
*/
|
|
37
|
+
onDebug?: (event: DebugEvent) => void;
|
|
38
|
+
}
|
|
29
39
|
/**
|
|
30
40
|
* Build a browser-side `CartCookieStore` backed by `document.cookie`.
|
|
31
41
|
*
|
|
@@ -45,5 +55,5 @@ import type { CartCookieStore } from '../core/cart/cart-recovery';
|
|
|
45
55
|
* });
|
|
46
56
|
* ```
|
|
47
57
|
*/
|
|
48
|
-
export declare function createBrowserCartCookieStore(): CartCookieStore;
|
|
58
|
+
export declare function createBrowserCartCookieStore(options?: BrowserCartCookieStoreOptions): CartCookieStore;
|
|
49
59
|
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIrD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACpF,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAG3D;AAGD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIrD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACpF,IAAI,CAON;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAM,GAAG,IAAI,CAG3D;AAGD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,MAAM,WAAW,6BAA6B;IAC5C;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,CAAC,EAAE,6BAA6B,GACtC,eAAe,CAcjB"}
|
package/dist/react/cookies.js
CHANGED
|
@@ -38,6 +38,7 @@ export function deleteCookie(name, path = '/') {
|
|
|
38
38
|
document.cookie = `${name}=;max-age=0;path=${path}`;
|
|
39
39
|
}
|
|
40
40
|
import { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from '../core/cart/cookie-config';
|
|
41
|
+
import { cookieDebugEvent } from '../core/client/remote-debug-transport';
|
|
41
42
|
/**
|
|
42
43
|
* Build a browser-side `CartCookieStore` backed by `document.cookie`.
|
|
43
44
|
*
|
|
@@ -57,12 +58,18 @@ import { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from '../core/cart/cookie-confi
|
|
|
57
58
|
* });
|
|
58
59
|
* ```
|
|
59
60
|
*/
|
|
60
|
-
export function createBrowserCartCookieStore() {
|
|
61
|
+
export function createBrowserCartCookieStore(options) {
|
|
62
|
+
const emit = options?.onDebug;
|
|
61
63
|
return {
|
|
62
64
|
get: () => getCookie(CART_COOKIE_NAME),
|
|
63
|
-
set: (cartId,
|
|
64
|
-
|
|
65
|
+
set: (cartId, setOptions) => {
|
|
66
|
+
const maxAge = setOptions?.maxAge ?? CART_COOKIE_MAX_AGE;
|
|
67
|
+
setCookie(CART_COOKIE_NAME, cartId, { maxAge });
|
|
68
|
+
emit?.(cookieDebugEvent(CART_COOKIE_NAME, 'set', cartId, maxAge));
|
|
69
|
+
},
|
|
70
|
+
clear: () => {
|
|
71
|
+
deleteCookie(CART_COOKIE_NAME);
|
|
72
|
+
emit?.(cookieDebugEvent(CART_COOKIE_NAME, 'clear'));
|
|
65
73
|
},
|
|
66
|
-
clear: () => deleteCookie(CART_COOKIE_NAME),
|
|
67
74
|
};
|
|
68
75
|
}
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
*/
|
|
88
88
|
import type { Cart, CartLineInput, CartLineUpdateInput, CartBuyerIdentityInput, CartAddressInput, CartAttributeInput, CartCompleteInput, CartSelectShippingMethodInput, CartSelectPaymentMethodInput, CartClearPaymentSelectionInput, CartApplyGiftCardInput, CartRemoveGiftCardInput, CartUpdateGiftCardRecipientInput, PaymentCreateInput, PaymentSession } from '../../core/cart/types';
|
|
89
89
|
import type { CartMutationOutcome, CartCompleteOutcome } from '../../core/cart/cart-client';
|
|
90
|
+
import type { DebugEvent } from '../../core/client/types';
|
|
90
91
|
import { type CartExpiredEvent } from '../../core/cart/cart-recovery';
|
|
91
92
|
/**
|
|
92
93
|
* Names of mutations exposed by the hook — narrows `status.operation` for
|
|
@@ -165,6 +166,12 @@ export interface UseCartManagerOptions extends CartManagerLifecycleCallbacks {
|
|
|
165
166
|
* customer service "view this cart", multi-cart B2B selectors.
|
|
166
167
|
*/
|
|
167
168
|
initialCartId?: string | null;
|
|
169
|
+
/**
|
|
170
|
+
* Optional debug sink — emits a `phase: 'cookie'` {@link DebugEvent} whenever the `cart-id`
|
|
171
|
+
* cookie is set or cleared (including the `clear()` that `complete()` performs after checkout).
|
|
172
|
+
* Wire it to the same transport the GraphQL client uses for a single shared debug timeline.
|
|
173
|
+
*/
|
|
174
|
+
cookieDebug?: (event: DebugEvent) => void;
|
|
168
175
|
}
|
|
169
176
|
export interface UseCartManagerResult {
|
|
170
177
|
getCart: () => Promise<Cart | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AAMH,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC9B,sBAAsB,EACtB,uBAAuB,EACvB,gCAAgC,EAChC,kBAAkB,EAClB,cAAc,EACf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC5F,OAAO,EAKL,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAGvC;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,YAAY,GACZ,YAAY,GACZ,qBAAqB,GACrB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,GACrB,YAAY,GACZ,kBAAkB,GAClB,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,eAAe,GACf,gBAAgB,GAChB,yBAAyB,GACzB,UAAU,GACV,eAAe,CAAC;AAEpB;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GAChE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAEzD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,6BAA6B;IAC5C,kEAAkE;IAClE,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5D,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC9D;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAC3E;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAsB,SAAQ,6BAA6B;IAC1E;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -117,8 +117,11 @@ function isDedicatedChannelError(err) {
|
|
|
117
117
|
export function useCartManager(options) {
|
|
118
118
|
const { cartClient } = useStorefrontClientContext();
|
|
119
119
|
const [status, setStatus] = useState({ type: 'idle' });
|
|
120
|
-
// Cookie store is stateless — keep one instance per hook mount.
|
|
121
|
-
|
|
120
|
+
// Cookie store is stateless — keep one instance per hook mount. A stable ref-backed wrapper lets
|
|
121
|
+
// the optional `cookieDebug` change without rebuilding the store (and therefore the runner).
|
|
122
|
+
const cookieDebugRef = useRef(options?.cookieDebug);
|
|
123
|
+
cookieDebugRef.current = options?.cookieDebug;
|
|
124
|
+
const cookieStore = useMemo(() => createBrowserCartCookieStore({ onDebug: (event) => cookieDebugRef.current?.(event) }), []);
|
|
122
125
|
// Latest-callback ref: the mutation wrapper reads `lifecycleRef.current` so it
|
|
123
126
|
// always fires the current callbacks while keeping its own deps array empty
|
|
124
127
|
// (a stable `wrapMutation` avoids rebuilding every mutation on each render).
|
|
@@ -29,19 +29,26 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { type ReactNode } from 'react';
|
|
31
31
|
import { type CartManagerLifecycleCallbacks, type UseCartManagerResult } from '../hooks/use-cart-manager';
|
|
32
|
+
import type { DebugEvent } from '../../core/client/types';
|
|
32
33
|
export interface CartManagerProviderProps extends CartManagerLifecycleCallbacks {
|
|
33
34
|
/**
|
|
34
35
|
* Server-known cart-id seed forwarded to `useCartManager`. Used only when the
|
|
35
36
|
* `cart-id` cookie is empty on mount — the cookie always wins when present.
|
|
36
37
|
*/
|
|
37
38
|
initialCartId?: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Optional debug sink forwarded to `useCartManager` — emits a `phase: 'cookie'` {@link DebugEvent}
|
|
41
|
+
* on every `cart-id` set/clear (including the `complete()` clear). Wire it to the same transport
|
|
42
|
+
* the GraphQL client uses for one shared debug timeline.
|
|
43
|
+
*/
|
|
44
|
+
cookieDebug?: (event: DebugEvent) => void;
|
|
38
45
|
children: ReactNode;
|
|
39
46
|
}
|
|
40
47
|
/**
|
|
41
48
|
* Creates one `useCartManager` instance and exposes it to descendants via
|
|
42
49
|
* Context. Lifecycle callbacks are forwarded to the underlying hook.
|
|
43
50
|
*/
|
|
44
|
-
export declare function CartManagerProvider({ initialCartId, onMutationStart, onMutationSuccess, onMutationError, children, }: CartManagerProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export declare function CartManagerProvider({ initialCartId, cookieDebug, onMutationStart, onMutationSuccess, onMutationError, children, }: CartManagerProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
45
52
|
/**
|
|
46
53
|
* Read the shared cart manager provided by the nearest `<CartManagerProvider>`.
|
|
47
54
|
* Throws when called outside a provider.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart-manager-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/cart-manager-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"cart-manager-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/cart-manager-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAEL,KAAK,6BAA6B,EAClC,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAK1D,MAAM,WAAW,wBAAyB,SAAQ,6BAA6B;IAC7E;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC1C,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,aAAa,EACb,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,QAAQ,GACT,EAAE,wBAAwB,2CAU1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,oBAAoB,CAM5D"}
|
|
@@ -37,9 +37,10 @@ CartManagerContext.displayName = 'CartManagerContext';
|
|
|
37
37
|
* Creates one `useCartManager` instance and exposes it to descendants via
|
|
38
38
|
* Context. Lifecycle callbacks are forwarded to the underlying hook.
|
|
39
39
|
*/
|
|
40
|
-
export function CartManagerProvider({ initialCartId, onMutationStart, onMutationSuccess, onMutationError, children, }) {
|
|
40
|
+
export function CartManagerProvider({ initialCartId, cookieDebug, onMutationStart, onMutationSuccess, onMutationError, children, }) {
|
|
41
41
|
const manager = useCartManager({
|
|
42
42
|
initialCartId,
|
|
43
|
+
cookieDebug,
|
|
43
44
|
onMutationStart,
|
|
44
45
|
onMutationSuccess,
|
|
45
46
|
onMutationError,
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { type StorefrontClientProviderProps } from './storefront-client-provider';
|
|
33
33
|
import { type CurrencyProviderProps } from './currency-provider';
|
|
34
|
+
import type { DebugEvent } from '../../core/client/types';
|
|
34
35
|
export interface StorefrontProviderProps extends StorefrontClientProviderProps {
|
|
35
36
|
shopData: CurrencyProviderProps['shopData'];
|
|
36
37
|
/**
|
|
@@ -91,6 +92,16 @@ export interface StorefrontProviderProps extends StorefrontClientProviderProps {
|
|
|
91
92
|
* scheduler arm on the first render (cold start) without a whoami round-trip.
|
|
92
93
|
*/
|
|
93
94
|
initialExpiresAt?: string | null;
|
|
95
|
+
/**
|
|
96
|
+
* Optional debug sink for cookie events. When provided, the currency and language stores emit a
|
|
97
|
+
* `phase: 'cookie'` {@link DebugEvent} whenever their cookie is written. Wire it to the same
|
|
98
|
+
* transport the GraphQL client uses (one shared sessionId) so cookie writes appear on the same
|
|
99
|
+
* timeline as GraphQL operations. (Cart-id events are wired separately on the cart cookie store.)
|
|
100
|
+
*
|
|
101
|
+
* Must be referentially stable (e.g. from a memoized module singleton) — the stores are created
|
|
102
|
+
* once at provider mount, so a later `cookieDebug` swap is not observed.
|
|
103
|
+
*/
|
|
104
|
+
cookieDebug?: (event: DebugEvent) => void;
|
|
94
105
|
}
|
|
95
|
-
export declare function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialAccessToken, initialExpiresAt, initialLanguage, autoRefresh, authBasePath, }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
106
|
+
export declare function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialAccessToken, initialExpiresAt, initialLanguage, autoRefresh, authBasePath, cookieDebug, }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
96
107
|
//# sourceMappingURL=storefront-provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storefront-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AASH,OAAO,EAA4B,KAAK,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC5G,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"storefront-provider.d.ts","sourceRoot":"","sources":["../../../src/react/providers/storefront-provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AASH,OAAO,EAA4B,KAAK,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAC5G,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAQnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,WAAW,uBAAwB,SAAQ,6BAA6B;IAC5E,QAAQ,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAC5C;;;;;;;;;;OAUG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;;;;;;;;;;;;;OAgBG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,MAAM,EACN,UAAU,EACV,QAAQ,EACR,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,EAAE,uBAAuB,2CAyCzB"}
|
|
@@ -45,10 +45,11 @@ import { BotProtectionWidget } from '../bot-protection/bot-protection-widget';
|
|
|
45
45
|
import { createSessionExpiredEmitter } from '../../core/auth/session-events';
|
|
46
46
|
import { SessionExpiredContext } from '../hooks/use-session-expired';
|
|
47
47
|
import { useSessionRefresh } from '../hooks/use-session-refresh';
|
|
48
|
-
export function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialAccessToken, initialExpiresAt, initialLanguage, autoRefresh, authBasePath, }) {
|
|
48
|
+
export function StorefrontProvider({ children, config, middleware, shopData, initialIsAuthenticated, initialAccessToken, initialExpiresAt, initialLanguage, autoRefresh, authBasePath, cookieDebug, }) {
|
|
49
49
|
const authStoreRef = useRef(createAuthStore({ initialIsAuthenticated, initialAccessToken, initialExpiresAt }));
|
|
50
|
-
const
|
|
51
|
-
const
|
|
50
|
+
const cookieDebugOptions = cookieDebug ? { onDebug: cookieDebug } : undefined;
|
|
51
|
+
const currencyStoreRef = useRef(createCurrencyStore(cookieDebugOptions));
|
|
52
|
+
const languageStoreRef = useRef(createLanguageStore(initialLanguage, cookieDebugOptions));
|
|
52
53
|
const sessionExpiredRef = useRef(createSessionExpiredEmitter());
|
|
53
54
|
const botProtectionRef = useRef(shopData.botProtection ? createBotProtectionManager(shopData.botProtection) : null);
|
|
54
55
|
return (_jsx(AuthStoreContext.Provider, { value: authStoreRef.current, children: _jsx(CurrencyStoreContext.Provider, { value: currencyStoreRef.current, children: _jsx(LanguageStoreContext.Provider, { value: languageStoreRef.current, children: _jsx(StorefrontClientProvider, { config: config, middleware: middleware, botProtection: botProtectionRef.current, botProtectionOperations: shopData.botProtection?.protectedOperations, sessionExpiredEmitter: sessionExpiredRef.current, authBasePath: authBasePath, children: _jsx(BotProtectionContext.Provider, { value: { manager: botProtectionRef.current }, children: _jsx(CurrencyProvider, { shopData: shopData, children: _jsxs(LanguageProvider, { shopData: shopData, children: [_jsx(BotProtectionWidget, { manager: botProtectionRef.current }), _jsx(SessionRefreshRunner, { autoRefresh: autoRefresh, emitter: sessionExpiredRef.current }), _jsx(SessionExpiredContext.Provider, { value: sessionExpiredRef.current, children: children })] }) }) }) }) }) }) }));
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* through React Context — eliminates module duplication in Turbopack.
|
|
7
7
|
*/
|
|
8
8
|
import type { ShopConfig } from '../types/shop-config';
|
|
9
|
+
import type { DebugEvent } from '../../core/client/types';
|
|
10
|
+
export interface CreateCurrencyStoreOptions {
|
|
11
|
+
/** Optional debug sink — emits a `phase: 'cookie'` event whenever the currency cookie is written. */
|
|
12
|
+
onDebug?: (event: DebugEvent) => void;
|
|
13
|
+
}
|
|
9
14
|
/**
|
|
10
15
|
* @deprecated Use ShopConfig instead. Kept for backward compatibility of type exports.
|
|
11
16
|
*/
|
|
@@ -18,7 +23,7 @@ export interface CurrencyStore {
|
|
|
18
23
|
initialize: (shopData: ShopConfig) => void;
|
|
19
24
|
setCurrency: (currency: string) => void;
|
|
20
25
|
}
|
|
21
|
-
export declare const createCurrencyStore: () => import("zustand").StoreApi<CurrencyStore>;
|
|
26
|
+
export declare const createCurrencyStore: (options?: CreateCurrencyStoreOptions) => import("zustand").StoreApi<CurrencyStore>;
|
|
22
27
|
export declare const selectCurrency: (state: CurrencyStore) => string | null;
|
|
23
28
|
export declare const selectBaseCurrency: (state: CurrencyStore) => string | null;
|
|
24
29
|
export declare const selectSupportedCurrencies: (state: CurrencyStore) => string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currency.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/currency.store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"currency.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/currency.store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG1D,MAAM,WAAW,0BAA0B;IACzC,qGAAqG;IACrG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE1C,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAGlB,UAAU,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,mBAAmB,GAAI,UAAU,0BAA0B,8CA2DvE,CAAC;AAMF,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,kBAAmB,CAAC;AACvE,eAAO,MAAM,kBAAkB,GAAI,OAAO,aAAa,kBAAuB,CAAC;AAC/E,eAAO,MAAM,yBAAyB,GAAI,OAAO,aAAa,aAA8B,CAAC;AAC7F,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,YAAmB,CAAC"}
|
|
@@ -8,48 +8,56 @@
|
|
|
8
8
|
import { createStore } from 'zustand/vanilla';
|
|
9
9
|
import { CURRENCY_COOKIE_NAME, CURRENCY_COOKIE_MAX_AGE } from '../../core/currency/cookie-config';
|
|
10
10
|
import { getCookie, setCookie } from '../cookies';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
currency
|
|
15
|
-
isLoaded: false,
|
|
16
|
-
initialize: (shopData) => {
|
|
17
|
-
// 1. Try saved currency from cookie (SSR-safe)
|
|
18
|
-
const saved = getCookie(CURRENCY_COOKIE_NAME);
|
|
19
|
-
// 2. Try to detect from browser locale
|
|
20
|
-
let detected;
|
|
21
|
-
if (shopData.localeToCurrencyMap && typeof navigator !== 'undefined') {
|
|
22
|
-
const browserLocale = navigator.language;
|
|
23
|
-
const match = shopData.localeToCurrencyMap.find((m) => m.locale === browserLocale);
|
|
24
|
-
detected = match?.currency;
|
|
25
|
-
}
|
|
26
|
-
// 3. Determine final currency (priority: saved > detected > base)
|
|
27
|
-
const finalCurrency = saved && shopData.supportedCurrencies.includes(saved)
|
|
28
|
-
? saved
|
|
29
|
-
: detected && shopData.supportedCurrencies.includes(detected)
|
|
30
|
-
? detected
|
|
31
|
-
: shopData.currencyCode;
|
|
32
|
-
set({
|
|
33
|
-
baseCurrency: shopData.currencyCode,
|
|
34
|
-
supportedCurrencies: shopData.supportedCurrencies,
|
|
35
|
-
currency: finalCurrency,
|
|
36
|
-
isLoaded: true,
|
|
37
|
-
});
|
|
38
|
-
// Ensure cookie is set if we determined a currency
|
|
39
|
-
if (finalCurrency && !saved) {
|
|
40
|
-
setCookie(CURRENCY_COOKIE_NAME, finalCurrency, { maxAge: CURRENCY_COOKIE_MAX_AGE });
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
setCurrency: (currency) => {
|
|
44
|
-
const { supportedCurrencies } = get();
|
|
45
|
-
if (!supportedCurrencies.includes(currency)) {
|
|
46
|
-
console.warn(`[CurrencyStore] Currency ${currency} not supported`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
set({ currency });
|
|
11
|
+
import { cookieDebugEvent } from '../../core/client/remote-debug-transport';
|
|
12
|
+
export const createCurrencyStore = (options) => {
|
|
13
|
+
const emit = options?.onDebug;
|
|
14
|
+
const persist = (currency) => {
|
|
50
15
|
setCookie(CURRENCY_COOKIE_NAME, currency, { maxAge: CURRENCY_COOKIE_MAX_AGE });
|
|
51
|
-
|
|
52
|
-
}
|
|
16
|
+
emit?.(cookieDebugEvent(CURRENCY_COOKIE_NAME, 'set', currency, CURRENCY_COOKIE_MAX_AGE));
|
|
17
|
+
};
|
|
18
|
+
return createStore()((set, get) => ({
|
|
19
|
+
baseCurrency: null,
|
|
20
|
+
supportedCurrencies: [],
|
|
21
|
+
currency: null,
|
|
22
|
+
isLoaded: false,
|
|
23
|
+
initialize: (shopData) => {
|
|
24
|
+
// 1. Try saved currency from cookie (SSR-safe)
|
|
25
|
+
const saved = getCookie(CURRENCY_COOKIE_NAME);
|
|
26
|
+
// 2. Try to detect from browser locale
|
|
27
|
+
let detected;
|
|
28
|
+
if (shopData.localeToCurrencyMap && typeof navigator !== 'undefined') {
|
|
29
|
+
const browserLocale = navigator.language;
|
|
30
|
+
const match = shopData.localeToCurrencyMap.find((m) => m.locale === browserLocale);
|
|
31
|
+
detected = match?.currency;
|
|
32
|
+
}
|
|
33
|
+
// 3. Determine final currency (priority: saved > detected > base)
|
|
34
|
+
const finalCurrency = saved && shopData.supportedCurrencies.includes(saved)
|
|
35
|
+
? saved
|
|
36
|
+
: detected && shopData.supportedCurrencies.includes(detected)
|
|
37
|
+
? detected
|
|
38
|
+
: shopData.currencyCode;
|
|
39
|
+
set({
|
|
40
|
+
baseCurrency: shopData.currencyCode,
|
|
41
|
+
supportedCurrencies: shopData.supportedCurrencies,
|
|
42
|
+
currency: finalCurrency,
|
|
43
|
+
isLoaded: true,
|
|
44
|
+
});
|
|
45
|
+
// Ensure cookie is set if we determined a currency
|
|
46
|
+
if (finalCurrency && !saved) {
|
|
47
|
+
persist(finalCurrency);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
setCurrency: (currency) => {
|
|
51
|
+
const { supportedCurrencies } = get();
|
|
52
|
+
if (!supportedCurrencies.includes(currency)) {
|
|
53
|
+
console.warn(`[CurrencyStore] Currency ${currency} not supported`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
set({ currency });
|
|
57
|
+
persist(currency);
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
};
|
|
53
61
|
// ---------------------------------------------------------------------------
|
|
54
62
|
// Selectors (for use with useCurrencyStore(selector))
|
|
55
63
|
// ---------------------------------------------------------------------------
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* - SDK language store (X-Lang header, framework-agnostic)
|
|
18
18
|
*/
|
|
19
19
|
import type { ShopConfig } from '../types/shop-config';
|
|
20
|
+
import type { DebugEvent } from '../../core/client/types';
|
|
21
|
+
export interface CreateLanguageStoreOptions {
|
|
22
|
+
/** Optional debug sink — emits a `phase: 'cookie'` event whenever the language cookie is written. */
|
|
23
|
+
onDebug?: (event: DebugEvent) => void;
|
|
24
|
+
}
|
|
20
25
|
export interface LanguageStore {
|
|
21
26
|
defaultLanguage: string | null;
|
|
22
27
|
supportedLanguages: string[];
|
|
@@ -25,7 +30,7 @@ export interface LanguageStore {
|
|
|
25
30
|
initialize: (shopData: ShopConfig) => void;
|
|
26
31
|
setLanguage: (language: string) => void;
|
|
27
32
|
}
|
|
28
|
-
export declare const createLanguageStore: (initialLanguage?: string | null) => import("zustand").StoreApi<LanguageStore>;
|
|
33
|
+
export declare const createLanguageStore: (initialLanguage?: string | null, options?: CreateLanguageStoreOptions) => import("zustand").StoreApi<LanguageStore>;
|
|
29
34
|
export declare const selectLanguage: (state: LanguageStore) => string | null;
|
|
30
35
|
export declare const selectDefaultLanguage: (state: LanguageStore) => string | null;
|
|
31
36
|
export declare const selectSupportedLanguages: (state: LanguageStore) => string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"language.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/language.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"language.store.d.ts","sourceRoot":"","sources":["../../../src/react/stores/language.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG1D,MAAM,WAAW,0BAA0B;IACzC,qGAAqG;IACrG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAE5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAGlB,UAAU,EAAE,CAAC,QAAQ,EAAE,UAAU,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,eAAO,MAAM,mBAAmB,GAC9B,kBAAkB,MAAM,GAAG,IAAI,EAC/B,UAAU,0BAA0B,8CAmDrC,CAAC;AAMF,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,kBAAmB,CAAC;AACvE,eAAO,MAAM,qBAAqB,GAAI,OAAO,aAAa,kBAA0B,CAAC;AACrF,eAAO,MAAM,wBAAwB,GAAI,OAAO,aAAa,aAA6B,CAAC;AAC3F,eAAO,MAAM,sBAAsB,GAAI,OAAO,aAAa,YAAmB,CAAC"}
|
|
@@ -19,45 +19,53 @@
|
|
|
19
19
|
import { createStore } from 'zustand/vanilla';
|
|
20
20
|
import { LANGUAGE_COOKIE_NAME, LANGUAGE_COOKIE_MAX_AGE } from '../../core/language/cookie-config';
|
|
21
21
|
import { getCookie, setCookie } from '../cookies';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
language
|
|
26
|
-
isLoaded: false,
|
|
27
|
-
initialize: (shopData) => {
|
|
28
|
-
const supported = shopData.supportedLanguages ?? [];
|
|
29
|
-
const current = get().language;
|
|
30
|
-
const saved = getCookie(LANGUAGE_COOKIE_NAME);
|
|
31
|
-
// Priority: current (from initialLanguage/URL) > cookie (validated) > default
|
|
32
|
-
const finalLanguage = current && (supported.length === 0 || supported.includes(current))
|
|
33
|
-
? current
|
|
34
|
-
: saved && (supported.length === 0 || supported.includes(saved))
|
|
35
|
-
? saved
|
|
36
|
-
: shopData.defaultLanguage ?? null;
|
|
37
|
-
set({
|
|
38
|
-
defaultLanguage: shopData.defaultLanguage ?? null,
|
|
39
|
-
supportedLanguages: supported,
|
|
40
|
-
language: finalLanguage,
|
|
41
|
-
isLoaded: true,
|
|
42
|
-
});
|
|
43
|
-
// Persist cookie if determined from fallback (not already saved)
|
|
44
|
-
if (finalLanguage && finalLanguage !== saved) {
|
|
45
|
-
setCookie(LANGUAGE_COOKIE_NAME, finalLanguage, { maxAge: LANGUAGE_COOKIE_MAX_AGE });
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
setLanguage: (language) => {
|
|
49
|
-
const { supportedLanguages } = get();
|
|
50
|
-
// Validate vs supported (bypass if empty — LanguageSyncProvider may call before initialize)
|
|
51
|
-
if (supportedLanguages.length > 0 && !supportedLanguages.includes(language)) {
|
|
52
|
-
console.warn(`[LanguageStore] Language ${language} not supported`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (get().language === language)
|
|
56
|
-
return;
|
|
22
|
+
import { cookieDebugEvent } from '../../core/client/remote-debug-transport';
|
|
23
|
+
export const createLanguageStore = (initialLanguage, options) => {
|
|
24
|
+
const emit = options?.onDebug;
|
|
25
|
+
const persist = (language) => {
|
|
57
26
|
setCookie(LANGUAGE_COOKIE_NAME, language, { maxAge: LANGUAGE_COOKIE_MAX_AGE });
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
27
|
+
emit?.(cookieDebugEvent(LANGUAGE_COOKIE_NAME, 'set', language, LANGUAGE_COOKIE_MAX_AGE));
|
|
28
|
+
};
|
|
29
|
+
return createStore()((set, get) => ({
|
|
30
|
+
defaultLanguage: null,
|
|
31
|
+
supportedLanguages: [],
|
|
32
|
+
language: initialLanguage ?? null,
|
|
33
|
+
isLoaded: false,
|
|
34
|
+
initialize: (shopData) => {
|
|
35
|
+
const supported = shopData.supportedLanguages ?? [];
|
|
36
|
+
const current = get().language;
|
|
37
|
+
const saved = getCookie(LANGUAGE_COOKIE_NAME);
|
|
38
|
+
// Priority: current (from initialLanguage/URL) > cookie (validated) > default
|
|
39
|
+
const finalLanguage = current && (supported.length === 0 || supported.includes(current))
|
|
40
|
+
? current
|
|
41
|
+
: saved && (supported.length === 0 || supported.includes(saved))
|
|
42
|
+
? saved
|
|
43
|
+
: shopData.defaultLanguage ?? null;
|
|
44
|
+
set({
|
|
45
|
+
defaultLanguage: shopData.defaultLanguage ?? null,
|
|
46
|
+
supportedLanguages: supported,
|
|
47
|
+
language: finalLanguage,
|
|
48
|
+
isLoaded: true,
|
|
49
|
+
});
|
|
50
|
+
// Persist cookie if determined from fallback (not already saved)
|
|
51
|
+
if (finalLanguage && finalLanguage !== saved) {
|
|
52
|
+
persist(finalLanguage);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
setLanguage: (language) => {
|
|
56
|
+
const { supportedLanguages } = get();
|
|
57
|
+
// Validate vs supported (bypass if empty — LanguageSyncProvider may call before initialize)
|
|
58
|
+
if (supportedLanguages.length > 0 && !supportedLanguages.includes(language)) {
|
|
59
|
+
console.warn(`[LanguageStore] Language ${language} not supported`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (get().language === language)
|
|
63
|
+
return;
|
|
64
|
+
persist(language);
|
|
65
|
+
set({ language });
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
61
69
|
// ---------------------------------------------------------------------------
|
|
62
70
|
// Selectors (for use with useLanguageStore(selector))
|
|
63
71
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.1.0",
|
|
4
4
|
"description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|