@doswiftly/storefront-sdk 4.0.0 → 4.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.
Files changed (113) hide show
  1. package/README.md +51 -9
  2. package/dist/core/bot-protection/abstract-manager.d.ts +57 -0
  3. package/dist/core/bot-protection/abstract-manager.d.ts.map +1 -0
  4. package/dist/core/bot-protection/abstract-manager.js +144 -0
  5. package/dist/core/bot-protection/create-manager.d.ts +15 -0
  6. package/dist/core/bot-protection/create-manager.d.ts.map +1 -0
  7. package/dist/core/bot-protection/create-manager.js +33 -0
  8. package/dist/core/bot-protection/eucaptcha-manager.d.ts +15 -0
  9. package/dist/core/bot-protection/eucaptcha-manager.d.ts.map +1 -0
  10. package/dist/core/bot-protection/eucaptcha-manager.js +76 -0
  11. package/dist/core/bot-protection/fallback-manager.d.ts +18 -0
  12. package/dist/core/bot-protection/fallback-manager.d.ts.map +1 -0
  13. package/dist/core/bot-protection/fallback-manager.js +42 -0
  14. package/dist/core/bot-protection/turnstile-manager.d.ts +15 -0
  15. package/dist/core/bot-protection/turnstile-manager.d.ts.map +1 -0
  16. package/dist/core/bot-protection/turnstile-manager.js +78 -0
  17. package/dist/core/cart/cookie-config.d.ts +14 -0
  18. package/dist/core/cart/cookie-config.d.ts.map +1 -0
  19. package/dist/core/cart/cookie-config.js +13 -0
  20. package/dist/core/currency/cookie-config.d.ts +14 -0
  21. package/dist/core/currency/cookie-config.d.ts.map +1 -0
  22. package/dist/core/currency/cookie-config.js +13 -0
  23. package/dist/core/image.d.ts +55 -0
  24. package/dist/core/image.d.ts.map +1 -0
  25. package/dist/core/image.js +48 -0
  26. package/dist/core/index.d.ts +8 -0
  27. package/dist/core/index.d.ts.map +1 -1
  28. package/dist/core/index.js +13 -0
  29. package/dist/core/language/cookie-config.d.ts +14 -0
  30. package/dist/core/language/cookie-config.d.ts.map +1 -0
  31. package/dist/core/language/cookie-config.js +13 -0
  32. package/dist/core/middleware/bot-protection.d.ts +71 -0
  33. package/dist/core/middleware/bot-protection.d.ts.map +1 -0
  34. package/dist/core/middleware/bot-protection.js +63 -0
  35. package/dist/core/middleware/currency.d.ts.map +1 -1
  36. package/dist/core/middleware/currency.js +2 -1
  37. package/dist/core/middleware/language.d.ts +18 -0
  38. package/dist/core/middleware/language.d.ts.map +1 -0
  39. package/dist/core/middleware/language.js +25 -0
  40. package/dist/react/bot-protection/bot-protection-context.d.ts +12 -0
  41. package/dist/react/bot-protection/bot-protection-context.d.ts.map +1 -0
  42. package/dist/react/bot-protection/bot-protection-context.js +9 -0
  43. package/dist/react/bot-protection/bot-protection-widget.d.ts +13 -0
  44. package/dist/react/bot-protection/bot-protection-widget.d.ts.map +1 -0
  45. package/dist/react/bot-protection/bot-protection-widget.js +34 -0
  46. package/dist/react/cookies.d.ts +17 -0
  47. package/dist/react/cookies.d.ts.map +1 -1
  48. package/dist/react/cookies.js +36 -3
  49. package/dist/react/hooks/use-bot-protection.d.ts +16 -0
  50. package/dist/react/hooks/use-bot-protection.d.ts.map +1 -0
  51. package/dist/react/hooks/use-bot-protection.js +24 -0
  52. package/dist/react/index.d.ts +10 -1
  53. package/dist/react/index.d.ts.map +1 -1
  54. package/dist/react/index.js +9 -1
  55. package/dist/react/providers/language-provider.d.ts +18 -0
  56. package/dist/react/providers/language-provider.d.ts.map +1 -0
  57. package/dist/react/providers/language-provider.js +24 -0
  58. package/dist/react/providers/storefront-client-provider.d.ts +7 -2
  59. package/dist/react/providers/storefront-client-provider.d.ts.map +1 -1
  60. package/dist/react/providers/storefront-client-provider.js +14 -3
  61. package/dist/react/providers/storefront-provider.d.ts +7 -1
  62. package/dist/react/providers/storefront-provider.d.ts.map +1 -1
  63. package/dist/react/providers/storefront-provider.js +11 -4
  64. package/dist/react/stores/cart.context.d.ts +12 -0
  65. package/dist/react/stores/cart.context.d.ts.map +1 -0
  66. package/dist/react/stores/cart.context.js +3 -0
  67. package/dist/react/stores/cart.store.d.ts +71 -0
  68. package/dist/react/stores/cart.store.d.ts.map +1 -0
  69. package/dist/react/stores/cart.store.js +166 -0
  70. package/dist/react/stores/currency.store.d.ts +6 -9
  71. package/dist/react/stores/currency.store.d.ts.map +1 -1
  72. package/dist/react/stores/currency.store.js +5 -22
  73. package/dist/react/stores/language.store.d.ts +33 -0
  74. package/dist/react/stores/language.store.d.ts.map +1 -0
  75. package/dist/react/stores/language.store.js +67 -0
  76. package/dist/react/stores/store-context.d.ts +5 -0
  77. package/dist/react/stores/store-context.d.ts.map +1 -1
  78. package/dist/react/stores/store-context.js +14 -0
  79. package/dist/react/types/shop-config.d.ts +19 -0
  80. package/dist/react/types/shop-config.d.ts.map +1 -0
  81. package/dist/react/types/shop-config.js +7 -0
  82. package/package.json +1 -1
  83. package/src/__tests__/unit/bot-protection.test.ts +461 -0
  84. package/src/__tests__/unit/cart-store.test.ts +349 -0
  85. package/src/core/bot-protection/abstract-manager.ts +185 -0
  86. package/src/core/bot-protection/create-manager.ts +37 -0
  87. package/src/core/bot-protection/eucaptcha-manager.ts +88 -0
  88. package/src/core/bot-protection/fallback-manager.ts +43 -0
  89. package/src/core/bot-protection/turnstile-manager.ts +92 -0
  90. package/src/core/bot-protection/types/eucaptcha.d.ts +28 -0
  91. package/src/core/bot-protection/types/turnstile.d.ts +33 -0
  92. package/src/core/cart/cookie-config.ts +13 -0
  93. package/src/core/currency/cookie-config.ts +13 -0
  94. package/src/core/image.ts +75 -0
  95. package/src/core/index.ts +30 -0
  96. package/src/core/language/cookie-config.ts +13 -0
  97. package/src/core/middleware/bot-protection.ts +140 -0
  98. package/src/core/middleware/currency.ts +2 -1
  99. package/src/core/middleware/language.ts +30 -0
  100. package/src/react/bot-protection/bot-protection-context.ts +17 -0
  101. package/src/react/bot-protection/bot-protection-widget.tsx +46 -0
  102. package/src/react/cookies.ts +39 -4
  103. package/src/react/hooks/use-bot-protection.ts +31 -0
  104. package/src/react/index.ts +27 -1
  105. package/src/react/providers/language-provider.tsx +34 -0
  106. package/src/react/providers/storefront-client-provider.tsx +20 -3
  107. package/src/react/providers/storefront-provider.tsx +34 -6
  108. package/src/react/stores/cart.context.ts +10 -0
  109. package/src/react/stores/cart.store.ts +254 -0
  110. package/src/react/stores/currency.store.ts +12 -32
  111. package/src/react/stores/language.store.ts +90 -0
  112. package/src/react/stores/store-context.tsx +21 -0
  113. package/src/react/types/shop-config.ts +22 -0
