@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.
Files changed (94) hide show
  1. package/CHANGELOG.md +1255 -0
  2. package/README.md +16 -4
  3. package/dist/core/auth/auth-client.d.ts +39 -3
  4. package/dist/core/auth/auth-client.d.ts.map +1 -1
  5. package/dist/core/auth/auth-client.js +51 -3
  6. package/dist/core/auth/cookie-config.d.ts +52 -3
  7. package/dist/core/auth/cookie-config.d.ts.map +1 -1
  8. package/dist/core/auth/cookie-config.js +60 -6
  9. package/dist/core/auth/handlers.d.ts +46 -0
  10. package/dist/core/auth/handlers.d.ts.map +1 -1
  11. package/dist/core/auth/handlers.js +9 -2
  12. package/dist/core/auth/session-events.d.ts +38 -0
  13. package/dist/core/auth/session-events.d.ts.map +1 -0
  14. package/dist/core/auth/session-events.js +35 -0
  15. package/dist/core/cart/cart-client.d.ts +10 -1
  16. package/dist/core/cart/cart-client.d.ts.map +1 -1
  17. package/dist/core/cart/cart-client.js +17 -1
  18. package/dist/core/cart/cart-recovery.d.ts +23 -0
  19. package/dist/core/cart/cart-recovery.d.ts.map +1 -1
  20. package/dist/core/cart/cart-recovery.js +20 -3
  21. package/dist/core/cart/types.d.ts +2 -1
  22. package/dist/core/cart/types.d.ts.map +1 -1
  23. package/dist/core/cart/types.js +7 -1
  24. package/dist/core/client/create-client.d.ts.map +1 -1
  25. package/dist/core/client/create-client.js +7 -3
  26. package/dist/core/client/execute.d.ts +29 -3
  27. package/dist/core/client/execute.d.ts.map +1 -1
  28. package/dist/core/client/execute.js +174 -3
  29. package/dist/core/client/types.d.ts +50 -2
  30. package/dist/core/client/types.d.ts.map +1 -1
  31. package/dist/core/errors.d.ts +6 -0
  32. package/dist/core/errors.d.ts.map +1 -1
  33. package/dist/core/errors.js +6 -0
  34. package/dist/core/generated/operation-types.d.ts +937 -182
  35. package/dist/core/generated/operation-types.d.ts.map +1 -1
  36. package/dist/core/generated/operation-types.js +560 -1
  37. package/dist/core/index.d.ts +6 -3
  38. package/dist/core/index.d.ts.map +1 -1
  39. package/dist/core/index.js +12 -2
  40. package/dist/core/middleware/session-retry.d.ts +47 -0
  41. package/dist/core/middleware/session-retry.d.ts.map +1 -0
  42. package/dist/core/middleware/session-retry.js +71 -0
  43. package/dist/core/operations/auth.d.ts.map +1 -1
  44. package/dist/core/operations/auth.js +1 -0
  45. package/dist/core/operations/cart.d.ts +7 -0
  46. package/dist/core/operations/cart.d.ts.map +1 -1
  47. package/dist/core/operations/cart.js +54 -3
  48. package/dist/react/components/PaymentInstrumentSection.d.ts +56 -0
  49. package/dist/react/components/PaymentInstrumentSection.d.ts.map +1 -0
  50. package/dist/react/components/PaymentInstrumentSection.js +89 -0
  51. package/dist/react/components/PaymentInstrumentTile.d.ts +56 -0
  52. package/dist/react/components/PaymentInstrumentTile.d.ts.map +1 -0
  53. package/dist/react/components/PaymentInstrumentTile.js +41 -0
  54. package/dist/react/components/index.d.ts +2 -0
  55. package/dist/react/components/index.d.ts.map +1 -1
  56. package/dist/react/components/index.js +2 -0
  57. package/dist/react/helpers/browser-data.d.ts +89 -0
  58. package/dist/react/helpers/browser-data.d.ts.map +1 -0
  59. package/dist/react/helpers/browser-data.js +84 -0
  60. package/dist/react/hooks/use-cart-manager.d.ts +104 -13
  61. package/dist/react/hooks/use-cart-manager.d.ts.map +1 -1
  62. package/dist/react/hooks/use-cart-manager.js +144 -12
  63. package/dist/react/hooks/use-login.d.ts.map +1 -1
  64. package/dist/react/hooks/use-login.js +3 -3
  65. package/dist/react/hooks/use-refresh-token.d.ts.map +1 -1
  66. package/dist/react/hooks/use-refresh-token.js +6 -4
  67. package/dist/react/hooks/use-session-expired.d.ts +16 -0
  68. package/dist/react/hooks/use-session-expired.d.ts.map +1 -0
  69. package/dist/react/hooks/use-session-expired.js +26 -0
  70. package/dist/react/hooks/use-session-refresh.d.ts +32 -0
  71. package/dist/react/hooks/use-session-refresh.d.ts.map +1 -0
  72. package/dist/react/hooks/use-session-refresh.js +147 -0
  73. package/dist/react/index.d.ts +5 -1
  74. package/dist/react/index.d.ts.map +1 -1
  75. package/dist/react/index.js +5 -1
  76. package/dist/react/providers/storefront-client-provider.d.ts +10 -1
  77. package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
  78. package/dist/react/providers/storefront-client-provider.js +38 -3
  79. package/dist/react/providers/storefront-provider.d.ts +51 -3
  80. package/dist/react/providers/storefront-provider.d.ts.map +1 -1
  81. package/dist/react/providers/storefront-provider.js +22 -5
  82. package/dist/react/server/create-storefront-auth-route.d.ts +63 -0
  83. package/dist/react/server/create-storefront-auth-route.d.ts.map +1 -0
  84. package/dist/react/server/create-storefront-auth-route.js +239 -0
  85. package/dist/react/server/get-initial-auth.d.ts +57 -0
  86. package/dist/react/server/get-initial-auth.d.ts.map +1 -0
  87. package/dist/react/server/get-initial-auth.js +55 -0
  88. package/dist/react/server/index.d.ts +3 -0
  89. package/dist/react/server/index.d.ts.map +1 -1
  90. package/dist/react/server/index.js +6 -0
  91. package/dist/react/stores/auth.store.d.ts +46 -2
  92. package/dist/react/stores/auth.store.d.ts.map +1 -1
  93. package/dist/react/stores/auth.store.js +19 -7
  94. 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 | 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
