@doswiftly/storefront-sdk 4.3.0 → 4.5.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 (96) hide show
  1. package/README.md +6 -14
  2. package/dist/core/cart/types.d.ts +53 -20
  3. package/dist/core/cart/types.d.ts.map +1 -1
  4. package/dist/core/cart/types.js +3 -0
  5. package/dist/core/image.d.ts +4 -46
  6. package/dist/core/image.d.ts.map +1 -1
  7. package/dist/core/image.js +4 -65
  8. package/dist/core/index.d.ts +1 -1
  9. package/dist/core/index.d.ts.map +1 -1
  10. package/dist/core/index.js +0 -2
  11. package/dist/core/operations/cart.d.ts +15 -9
  12. package/dist/core/operations/cart.d.ts.map +1 -1
  13. package/dist/core/operations/cart.js +130 -58
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/package.json +9 -4
  17. package/src/__tests__/contract/storefront-api.contract.test.ts +0 -450
  18. package/src/__tests__/unit/auth-client.test.ts +0 -210
  19. package/src/__tests__/unit/bot-protection.test.ts +0 -461
  20. package/src/__tests__/unit/cart-client.test.ts +0 -233
  21. package/src/__tests__/unit/cart-store.test.ts +0 -349
  22. package/src/__tests__/unit/create-client.test.ts +0 -356
  23. package/src/__tests__/unit/helpers.test.ts +0 -377
  24. package/src/__tests__/unit/middleware.test.ts +0 -374
  25. package/src/__tests__/unit/test-helpers.ts +0 -103
  26. package/src/core/auth/auth-client.ts +0 -123
  27. package/src/core/auth/cookie-config.ts +0 -23
  28. package/src/core/auth/handlers.ts +0 -168
  29. package/src/core/auth/routes.ts +0 -26
  30. package/src/core/auth/token-client.ts +0 -51
  31. package/src/core/auth/types.ts +0 -54
  32. package/src/core/bot-protection/abstract-manager.ts +0 -185
  33. package/src/core/bot-protection/create-manager.ts +0 -37
  34. package/src/core/bot-protection/eucaptcha-manager.ts +0 -88
  35. package/src/core/bot-protection/fallback-manager.ts +0 -43
  36. package/src/core/bot-protection/turnstile-manager.ts +0 -92
  37. package/src/core/bot-protection/types/eucaptcha.d.ts +0 -28
  38. package/src/core/bot-protection/types/turnstile.d.ts +0 -33
  39. package/src/core/cache.ts +0 -102
  40. package/src/core/cart/cart-client.ts +0 -150
  41. package/src/core/cart/cookie-config.ts +0 -13
  42. package/src/core/cart/types.ts +0 -104
  43. package/src/core/client/compose.ts +0 -15
  44. package/src/core/client/create-client.ts +0 -129
  45. package/src/core/client/dedupe.ts +0 -19
  46. package/src/core/client/execute.ts +0 -70
  47. package/src/core/client/hash.ts +0 -21
  48. package/src/core/client/operation-name.ts +0 -12
  49. package/src/core/client/types.ts +0 -171
  50. package/src/core/currency/cookie-config.ts +0 -13
  51. package/src/core/errors.ts +0 -67
  52. package/src/core/format.ts +0 -254
  53. package/src/core/helpers/assert-no-user-errors.ts +0 -21
  54. package/src/core/helpers/normalize-connection.ts +0 -48
  55. package/src/core/helpers/sanitize-html.ts +0 -42
  56. package/src/core/image.ts +0 -103
  57. package/src/core/index.ts +0 -180
  58. package/src/core/language/cookie-config.ts +0 -13
  59. package/src/core/middleware/auth.ts +0 -27
  60. package/src/core/middleware/bot-protection.ts +0 -140
  61. package/src/core/middleware/currency.ts +0 -27
  62. package/src/core/middleware/errors.ts +0 -86
  63. package/src/core/middleware/language.ts +0 -30
  64. package/src/core/middleware/retry.ts +0 -75
  65. package/src/core/middleware/timeout.ts +0 -61
  66. package/src/core/operations/auth.ts +0 -123
  67. package/src/core/operations/cart.ts +0 -185
  68. package/src/index.ts +0 -25
  69. package/src/react/bot-protection/bot-protection-context.ts +0 -17
  70. package/src/react/bot-protection/bot-protection-widget.tsx +0 -46
  71. package/src/react/cookies.ts +0 -89
  72. package/src/react/helpers/create-store-context.ts +0 -56
  73. package/src/react/hooks/use-auth.ts +0 -218
  74. package/src/react/hooks/use-bot-protection.ts +0 -31
  75. package/src/react/hooks/use-cart-manager.ts +0 -236
  76. package/src/react/hooks/use-currency.ts +0 -23
  77. package/src/react/hooks/use-debounced-value.ts +0 -30
  78. package/src/react/hooks/use-hydrated.ts +0 -20
  79. package/src/react/hooks/use-storefront-client.ts +0 -12
  80. package/src/react/index.ts +0 -71
  81. package/src/react/providers/currency-provider.tsx +0 -30
  82. package/src/react/providers/language-provider.tsx +0 -34
  83. package/src/react/providers/storefront-client-provider.tsx +0 -107
  84. package/src/react/providers/storefront-provider.tsx +0 -99
  85. package/src/react/server/get-storefront-client.ts +0 -60
  86. package/src/react/server/index.ts +0 -1
  87. package/src/react/stores/auth.store.ts +0 -112
  88. package/src/react/stores/cart.context.ts +0 -10
  89. package/src/react/stores/cart.store.ts +0 -254
  90. package/src/react/stores/currency.store.ts +0 -93
  91. package/src/react/stores/index.ts +0 -17
  92. package/src/react/stores/language.store.ts +0 -90
  93. package/src/react/stores/store-context.tsx +0 -103
  94. package/src/react/types/shop-config.ts +0 -22
  95. package/tsconfig.json +0 -20
  96. package/vitest.config.ts +0 -14