package/README.md CHANGED
@@ -7,8 +7,8 @@ Layered runtime SDK for DoSwiftly Commerce storefronts. Hydrogen-aligned archite
7
7
  ```
8
8
  @doswiftly/storefront-sdk
9
9
  ├── core (.) — Framework-agnostic: transport, middleware, CartClient, AuthClient,
10
- │ cache, format utilities, sanitizeHtml, normalizeConnection,
11
- │ auth cookie config/handlers/token client, route matching
10
+ │ cache, format utilities, image loader, sanitizeHtml,
11
+ normalizeConnection, auth cookie config/handlers/token client, route matching
12
12
  ├── react (./react) — React adapter: providers, Zustand stores (Context-based), hooks,
13
13
  │ useHydrated, useDebouncedValue, createStoreContext
14
14
  ├── react/server — Server-side client factory
@@ -93,7 +93,7 @@ const { currency, setCurrency } = useCurrencyStore();
93
93
 
94
94
  | Path | Description | Dependencies |
95
95
  |------|-------------|-------------|
96
- | `@doswiftly/storefront-sdk` | Core: transport, middleware, clients, errors, format, sanitize, auth handlers, route matching | **0** |
96
+ | `@doswiftly/storefront-sdk` | Core: transport, middleware, clients, errors, format, image loader, sanitize, auth handlers, route matching | **0** |
97
97
  | `@doswiftly/storefront-sdk/react` | Providers, hooks, store hooks, useHydrated, useDebouncedValue, createStoreContext | react, zustand |
98
98
  | `@doswiftly/storefront-sdk/react/server` | Server-side client factory | react |
99
99
  | `@doswiftly/storefront-sdk/cache` | Cache strategy functions | **0** |
@@ -213,6 +213,20 @@ formatDate(new Date()); // "Dec 9, 2025"
213
213
  formatPercentage(0.15); // "15%"
214
214
  ```