- * | `updateItem` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
18
- * | `removeItem` | Bail + `cart-expired` event | jw. |
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAMH,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAGL,KAAK,gBAAgB,EAEtB,MAAM,+BAA+B,CAAC;AAGvC;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,YAAY,GACZ,YAAY,GACZ,qBAAqB,GACrB,oBAAoB,GACpB,qBAAqB,GACrB,YAAY,CAAC;AAEjB;;;;;;;;;;;;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,MAAM,WAAW,oBAAoB;IAEnC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAG/B,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClE,mBAAmB,EAAE,CAAC,aAAa,EAAE,sBAAsB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7F,kBAAkB,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAChF,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvE,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAG3D,UAAU,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3E,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAGhE,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;IAGvE,MAAM,EAAE,iBAAiB,CAAC;IAG1B,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,cAAc,IAAI,oBAAoB,CA0LrD"}
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 | 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
- * | `updateItem` | Bail + `cart-expired` event | `lineId` refers to a line in the dead cart |
18
- * | `removeItem` | Bail + `cart-expired` event | jw. |
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
- const runner = useMemo(() => createCartRecoveryRunner({ cartClient, cookieStore }), [cartClient, cookieStore]);
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,CA+DtE"}
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,CA+C3F"}
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
- const currentCustomer = authStore.getState().customer;
40
- if (currentCustomer) {
41
- setAuth(currentCustomer, result.accessToken);
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"}