@doswiftly/storefront-sdk 21.0.1 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +83 -0
  3. package/dist/core/generated/operation-types.d.ts +2 -2
  4. package/dist/core/generated/operation-types.d.ts.map +1 -1
  5. package/dist/core/index.d.ts +2 -0
  6. package/dist/core/index.d.ts.map +1 -1
  7. package/dist/core/index.js +3 -0
  8. package/dist/core/middleware/forwarded-ip.d.ts +82 -0
  9. package/dist/core/middleware/forwarded-ip.d.ts.map +1 -0
  10. package/dist/core/middleware/forwarded-ip.js +109 -0
  11. package/dist/core/referral/cookie-config.d.ts +56 -0
  12. package/dist/core/referral/cookie-config.d.ts.map +1 -0
  13. package/dist/core/referral/cookie-config.js +83 -0
  14. package/dist/react/hooks/use-referral-capture.d.ts +9 -0
  15. package/dist/react/hooks/use-referral-capture.d.ts.map +1 -0
  16. package/dist/react/hooks/use-referral-capture.js +40 -0
  17. package/dist/react/index.d.ts +2 -0
  18. package/dist/react/index.d.ts.map +1 -1
  19. package/dist/react/index.js +5 -1
  20. package/dist/react/referral.d.ts +53 -0
  21. package/dist/react/referral.d.ts.map +1 -0
  22. package/dist/react/referral.js +51 -0
  23. package/dist/react/server/cookie-readers.d.ts +7 -0
  24. package/dist/react/server/cookie-readers.d.ts.map +1 -1
  25. package/dist/react/server/cookie-readers.js +10 -0
  26. package/dist/react/server/get-storefront-client.d.ts +19 -1
  27. package/dist/react/server/get-storefront-client.d.ts.map +1 -1
  28. package/dist/react/server/get-storefront-client.js +36 -2
  29. package/dist/react/server/index.d.ts +1 -1
  30. package/dist/react/server/index.d.ts.map +1 -1
  31. package/dist/react/server/index.js +2 -2
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 22.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 32ee745: Forward the real buyer IP from server-rendered storefronts (per-buyer rate limiting)
8
+
9
+ A server-rendered (BFF) storefront fetches from its own server, so the API sees one
10
+ source IP for every buyer and per-IP rate limits collapse onto that single address.
11
+ The server client now forwards the real buyer IP to the API so rate limiting applies
12
+ per buyer again.
13
+
14
+ **Automatic on the server — no wiring, fully backward-compatible.** Call
15
+ `getStorefrontClient({ apiUrl, shopSlug })` as before; forwarding configures itself
16
+ and stays inert when it cannot apply, so existing setups are unaffected. Server-only.
17
+
18
+ Non-Next server runtimes can supply the buyer IP explicitly via `getBuyerIp` (and
19
+ `getForwardedIpSecret`). The lower-level `forwardedIpMiddleware` is also exported.
20
+
21
+ (`@doswiftly/storefront-operations` is a version-sync bump — no code change.)
22
+
23
+ ## 22.0.0
24
+
25
+ ### Major Changes
26
+
27
+ - 49139e2: Remove the `pendingPoints` field from `LoyaltyPointsSummary` (GraphQL schema, the `LoyaltyPointsSummary` fragment and the generated types). The field was never populated — it always returned `0` — because purchase points are credited directly when the payment is captured, so there is no "pending" state to report.
28
+
29
+ **Migration:** if your storefront reads `points.pendingPoints` (or selects `pendingPoints` in a custom query or fragment), remove that usage and re-run your codegen. No replacement field is needed — `currentPoints` has always been the spendable balance.
30
+
31
+ ### Minor Changes
32
+
33
+ - 49139e2: Referral program signup support — the loyalty referral program now works end-to-end:
34
+ - `customerSignup` accepts an optional `referralCode` in its input. When the shop's referral program is active, a valid code grants the configured bonus points to the new customer and, after their first paid order, rewards the referrer. An invalid, expired or own code is silently ignored — the signup always succeeds.
35
+ - New referral capture helpers in the SDK: `useReferralCapture()` / `captureReferralCode()` persist the `?ref=CODE` parameter from a referral landing link into the readable `referral-code` cookie (30 days by default), and `readReferralCodeCookie()` / `clearReferralCodeCookie()` read and clear it at signup time. A server-side `readReferralCodeCookie()` is available from `@doswiftly/storefront-sdk/react/server` for SSR-rendered signup forms. Core constants and the URL extractor (`REFERRAL_COOKIE_NAME`, `REFERRAL_COOKIE_MAX_AGE`, `extractReferralCodeFromUrl`) are exported from the framework-agnostic core entry.
36
+
37
+ The cookie lifetime is the _landing → signup_ window and is intentionally independent of the shop's referral validity setting, which starts at signup and is enforced by the API.
38
+
3
39
  ## 21.0.1