215
215
 
216
+ ### Image Loader (CDN)
217
+
218
+ Template ships with global `loaderFile` in `next.config.ts` — ALL `<Image>` components are optimized automatically. No per-component configuration needed.
219
+
220
+ ```typescript
221
+ // For custom frameworks (Svelte, Astro, etc.) — import the loader function directly:
222
+ import { storefrontImageLoader } from '@doswiftly/storefront-sdk';
223
+
224
+ const url = storefrontImageLoader({ src: image.url, width: 800, quality: 85 });
225
+ // → image.url + "?width=800&quality=85&format=webp"
226
+ ```
227
+
228
+ Width quantized to Next.js-aligned breakpoints for CDN cache efficiency. `ImageData` type matches GraphQL `Image` fragment: `{ url, altText?, width?, height?, id? }`.
229
+
216
230
  ### HTML Sanitizer
217
231
 
218
232
  ```typescript
@@ -317,7 +331,33 @@ const {
317
331
  });
318
332
  ```
319
333
 
320
- ### useCartManager
334
+ ### Cart Store (DI-based) — recommended for templates
335
+
336
+ ```typescript
337
+ import { createCartStore, CartProvider, useCartStore, type CartActions } from '@doswiftly/storefront-sdk/react';
338
+
339
+ // Template provides CartActions implementation (transport layer)
340
+ const store = createCartStore({
341
+ getActions: () => cartActions, // getter — called per operation (fresh refs)
342
+ onMutationSuccess: (action, cart) => { /* toast, cache invalidation */ },
343
+ onMutationError: (action, error) => { /* error toast */ },
344
+ });
345
+
346
+ // Provider (separate from StorefrontProvider — template composes in StoresProvider)
347
+ <CartProvider store={store}>{children}</CartProvider>
348
+
349
+ // Hooks (Context-based, selector overloads)
350
+ const { cartId, isOpen, isLoading, addToCart, clearCart, openCart } = useCartStore();
351
+ const cartId = useCartStore(s => s.cartId); // with selector
352
+
353
+ // Selectors
354
+ import { selectCartId, selectIsCartOpen, selectCartIsLoading } from '@doswiftly/storefront-sdk/react';
355
+ ```
356
+
357
+ SDK orchestrates: auto-init (fetch or create), expired cart recovery, loading/error state, mutation callbacks.
358
+ Template provides: CartActions DI implementation (GraphQL hooks, React Query, fetch — any transport).
359
+
360
+ ### useCartManager (alternative — cookie-based, simple)
321
361
 
322
362
  ```typescript
323
363
  const {
@@ -330,7 +370,7 @@ const {
330
370
  } = useCartManager();
331
371
  ```
332
372
 
333
- Cart ID is persisted in a cookie (SSR/edge visible).
373
+ Cart ID is persisted in a cookie (SSR/edge visible). Plain async + useState, no React Query dependency.
334
374
 
335
375
  ### useHydrated
336
376
 
@@ -412,10 +452,12 @@ Storefronts (Next.js templates) import SDK for **infrastructure** and own their
412
452
  SDK provides: Template owns:
413
453
  ├── Transport + Middleware ├── codegen.ts + generated/graphql.ts
414
454
  ├── CartClient + AuthClient ├── lib/graphql/hooks.ts (React Query)
415
- ├── Providers + Zustand stores ├── lib/graphql/server.ts (React cache)
416
- ├── Format utilities ├── lib/graphql/fragments/
417
- ├── sanitizeHtml ├── stores/ (UI state via createStoreContext)
418
- ├── normalizeConnection ├── hooks/ (use-auth, use-cart-actions, etc.)
455
+ ├── Cart Store (DI-based) ├── lib/graphql/server.ts (React cache)
456
+ ├── Providers + Zustand stores ├── lib/graphql/fragments/
457
+ ├── Format utilities ├── hooks/use-cart-di.ts (CartActions DI impl)
458
+ ├── sanitizeHtml ├── hooks/use-cart-actions.ts (UX wrapper)
459
+ ├── createImageLoader (CDN) ├── components/product/product-image.tsx
460
+ ├── normalizeConnection ├── stores/ (checkout, wishlist via createStoreContext)
419
461
  ├── Auth handlers + token client ├── lib/auth/routes.ts (route config)
