@doswiftly/storefront-sdk 16.1.0 → 18.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 +1255 -0
- package/README.md +16 -4
- package/dist/core/auth/auth-client.d.ts +39 -3
- package/dist/core/auth/auth-client.d.ts.map +1 -1
- package/dist/core/auth/auth-client.js +51 -3
- package/dist/core/auth/cookie-config.d.ts +52 -3
- package/dist/core/auth/cookie-config.d.ts.map +1 -1
- package/dist/core/auth/cookie-config.js +60 -6
- package/dist/core/auth/handlers.d.ts +46 -0
- package/dist/core/auth/handlers.d.ts.map +1 -1
- package/dist/core/auth/handlers.js +9 -2
- package/dist/core/auth/session-events.d.ts +38 -0
- package/dist/core/auth/session-events.d.ts.map +1 -0
- package/dist/core/auth/session-events.js +35 -0
- package/dist/core/cart/cart-client.d.ts +10 -1
- package/dist/core/cart/cart-client.d.ts.map +1 -1
- package/dist/core/cart/cart-client.js +17 -1
- package/dist/core/cart/cart-recovery.d.ts +23 -0
- package/dist/core/cart/cart-recovery.d.ts.map +1 -1
- package/dist/core/cart/cart-recovery.js +20 -3
- package/dist/core/cart/types.d.ts +2 -1
- package/dist/core/cart/types.d.ts.map +1 -1
- package/dist/core/cart/types.js +7 -1
- package/dist/core/client/create-client.d.ts.map +1 -1
- package/dist/core/client/create-client.js +7 -3
- package/dist/core/client/execute.d.ts +29 -3
- package/dist/core/client/execute.d.ts.map +1 -1
- package/dist/core/client/execute.js +174 -3
- package/dist/core/client/types.d.ts +50 -2
- package/dist/core/client/types.d.ts.map +1 -1
- package/dist/core/errors.d.ts +6 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/generated/operation-types.d.ts +937 -182
- package/dist/core/generated/operation-types.d.ts.map +1 -1
- package/dist/core/generated/operation-types.js +560 -1
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +12 -2
- package/dist/core/middleware/session-retry.d.ts +47 -0
- package/dist/core/middleware/session-retry.d.ts.map +1 -0
- package/dist/core/middleware/session-retry.js +71 -0
- package/dist/core/operations/auth.d.ts.map +1 -1
- package/dist/core/operations/auth.js +1 -0
- package/dist/core/operations/cart.d.ts +7 -0
- package/dist/core/operations/cart.d.ts.map +1 -1
- package/dist/core/operations/cart.js +54 -3
- package/dist/react/components/PaymentInstrumentSection.d.ts +56 -0
- package/dist/react/components/PaymentInstrumentSection.d.ts.map +1 -0
- package/dist/react/components/PaymentInstrumentSection.js +89 -0
- package/dist/react/components/PaymentInstrumentTile.d.ts +56 -0
- package/dist/react/components/PaymentInstrumentTile.d.ts.map +1 -0
- package/dist/react/components/PaymentInstrumentTile.js +41 -0
- package/dist/react/components/index.d.ts +2 -0
- package/dist/react/components/index.d.ts.map +1 -1
- package/dist/react/components/index.js +2 -0
- package/dist/react/helpers/browser-data.d.ts +89 -0
- package/dist/react/helpers/browser-data.d.ts.map +1 -0
- package/dist/react/helpers/browser-data.js +84 -0
- package/dist/react/hooks/use-cart-manager.d.ts +104 -13
- package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
- package/dist/react/hooks/use-cart-manager.js +144 -12
- package/dist/react/hooks/use-login.d.ts.map +1 -1
- package/dist/react/hooks/use-login.js +3 -3
- package/dist/react/hooks/use-refresh-token.d.ts.map +1 -1
- package/dist/react/hooks/use-refresh-token.js +6 -4
- package/dist/react/hooks/use-session-expired.d.ts +16 -0
- package/dist/react/hooks/use-session-expired.d.ts.map +1 -0
- package/dist/react/hooks/use-session-expired.js +26 -0
- package/dist/react/hooks/use-session-refresh.d.ts +32 -0
- package/dist/react/hooks/use-session-refresh.d.ts.map +1 -0
- package/dist/react/hooks/use-session-refresh.js +147 -0
- package/dist/react/index.d.ts +5 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -1
- package/dist/react/providers/storefront-client-provider.d.ts +10 -1
- package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-client-provider.js +38 -3
- package/dist/react/providers/storefront-provider.d.ts +51 -3
- package/dist/react/providers/storefront-provider.d.ts.map +1 -1
- package/dist/react/providers/storefront-provider.js +22 -5
- package/dist/react/server/create-storefront-auth-route.d.ts +63 -0
- package/dist/react/server/create-storefront-auth-route.d.ts.map +1 -0
- package/dist/react/server/create-storefront-auth-route.js +239 -0
- package/dist/react/server/get-initial-auth.d.ts +57 -0
- package/dist/react/server/get-initial-auth.d.ts.map +1 -0
- package/dist/react/server/get-initial-auth.js +55 -0
- package/dist/react/server/index.d.ts +3 -0
- package/dist/react/server/index.d.ts.map +1 -1
- package/dist/react/server/index.js +6 -0
- package/dist/react/stores/auth.store.d.ts +46 -2
- package/dist/react/stores/auth.store.d.ts.map +1 -1
- package/dist/react/stores/auth.store.js +19 -7
- package/package.json +4 -2
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `getBrowserDataForPayment()` — collects browser context required by PSD2/3DS2
|
|
3
|
+
* authentication flows (card-on-file z 3DS challenge, BLIK confirmation,
|
|
4
|
+
* Apple/Google Pay z risk scoring).
|
|
5
|
+
*
|
|
6
|
+
* **Browser-only** — throws gdy `typeof window === 'undefined'` (server-side
|
|
7
|
+
* rendering). Caller MUSI gate'ować call w useEffect / event handler / browser
|
|
8
|
+
* code path. NEVER call this w Server Component lub Route Handler.
|
|
9
|
+
*
|
|
10
|
+
* Wartości pochodzą z standard Web APIs:
|
|
11
|
+
* - `userAgent` — `navigator.userAgent`.
|
|
12
|
+
* - `language` — `navigator.language` (BCP 47, fallback `en-US`).
|
|
13
|
+
* - `screen{Width,Height}` — `window.screen.{width,height}`.
|
|
14
|
+
* - `colorDepth` — `window.screen.colorDepth`.
|
|
15
|
+
* - `timezoneOffset` — `new Date().getTimezoneOffset()` (signed integer, minutes,
|
|
16
|
+
* reverse signed per ECMA: dodatnie = behind UTC, ujemne = ahead).
|
|
17
|
+
* - `javaEnabled` — `navigator.javaEnabled?.()` (deprecated ale wymagane PSD2/EMV
|
|
18
|
+
* specification; fallback `false` gdy browser nie expose'uje).
|
|
19
|
+
* - `acceptHeader` — NIE dostępne client-side (`navigator` nie expose'uje request
|
|
20
|
+
* headers). Caller passes z server context lub omits (gateway-dependent
|
|
21
|
+
* requirement). Helper zwraca undefined.
|
|
22
|
+
*
|
|
23
|
+
* Shape matches PSD2/3DS2 BrowserData specification (EMVCo) — adapter na backend
|
|
24
|
+
* `IPaymentProvider.createPayment` może merge wprost do gateway-specific body
|
|
25
|
+
* (Adyen `browserInfo`, Stripe `payment_method_data.billing_details.browser_info`,
|
|
26
|
+
* Mollie `cardToken` z 3DS lookup).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* // W event handler (browser-only):
|
|
31
|
+
* function handleCheckoutSubmit() {
|
|
32
|
+
* const browserData = getBrowserDataForPayment();
|
|
33
|
+
* await cart.createPayment({ ..., browserData });
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* // SSR-safe wrap:
|
|
37
|
+
* if (typeof window !== 'undefined') {
|
|
38
|
+
* const browserData = getBrowserDataForPayment();
|
|
39
|
+
* ...
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Added by payment-instrument-preselection-advanced sub-sprint Adv-2 Req 9.5
|
|
44
|
+
* (carry-over z Adv-1 plan — moved here as standalone utility, no backend
|
|
45
|
+
* mutation impact yet — Adv-3 będzie consumer'em w `paymentCreate` input).
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Throw'd gdy helper wywołany w SSR context (Server Component, Route Handler,
|
|
49
|
+
* Node bez JSDOM). Caller MUSI guard'ować przez `typeof window` check albo
|
|
50
|
+
* wywołać tylko w event handler / `useEffect`.
|
|
51
|
+
*/
|
|
52
|
+
export class BrowserDataNotAvailableError extends Error {
|
|
53
|
+
constructor() {
|
|
54
|
+
super('getBrowserDataForPayment requires browser context (window/navigator/screen unavailable).');
|
|
55
|
+
this.name = 'BrowserDataNotAvailableError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function getBrowserDataForPayment() {
|
|
59
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined' || typeof window.screen === 'undefined') {
|
|
60
|
+
throw new BrowserDataNotAvailableError();
|
|
61
|
+
}
|
|
62
|
+
// `navigator.javaEnabled` jest deprecated ale wciąż obecny w major browsers — guard
|
|
63
|
+
// dla future removal. Falsy fallback gdy missing (PSD2 spec wymaga boolean, not undefined).
|
|
64
|
+
const javaEnabled = typeof navigator.javaEnabled === 'function' ? Boolean(navigator.javaEnabled()) : false;
|
|
65
|
+
// `Intl.DateTimeFormat` jest dostępny we wszystkich modern browsers — optional capture
|
|
66
|
+
// dla gateways które wymagają IANA name zamiast offset (np. Adyen).
|
|
67
|
+
let timezone;
|
|
68
|
+
try {
|
|
69
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
timezone = undefined;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
userAgent: navigator.userAgent,
|
|
76
|
+
language: navigator.language || 'en-US',
|
|
77
|
+
screenWidth: window.screen.width,
|
|
78
|
+
screenHeight: window.screen.height,
|
|
79
|
+
colorDepth: window.screen.colorDepth,
|
|
80
|
+
timezoneOffset: new Date().getTimezoneOffset(),
|
|
81
|
+
javaEnabled,
|
|
82
|
+
timezone,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -7,23 +7,53 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Per-operation strategy:
|
|
9
9
|
*
|
|
10
|
-
* | Operation
|
|
11
|
-
* |
|
|
12
|
-
* | `addItem`
|
|
13
|
-
* | `updateBuyerIdentity`
|
|
14
|
-
* | `setShippingAddress`
|
|
15
|
-
* | `updateDiscountCodes`
|
|
16
|
-
* | `updateNote`
|
|
17
|
-
* | `
|
|
18
|
-
* | `
|
|
10
|
+
* | Operation | Strategy | Why |
|
|
11
|
+
* | -------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
12
|
+
* | `addItem` | Auto-replay (atomic `cartCreate`) | Storefront expects "add to cart" always works |
|
|
13
|
+
* | `updateBuyerIdentity` | Auto-replay | User just typed email/phone — keep it |
|
|
14
|
+
* | `setShippingAddress` | Auto-replay | User just typed address — keep it |
|
|
15
|
+
* | `updateDiscountCodes` | Auto-replay | Coupon valid independently of cart |
|
|
16
|
+
* | `updateNote` | Auto-replay | Stateless / idempotent |
|
|
17
|
+
* | `updateAttributes` | Auto-replay | Stateless metadata — atomic re-create OK |
|
|
18
|
+
* | `updateItem` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
|
|
19
|
+
* | `removeItem` | Bail + `cart-expired` event | jw. |
|
|
20
|
+
* | `setBillingAddress` | Bail + `cart-expired` event | Cart must exist (separate from shipping) |
|
|
21
|
+
* | `selectShippingMethod` | Bail + `cart-expired` event | Method tied to cart contents + address |
|
|
22
|
+
* | `selectPaymentMethod` | Bail + `cart-expired` event | Payment selection on existing cart state |
|
|
23
|
+
* | `clearPaymentSelection` | Bail + `cart-expired` event | Operates on existing cart payment fields |
|
|
24
|
+
* | `applyGiftCard` | Bail + `cart-expired` event | Balance allocation tied to cart total |
|
|
25
|
+
* | `removeGiftCard` | Bail + `cart-expired` event | `giftCardId` refers to row on dead cart |
|
|
26
|
+
* | `updateGiftCardRecipient` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
|
|
27
|
+
* | `complete` | Bail + `cart-expired` event | Finalised cart cannot be auto-recreated |
|
|
28
|
+
* | `createPayment` | Out of recovery scope | Operates on `orderId` (post-complete) |
|
|
19
29
|
*
|
|
20
30
|
* On bail the runner clears the cookie and calls every `onExpired` listener
|
|
21
31
|
* with a `CartExpiredEvent`. UI subscribes once globally and shows a toast /
|
|
22
32
|
* banner — caller code never writes `try / catch` per mutation.
|
|
23
33
|
*
|
|
34
|
+
* After `complete` success the hook auto-clears the `cart-id` cookie and
|
|
35
|
+
* resets status to `idle` — buyer returning to `/checkout` (back from the
|
|
36
|
+
* payment gateway, deep link, new tab) gets a fresh empty cart instead of the
|
|
37
|
+
* CONVERTED cart. Manual `clearCart()` remains available as an escape hatch.
|
|
38
|
+
*
|
|
24
39
|
* Auto-creates cart on first add. Cart id persisted in `cart-id` cookie
|
|
25
40
|
* (SSR/edge visible, 30 days, samesite=lax).
|
|
26
41
|
*
|
|
42
|
+
* ### Server-known cart-id (env seed, magic-link, iframe, customer service)
|
|
43
|
+
*
|
|
44
|
+
* Pass `{ initialCartId }` to seed the hook with a cart-id resolved server-side
|
|
45
|
+
* (read from URL params in a Route Handler, env var for dev fixtures, parent
|
|
46
|
+
* `postMessage` for embedded iframe, admin "view this cart" lookup). The hook
|
|
47
|
+
* applies priority `cookie wins → seed → auto-create`. The seed is eagerly
|
|
48
|
+
* written to the cart-id cookie so cross-tab tabs and standard recovery
|
|
49
|
+
* semantics operate on a canonical value. A stale seed goes through the same
|
|
50
|
+
* recovery flow as a stale cookie — `addItem` auto-replays through
|
|
51
|
+
* `cartCreate({ lines })`, state-dependent ops bail with `cart-expired`.
|
|
52
|
+
*
|
|
53
|
+
* Mirrors the `<StorefrontProvider initialAccessToken>` pattern for the
|
|
54
|
+
* cart-id half of the checkout state — both are seeds for the first render
|
|
55
|
+
* when the client cannot read the canonical source itself.
|
|
56
|
+
*
|
|
27
57
|
* @example
|
|
28
58
|
* ```tsx
|
|
29
59
|
* function CartUI() {
|
|
@@ -34,15 +64,35 @@
|
|
|
34
64
|
* return <button onClick={() => addItem([{ variantId, quantity: 1 }])}>Add</button>;
|
|
35
65
|
* }
|
|
36
66
|
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Server-known cart-id
|
|
69
|
+
* ```tsx
|
|
70
|
+
* // app/checkout/page.tsx — Server Component
|
|
71
|
+
* import { cookies } from 'next/headers';
|
|
72
|
+
* import { CART_COOKIE_NAME } from '@doswiftly/storefront-sdk';
|
|
73
|
+
*
|
|
74
|
+
* export default async function CheckoutPage() {
|
|
75
|
+
* const cookieJar = await cookies();
|
|
76
|
+
* const initialCartId =
|
|
77
|
+
* cookieJar.get(CART_COOKIE_NAME)?.value ?? process.env.NEXT_PUBLIC_DEV_CART_ID ?? null;
|
|
78
|
+
* return <CheckoutClient initialCartId={initialCartId} />;
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* // CheckoutClient.tsx — 'use client'
|
|
82
|
+
* function CheckoutClient({ initialCartId }: { initialCartId: string | null }) {
|
|
83
|
+
* const { complete, selectPaymentMethod, addItem } = useCartManager({ initialCartId });
|
|
84
|
+
* // ... rest unchanged — hook handles seed → cookie → recovery transparently
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
37
87
|
*/
|
|
38
|
-
import type { Cart, CartLineInput, CartLineUpdateInput, CartBuyerIdentityInput, CartAddressInput } from '../../core/cart/types';
|
|
39
|
-
import type { CartMutationOutcome } from '../../core/cart/cart-client';
|
|
88
|
+
import type { Cart, CartLineInput, CartLineUpdateInput, CartBuyerIdentityInput, CartAddressInput, CartAttributeInput, CartCompleteInput, CartSelectShippingMethodInput, CartSelectPaymentMethodInput, CartClearPaymentSelectionInput, CartApplyGiftCardInput, CartRemoveGiftCardInput, CartUpdateGiftCardRecipientInput, PaymentCreateInput, PaymentSession } from '../../core/cart/types';
|
|
89
|
+
import type { CartMutationOutcome, CartCompleteOutcome } from '../../core/cart/cart-client';
|
|
40
90
|
import { type CartExpiredEvent } from '../../core/cart/cart-recovery';
|
|
41
91
|
/**
|
|
42
92
|
* Names of mutations exposed by the hook — narrows `status.operation` for
|
|
43
93
|
* exhaustive consumer-side switching (e.g. operation-specific spinners).
|
|
44
94
|
*/
|
|
45
|
-
export type CartManagerOperation = 'addItem' | 'updateItem' | 'removeItem' | 'updateBuyerIdentity' | 'setShippingAddress' | 'updateDiscountCodes' | 'updateNote';
|
|
95
|
+
export type CartManagerOperation = 'addItem' | 'updateItem' | 'removeItem' | 'updateBuyerIdentity' | 'setShippingAddress' | 'setBillingAddress' | 'updateDiscountCodes' | 'updateNote' | 'updateAttributes' | 'selectShippingMethod' | 'selectPaymentMethod' | 'clearPaymentSelection' | 'applyGiftCard' | 'removeGiftCard' | 'updateGiftCardRecipient' | 'complete' | 'createPayment';
|
|
46
96
|
/**
|
|
47
97
|
* Tagged union of cart mutation lifecycle states. Lets callers do exhaustive
|
|
48
98
|
* switching without remembering which boolean flag pairs with which:
|
|
@@ -69,6 +119,24 @@ export type CartManagerStatus = {
|
|
|
69
119
|
type: 'success';
|
|
70
120
|
operation: CartManagerOperation;
|
|
71
121
|
};
|
|
122
|
+
/**
|
|
123
|
+
* Optional configuration for `useCartManager`. All fields additive — calling
|
|
124
|
+
* the hook with no arguments preserves the original cookie-driven behaviour.
|
|
125
|
+
*/
|
|
126
|
+
export interface UseCartManagerOptions {
|
|
127
|
+
/**
|
|
128
|
+
* Server-known cart-id seed used when the `cart-id` cookie is empty on
|
|
129
|
+
* mount. Cookie wins when present; the seed only fills the gap on the
|
|
130
|
+
* first interaction. Eagerly promoted to the cookie store so cross-tab
|
|
131
|
+
* tabs and standard recovery semantics operate on a canonical value.
|
|
132
|
+
*
|
|
133
|
+
* See the hook-level `@example Server-known cart-id` block for the full
|
|
134
|
+
* Server-Component → Client-Component data flow. Use for env seed (dev),
|
|
135
|
+
* magic-link checkout, embedded iframe (parent supplies cart-id),
|
|
136
|
+
* customer service "view this cart", multi-cart B2B selectors.
|
|
137
|
+
*/
|
|
138
|
+
initialCartId?: string | null;
|
|
139
|
+
}
|
|
72
140
|
export interface UseCartManagerResult {
|
|
73
141
|
getCart: () => Promise<Cart | null>;
|
|
74
142
|
getCartId: () => string | null;
|
|
@@ -77,8 +145,31 @@ export interface UseCartManagerResult {
|
|
|
77
145
|
setShippingAddress: (address: CartAddressInput) => Promise<CartMutationOutcome>;
|
|
78
146
|
updateDiscountCodes: (codes: string[]) => Promise<CartMutationOutcome>;
|
|
79
147
|
updateNote: (note: string) => Promise<CartMutationOutcome>;
|
|
148
|
+
updateAttributes: (attributes: CartAttributeInput[]) => Promise<CartMutationOutcome>;
|
|
80
149
|
updateItem: (lines: CartLineUpdateInput[]) => Promise<CartMutationOutcome>;
|
|
81
150
|
removeItem: (lineIds: string[]) => Promise<CartMutationOutcome>;
|
|
151
|
+
setBillingAddress: (address: CartAddressInput) => Promise<CartMutationOutcome>;
|
|
152
|
+
selectShippingMethod: (input: Omit<CartSelectShippingMethodInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
153
|
+
selectPaymentMethod: (input: Omit<CartSelectPaymentMethodInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
154
|
+
clearPaymentSelection: (input?: Omit<CartClearPaymentSelectionInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
155
|
+
applyGiftCard: (input: Omit<CartApplyGiftCardInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
156
|
+
removeGiftCard: (input: Omit<CartRemoveGiftCardInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
157
|
+
updateGiftCardRecipient: (input: Omit<CartUpdateGiftCardRecipientInput, 'cartId'>) => Promise<CartMutationOutcome>;
|
|
158
|
+
/**
|
|
159
|
+
* Finalise the cart into an Order. On success the `cart-id` cookie is
|
|
160
|
+
* cleared and status resets to `idle` — a follow-up `addItem` auto-creates
|
|
161
|
+
* a fresh cart, matching shop-again UX. The cart itself is NOT returned
|
|
162
|
+
* (CONVERTED/locked after completion); work with `result.order` for
|
|
163
|
+
* post-checkout flows (`order.canCreatePayment`, `order.accessToken`).
|
|
164
|
+
*/
|
|
165
|
+
complete: (input?: Omit<CartCompleteInput, 'cartId'>) => Promise<CartCompleteOutcome>;
|
|
166
|
+
/**
|
|
167
|
+
* Initiate a payment session for an order created by `complete`. NOT a
|
|
168
|
+
* cart operation (works on `orderId`, not `cartId`) — included on the
|
|
169
|
+
* manager for a single checkout-lifecycle API. Throws on
|
|
170
|
+
* `userErrors[].code === 'PAYMENT_*'` — branch on `err.userErrors[0].code`.
|
|
171
|
+
*/
|
|
172
|
+
createPayment: (input: PaymentCreateInput) => Promise<PaymentSession>;
|
|
82
173
|
clearCart: () => void;
|
|
83
174
|
onExpired: (listener: (event: CartExpiredEvent) => void) => () => void;
|
|
84
175
|
status: CartManagerStatus;
|
|
@@ -87,5 +178,5 @@ export interface UseCartManagerResult {
|
|
|
87
178
|
/** Error message when status.type === 'error', otherwise null. */
|
|
88
179
|
error: string | null;
|
|
89
180
|
}
|
|
90
|
-
export declare function useCartManager(): UseCartManagerResult;
|
|
181
|
+
export declare function useCartManager(options?: UseCartManagerOptions): UseCartManagerResult;
|
|
91
182
|
//# sourceMappingURL=use-cart-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"use-cart-manager.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-cart-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,23 +7,53 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Per-operation strategy:
|
|
9
9
|
*
|
|
10
|
-
* | Operation
|
|
11
|
-
* |
|
|
12
|
-
* | `addItem`
|
|
13
|
-
* | `updateBuyerIdentity`
|
|
14
|
-
* | `setShippingAddress`
|
|
15
|
-
* | `updateDiscountCodes`
|
|
16
|
-
* | `updateNote`
|
|
17
|
-
* | `
|
|
18
|
-
* | `
|
|
10
|
+
* | Operation | Strategy | Why |
|
|
11
|
+
* | -------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
12
|
+
* | `addItem` | Auto-replay (atomic `cartCreate`) | Storefront expects "add to cart" always works |
|
|
13
|
+
* | `updateBuyerIdentity` | Auto-replay | User just typed email/phone — keep it |
|
|
14
|
+
* | `setShippingAddress` | Auto-replay | User just typed address — keep it |
|
|
15
|
+
* | `updateDiscountCodes` | Auto-replay | Coupon valid independently of cart |
|
|
16
|
+
* | `updateNote` | Auto-replay | Stateless / idempotent |
|
|
17
|
+
* | `updateAttributes` | Auto-replay | Stateless metadata — atomic re-create OK |
|
|
18
|
+
* | `updateItem` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
|
|
19
|
+
* | `removeItem` | Bail + `cart-expired` event | jw. |
|
|
20
|
+
* | `setBillingAddress` | Bail + `cart-expired` event | Cart must exist (separate from shipping) |
|
|
21
|
+
* | `selectShippingMethod` | Bail + `cart-expired` event | Method tied to cart contents + address |
|
|
22
|
+
* | `selectPaymentMethod` | Bail + `cart-expired` event | Payment selection on existing cart state |
|
|
23
|
+
* | `clearPaymentSelection` | Bail + `cart-expired` event | Operates on existing cart payment fields |
|
|
24
|
+
* | `applyGiftCard` | Bail + `cart-expired` event | Balance allocation tied to cart total |
|
|
25
|
+
* | `removeGiftCard` | Bail + `cart-expired` event | `giftCardId` refers to row on dead cart |
|
|
26
|
+
* | `updateGiftCardRecipient` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
|
|
27
|
+
* | `complete` | Bail + `cart-expired` event | Finalised cart cannot be auto-recreated |
|
|
28
|
+
* | `createPayment` | Out of recovery scope | Operates on `orderId` (post-complete) |
|
|
19
29
|
*
|
|
20
30
|
* On bail the runner clears the cookie and calls every `onExpired` listener
|
|
21
31
|
* with a `CartExpiredEvent`. UI subscribes once globally and shows a toast /
|
|
22
32
|
* banner — caller code never writes `try / catch` per mutation.
|
|
23
33
|
*
|
|
34
|
+
* After `complete` success the hook auto-clears the `cart-id` cookie and
|
|
35
|
+
* resets status to `idle` — buyer returning to `/checkout` (back from the
|
|
36
|
+
* payment gateway, deep link, new tab) gets a fresh empty cart instead of the
|
|
37
|
+
* CONVERTED cart. Manual `clearCart()` remains available as an escape hatch.
|
|
38
|
+
*
|
|
24
39
|
* Auto-creates cart on first add. Cart id persisted in `cart-id` cookie
|
|
25
40
|
* (SSR/edge visible, 30 days, samesite=lax).
|
|
26
41
|
*
|
|
42
|
+
* ### Server-known cart-id (env seed, magic-link, iframe, customer service)
|
|
43
|
+
*
|
|
44
|
+
* Pass `{ initialCartId }` to seed the hook with a cart-id resolved server-side
|
|
45
|
+
* (read from URL params in a Route Handler, env var for dev fixtures, parent
|
|
46
|
+
* `postMessage` for embedded iframe, admin "view this cart" lookup). The hook
|
|
47
|
+
* applies priority `cookie wins → seed → auto-create`. The seed is eagerly
|
|
48
|
+
* written to the cart-id cookie so cross-tab tabs and standard recovery
|
|
49
|
+
* semantics operate on a canonical value. A stale seed goes through the same
|
|
50
|
+
* recovery flow as a stale cookie — `addItem` auto-replays through
|
|
51
|
+
* `cartCreate({ lines })`, state-dependent ops bail with `cart-expired`.
|
|
52
|
+
*
|
|
53
|
+
* Mirrors the `<StorefrontProvider initialAccessToken>` pattern for the
|
|
54
|
+
* cart-id half of the checkout state — both are seeds for the first render
|
|
55
|
+
* when the client cannot read the canonical source itself.
|
|
56
|
+
*
|
|
27
57
|
* @example
|
|
28
58
|
* ```tsx
|
|
29
59
|
* function CartUI() {
|
|
@@ -34,21 +64,46 @@
|
|
|
34
64
|
* return <button onClick={() => addItem([{ variantId, quantity: 1 }])}>Add</button>;
|
|
35
65
|
* }
|
|
36
66
|
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example Server-known cart-id
|
|
69
|
+
* ```tsx
|
|
70
|
+
* // app/checkout/page.tsx — Server Component
|
|
71
|
+
* import { cookies } from 'next/headers';
|
|
72
|
+
* import { CART_COOKIE_NAME } from '@doswiftly/storefront-sdk';
|
|
73
|
+
*
|
|
74
|
+
* export default async function CheckoutPage() {
|
|
75
|
+
* const cookieJar = await cookies();
|
|
76
|
+
* const initialCartId =
|
|
77
|
+
* cookieJar.get(CART_COOKIE_NAME)?.value ?? process.env.NEXT_PUBLIC_DEV_CART_ID ?? null;
|
|
78
|
+
* return <CheckoutClient initialCartId={initialCartId} />;
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* // CheckoutClient.tsx — 'use client'
|
|
82
|
+
* function CheckoutClient({ initialCartId }: { initialCartId: string | null }) {
|
|
83
|
+
* const { complete, selectPaymentMethod, addItem } = useCartManager({ initialCartId });
|
|
84
|
+
* // ... rest unchanged — hook handles seed → cookie → recovery transparently
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
37
87
|
*/
|
|
38
88
|
'use client';
|
|
39
89
|
import { useCallback, useMemo, useState } from 'react';
|
|
40
90
|
import { useStorefrontClientContext } from '../providers/storefront-client-provider';
|
|
41
91
|
import { createCartRecoveryRunner, recreateWithInput, } from '../../core/cart/cart-recovery';
|
|
42
92
|
import { createBrowserCartCookieStore } from '../cookies';
|
|
43
|
-
export function useCartManager() {
|
|
93
|
+
export function useCartManager(options) {
|
|
44
94
|
const { cartClient } = useStorefrontClientContext();
|
|
45
95
|
const [status, setStatus] = useState({ type: 'idle' });
|
|
46
96
|
// Cookie store is stateless — keep one instance per hook mount.
|
|
47
97
|
const cookieStore = useMemo(() => createBrowserCartCookieStore(), []);
|
|
98
|
+
// Normalize seed so the runner identity is stable across `undefined` /
|
|
99
|
+
// `null` props and only flips when the storefront actually swaps the seed.
|
|
100
|
+
const initialCartId = options?.initialCartId ?? null;
|
|
48
101
|
// Recovery runner is bound to cartClient identity (changes only on provider
|
|
49
102
|
// re-mount). Sharing one runner per hook gives concurrent operations a single
|
|
50
|
-
// recovery coordinator (Phase 0 mutex).
|
|
51
|
-
|
|
103
|
+
// recovery coordinator (Phase 0 mutex). `initialCartId` is part of the
|
|
104
|
+
// identity so a switch (e.g. cart switcher in a multi-cart B2B UI) rebuilds
|
|
105
|
+
// the runner instead of carrying the previous seed forward.
|
|
106
|
+
const runner = useMemo(() => createCartRecoveryRunner({ cartClient, cookieStore, initialCartId }), [cartClient, cookieStore, initialCartId]);
|
|
52
107
|
// Subscribe to cart-expired events. The returned unsubscribe function is
|
|
53
108
|
// bound to the current runner identity — wrap calls in
|
|
54
109
|
// `useEffect(() => onExpired(...), [onExpired])` so React re-subscribes when
|
|
@@ -98,6 +153,11 @@ export function useCartManager() {
|
|
|
98
153
|
run: (cartId) => cartClient.updateNote(cartId, note),
|
|
99
154
|
recreateAndRun: recreateWithInput({ note }),
|
|
100
155
|
}), 'Failed to update note'), [runner, cartClient, wrapMutation]);
|
|
156
|
+
const updateAttributes = useCallback((attributes) => wrapMutation('updateAttributes', () => runner.execute({
|
|
157
|
+
name: 'updateAttributes',
|
|
158
|
+
run: (cartId) => cartClient.updateAttributes(cartId, attributes),
|
|
159
|
+
recreateAndRun: recreateWithInput({ attributes }),
|
|
160
|
+
}), 'Failed to update cart attributes'), [runner, cartClient, wrapMutation]);
|
|
101
161
|
// --- Bail-on-stale mutations (no recreateAndRun — fires onExpired + throws) ---
|
|
102
162
|
const updateItem = useCallback((lines) => wrapMutation('updateItem', () => runner.execute({
|
|
103
163
|
name: 'updateItem',
|
|
@@ -107,6 +167,68 @@ export function useCartManager() {
|
|
|
107
167
|
name: 'removeItem',
|
|
108
168
|
run: (cartId) => cartClient.removeItems(cartId, lineIds),
|
|
109
169
|
}), 'Failed to remove from cart'), [runner, cartClient, wrapMutation]);
|
|
170
|
+
const setBillingAddress = useCallback((address) => wrapMutation('setBillingAddress', () => runner.execute({
|
|
171
|
+
name: 'setBillingAddress',
|
|
172
|
+
run: (cartId) => cartClient.setBillingAddress({ cartId, address }),
|
|
173
|
+
}), 'Failed to set billing address'), [runner, cartClient, wrapMutation]);
|
|
174
|
+
const selectShippingMethod = useCallback((input) => wrapMutation('selectShippingMethod', () => runner.execute({
|
|
175
|
+
name: 'selectShippingMethod',
|
|
176
|
+
run: (cartId) => cartClient.selectShippingMethod({ cartId, ...input }),
|
|
177
|
+
}), 'Failed to select shipping method'), [runner, cartClient, wrapMutation]);
|
|
178
|
+
const selectPaymentMethod = useCallback((input) => wrapMutation('selectPaymentMethod', () => runner.execute({
|
|
179
|
+
name: 'selectPaymentMethod',
|
|
180
|
+
run: (cartId) => cartClient.selectPaymentMethod({ cartId, ...input }),
|
|
181
|
+
}), 'Failed to select payment method'), [runner, cartClient, wrapMutation]);
|
|
182
|
+
const clearPaymentSelection = useCallback((input) => wrapMutation('clearPaymentSelection', () => runner.execute({
|
|
183
|
+
name: 'clearPaymentSelection',
|
|
184
|
+
run: (cartId) => cartClient.clearPaymentSelection({ cartId, ...(input ?? {}) }),
|
|
185
|
+
}), 'Failed to clear payment selection'), [runner, cartClient, wrapMutation]);
|
|
186
|
+
const applyGiftCard = useCallback((input) => wrapMutation('applyGiftCard', () => runner.execute({
|
|
187
|
+
name: 'applyGiftCard',
|
|
188
|
+
run: (cartId) => cartClient.applyGiftCard({ cartId, ...input }),
|
|
189
|
+
}), 'Failed to apply gift card'), [runner, cartClient, wrapMutation]);
|
|
190
|
+
const removeGiftCard = useCallback((input) => wrapMutation('removeGiftCard', () => runner.execute({
|
|
191
|
+
name: 'removeGiftCard',
|
|
192
|
+
run: (cartId) => cartClient.removeGiftCard({ cartId, ...input }),
|
|
193
|
+
}), 'Failed to remove gift card'), [runner, cartClient, wrapMutation]);
|
|
194
|
+
const updateGiftCardRecipient = useCallback((input) => wrapMutation('updateGiftCardRecipient', () => runner.execute({
|
|
195
|
+
name: 'updateGiftCardRecipient',
|
|
196
|
+
run: (cartId) => cartClient.updateGiftCardRecipient({ cartId, ...input }),
|
|
197
|
+
}), 'Failed to update gift card recipient'), [runner, cartClient, wrapMutation]);
|
|
198
|
+
// --- Completion lifecycle ---
|
|
199
|
+
/**
|
|
200
|
+
* Bail-on-stale completion. On success the cart is CONVERTED/locked, so the
|
|
201
|
+
* hook auto-clears the cart cookie and resets status to `idle`. A follow-up
|
|
202
|
+
* `addItem` recreates a fresh cart through the standard recovery runner.
|
|
203
|
+
* `wrapMutation` is intentionally bypassed — its success path leaves status
|
|
204
|
+
* as `{ type: 'success', operation: 'complete' }`, but the cart no longer
|
|
205
|
+
* exists in the cookie state, so `idle` is the truthful representation.
|
|
206
|
+
*/
|
|
207
|
+
const complete = useCallback(async (input) => {
|
|
208
|
+
setStatus({ type: 'loading', operation: 'complete' });
|
|
209
|
+
try {
|
|
210
|
+
const result = await runner.execute({
|
|
211
|
+
name: 'complete',
|
|
212
|
+
run: (cartId) => cartClient.complete({ cartId, ...(input ?? {}) }),
|
|
213
|
+
});
|
|
214
|
+
cookieStore.clear();
|
|
215
|
+
setStatus({ type: 'idle' });
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
const error = err instanceof Error ? err : new Error('Failed to complete cart');
|
|
220
|
+
setStatus({ type: 'error', operation: 'complete', error });
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
}, [runner, cartClient, cookieStore]);
|
|
224
|
+
/**
|
|
225
|
+
* Payment session creation. NOT a cart operation (works on `orderId` from
|
|
226
|
+
* `complete().order.id`) so the recovery runner is bypassed — there is no
|
|
227
|
+
* cart to recover from. Wrapped in `wrapMutation` only for status tracking
|
|
228
|
+
* so consumers can render a single `<Spinner label={status.operation} />`
|
|
229
|
+
* across the whole checkout lifecycle.
|
|
230
|
+
*/
|
|
231
|
+
const createPayment = useCallback((input) => wrapMutation('createPayment', () => cartClient.createPayment(input), 'Failed to create payment'), [cartClient, wrapMutation]);
|
|
110
232
|
// --- Lifecycle ---
|
|
111
233
|
const clearCart = useCallback(() => {
|
|
112
234
|
cookieStore.clear();
|
|
@@ -123,8 +245,18 @@ export function useCartManager() {
|
|
|
123
245
|
setShippingAddress,
|
|
124
246
|
updateDiscountCodes,
|
|
125
247
|
updateNote,
|
|
248
|
+
updateAttributes,
|
|
126
249
|
updateItem,
|
|
127
250
|
removeItem,
|
|
251
|
+
setBillingAddress,
|
|
252
|
+
selectShippingMethod,
|
|
253
|
+
selectPaymentMethod,
|
|
254
|
+
clearPaymentSelection,
|
|
255
|
+
applyGiftCard,
|
|
256
|
+
removeGiftCard,
|
|
257
|
+
updateGiftCardRecipient,
|
|
258
|
+
complete,
|
|
259
|
+
createPayment,
|
|
128
260
|
clearCart,
|
|
129
261
|
onExpired,
|
|
130
262
|
status,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-login.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,gHAAgH;IAChH,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACjE,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,0GAA0G;IAC1G,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"use-login.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,gHAAgH;IAChH,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACjE,iDAAiD;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,0GAA0G;IAC1G,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,cAAc,CAgEtE"}
|
|
@@ -40,14 +40,14 @@ export function useLogin(options = {}) {
|
|
|
40
40
|
firstName: customer.firstName ?? undefined,
|
|
41
41
|
lastName: customer.lastName ?? undefined,
|
|
42
42
|
phone: customer.phone ?? undefined,
|
|
43
|
-
}, result.accessToken);
|
|
43
|
+
}, result.accessToken, result.expiresAt);
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
|
-
setAuth({ id: '', email }, result.accessToken);
|
|
46
|
+
setAuth({ id: '', email }, result.accessToken, result.expiresAt);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
catch {
|
|
50
|
-
setAuth({ id: '', email }, result.accessToken);
|
|
50
|
+
setAuth({ id: '', email }, result.accessToken, result.expiresAt);
|
|
51
51
|
}
|
|
52
52
|
return {
|
|
53
53
|
success: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-refresh-token.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-refresh-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,MAAM,WAAW,sBAAsB;IACrC,qFAAqF;IACrF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,YAAY,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChD,mDAAmD;IACnD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,
|
|
1
|
+
{"version":3,"file":"use-refresh-token.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-refresh-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,MAAM,WAAW,sBAAsB;IACrC,qFAAqF;IACrF,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,YAAY,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAChD,mDAAmD;IACnD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,yFAAyF;IACzF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,qBAAqB,CAiD3F"}
|
|
@@ -36,10 +36,12 @@ export function useRefreshToken(options = {}) {
|
|
|
36
36
|
if (options.onSetToken) {
|
|
37
37
|
await options.onSetToken(result.accessToken);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
// Thread the new token + expiry into the store unconditionally — `setAuth`
|
|
40
|
+
// accepts a null customer, so a server-seeded session without a loaded
|
|
41
|
+
// profile (SSO callback / magic link / dev seed) still gets its refreshed
|
|
42
|
+
// `expiresAt`, keeping the proactive scheduler armed (parity with the
|
|
43
|
+
// in-provider refresh + scheduler, which also pass `getState().customer`).
|
|
44
|
+
setAuth(authStore.getState().customer, result.accessToken, result.expiresAt);
|
|
43
45
|
return {
|
|
44
46
|
success: true,
|
|
45
47
|
userErrors: [],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSessionExpired — subscribe to the global session-expired signal.
|
|
3
|
+
*
|
|
4
|
+
* Fired when the SDK can no longer keep the customer session alive: a proactive
|
|
5
|
+
* refresh failed on tab wake (R6.3), or a reactive refresh after a 401 also
|
|
6
|
+
* failed (R2.4). Use once near the app root to react globally — show a notice
|
|
7
|
+
* and redirect to sign-in (R14.2). No-op outside `StorefrontProvider`.
|
|
8
|
+
*/
|
|
9
|
+
import type { SessionExpiredEmitter, SessionExpiredEvent } from '../../core/auth/session-events';
|
|
10
|
+
/**
|
|
11
|
+
* Context carrying the provider-scoped session-expired emitter. Created in
|
|
12
|
+
* `StorefrontProvider` via `useRef` (Inv-3 — never a module-level singleton).
|
|
13
|
+
*/
|
|
14
|
+
export declare const SessionExpiredContext: import("react").Context<SessionExpiredEmitter | null>;
|
|
15
|
+
export declare function useSessionExpired(listener: (event: SessionExpiredEvent) => void): void;
|
|
16
|
+
//# sourceMappingURL=use-session-expired.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-session-expired.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-session-expired.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAEjG;;;GAGG;AACH,eAAO,MAAM,qBAAqB,uDAAoD,CAAC;AAEvF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI,CAUtF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSessionExpired — subscribe to the global session-expired signal.
|
|
3
|
+
*
|
|
4
|
+
* Fired when the SDK can no longer keep the customer session alive: a proactive
|
|
5
|
+
* refresh failed on tab wake (R6.3), or a reactive refresh after a 401 also
|
|
6
|
+
* failed (R2.4). Use once near the app root to react globally — show a notice
|
|
7
|
+
* and redirect to sign-in (R14.2). No-op outside `StorefrontProvider`.
|
|
8
|
+
*/
|
|
9
|
+
'use client';
|
|
10
|
+
import { createContext, useContext, useEffect, useRef } from 'react';
|
|
11
|
+
/**
|
|
12
|
+
* Context carrying the provider-scoped session-expired emitter. Created in
|
|
13
|
+
* `StorefrontProvider` via `useRef` (Inv-3 — never a module-level singleton).
|
|
14
|
+
*/
|
|
15
|
+
export const SessionExpiredContext = createContext(null);
|
|
16
|
+
export function useSessionExpired(listener) {
|
|
17
|
+
const emitter = useContext(SessionExpiredContext);
|
|
18
|
+
// Keep the latest listener without re-subscribing on every render.
|
|
19
|
+
const ref = useRef(listener);
|
|
20
|
+
ref.current = listener;
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!emitter)
|
|
23
|
+
return;
|
|
24
|
+
return emitter.subscribe((event) => ref.current(event));
|
|
25
|
+
}, [emitter]);
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSessionRefresh — proactive, browser-only session-refresh scheduler.
|
|
3
|
+
*
|
|
4
|
+
* Renews the access token shortly *before* it expires so an active buyer is
|
|
5
|
+
* never logged out mid-session, reschedules from each new expiry, and never runs
|
|
6
|
+
* on the server. On tab wake after the token already lapsed it tries once and —
|
|
7
|
+
* if the session can no longer be recovered — emits `session-expired` and clears
|
|
8
|
+
* local auth.
|
|
9
|
+
*
|
|
10
|
+
* The refresh goes through the same-origin BFF route via
|
|
11
|
+
* `AuthClient.refreshSession()` (`POST {authBasePath}/refresh`). The route handler
|
|
12
|
+
* reads the httpOnly refresh cookie server-side, rotates it against the backend,
|
|
13
|
+
* and sets the new first-party cookies — so an ALREADY-EXPIRED access token still
|
|
14
|
+
* refreshes (the old GraphQL `customerRefreshToken` could not, as it needed a
|
|
15
|
+
* valid access token). No `setToken` round-trip and no consumer callback.
|
|
16
|
+
*
|
|
17
|
+
* The timer lives in the effect closure (provider lifecycle), never in module state.
|
|
18
|
+
*/
|
|
19
|
+
import type { SessionExpiredEmitter } from '../../core/auth/session-events';
|
|
20
|
+
export interface UseSessionRefreshOptions {
|
|
21
|
+
/** Master switch. Defaults to `true` in the browser, `false` on the server. */
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
/** Refresh this many ms before the token's `expiresAt` (default 60_000). */
|
|
24
|
+
bufferMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Emitter the scheduler notifies when a wake-time refresh cannot recover the
|
|
27
|
+
* session. Provided by `StorefrontProvider`; subscribe via `useSessionExpired`.
|
|
28
|
+
*/
|
|
29
|
+
sessionExpiredEmitter?: SessionExpiredEmitter;
|
|
30
|
+
}
|
|
31
|
+
export declare function useSessionRefresh(options?: UseSessionRefreshOptions): void;
|
|
32
|
+
//# sourceMappingURL=use-session-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-session-refresh.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/use-session-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAE5E,MAAM,WAAW,wBAAwB;IACvC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAYD,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,IAAI,CAkH9E"}
|