4
40
 
5
41
  ### Patch Changes
package/README.md CHANGED
@@ -667,6 +667,50 @@ const {
667
667
  });
668
668
  ```
669
669
 
670
+ ## Referral program
671
+
672
+ When the shop's loyalty referral program is enabled, customers share links like
673
+ `https://shop.example/register?ref=REF-AB12CD34` (the code and share URL come
674
+ from the `referralStats` / `loyaltyGenerateReferralCode` GraphQL operations).
675
+ The SDK bridges the gap between landing on such a link and the actual signup:
676
+
677
+ ```tsx
678
+ // 1. Capture the code on landing — mount once near the top of the app.
679
+ 'use client';
680
+ import { useReferralCapture } from '@doswiftly/storefront-sdk/react';
681
+
682
+ export function ReferralCapture() {
683
+ useReferralCapture(); // reads ?ref=... and stores it in the `referral-code` cookie (30 days)
684
+ return null;
685
+ }
686
+ ```
687
+
688
+ ```tsx
689
+ // 2. At signup, pass the stored code in the customerSignup input and clear it on success.
690
+ import { getReferralCodeCookie, clearReferralCodeCookie } from '@doswiftly/storefront-sdk/react';
691
+
692
+ const referralCode = getReferralCodeCookie();
693
+ await signup({ email, password, firstName, lastName, referralCode: referralCode ?? undefined });
694
+ clearReferralCodeCookie();
695
+ ```
696
+
697
+ Codes are case-insensitive and an invalid, malformed, expired or own code is
698
+ silently ignored by the API — the signup always succeeds. The capture step only
699
+ persists values matching the platform code shape (letters, digits, dashes —
700
+ `REFERRAL_CODE_PATTERN`), so junk `?ref=` values never reach the cookie. Server
701
+ Components can read the stored code with the async `readReferralCodeCookie()`
702
+ from `@doswiftly/storefront-sdk/react/server` (e.g. to pre-fill an SSR-rendered
703
+ form) — note the deliberate naming split: synchronous `getReferralCodeCookie()`
704
+ on the client entry vs server-first `readReferralCodeCookie()` on the server
705
+ entry. Non-React runtimes can use the core helpers directly:
706
+ `extractReferralCodeFromUrl(url)` plus the `REFERRAL_COOKIE_NAME` /
707
+ `REFERRAL_COOKIE_MAX_AGE` constants.
708
+
709
+ The cookie lifetime (30 days, override via `captureReferralCode({ maxAge })`)
710
+ is the *landing → signup* window. It is independent of the shop's referral
711
+ validity setting — that one starts at signup, limits the time the referred
712
+ customer has to place their first order, and is enforced by the backend.
713
+
670
714
  ## Pre-built React components
671
715
 
672
716
  Headless, accessibility-aware, zero styling — pass `className` to integrate with