420
462
  ├── useHydrated + useDebouncedValue
421
463
  ├── createStoreContext └── components/providers/
@@ -0,0 +1,57 @@
1
+ /**
2
+ * AbstractBotProtectionManager — base class for all bot protection providers.
3
+ *
4
+ * DRY: singleton script loading, lazy mount, in-flight deduplication, timeout.
5
+ * Subclasses override only: _loadScript(), _renderWidget(), _executeChallenge(), _destroyWidget().
6
+ *
7
+ * Framework-agnostic (DOM APIs only) — works in React, Vue, Svelte, vanilla JS.
8
+ */
9
+ import type { BotProtectionTokenProvider } from '../middleware/bot-protection';
10
+ export declare abstract class AbstractBotProtectionManager implements BotProtectionTokenProvider {
11
+ protected readonly siteKey: string;
12
+ protected readonly scriptUrl: string;
13
+ /** Singleton script loading promise — one load per provider globally */
14
+ private scriptPromise;
15
+ /** In-flight deduplication — prevents double-submit */
16
+ private inFlight;
17
+ /** Whether the widget has been mounted */
18
+ protected widgetId: string | null;
19
+ /** Container element for the widget */
20
+ protected container: HTMLElement | null;
21
+ constructor(siteKey: string, scriptUrl: string);
22
+ /**
23
+ * Load the provider's script tag. Singleton — only loads once.
24
+ * Exposed publicly so React wrapper can trigger preload.
25
+ */
26
+ loadScript(): Promise<void>;
27
+ /**
28
+ * Mount the invisible widget into a container element.
29
+ * Lazy — called automatically on first execute() if not mounted.
30
+ */
31
+ mount(container: HTMLElement): void;
32
+ /**
33
+ * Execute challenge and get a fresh token.
34
+ * In-flight dedup + timeout + lazy mount + lazy script load.
35
+ */
36
+ execute(options?: {
37
+ action?: string;
38
+ timeoutMs?: number;
39
+ }): Promise<string | null>;
40
+ /**
41
+ * Cleanup widget and resources.
42
+ */
43
+ destroy(): void;
44
+ private _doExecute;
45
+ private _ensureMounted;
46
+ /** Wait for the provider API to be available on window */
47
+ protected abstract _waitForApi(): Promise<void>;
48
+ /** Optional: setup global onload callback for the script */
49
+ protected _setupOnloadCallback(_resolve: () => void): void;
50
+ /** Render the invisible widget. Return widget ID. */
51
+ protected abstract _renderWidget(container: HTMLElement, action?: string): string;
52
+ /** Execute the challenge and return a token. Called after mount. */
53
+ protected abstract _executeChallenge(widgetId: string, action?: string): Promise<string | null>;
54
+ /** Destroy/remove the widget. */
55
+ protected abstract _destroyWidget(widgetId: string): void;
56
+ }
57
+ //# sourceMappingURL=abstract-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract-manager.d.ts","sourceRoot":"","sources":["../../../src/core/bot-protection/abstract-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E,8BAAsB,4BAA6B,YAAW,0BAA0B;IACtF,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACnC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAErC,wEAAwE;IACxE,OAAO,CAAC,aAAa,CAA8B;IACnD,uDAAuD;IACvD,OAAO,CAAC,QAAQ,CAAuC;IACvD,0CAA0C;IAC1C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IACzC,uCAAuC;IACvC,SAAS,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAQ;gBAEnC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAK9C;;;OAGG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC3B;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IAInC;;;OAGG;IACG,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAaxF;;OAEG;IACH,OAAO,IAAI,IAAI;YAgBD,UAAU;YA4BV,cAAc;IAsB5B,0DAA0D;IAC1D,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAE/C,4DAA4D;IAC5D,SAAS,CAAC,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI1D,qDAAqD;IACrD,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAEjF,oEAAoE;IACpE,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAE/F,iCAAiC;IACjC,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAC1D"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * AbstractBotProtectionManager — base class for all bot protection providers.
3
+ *
4
+ * DRY: singleton script loading, lazy mount, in-flight deduplication, timeout.
5
+ * Subclasses override only: _loadScript(), _renderWidget(), _executeChallenge(), _destroyWidget().
6
+ *
7
+ * Framework-agnostic (DOM APIs only) — works in React, Vue, Svelte, vanilla JS.
8
+ */
9
+ export class AbstractBotProtectionManager {
10
+ siteKey;
11
+ scriptUrl;
12
+ /** Singleton script loading promise — one load per provider globally */
13
+ scriptPromise = null;
14
+ /** In-flight deduplication — prevents double-submit */
15
+ inFlight = null;
16
+ /** Whether the widget has been mounted */
17
+ widgetId = null;
18
+ /** Container element for the widget */
19
+ container = null;
20
+ constructor(siteKey, scriptUrl) {
21
+ this.siteKey = siteKey;
22
+ this.scriptUrl = scriptUrl;
23
+ }
24
+ /**
25
+ * Load the provider's script tag. Singleton — only loads once.
26
+ * Exposed publicly so React wrapper can trigger preload.
27
+ */
28
+ loadScript() {
29
+ if (this.scriptPromise)
30
+ return this.scriptPromise;
31
+ this.scriptPromise = new Promise((resolve, reject) => {
32
+ // Skip in SSR
33
+ if (typeof document === 'undefined') {
34
+ resolve();
35
+ return;
36
+ }
37
+ // Check if script already exists
38
+ const existing = document.querySelector(`script[src="${this.scriptUrl}"]`);
39
+ if (existing) {
40
+ // Script tag exists — wait for API to be available
41
+ this._waitForApi().then(resolve).catch(reject);
42
+ return;
43
+ }
44
+ const script = document.createElement('script');
45
+ script.src = this.scriptUrl;
46
+ script.async = true;
47
+ script.defer = true;
48
+ // Set up onload callback if provider supports it
49
+ this._setupOnloadCallback(resolve);
50
+ script.onload = () => {
51
+ this._waitForApi().then(resolve).catch(reject);
52
+ };
53
+ script.onerror = () => {
54
+ this.scriptPromise = null; // Allow retry
55
+ reject(new Error(`Failed to load bot protection script: ${this.scriptUrl}`));
56
+ };
57
+ document.head.appendChild(script);
58
+ });
59
+ return this.scriptPromise;
60
+ }
61
+ /**
62
+ * Mount the invisible widget into a container element.
63
+ * Lazy — called automatically on first execute() if not mounted.
64
+ */
65
+ mount(container) {
66
+ this.container = container;
67
+ }
68
+ /**
69
+ * Execute challenge and get a fresh token.
70
+ * In-flight dedup + timeout + lazy mount + lazy script load.
71
+ */
72
+ async execute(options) {
73
+ // In-flight deduplication — return existing promise if one is running
74
+ if (this.inFlight)
75
+ return this.inFlight;
76
+ const timeoutMs = options?.timeoutMs ?? 10000;
77
+ this.inFlight = this._doExecute(options?.action, timeoutMs).finally(() => {
78
+ this.inFlight = null;
79
+ });
80
+ return this.inFlight;
81
+ }
82
+ /**
83
+ * Cleanup widget and resources.
84
+ */
85
+ destroy() {
86
+ try {
87
+ if (this.widgetId) {
88
+ this._destroyWidget(this.widgetId);
89
+ }
90
+ }
91
+ catch {
92
+ // Ignore cleanup errors
93
+ }
94
+ this.widgetId = null;
95
+ this.container = null;
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Private execution pipeline
99
+ // ---------------------------------------------------------------------------
100
+ async _doExecute(action, timeoutMs) {
101
+ try {
102
+ // 1. Lazy script load
103
+ await this.loadScript();
104
+ // 2. Lazy mount — create container if needed
105
+ if (!this.widgetId) {
106
+ await this._ensureMounted(action);
107
+ }
108
+ if (!this.widgetId) {
109
+ return null; // Mount failed — fail-open
110
+ }
111
+ // 3. Execute challenge with timeout
112
+ const tokenPromise = this._executeChallenge(this.widgetId, action);
113
+ const timeoutPromise = new Promise((resolve) => {
114
+ setTimeout(() => resolve(null), timeoutMs);
115
+ });
116
+ return await Promise.race([tokenPromise, timeoutPromise]);
117
+ }
118
+ catch {
119
+ // Any error → return null (fail-open at middleware level)
120
+ return null;
121
+ }
122
+ }
123
+ async _ensureMounted(action) {
124
+ // Create an invisible container if none provided
125
+ if (!this.container && typeof document !== 'undefined') {
126
+ this.container = document.createElement('div');
127
+ this.container.style.display = 'none';
128
+ this.container.setAttribute('data-bot-protection', 'true');
129
+ document.body.appendChild(this.container);
130
+ }
131
+ if (!this.container)
132
+ return;
133
+ try {
134
+ this.widgetId = this._renderWidget(this.container, action);
135
+ }
136
+ catch {
137
+ this.widgetId = null;
138
+ }
139
+ }
140
+ /** Optional: setup global onload callback for the script */
141
+ _setupOnloadCallback(_resolve) {
142
+ // Default: no-op. Override if provider uses a callback pattern.
143
+ }
144
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Bot protection manager factory.
3
+ *
4
+ * Creates the appropriate manager based on provider config from shop query.
5
+ * Supports runtime fallback chain via FallbackBotProtectionManager.
6
+ */
7
+ import type { BotProtectionTokenProvider, BotProtectionConfig } from '../middleware/bot-protection';
8
+ /**
9
+ * Create a bot protection manager with optional fallback chain.
10
+ *
11
+ * @param config - Bot protection configuration from shop query
12
+ * @returns BotProtectionTokenProvider (FallbackBotProtectionManager if fallback configured)
13
+ */
14
+ export declare function createBotProtectionManager(config: BotProtectionConfig): BotProtectionTokenProvider;
15
+ //# sourceMappingURL=create-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-manager.d.ts","sourceRoot":"","sources":["../../../src/core/bot-protection/create-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,mBAAmB,EAA+B,MAAM,8BAA8B,CAAC;AAmBjI;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,mBAAmB,GAAG,0BAA0B,CAIlG"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Bot protection manager factory.
3
+ *
4
+ * Creates the appropriate manager based on provider config from shop query.
5
+ * Supports runtime fallback chain via FallbackBotProtectionManager.
6
+ */
7
+ import { EuCaptchaManager } from './eucaptcha-manager';
8
+ import { TurnstileManager } from './turnstile-manager';
9
+ import { FallbackBotProtectionManager } from './fallback-manager';
10
+ /**
11
+ * Create a single provider manager instance.
12
+ */
13
+ function createSingleManager(config) {
14
+ switch (config.provider) {
15
+ case 'eucaptcha':
16
+ return new EuCaptchaManager(config.siteKey, config.scriptUrl);
17
+ case 'turnstile':
18
+ return new TurnstileManager(config.siteKey, config.scriptUrl);
19
+ default:
20
+ throw new Error(`Unknown bot protection provider: ${config.provider}`);
21
+ }
22
+ }
23
+ /**
24
+ * Create a bot protection manager with optional fallback chain.
25
+ *
26
+ * @param config - Bot protection configuration from shop query
27
+ * @returns BotProtectionTokenProvider (FallbackBotProtectionManager if fallback configured)
28
+ */
29
+ export function createBotProtectionManager(config) {
30
+ const primary = createSingleManager(config.primary);
31
+ const fallback = config.fallback ? createSingleManager(config.fallback) : null;
32
+ return new FallbackBotProtectionManager(primary, fallback);
33
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * EuCaptchaManager — EU CAPTCHA (Myra Security) bot protection provider.
3
+ *
4
+ * Default provider — GDPR by design, EU-hosted (Germany), privacy-first.
5
+ * Implements BotProtectionTokenProvider via AbstractBotProtectionManager.
6
+ * Invisible behavioral analysis, zero user interaction.
7
+ */
8
+ import { AbstractBotProtectionManager } from './abstract-manager';
9
+ export declare class EuCaptchaManager extends AbstractBotProtectionManager {
10
+ protected _waitForApi(): Promise<void>;
11
+ protected _renderWidget(container: HTMLElement, action?: string): string;
12
+ protected _executeChallenge(widgetId: string, action?: string): Promise<string | null>;
13
+ protected _destroyWidget(widgetId: string): void;
14
+ }
15
+ //# sourceMappingURL=eucaptcha-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eucaptcha-manager.d.ts","sourceRoot":"","sources":["../../../src/core/bot-protection/eucaptcha-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAElE,qBAAa,gBAAiB,SAAQ,4BAA4B;IAChE,SAAS,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtC,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAYxE,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqCtF,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAKjD"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * EuCaptchaManager — EU CAPTCHA (Myra Security) bot protection provider.
3
+ *
4
+ * Default provider — GDPR by design, EU-hosted (Germany), privacy-first.
5
+ * Implements BotProtectionTokenProvider via AbstractBotProtectionManager.
6
+ * Invisible behavioral analysis, zero user interaction.
7
+ */
8
+ /// <reference path="./types/eucaptcha.d.ts" />
9
+ import { AbstractBotProtectionManager } from './abstract-manager';
10
+ export class EuCaptchaManager extends AbstractBotProtectionManager {
11
+ _waitForApi() {
12
+ if (typeof window !== 'undefined' && window.eucaptcha) {
13
+ return Promise.resolve();
14
+ }
15
+ return new Promise((resolve) => {
16
+ const check = setInterval(() => {
17
+ if (typeof window !== 'undefined' && window.eucaptcha) {
18
+ clearInterval(check);
19
+ resolve();
20
+ }
21
+ }, 50);
22
+ // Safety timeout
23
+ setTimeout(() => {
24
+ clearInterval(check);
25
+ resolve();
26
+ }, 15000);
27
+ });
28
+ }
29
+ _renderWidget(container, action) {
30
+ if (!window.eucaptcha) {
31
+ throw new Error('EU CAPTCHA API not loaded');
32
+ }
33
+ return window.eucaptcha.render(container, {
34
+ sitekey: this.siteKey,
35
+ size: 'invisible',
36
+ action,
37
+ });
38
+ }
39
+ _executeChallenge(widgetId, action) {
40
+ return new Promise((resolve) => {
41
+ if (!window.eucaptcha) {
42
+ resolve(null);
43
+ return;
44
+ }
45
+ // Reset for fresh token
46
+ window.eucaptcha.reset(widgetId);
47
+ // Remove old widget and re-render with callback
48
+ if (!this.container) {
49
+ resolve(null);
50
+ return;
51
+ }
52
+ // Re-render with callback to capture token
53
+ this.widgetId = window.eucaptcha.render(this.container, {
54
+ sitekey: this.siteKey,
55
+ size: 'invisible',
56
+ action,
57
+ callback: (token) => {
58
+ resolve(token);
59
+ },
60
+ 'error-callback': () => {
61
+ resolve(null);
62
+ },
63
+ 'expired-callback': () => {
64
+ resolve(null);
65
+ },
66
+ });
67
+ // Trigger execution
68
+ window.eucaptcha.execute(this.widgetId);
69
+ });
70
+ }
71
+ _destroyWidget(widgetId) {
72
+ if (window.eucaptcha) {
73
+ window.eucaptcha.reset(widgetId);
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * FallbackBotProtectionManager — runtime fallback chain.
3
+ *
4
+ * Tries primary provider, falls back to secondary, ultimately returns null (fail-open).
5
+ * RULE: NEVER block the customer — fail-open is the ultimate fallback.
6
+ */
7
+ import type { BotProtectionTokenProvider } from '../middleware/bot-protection';
8
+ export declare class FallbackBotProtectionManager implements BotProtectionTokenProvider {
9
+ private readonly primary;
10
+ private readonly fallback;
11
+ constructor(primary: BotProtectionTokenProvider, fallback: BotProtectionTokenProvider | null);
12
+ execute(options?: {
13
+ action?: string;
14
+ timeoutMs?: number;
15
+ }): Promise<string | null>;
16
+ destroy(): void;
17
+ }
18
+ //# sourceMappingURL=fallback-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fallback-manager.d.ts","sourceRoot":"","sources":["../../../src/core/bot-protection/fallback-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE/E,qBAAa,4BAA6B,YAAW,0BAA0B;IAE3E,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBADR,OAAO,EAAE,0BAA0B,EACnC,QAAQ,EAAE,0BAA0B,GAAG,IAAI;IAGxD,OAAO,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuBxF,OAAO,IAAI,IAAI;CAIhB"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * FallbackBotProtectionManager — runtime fallback chain.
3
+ *
4
+ * Tries primary provider, falls back to secondary, ultimately returns null (fail-open).
5
+ * RULE: NEVER block the customer — fail-open is the ultimate fallback.
6
+ */
7
+ export class FallbackBotProtectionManager {
8
+ primary;
9
+ fallback;
10
+ constructor(primary, fallback) {
11
+ this.primary = primary;
12
+ this.fallback = fallback;
13
+ }
14
+ async execute(options) {
15
+ // 1. Try primary
16
+ try {
17
+ const token = await this.primary.execute(options);
18
+ if (token)
19
+ return token;
20
+ }
21
+ catch {
22
+ // Primary failed — try fallback
23
+ }
24
+ // 2. Try fallback
25
+ if (this.fallback) {
26
+ try {
27
+ const token = await this.fallback.execute(options);
28
+ if (token)
29
+ return token;
30
+ }
31
+ catch {
32
+ // Fallback also failed
33
+ }
34
+ }
35
+ // 3. Ultimate fail-open: return null -> middleware decides per-operation strategy
36
+ return null;
37
+ }
38
+ destroy() {
39
+ this.primary.destroy();
40
+ this.fallback?.destroy();
41
+ }
42
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * TurnstileManager — Cloudflare Turnstile bot protection provider.
3
+ *
4
+ * Implements BotProtectionTokenProvider via AbstractBotProtectionManager.
5
+ * Invisible widget, execution-mode challenge (no user interaction).
6
+ */
7
+ import { AbstractBotProtectionManager } from './abstract-manager';
8
+ export declare class TurnstileManager extends AbstractBotProtectionManager {
9
+ private callbackName;
10
+ protected _waitForApi(): Promise<void>;
11
+ protected _renderWidget(container: HTMLElement, action?: string): string;
12
+ protected _executeChallenge(widgetId: string, action?: string): Promise<string | null>;
13
+ protected _destroyWidget(widgetId: string): void;
14
+ }
15
+ //# sourceMappingURL=turnstile-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turnstile-manager.d.ts","sourceRoot":"","sources":["../../../src/core/bot-protection/turnstile-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAElE,qBAAa,gBAAiB,SAAQ,4BAA4B;IAChE,OAAO,CAAC,YAAY,CAAoE;IAExF,SAAS,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtC,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM;IAaxE,SAAS,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuCtF,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;CAKjD"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * TurnstileManager — Cloudflare Turnstile bot protection provider.
3
+ *
4
+ * Implements BotProtectionTokenProvider via AbstractBotProtectionManager.
5
+ * Invisible widget, execution-mode challenge (no user interaction).
6
+ */
7
+ /// <reference path="./types/turnstile.d.ts" />
8
+ import { AbstractBotProtectionManager } from './abstract-manager';
9
+ export class TurnstileManager extends AbstractBotProtectionManager {
10
+ callbackName = `onloadTurnstileCallback_${Math.random().toString(36).slice(2)}`;
11
+ _waitForApi() {
12
+ if (typeof window !== 'undefined' && window.turnstile) {
13
+ return Promise.resolve();
14
+ }
15
+ return new Promise((resolve) => {
16
+ const check = setInterval(() => {
17
+ if (typeof window !== 'undefined' && window.turnstile) {
18
+ clearInterval(check);
19
+ resolve();
20
+ }
21
+ }, 50);
22
+ // Safety timeout — don't poll forever
23
+ setTimeout(() => {
24
+ clearInterval(check);
25
+ resolve(); // Resolve anyway — execute will fail gracefully
26
+ }, 15000);
27
+ });
28
+ }
29
+ _renderWidget(container, action) {
30
+ if (!window.turnstile) {
31
+ throw new Error('Turnstile API not loaded');
32
+ }
33
+ return window.turnstile.render(container, {
34
+ sitekey: this.siteKey,
35
+ size: 'invisible',
36
+ execution: 'execute',
37
+ action,
38
+ });
39
+ }
40
+ _executeChallenge(widgetId, action) {
41
+ return new Promise((resolve) => {
42
+ if (!window.turnstile) {
43
+ resolve(null);
44
+ return;
45
+ }
46
+ // Reset to get a fresh token (tokens are single-use)
47
+ window.turnstile.reset(widgetId);
48
+ // Remove old widget and re-render with callback
49
+ window.turnstile.remove(widgetId);
50
+ if (!this.container) {
51
+ resolve(null);
52
+ return;
53
+ }
54
+ this.widgetId = window.turnstile.render(this.container, {
55
+ sitekey: this.siteKey,
56
+ size: 'invisible',
57
+ execution: 'execute',
58
+ action,
59
+ callback: (token) => {
60
+ resolve(token);
61
+ },
62
+ 'error-callback': () => {
63
+ resolve(null);
64
+ },
65
+ 'expired-callback': () => {
66
+ resolve(null);
67
+ },
68
+ });
69
+ // Trigger execution
70
+ window.turnstile.execute(this.widgetId, { action });
71
+ });
72
+ }
73
+ _destroyWidget(widgetId) {
74
+ if (window.turnstile) {
75
+ window.turnstile.remove(widgetId);
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Cart cookie configuration — platform contract.
3
+ *
4
+ * Used by:
5
+ * - SDK cart store (client-side cookie read/write for cartId)
6
+ * - Server-side cart prefetching (SSR cart badge, middleware)
7
+ * - proxy.ts (edge cart ID detection)
8
+ *
9
+ * Single cookie for cart ID persistence. Value is a plain cart ID string
10
+ * (not JSON) — server can read it directly via cookies().get('cart-id').
11
+ */
12
+ export declare const CART_COOKIE_NAME = "cart-id";
13
+ export declare const CART_COOKIE_MAX_AGE: number;
14
+ //# sourceMappingURL=cookie-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookie-config.d.ts","sourceRoot":"","sources":["../../../src/core/cart/cookie-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAC1C,eAAO,MAAM,mBAAmB,QAAoB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Cart cookie configuration — platform contract.
3
+ *
4
+ * Used by:
5
+ * - SDK cart store (client-side cookie read/write for cartId)
6
+ * - Server-side cart prefetching (SSR cart badge, middleware)
7
+ * - proxy.ts (edge cart ID detection)
8
+ *
9
+ * Single cookie for cart ID persistence. Value is a plain cart ID string
10
+ * (not JSON) — server can read it directly via cookies().get('cart-id').
11
+ */
12
+ export const CART_COOKIE_NAME = 'cart-id';
13
+ export const CART_COOKIE_MAX_AGE = 30 * 24 * 60 * 60; // 30 days