@@ -1,42 +0,0 @@
1
- /**
2
- * Lightweight HTML sanitizer for defense-in-depth.
3
- * Primary sanitization happens on the backend (sanitize-html library).
4
- * This strips dangerous tags/attributes as an extra safety layer.
5
- * Works in both Node.js and Edge runtimes (no external dependencies).
6
- */
7
-
8
- const DANGEROUS_TAG_PATTERNS = [
9
- // Tags with content that must be fully removed
10
- /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi,
11
- /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe\s*>/gi,
12
- /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object\s*>/gi,
13
- /<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form\s*>/gi,
14
- /<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style\s*>/gi,
15
- // Self-closing / void dangerous tags
16
- /<embed\b[^>]*\/?>/gi,
17
- /<link\b[^>]*\/?>/gi,
18
- /<meta\b[^>]*\/?>/gi,
19
- /<base\b[^>]*\/?>/gi,
20
- /<applet\b[^<]*(?:(?!<\/applet>)<[^<]*)*<\/applet\s*>/gi,
21
- ];
22
-
23
- // Event handler attributes (onclick, onerror, onload, etc.)
24
- const EVENT_HANDLER_PATTERN = /\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi;
25
-
26
- // javascript: protocol in href/src/action attributes
27
- const JS_PROTOCOL_PATTERN = /(href|src|action)\s*=\s*["']\s*javascript\s*:/gi;
28
-
29
- export function sanitizeHtml(html: string): string {
30
- if (!html) return html;
31
-
32
- let result = html;
33
-
34
- for (const pattern of DANGEROUS_TAG_PATTERNS) {
35
- result = result.replace(pattern, '');
36
- }
37
-
38
- result = result.replace(EVENT_HANDLER_PATTERN, '');
39
- result = result.replace(JS_PROTOCOL_PATTERN, '$1="');
40
-
41
- return result;
42
- }
package/src/core/image.ts DELETED
@@ -1,103 +0,0 @@
1
- /**
2
- * Image utilities for DoSwiftly storefronts.
3
- *
4
- * Zero configuration — GraphQL API returns ready-to-use CDN URLs.
5
- * The loader just appends resize query params. No keys, no env vars.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { storefrontImageLoader, type ImageData } from '@doswiftly/storefront-sdk';
10
- *
11
- * <Image loader={storefrontImageLoader} src={product.featuredImage.url} width={800} alt="..." />
12
- * ```
13
- */
14
-
15
- /**
16
- * Image data from GraphQL API (matches Image type in storefront schema).
17
- */
18
- export interface ImageData {
19
- /** Image URL from GraphQL (ready-to-use CDN URL) */
20
- url: string;
21
- /** Alt text for accessibility + SEO */
22
- altText?: string | null;
23
- /** Original image width in pixels */
24
- width?: number | null;
25
- /** Original image height in pixels */
26
- height?: number | null;
27
- /** Image ID */
28
- id?: string | null;
29
- }
30
-
31
- /**
32
- * Next.js Image loader function signature.
33
- */
34
- export interface ImageLoaderParams {
35
- src: string;
36
- width: number;
37
- quality?: number;
38
- }
39
-
40
- /**
41
- * Image format for imgproxy output.
42
- */
43
- export type ImageFormat = 'webp' | 'avif' | 'jpeg';
44
-
45
- /**
46
- * Preset widths for CDN cache optimization.
47
- * Loader quantizes requested width to the nearest preset — limits cache variants.
48
- */
49
- const PRESET_WIDTHS = [150, 320, 640, 750, 828, 1080, 1200, 1600, 1920, 2048] as const;
50
-
51
- function nearestPresetWidth(requested: number): number {
52
- for (const w of PRESET_WIDTHS) {
53
- if (w >= requested) return w;
54
- }
55
- return PRESET_WIDTHS[PRESET_WIDTHS.length - 1];
56
- }
57
-
58
- /**
59
- * Default image loader — WebP format. Used as global loaderFile in next.config.ts.
60
- * Zero configuration: works out-of-the-box with GraphQL image URLs.
61
- * Width quantized to preset breakpoints for optimal CDN cache.
62
- *
63
- * @example
64
- * ```tsx
65
- * import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
66
- * <Image loader={storefrontImageLoader} src={image.url} width={800} alt="..." />
67
- * ```
68
- */
69
- export function storefrontImageLoader({ src, width, quality }: ImageLoaderParams): string {
70
- return buildImgproxyUrl(src, width, quality);
71
- }
72
-
73
- /**
74
- * Create a custom loader with specific format (avif, jpeg).
75
- * Use as per-component override when you need a different format than default WebP.
76
- *
77
- * @example
78
- * ```tsx
79
- * const avifLoader = createImageLoader('avif');
80
- * <Image loader={avifLoader} src={image.url} width={800} alt="..." />
81
- * ```
82
- */
83
- export function createImageLoader(format: ImageFormat): (params: ImageLoaderParams) => string {
84
- return ({ src, width, quality }) => buildImgproxyUrl(src, width, quality, format);
85
- }
86
-
87
- function buildImgproxyUrl(src: string, width: number, quality?: number, format: ImageFormat = 'webp'): string {
88
- const w = nearestPresetWidth(width);
89
- const q = quality ?? 85;
90
-
91
- if (!src.startsWith('http')) {
92
- return src;
93
- }
94
-
95
- try {
96
- const url = new URL(src);
97
- const path = url.pathname.slice(1);
98
- return `${url.origin}/rs:fit:${w}:0:0/q:${q}/f:${format}/plain/${path}`;
99
- } catch {
100
- // Malformed URL — return as-is, let browser handle it
101
- return src;
102
- }
103
- }
package/src/core/index.ts DELETED
@@ -1,180 +0,0 @@
1
- /**
2
- * @doswiftly/storefront-sdk — Core (framework-agnostic)
3
- *
4
- * 100% framework-agnostic, 0 runtime dependencies.
5
- * Works in Node.js, Edge Workers, Deno, Bun, CLI scripts — without React.
6
- *
7
- * @example
8
- * ```typescript
9
- * import {
10
- * createStorefrontClient,
11
- * authMiddleware,
12
- * currencyMiddleware,
13
- * retryMiddleware,
14
- * timeoutMiddleware,
15
- * errorMiddleware,
16
- * CartClient,
17
- * AuthClient,
18
- * } from '@doswiftly/storefront-sdk';
19
- *
20
- * const client = createStorefrontClient({
21
- * apiUrl: 'https://api.doswiftly.pl',
22
- * shopSlug: 'my-shop',
23
- * middleware: [
24
- * authMiddleware(() => getToken()),
25
- * currencyMiddleware(() => getCurrency()),
26
- * retryMiddleware({ maxRetries: 2 }),
27
- * timeoutMiddleware({ timeout: 5000 }),
28
- * errorMiddleware(),
29
- * ],
30
- * });
31
- *
32
- * const cartClient = new CartClient(client);
33
- * const cart = await cartClient.create();
34
- * ```
35
- */
36
-
37
- // Client factory
38
- export { createStorefrontClient } from './client/create-client';
39
-
40
- // Client types
41
- export type {
42
- StorefrontClient,
43
- StorefrontClientConfig,
44
- Middleware,
45
- ExecuteFn,
46
- GraphQLRequest,
47
- GraphQLResponse,
48
- GraphQLErrorInfo,
49
- UserError,
50
- CacheStrategy,
51
- CacheOptions,
52
- TypedDocumentString,
53
- } from './client/types';
54
-
55
- // Middleware
56
- export { authMiddleware } from './middleware/auth';
57
- export { currencyMiddleware } from './middleware/currency';
58
- export { languageMiddleware } from './middleware/language';
59
- export {
60
- botProtectionMiddleware,
61
- BOT_PROTECTION_HEADER,
62
- type BotProtectionTokenProvider,
63
- type BotProtectionConfig,
64
- type BotProtectionProviderConfig,
65
- type BotProtectionMiddlewareOptions,
66
- type FailStrategy,
67
- } from './middleware/bot-protection';
68
- export { retryMiddleware, type RetryOptions } from './middleware/retry';
69
- export { timeoutMiddleware, type TimeoutOptions } from './middleware/timeout';
70
- export { errorMiddleware } from './middleware/errors';
71
-
72
- // Bot protection managers (framework-agnostic, DOM APIs)
73
- export { createBotProtectionManager } from './bot-protection/create-manager';
74
- export { FallbackBotProtectionManager } from './bot-protection/fallback-manager';
75
-
76
- // Errors
77
- export { StorefrontError, ErrorCodes, type StorefrontErrorOptions } from './errors';
78
-
79
- // Cache strategies
80
- export {
81
- cacheNone,
82
- cacheShort,
83
- cacheLong,
84
- cachePrivate,
85
- cacheCustom,
86
- generateCacheControlHeader,
87
- type CacheOverrides,
88
- } from './cache';
89
-
90
- // Cart client
91
- export { CartClient } from './cart/cart-client';
92
- export type {
93
- Cart,
94
- CartLine,
95
- CartLineMerchandise,
96
- CartLineCost,
97
- CartCost,
98
- CartBuyerIdentity,
99
- CartDiscountCode,
100
- CartDiscountAllocation,
101
- CartLineInput,
102
- CartLineUpdateInput,
103
- CartCreateInput,
104
- CartBuyerIdentityInput,
105
- Money,
106
- } from './cart/types';
107
-
108
- // Auth client
109
- export { AuthClient } from './auth/auth-client';
110
- export type {
111
- Customer,
112
- CustomerAccessToken,
113
- MailingAddress,
114
- AuthResult,
115
- CustomerCreateInput,
116
- } from './auth/types';
117
-
118
- // Helpers
119
- export { assertNoUserErrors } from './helpers/assert-no-user-errors';
120
- export { sanitizeHtml } from './helpers/sanitize-html';
121
- export {
122
- normalizeConnection,
123
- type Connection,
124
- type ConnectionEdge,
125
- type ConnectionPageInfo,
126
- type NormalizedConnection,
127
- } from './helpers/normalize-connection';
128
-
129
- // Format utilities
130
- export {
131
- formatPrice,
132
- formatPriceRange,
133
- formatAmount,
134
- formatDate,
135
- formatDateTime,
136
- formatNumber,
137
- formatPercentage,
138
- getCurrencySymbol,
139
- CURRENCY_SYMBOLS,
140
- CURRENCY_LOCALES,
141
- type PriceMoney,
142
- } from './format';
143
-
144
- // Auth cookie config (platform contract)
145
- export {
146
- AUTH_COOKIE_NAME,
147
- AUTH_COOKIE_DEFAULTS,
148
- type AuthCookieConfig,
149
- } from './auth/cookie-config';
150
-
151
- // Language config
152
- export { LANGUAGE_COOKIE_NAME, LANGUAGE_COOKIE_MAX_AGE, LANGUAGE_HEADER_NAME } from './language/cookie-config';
153
-
154
- // Currency config
155
- export { CURRENCY_COOKIE_NAME, CURRENCY_COOKIE_MAX_AGE, CURRENCY_HEADER_NAME } from './currency/cookie-config';
156
-
157
- // Cart cookie config
158
- export { CART_COOKIE_NAME, CART_COOKIE_MAX_AGE } from './cart/cookie-config';
159
-
160
- // Auth route matching
161
- export { matchesRoute, type RouteProtectionConfig } from './auth/routes';
162
-
163
- // Auth cookie handlers (API route factories)
164
- export { createSetTokenHandler, createClearTokenHandler } from './auth/handlers';
165
-
166
- // Auth token client (client-side fetch helpers)
167
- export { createAuthTokenClient, type AuthTokenClient } from './auth/token-client';
168
-
169
- // Image utilities
170
- export {
171
- storefrontImageLoader,
172
- createImageLoader,
173
- type ImageData,
174
- type ImageLoaderParams,
175
- type ImageFormat,
176
- } from './image';
177
-
178
- // Utilities
179
- export { getOperationName } from './client/operation-name';
180
- export { hashQuery } from './client/hash';
@@ -1,13 +0,0 @@
1
- /**
2
- * Language configuration constants — platform contract.
3
- *
4
- * Used by:
5
- * - SDK language store (client-side cookie read/write)
6
- * - SDK language middleware (X-Lang header)
7
- * - next-intl middleware `localeCookie.name` (server-side locale detection)
8
- *
9
- * Single cookie replaces both next-intl's NEXT_LOCALE and SDK's preferred-language.
10
- */
11
- export const LANGUAGE_COOKIE_NAME = 'preferred-language';
12
- export const LANGUAGE_COOKIE_MAX_AGE = 365 * 24 * 60 * 60; // 1 year
13
- export const LANGUAGE_HEADER_NAME = 'X-Lang';
@@ -1,27 +0,0 @@
1
- /**
2
- * Auth middleware — adds Authorization header from token getter.
3
- *
4
- * Token is resolved lazily (on each request) so it picks up
5
- * store changes, token renewals, etc.
6
- *
7
- * @example
8
- * ```typescript
9
- * import { authMiddleware } from '@doswiftly/storefront-sdk';
10
- *
11
- * client.use(authMiddleware(() => authStore.getState().accessToken));
12
- * ```
13
- */
14
-
15
- import type { Middleware } from '../client/types';
16
-
17
- export function authMiddleware(
18
- getToken: () => string | null | undefined,
19
- ): Middleware {
20
- return (request, next) => {
21
- const token = getToken();
22
- if (token) {
23
- request.headers['Authorization'] = `Bearer ${token}`;
24
- }
25
- return next(request);
26
- };
27
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Bot protection middleware — vendor-agnostic token injection.
3
- *
4
- * Intercepts protected mutations and adds a bot verification token
5
- * via the X-Bot-Protection-Token header. Token acquisition is delegated
6
- * to a BotProtectionTokenProvider (EU CAPTCHA, Turnstile, etc.).
7
- *
8
- * Fail strategy is per-operation: 'open' = continue without token,
9
- * 'closed' = throw if token unavailable.
10
- */
11
-
12
- import type { Middleware } from '../client/types';
13
- import { StorefrontError } from '../errors';
14
-
15
- // ---------------------------------------------------------------------------
16
- // Token provider interface (implemented by managers)
17
- // ---------------------------------------------------------------------------
18
-
19
- /** Vendor-agnostic token provider interface */
20
- export interface BotProtectionTokenProvider {
21
- /**
22
- * Get a fresh bot protection token.
23
- * Handles: in-flight deduplication, timeout, singleton script loading.
24
- *
25
- * @param options.action - Operation name (e.g. 'CustomerCreate') for action validation
26
- * @param options.timeoutMs - Timeout in ms (default: 10000)
27
- * @returns Token string or null if unavailable (adblock, timeout, error)
28
- */
29
- execute(options?: { action?: string; timeoutMs?: number }): Promise<string | null>;
30
-
31
- /** Cleanup widget and script resources */
32
- destroy(): void;
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // Configuration types
37
- // ---------------------------------------------------------------------------
38
-
39
- /** Single provider config (from shop query) */
40
- export interface BotProtectionProviderConfig {
41
- /** Provider identifier: 'eucaptcha' | 'turnstile' | 'recaptcha' | extensible */
42
- provider: string;
43
- /** Site key for the bot protection widget */
44
- siteKey: string;
45
- /** Script URL — configurable for region-specific / enterprise overrides */
46
- scriptUrl: string;
47
- }
48
-
49
- /** Bot protection configuration from shop query */
50
- export interface BotProtectionConfig {
51
- /** Primary provider */
52
- primary: BotProtectionProviderConfig;
53
- /** Fallback provider (runtime: if primary fails -> fallback -> fail-open) */
54
- fallback?: BotProtectionProviderConfig | null;
55
- /** GraphQL operation names requiring verification */
56
- protectedOperations: string[];
57
- }
58
-
59
- /** Fail strategy per request */
60
- export type FailStrategy = 'open' | 'closed';
61
-
62
- // ---------------------------------------------------------------------------
63
- // Middleware options
64
- // ---------------------------------------------------------------------------
65
-
66
- export interface BotProtectionMiddlewareOptions {
67
- /** Token provider instance (manager or fallback chain) */
68
- tokenProvider: BotProtectionTokenProvider;
69
- /** GraphQL operation names that require bot protection */
70
- protectedOperations: string[];
71
- /** Default fail strategy when token acquisition fails (default: 'open') */
72
- defaultFailStrategy?: FailStrategy;
73
- /** Override fail strategy per operation (e.g. CheckoutComplete = closed) */
74
- failStrategyOverrides?: Record<string, FailStrategy>;
75
- }
76
-
77
- // ---------------------------------------------------------------------------
78
- // Constants
79
- // ---------------------------------------------------------------------------
80
-
81
- /** Header name for bot protection token */
82
- export const BOT_PROTECTION_HEADER = 'X-Bot-Protection-Token';
83
-
84
- // ---------------------------------------------------------------------------
85
- // Middleware factory
86
- // ---------------------------------------------------------------------------
87
-
88
- /**
89
- * Creates bot protection middleware for the SDK pipeline.
90
- *
91
- * Only intercepts mutations whose operationName is in protectedOperations.
92
- * Token is fetched fresh on every call (single-use tokens, retry-safe).
93
- *
94
- * Pipeline position: AFTER auth/currency, BEFORE retry/timeout/errors.
95
- * Retry middleware re-executes the full chain, so each attempt gets a fresh token.
96
- */
97
- export function botProtectionMiddleware(options: BotProtectionMiddlewareOptions): Middleware {
98
- const {
99
- tokenProvider,
100
- protectedOperations,
101
- defaultFailStrategy = 'open',
102
- failStrategyOverrides = {},
103
- } = options;
104
-
105
- const protectedSet = new Set(protectedOperations);
106
-
107
- return async (request, next) => {
108
- // Only intercept mutations
109
- if (!request.isMutation) return next(request);
110
-
111
- // Only intercept protected operations
112
- const opName = request.operationName;
113
- if (!opName || !protectedSet.has(opName)) {
114
- return next(request);
115
- }
116
-
117
- // Fetch fresh token — action param enables backend action validation
118
- const token = await tokenProvider.execute({
119
- action: opName,
120
- timeoutMs: 10000,
121
- });
122
-
123
- if (token) {
124
- request.headers[BOT_PROTECTION_HEADER] = token;
125
- } else {
126
- // Fail strategy: open = continue without token, closed = throw
127
- const strategy = failStrategyOverrides[opName] ?? defaultFailStrategy;
128
- if (strategy === 'closed') {
129
- throw new StorefrontError({
130
- code: 'BOT_PROTECTION_REQUIRED',
131
- message: 'Bot protection verification failed. Please try again.',
132
- status: 0,
133
- });
134
- }
135
- // fail-open: continue without token — backend guard may still reject
136
- }
137
-
138
- return next(request);
139
- };
140
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Currency middleware — adds X-Preferred-Currency header.
3
- *
4
- * Currency is resolved lazily so currency switches are reflected immediately.
5
- *
6
- * @example
7
- * ```typescript
8
- * import { currencyMiddleware } from '@doswiftly/storefront-sdk';
9
- *
10
- * client.use(currencyMiddleware(() => currencyStore.getState().currency));
11
- * ```
12
- */
13
-
14
- import type { Middleware } from '../client/types';
15
- import { CURRENCY_HEADER_NAME } from '../currency/cookie-config';
16
-
17
- export function currencyMiddleware(
18
- getCurrency: () => string | null | undefined,
19
- ): Middleware {
20
- return (request, next) => {
21
- const currency = getCurrency();
22
- if (currency) {
23
- request.headers[CURRENCY_HEADER_NAME] = currency;
24
- }
25
- return next(request);
26
- };
27
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Error normalization middleware — ALWAYS LAST in the pipeline.
3
- *
4
- * Catches all errors from downstream middleware and the transport,
5
- * normalizes them into StorefrontError instances.
6
- *
7
- * Handles:
8
- * - Network errors (fetch failures)
9
- * - HTTP errors (non-200 responses)
10
- * - GraphQL errors (errors array in response)
11
- * - Abort/timeout errors
12
- * - Missing data in response
13
- *
14
- * @example
15
- * ```typescript
16
- * import { errorMiddleware } from '@doswiftly/storefront-sdk';
17
- *
18
- * // Always add as the LAST middleware
19
- * client.use(errorMiddleware());
20
- * ```
21
- */
22
-
23
- import type { Middleware } from '../client/types';
24
- import { StorefrontError, ErrorCodes } from '../errors';
25
-
26
- export function errorMiddleware(): Middleware {
27
- return async (request, next) => {
28
- let response;
29
-
30
- try {
31
- response = await next(request);
32
- } catch (error) {
33
- // Already a StorefrontError — re-throw as-is
34
- if (error instanceof StorefrontError) {
35
- throw error;
36
- }
37
-
38
- // AbortError (timeout handled by timeout middleware, manual abort here)
39
- if (error instanceof Error && error.name === 'AbortError') {
40
- throw new StorefrontError({
41
- code: ErrorCodes.TIMEOUT,
42
- message: 'Request was aborted',
43
- cause: error,
44
- });
45
- }
46
-
47
- // Network error (fetch failed — DNS, connection refused, etc.)
48
- throw new StorefrontError({
49
- code: ErrorCodes.NETWORK_ERROR,
50
- message: error instanceof Error ? error.message : 'Network request failed',
51
- cause: error,
52
- });
53
- }
54
-
55
- // HTTP error (non-2xx response)
56
- if (response.status >= 400) {
57
- throw new StorefrontError({
58
- code: ErrorCodes.HTTP_ERROR,
59
- message: `HTTP ${response.status}`,
60
- status: response.status,
61
- graphqlErrors: response.errors ?? [],
62
- });
63
- }
64
-
65
- // GraphQL errors in the response
66
- if (response.errors?.length) {
67
- throw new StorefrontError({
68
- code: ErrorCodes.GRAPHQL_ERROR,
69
- message: response.errors[0].message,
70
- status: response.status,
71
- graphqlErrors: response.errors,
72
- });
73
- }
74
-
75
- // No data returned
76
- if (response.data === undefined || response.data === null) {
77
- throw new StorefrontError({
78
- code: ErrorCodes.NO_DATA,
79
- message: 'No data returned from GraphQL',
80
- status: response.status,
81
- });
82
- }
83
-
84
- return response;
85
- };
86
- }
@@ -1,30 +0,0 @@
1
- /**
2
- * Language middleware — adds X-Lang header.
3
- *
4
- * Language is resolved lazily so language switches are reflected immediately.
5
- * CRITICAL: does NOT send header when language=null (store not yet initialized)
6
- * — without this, backend returns default language, React Query caches it,
7
- * and after setLanguage() the cache is stale.
8
- *
9
- * @example
10
- * ```typescript
11
- * import { languageMiddleware } from '@doswiftly/storefront-sdk';
12
- *
13
- * client.use(languageMiddleware(() => languageStore.getState().language));
14
- * ```
15
- */
16
-
17
- import type { Middleware } from '../client/types';
18
- import { LANGUAGE_HEADER_NAME } from '../language/cookie-config';
19
-
20
- export function languageMiddleware(
21
- getLanguage: () => string | null | undefined,
22
- ): Middleware {
23
- return (request, next) => {
24
- const language = getLanguage();
25
- if (language) {
26
- request.headers[LANGUAGE_HEADER_NAME] = language;
27
- }
28
- return next(request);
29
- };
30
- }