@@ -728,6 +772,11 @@ Middleware that reads mutable state takes a **lazy getter**
728
772
  (`authMiddleware(() => store.getState().accessToken)`) so rotated values are
729
773
  picked up without rebuilding the client.
730
774
 
775
+ A server-only `forwardedIpMiddleware` is also available for server-rendered (BFF)
776
+ storefronts — it forwards the real buyer IP to the backend so per-IP rate limits
777
+ do not collapse onto the storefront server's address. See
778
+ [Server-side](#server-side-reactserver).
779
+
731
780
  ## Core API
732
781
 
733
782
  ### createStorefrontClient
@@ -1026,6 +1075,40 @@ middleware: [
1026
1075
  ],
1027
1076
  ```
1028
1077
 
1078
+ ### Forwarding the buyer IP for rate limiting
1079
+
1080
+ When a storefront fetches on the server, the API sees the storefront server's IP for
1081
+ every buyer, so per-IP rate limits collapse onto a single address. The server client
1082
+ forwards the buyer's real IP so rate limiting applies per buyer again.
1083
+
1084
+ **Automatic on the server — nothing to wire.** Call `getStorefrontClient` as usual:
1085
+
1086
+ ```typescript
1087
+ const client = getStorefrontClient({
1088
+ apiUrl: process.env.DOSWIFTLY_API_URL!,
1089
+ shopSlug: process.env.DOSWIFTLY_SHOP_SLUG!,
1090
+ });
1091
+ ```
1092
+
1093
+ It applies only inside a server request with forwarding configured by your
1094
+ deployment, and is otherwise an inert pass-through — existing setups are unaffected.
1095
+ **Server-only**: nothing IP-related reaches the browser.
1096
+
1097
+ **Non-Next server runtimes** — supply the buyer IP yourself (e.g. from
1098
+ `cf-connecting-ip`), plus the signing secret if your runtime has no `process.env`:
1099
+
1100
+ ```typescript
1101
+ const client = getStorefrontClient({
1102
+ apiUrl,
1103
+ shopSlug,
1104
+ getBuyerIp: () => incomingRequest.headers.get('cf-connecting-ip'),
1105
+ // getForwardedIpSecret: () => ...
1106
+ });
1107
+ ```
1108
+
1109
+ The lower-level `forwardedIpMiddleware({ getBuyerIp, getSecret })` is also exported
1110
+ for fully custom clients.
1111
+
1029
1112
  ## Caching
1030
1113
 
1031
1114
  ```typescript
@@ -1553,6 +1553,8 @@ export type CustomerCreateInput = {
1553
1553
  password: Scalars['String']['input'];
1554
1554
  /** Phone number (free-form). */
1555
1555
  phone?: InputMaybe<Scalars['String']['input']>;
1556
+ /** Referral code of an existing customer, usually taken from a referral link's `ref` URL parameter (codes are case-insensitive). Optional — an invalid, malformed, expired or own code is silently ignored and the signup still succeeds; only a value longer than 50 characters is rejected. When the shop's referral program is active, a valid code grants the configured bonus points to the new customer and, after their first paid order, rewards the referrer. */
1557
+ referralCode?: InputMaybe<Scalars['String']['input']>;
1556
1558
  };
1557
1559
  export type CustomerGroup = {
1558
1560
  /** Group code */
@@ -2092,8 +2094,6 @@ export type LoyaltyPointsSummary = {
2092
2094
  expiringPoints?: Maybe<Scalars['Int']['output']>;
2093
2095
  /** Next expiry date (ISO 8601) */
2094
2096
  nextExpiryDate?: Maybe<Scalars['DateTime']['output']>;
2095
- /** Points pending (from incomplete orders) */
2096
- pendingPoints: Scalars['Int']['output'];
2097
2097
  /** Points redeemed */
2098
2098
  redeemedPoints: Scalars['Int']['output'];
2099
2099
  /** Total points earned all-time */