@doswiftly/storefront-sdk 22.1.0 → 22.2.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
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 22.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 27934d1: Server `getStorefrontClient`: forwarded-IP signing is now **opt-in** via `getBuyerIp`.
|
|
8
|
+
|
|
9
|
+
Previously the server client auto-read the buyer IP through `next/headers` on every
|
|
10
|
+
request. That is a dynamic API and is illegal in statically-generated / ISR routes —
|
|
11
|
+
it crashed such pages with "Page changed from static to dynamic at runtime".
|
|
12
|
+
|
|
13
|
+
Forwarded-IP is now wired only when you pass `getBuyerIp`, which you should do **only
|
|
14
|
+
on routes that are already dynamic** (per-request rendered). Without it the client is
|
|
15
|
+
a fully static-safe, inert pass-through.
|
|
16
|
+
|
|
17
|
+
Migration — to keep forwarding the buyer IP, pass `getBuyerIp` on your dynamic routes:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { headers } from "next/headers";
|
|
21
|
+
|
|
22
|
+
getStorefrontClient({
|
|
23
|
+
apiUrl,
|
|
24
|
+
shopSlug,
|
|
25
|
+
getBuyerIp: async () => (await headers()).get("cf-connecting-ip"),
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
3
29
|
## 22.1.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1078,34 +1078,32 @@ middleware: [
|
|
|
1078
1078
|
### Forwarding the buyer IP for rate limiting
|
|
1079
1079
|
|
|
1080
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.
|
|
1082
|
-
|
|
1081
|
+
every buyer, so per-IP rate limits collapse onto a single address. Forwarding the
|
|
1082
|
+
buyer's real IP restores per-buyer limits.
|
|
1083
1083
|
|
|
1084
|
-
**
|
|
1084
|
+
**Opt-in — supply `getBuyerIp`.** Reading the buyer IP requires a request-scoped
|
|
1085
|
+
dynamic API (e.g. `next/headers` `headers()`), which is **illegal in
|
|
1086
|
+
statically-generated / ISR routes** and would crash them ("static to dynamic at
|
|
1087
|
+
runtime"). So enable it **only on routes that are already dynamic** (per-request
|
|
1088
|
+
rendered):
|
|
1085
1089
|
|
|
1086
1090
|
```typescript
|
|
1091
|
+
import { headers } from 'next/headers';
|
|
1092
|
+
|
|
1093
|
+
// In a DYNAMIC route/segment only — headers() forces dynamic rendering:
|
|
1087
1094
|
const client = getStorefrontClient({
|
|
1088
1095
|
apiUrl: process.env.DOSWIFTLY_API_URL!,
|
|
1089
1096
|
shopSlug: process.env.DOSWIFTLY_SHOP_SLUG!,
|
|
1097
|
+
getBuyerIp: async () => (await headers()).get('cf-connecting-ip'),
|
|
1090
1098
|
});
|
|
1091
1099
|
```
|
|
1092
1100
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1101
|
+
Without `getBuyerIp` the client never reads the IP and never signs — a fully
|
|
1102
|
+
static-safe, inert pass-through (static / ISR routes are unaffected). Signing also
|
|
1103
|
+
requires the secret `process.env.DOSWIFTLY_FORWARDED_IP_SECRET` (set by your
|
|
1104
|
+
deployment; override via `getForwardedIpSecret` for runtimes without `process.env`).
|
|
1095
1105
|
**Server-only**: nothing IP-related reaches the browser.
|
|
1096
1106
|
|
|
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
1107
|
The lower-level `forwardedIpMiddleware({ getBuyerIp, getSecret })` is also exported
|
|
1110
1108
|
for fully custom clients.
|
|
1111
1109
|
|
|
@@ -41,12 +41,22 @@ export interface ServerClientOptions extends Omit<StorefrontClientConfig, 'middl
|
|
|
41
41
|
*/
|
|
42
42
|
middleware?: Middleware[];
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* Buyer-IP source that ENABLES forwarded-IP signing (opt-in). The forwarded-IP
|
|
45
|
+
* middleware is wired ONLY when this is provided — without it the client never
|
|
46
|
+
* reads the buyer IP and never signs (a fully static-safe, inert pass-through).
|
|
47
|
+
*
|
|
48
|
+
* Reading the buyer IP needs a request-scoped dynamic API (e.g. `next/headers`
|
|
49
|
+
* `headers()`), which is ILLEGAL in statically-generated / ISR routes — calling it
|
|
50
|
+
* there crashes the page ("static to dynamic at runtime"). So provide this ONLY on
|
|
51
|
+
* routes that are already dynamic (per-request rendered). A server-rendered
|
|
52
|
+
* storefront otherwise collapses every buyer onto its own server IP for rate
|
|
53
|
+
* limiting; forwarding the real IP restores per-buyer limits. May be async.
|
|
49
54
|
* Server-side only.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // ONLY on a dynamic route — `headers()` forces dynamic rendering:
|
|
58
|
+
* import { headers } from 'next/headers';
|
|
59
|
+
* getStorefrontClient({ apiUrl, shopSlug, getBuyerIp: async () => (await headers()).get('cf-connecting-ip') });
|
|
50
60
|
*/
|
|
51
61
|
getBuyerIp?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
52
62
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC;IACrF;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAE1B
|
|
1
|
+
{"version":3,"file":"get-storefront-client.d.ts","sourceRoot":"","sources":["../../../src/react/server/get-storefront-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAEpG,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC;IACrF;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;IAE1B;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAElF;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CAC7F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,gBAAgB,CAkClF"}
|
|
@@ -24,25 +24,6 @@ import { retryMiddleware } from '../../core/middleware/retry';
|
|
|
24
24
|
import { timeoutMiddleware } from '../../core/middleware/timeout';
|
|
25
25
|
import { errorMiddleware } from '../../core/middleware/errors';
|
|
26
26
|
import { forwardedIpMiddleware } from '../../core/middleware/forwarded-ip';
|
|
27
|
-
/**
|
|
28
|
-
* Default buyer-IP source: the request's `cf-connecting-ip` header, read via
|
|
29
|
-
* `next/headers` (the same dynamic-import pattern used to read request cookies — a
|
|
30
|
-
* graceful no-op outside a Next request scope or in runtimes without
|
|
31
|
-
* `next/headers`). Override via `getBuyerIp` for other server frameworks.
|
|
32
|
-
*/
|
|
33
|
-
async function readCfConnectingIp() {
|
|
34
|
-
try {
|
|
35
|
-
const { headers } = await import('next/headers');
|
|
36
|
-
const store = await headers();
|
|
37
|
-
// Prefer a forwarded client-IP header when present: if the request was
|
|
38
|
-
// proxied, the direct `cf-connecting-ip` is the proxy's address while this
|
|
39
|
-
// header carries the original client IP. Fall back to the direct connection IP.
|
|
40
|
-
return store.get('x-doswiftly-client-ip') ?? store.get('cf-connecting-ip');
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
27
|
/**
|
|
47
28
|
* Create a StorefrontClient for server-side use.
|
|
48
29
|
*
|
|
@@ -52,23 +33,29 @@ async function readCfConnectingIp() {
|
|
|
52
33
|
*/
|
|
53
34
|
export function getStorefrontClient(options) {
|
|
54
35
|
const { middleware: customMiddleware = [], getBuyerIp, getForwardedIpSecret, ...config } = options;
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// `
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
36
|
+
// Forwarded-IP signing is OPT-IN: the middleware is wired ONLY when the caller
|
|
37
|
+
// supplies `getBuyerIp`. Reading the buyer IP needs a request-scoped dynamic API
|
|
38
|
+
// (`next/headers` `headers()`), which is illegal in statically-generated / ISR
|
|
39
|
+
// routes — auto-reading it there crashes the page ("static to dynamic at runtime").
|
|
40
|
+
// So the caller provides the IP source and uses it ONLY on dynamic routes; without
|
|
41
|
+
// `getBuyerIp` this client is a fully static-safe pass-through. The slug comes from
|
|
42
|
+
// the `X-Shop-Slug` header the client already sends, so the signed value matches
|
|
43
|
+
// what the backend verifies. The secret defaults to
|
|
44
|
+
// `process.env.DOSWIFTLY_FORWARDED_IP_SECRET` (override via `getForwardedIpSecret`);
|
|
45
|
+
// signing happens only when BOTH a buyer IP and a secret resolve at request time.
|
|
46
|
+
const forwardedIp = getBuyerIp
|
|
47
|
+
? [
|
|
48
|
+
forwardedIpMiddleware({
|
|
49
|
+
getBuyerIp,
|
|
50
|
+
getSecret: getForwardedIpSecret ??
|
|
51
|
+
(() => (typeof process !== 'undefined' ? process.env?.DOSWIFTLY_FORWARDED_IP_SECRET : undefined)),
|
|
52
|
+
}),
|
|
53
|
+
]
|
|
54
|
+
: [];
|
|
68
55
|
return createStorefrontClient({
|
|
69
56
|
...config,
|
|
70
57
|
middleware: [
|
|
71
|
-
|
|
58
|
+
...forwardedIp,
|
|
72
59
|
...customMiddleware,
|
|
73
60
|
retryMiddleware({ maxRetries: 2 }),
|
|
74
61
|
timeoutMiddleware({ timeout: 10000 }), // Server-side: 10s timeout
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doswiftly/storefront-sdk",
|
|
3
|
-
"version": "22.
|
|
3
|
+
"version": "22.2.0",
|
|
4
4
|
"description": "Storefront runtime SDK for DoSwiftly Commerce — layered transport, middleware pipeline, React providers, Zustand stores, cache strategies. 0 runtime dependencies in core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|