@doswiftly/storefront-sdk 19.1.0 → 20.0.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 +119 -0
- package/dist/core/auth/session-events.d.ts +6 -9
- package/dist/core/auth/session-events.d.ts.map +1 -1
- package/dist/core/auth/session-events.js +6 -9
- package/dist/core/cart/cart-client.d.ts +39 -2
- package/dist/core/cart/cart-client.d.ts.map +1 -1
- package/dist/core/cart/cart-client.js +53 -3
- package/dist/core/cart/cart-recovery.d.ts +41 -71
- package/dist/core/cart/cart-recovery.d.ts.map +1 -1
- package/dist/core/cart/cart-recovery.js +37 -95
- package/dist/core/cart/cookie-config.d.ts +29 -3
- package/dist/core/cart/cookie-config.d.ts.map +1 -1
- package/dist/core/cart/cookie-config.js +35 -3
- package/dist/core/generated/operation-types.d.ts +268 -4
- package/dist/core/generated/operation-types.d.ts.map +1 -1
- package/dist/core/generated/operation-types.js +8 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -2
- package/dist/core/middleware/cart-secret.d.ts +43 -0
- package/dist/core/middleware/cart-secret.d.ts.map +1 -0
- package/dist/core/middleware/cart-secret.js +55 -0
- package/dist/core/middleware/session-retry.d.ts +2 -2
- package/dist/core/middleware/session-retry.js +2 -2
- package/dist/core/operations/cart.d.ts +23 -0
- package/dist/core/operations/cart.d.ts.map +1 -1
- package/dist/core/operations/cart.js +71 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/cookies.d.ts +6 -0
- package/dist/react/cookies.d.ts.map +1 -1
- package/dist/react/cookies.js +23 -4
- package/dist/react/hooks/use-cart-manager.d.ts +20 -13
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
- package/dist/react/hooks/use-cart-manager.js +14 -12
- package/dist/react/hooks/use-logout.d.ts +7 -5
- package/dist/react/hooks/use-logout.d.ts.map +1 -1
- package/dist/react/hooks/use-logout.js +37 -7
- package/dist/react/providers/cart-manager-provider.d.ts +3 -0
- package/dist/react/providers/cart-manager-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-client-provider.js +6 -0
- package/dist/react/server/cookie-readers.d.ts +20 -0
- package/dist/react/server/cookie-readers.d.ts.map +1 -1
- package/dist/react/server/cookie-readers.js +22 -1
- package/dist/react/server/index.d.ts +2 -1
- package/dist/react/server/index.d.ts.map +1 -1
- package/dist/react/server/index.js +4 -1
- package/dist/react/stores/cart.context.d.ts +5 -0
- package/dist/react/stores/cart.context.d.ts.map +1 -1
- package/dist/react/stores/cart.context.js +5 -0
- package/dist/react/stores/cart.store.d.ts +6 -0
- package/dist/react/stores/cart.store.d.ts.map +1 -1
- package/dist/react/stores/cart.store.js +6 -0
- package/package.json +1 -1
|
@@ -32,14 +32,16 @@
|
|
|
32
32
|
* provides browser / Next.js server / AsyncStorage / in-memory implementation).
|
|
33
33
|
*/
|
|
34
34
|
import { StorefrontError } from '../errors';
|
|
35
|
+
import { parseCartCookieValue } from './cookie-config';
|
|
35
36
|
// ---------------------------------------------------------------------------
|
|
36
37
|
// Error codes — must mirror backend CartErrorCode enum (guarded by drift test)
|
|
37
38
|
// ---------------------------------------------------------------------------
|
|
38
39
|
/**
|
|
39
40
|
* Cart error codes that require recreation of the cart resource.
|
|
40
41
|
*
|
|
41
|
-
* - `CART_NOT_FOUND` — cart expired, missing, or
|
|
42
|
-
*
|
|
42
|
+
* - `CART_NOT_FOUND` — cart expired, missing, or the access secret was absent
|
|
43
|
+
* or did not match. The backend returns one uniform code so it never leaks
|
|
44
|
+
* whether a given cart exists.
|
|
43
45
|
* - `ALREADY_COMPLETED` — cart status not in `{ACTIVE, RECOVERED}` (CONVERTED
|
|
44
46
|
* after checkout, EXPIRED via TTL, or ABANDONED by purge worker).
|
|
45
47
|
*
|
|
@@ -65,53 +67,6 @@ export function isCartRecoverableError(err) {
|
|
|
65
67
|
return false;
|
|
66
68
|
return err.userErrors.some((ue) => typeof ue.code === 'string' && RECOVERABLE_CODE_SET.has(ue.code));
|
|
67
69
|
}
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// Session error codes — auth-recoverable, distinct from cart-resource recovery
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
/**
|
|
72
|
-
* Cart error codes that signal a missing or invalid auth context on a
|
|
73
|
-
* customer-owned cart — distinct from cart-resource recovery
|
|
74
|
-
* (`CART_RECOVERABLE_ERROR_CODES`).
|
|
75
|
-
*
|
|
76
|
-
* - `CART_UNAUTHENTICATED` — anonymous request OR present-but-invalid token
|
|
77
|
-
* (expired JWT, malformed). The cart resource is intact and reachable
|
|
78
|
-
* again after re-auth.
|
|
79
|
-
*
|
|
80
|
-
* Storefronts SHOULD preserve the `cart-id` cookie when handling these
|
|
81
|
-
* codes and prompt sign-in — the same cart resumes after a successful login.
|
|
82
|
-
*/
|
|
83
|
-
export const CART_SESSION_ERROR_CODES = ['CART_UNAUTHENTICATED'];
|
|
84
|
-
const SESSION_CODE_SET = new Set(CART_SESSION_ERROR_CODES);
|
|
85
|
-
/**
|
|
86
|
-
* Type-safe predicate for "this error means the session is gone, but the
|
|
87
|
-
* cart resource is intact". Distinct from `isCartRecoverableError`
|
|
88
|
-
* (cart-resource recovery) — `isCartSessionError` matches auth-recoverable
|
|
89
|
-
* codes that should trigger re-auth flow, NOT cart cookie cleanup.
|
|
90
|
-
*
|
|
91
|
-
* Inspects `err.userErrors[].code` (structured field) — never matches against
|
|
92
|
-
* `err.message` (locale-dependent, non-stable).
|
|
93
|
-
*/
|
|
94
|
-
export function isCartSessionError(err) {
|
|
95
|
-
if (!(err instanceof StorefrontError))
|
|
96
|
-
return false;
|
|
97
|
-
if (err.userErrors.length === 0)
|
|
98
|
-
return false;
|
|
99
|
-
return err.userErrors.some((ue) => typeof ue.code === 'string' && SESSION_CODE_SET.has(ue.code));
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Thrown when a cart operation fails with a session error
|
|
103
|
-
* (`CART_UNAUTHENTICATED`). Distinct from `CartRecoveryNotPossibleError`
|
|
104
|
-
* (cart-resource issue) — storefronts should redirect to a sign-in flow
|
|
105
|
-
* and retry the operation after re-auth.
|
|
106
|
-
*/
|
|
107
|
-
export class CartSessionRequiredError extends Error {
|
|
108
|
-
cause;
|
|
109
|
-
constructor(cause, message) {
|
|
110
|
-
super(message ?? 'Session required — please sign in to continue', { cause });
|
|
111
|
-
this.name = 'CartSessionRequiredError';
|
|
112
|
-
this.cause = cause;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
70
|
/**
|
|
116
71
|
* Thrown when an operation hits a recoverable cart error but recovery is not
|
|
117
72
|
* possible (no `recreateAndRun`, or recovery itself failed). UI should clear
|
|
@@ -150,13 +105,32 @@ async function acquireCartId(coord, factory) {
|
|
|
150
105
|
coord.inFlight = p;
|
|
151
106
|
return p;
|
|
152
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Promote a server-known seed into the cookie store and return its cart id.
|
|
110
|
+
*
|
|
111
|
+
* The seed may be a bare cart id or the composite `<cartId>.<secret>` value.
|
|
112
|
+
* When it carries a secret the composite is written so later requests send the
|
|
113
|
+
* access secret and reach the seeded cart; a bare id is written without one and
|
|
114
|
+
* degrades to a fresh cart on the next write. Returns `null` when the seed does
|
|
115
|
+
* not parse to a usable cart id.
|
|
116
|
+
*/
|
|
117
|
+
function seedCookieFromInitialCartId(cookieStore, initialCartId, cookieMaxAge) {
|
|
118
|
+
const parsed = parseCartCookieValue(initialCartId);
|
|
119
|
+
if (!parsed)
|
|
120
|
+
return null;
|
|
121
|
+
cookieStore.set(parsed.cartId, {
|
|
122
|
+
secret: parsed.cartSecret,
|
|
123
|
+
...(cookieMaxAge !== undefined ? { maxAge: cookieMaxAge } : {}),
|
|
124
|
+
});
|
|
125
|
+
return parsed.cartId;
|
|
126
|
+
}
|
|
153
127
|
/**
|
|
154
128
|
* Runs an operation against the current cart, recovering once on
|
|
155
129
|
* `CART_NOT_FOUND` / `ALREADY_COMPLETED`. Pure async function — non-React
|
|
156
130
|
* consumers can wire this directly without the runner factory.
|
|
157
131
|
*/
|
|
158
132
|
export async function executeWithCartRecovery(opts) {
|
|
159
|
-
const { cartClient, cookieStore, operation, ensureCart, cookieMaxAge, onExpired,
|
|
133
|
+
const { cartClient, cookieStore, operation, ensureCart, cookieMaxAge, onExpired, initialCartId, } = opts;
|
|
160
134
|
const coord = opts.recoveryCoordinator ?? createCoordinator();
|
|
161
135
|
const opName = operation.name ?? 'unknown';
|
|
162
136
|
// Phase 0 — ensure cart exists (cookie may be empty on first interaction).
|
|
@@ -167,14 +141,16 @@ export async function executeWithCartRecovery(opts) {
|
|
|
167
141
|
// path (Phase 2a/2b) operates on a known cookie when the seed turns out stale.
|
|
168
142
|
let cartId = cookieStore.get();
|
|
169
143
|
if (!cartId && initialCartId) {
|
|
170
|
-
cookieStore
|
|
171
|
-
cartId = initialCartId;
|
|
144
|
+
cartId = seedCookieFromInitialCartId(cookieStore, initialCartId, cookieMaxAge);
|
|
172
145
|
}
|
|
173
146
|
if (!cartId) {
|
|
174
147
|
cartId = await acquireCartId(coord, async () => {
|
|
175
|
-
const created = ensureCart ? await ensureCart() :
|
|
176
|
-
cookieStore.set(created.id,
|
|
177
|
-
|
|
148
|
+
const created = ensureCart ? await ensureCart() : await cartClient.create();
|
|
149
|
+
cookieStore.set(created.cart.id, {
|
|
150
|
+
secret: created.secret ?? null,
|
|
151
|
+
...(cookieMaxAge !== undefined ? { maxAge: cookieMaxAge } : {}),
|
|
152
|
+
});
|
|
153
|
+
return created.cart.id;
|
|
178
154
|
});
|
|
179
155
|
}
|
|
180
156
|
// Phase 1 — happy path.
|
|
@@ -182,17 +158,6 @@ export async function executeWithCartRecovery(opts) {
|
|
|
182
158
|
return await operation.run(cartId);
|
|
183
159
|
}
|
|
184
160
|
catch (err) {
|
|
185
|
-
// Session loss — cart resource intact, re-auth needed. Preserve cookie
|
|
186
|
-
// so the same cart resumes after a successful login.
|
|
187
|
-
if (isCartSessionError(err)) {
|
|
188
|
-
const sessionEvent = {
|
|
189
|
-
oldCartId: cartId,
|
|
190
|
-
operation: opName,
|
|
191
|
-
cause: err,
|
|
192
|
-
};
|
|
193
|
-
onSessionExpired?.(sessionEvent);
|
|
194
|
-
throw new CartSessionRequiredError(err);
|
|
195
|
-
}
|
|
196
161
|
if (!isCartRecoverableError(err))
|
|
197
162
|
throw err;
|
|
198
163
|
const oldCartId = cartId;
|
|
@@ -220,7 +185,10 @@ export async function executeWithCartRecovery(opts) {
|
|
|
220
185
|
try {
|
|
221
186
|
cookieStore.clear();
|
|
222
187
|
const recreated = await operation.recreateAndRun(cartClient);
|
|
223
|
-
cookieStore.set(recreated.cart.id,
|
|
188
|
+
cookieStore.set(recreated.cart.id, {
|
|
189
|
+
secret: recreated.secret ?? null,
|
|
190
|
+
...(cookieMaxAge !== undefined ? { maxAge: cookieMaxAge } : {}),
|
|
191
|
+
});
|
|
224
192
|
result = recreated.result;
|
|
225
193
|
}
|
|
226
194
|
catch (recreateErr) {
|
|
@@ -249,7 +217,6 @@ export function createCartRecoveryRunner(options) {
|
|
|
249
217
|
const { cartClient, cookieStore, ensureCart, cookieMaxAge, initialCartId } = options;
|
|
250
218
|
const coordinator = createCoordinator();
|
|
251
219
|
const expiredListeners = new Set();
|
|
252
|
-
const sessionListeners = new Set();
|
|
253
220
|
function emitExpired(event) {
|
|
254
221
|
for (const listener of expiredListeners) {
|
|
255
222
|
try {
|
|
@@ -260,16 +227,6 @@ export function createCartRecoveryRunner(options) {
|
|
|
260
227
|
}
|
|
261
228
|
}
|
|
262
229
|
}
|
|
263
|
-
function emitSessionExpired(event) {
|
|
264
|
-
for (const listener of sessionListeners) {
|
|
265
|
-
try {
|
|
266
|
-
listener(event);
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
// Listeners must not break recovery flow — swallow listener exceptions.
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
230
|
return {
|
|
274
231
|
cartClient,
|
|
275
232
|
cookieStore,
|
|
@@ -283,7 +240,6 @@ export function createCartRecoveryRunner(options) {
|
|
|
283
240
|
initialCartId,
|
|
284
241
|
recoveryCoordinator: coordinator,
|
|
285
242
|
onExpired: emitExpired,
|
|
286
|
-
onSessionExpired: emitSessionExpired,
|
|
287
243
|
};
|
|
288
244
|
return executeWithCartRecovery(internalOpts);
|
|
289
245
|
},
|
|
@@ -293,8 +249,7 @@ export function createCartRecoveryRunner(options) {
|
|
|
293
249
|
// from the canonical source without re-reading the seed.
|
|
294
250
|
let cartId = cookieStore.get();
|
|
295
251
|
if (!cartId && initialCartId) {
|
|
296
|
-
cookieStore
|
|
297
|
-
cartId = initialCartId;
|
|
252
|
+
cartId = seedCookieFromInitialCartId(cookieStore, initialCartId, cookieMaxAge);
|
|
298
253
|
}
|
|
299
254
|
if (!cartId)
|
|
300
255
|
return null;
|
|
@@ -302,15 +257,6 @@ export function createCartRecoveryRunner(options) {
|
|
|
302
257
|
return await cartClient.get(cartId);
|
|
303
258
|
}
|
|
304
259
|
catch (err) {
|
|
305
|
-
if (isCartSessionError(err)) {
|
|
306
|
-
// Cart resource intact — re-auth required. Preserve cookie.
|
|
307
|
-
emitSessionExpired({
|
|
308
|
-
oldCartId: cartId,
|
|
309
|
-
operation: 'getCart',
|
|
310
|
-
cause: err,
|
|
311
|
-
});
|
|
312
|
-
throw new CartSessionRequiredError(err);
|
|
313
|
-
}
|
|
314
260
|
if (isCartRecoverableError(err)) {
|
|
315
261
|
cookieStore.clear();
|
|
316
262
|
emitExpired({
|
|
@@ -328,10 +274,6 @@ export function createCartRecoveryRunner(options) {
|
|
|
328
274
|
expiredListeners.add(listener);
|
|
329
275
|
return () => expiredListeners.delete(listener);
|
|
330
276
|
},
|
|
331
|
-
onSessionExpired(listener) {
|
|
332
|
-
sessionListeners.add(listener);
|
|
333
|
-
return () => sessionListeners.delete(listener);
|
|
334
|
-
},
|
|
335
277
|
};
|
|
336
278
|
}
|
|
337
279
|
// ---------------------------------------------------------------------------
|
|
@@ -359,7 +301,7 @@ export function createCartRecoveryRunner(options) {
|
|
|
359
301
|
export function recreateWithInput(input) {
|
|
360
302
|
return async (cartClient) => {
|
|
361
303
|
const outcome = await cartClient.create(input);
|
|
362
|
-
return { cart: outcome.cart, result: outcome };
|
|
304
|
+
return { cart: outcome.cart, secret: outcome.secret, result: outcome };
|
|
363
305
|
};
|
|
364
306
|
}
|
|
365
307
|
/**
|
|
@@ -2,13 +2,39 @@
|
|
|
2
2
|
* Cart cookie configuration — platform contract.
|
|
3
3
|
*
|
|
4
4
|
* Used by:
|
|
5
|
-
* - SDK cart store (client-side cookie read/write
|
|
5
|
+
* - SDK cart store (client-side cookie read/write)
|
|
6
6
|
* - Server-side cart prefetching (SSR cart badge, middleware)
|
|
7
7
|
* - proxy.ts (edge cart ID detection)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* The cookie value is composite: `<cartId>.<secret>`. The cart id is a UUID and
|
|
10
|
+
* the secret is base64url — neither contains a dot, so the first dot separates
|
|
11
|
+
* the two halves. A legacy plain `<cartId>` value (written before the cart
|
|
12
|
+
* carried a secret) parses with `cartSecret: null`; the client then operates
|
|
13
|
+
* without the `x-cart-secret` header and the backend treats the cart as
|
|
14
|
+
* unreachable, so the SDK recreates a fresh one.
|
|
11
15
|
*/
|
|
12
16
|
export declare const CART_COOKIE_NAME = "cart-id";
|
|
13
17
|
export declare const CART_COOKIE_MAX_AGE: number;
|
|
18
|
+
/**
|
|
19
|
+
* Structured cart cookie value — the cart identifier plus its access secret.
|
|
20
|
+
* `cartSecret` is `null` for a legacy plain-id cookie (no secret half).
|
|
21
|
+
*/
|
|
22
|
+
export interface CartCredentials {
|
|
23
|
+
cartId: string;
|
|
24
|
+
cartSecret: string | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse the raw `cart-id` cookie value into `{ cartId, cartSecret }`.
|
|
28
|
+
*
|
|
29
|
+
* - `"<uuid>.<secret>"` → both halves (split on the first dot).
|
|
30
|
+
* - `"<uuid>"` (no dot, legacy) → `{ cartId, cartSecret: null }`.
|
|
31
|
+
* - trailing dot (`"<uuid>."`) → id only, `cartSecret: null`.
|
|
32
|
+
* - empty / leading-dot / blank → `null` (no usable cart id).
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseCartCookieValue(raw: string | null | undefined): CartCredentials | null;
|
|
35
|
+
/** Build the composite `<cartId>.<secret>` cookie value. */
|
|
36
|
+
export declare function formatCartCookieValue(input: {
|
|
37
|
+
cartId: string;
|
|
38
|
+
cartSecret: string;
|
|
39
|
+
}): string;
|
|
14
40
|
//# sourceMappingURL=cookie-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookie-config.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cookie-config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cookie-config.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cookie-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAC1C,eAAO,MAAM,mBAAmB,QAAoB,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,GAAG,IAAI,CAc3F;AAED,4DAA4D;AAC5D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE3F"}
|
|
@@ -2,12 +2,44 @@
|
|
|
2
2
|
* Cart cookie configuration — platform contract.
|
|
3
3
|
*
|
|
4
4
|
* Used by:
|
|
5
|
-
* - SDK cart store (client-side cookie read/write
|
|
5
|
+
* - SDK cart store (client-side cookie read/write)
|
|
6
6
|
* - Server-side cart prefetching (SSR cart badge, middleware)
|
|
7
7
|
* - proxy.ts (edge cart ID detection)
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* The cookie value is composite: `<cartId>.<secret>`. The cart id is a UUID and
|
|
10
|
+
* the secret is base64url — neither contains a dot, so the first dot separates
|
|
11
|
+
* the two halves. A legacy plain `<cartId>` value (written before the cart
|
|
12
|
+
* carried a secret) parses with `cartSecret: null`; the client then operates
|
|
13
|
+
* without the `x-cart-secret` header and the backend treats the cart as
|
|
14
|
+
* unreachable, so the SDK recreates a fresh one.
|
|
11
15
|
*/
|
|
12
16
|
export const CART_COOKIE_NAME = 'cart-id';
|
|
13
17
|
export const CART_COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 days
|
|
18
|
+
/**
|
|
19
|
+
* Parse the raw `cart-id` cookie value into `{ cartId, cartSecret }`.
|
|
20
|
+
*
|
|
21
|
+
* - `"<uuid>.<secret>"` → both halves (split on the first dot).
|
|
22
|
+
* - `"<uuid>"` (no dot, legacy) → `{ cartId, cartSecret: null }`.
|
|
23
|
+
* - trailing dot (`"<uuid>."`) → id only, `cartSecret: null`.
|
|
24
|
+
* - empty / leading-dot / blank → `null` (no usable cart id).
|
|
25
|
+
*/
|
|
26
|
+
export function parseCartCookieValue(raw) {
|
|
27
|
+
if (!raw)
|
|
28
|
+
return null;
|
|
29
|
+
const trimmed = raw.trim();
|
|
30
|
+
if (!trimmed)
|
|
31
|
+
return null;
|
|
32
|
+
const dotIndex = trimmed.indexOf('.');
|
|
33
|
+
if (dotIndex === -1) {
|
|
34
|
+
return { cartId: trimmed, cartSecret: null };
|
|
35
|
+
}
|
|
36
|
+
const cartId = trimmed.slice(0, dotIndex);
|
|
37
|
+
const cartSecret = trimmed.slice(dotIndex + 1);
|
|
38
|
+
if (!cartId)
|
|
39
|
+
return null;
|
|
40
|
+
return { cartId, cartSecret: cartSecret || null };
|
|
41
|
+
}
|
|
42
|
+
/** Build the composite `<cartId>.<secret>` cookie value. */
|
|
43
|
+
export function formatCartCookieValue(input) {
|
|
44
|
+
return `${input.cartId}.${input.cartSecret}`;
|
|
45
|
+
}
|