@elevateab/sdk 1.2.0 → 1.2.2

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @elevateab/sdk
2
2
 
3
- Elevate AB Testing SDK for Hydrogen and Remix frameworks.
3
+ A/B Testing SDK for Shopify Hydrogen and Next.js stores.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,147 +8,411 @@ Elevate AB Testing SDK for Hydrogen and Remix frameworks.
8
8
  npm install @elevateab/sdk
9
9
  ```
10
10
 
11
- ## Usage
11
+ **Peer Dependencies:**
12
+ - `react` >= 18.0.0 or >= 19.0.0
13
+ - `@shopify/hydrogen` >= 2023.10.0 (Hydrogen only)
14
+ - `next` >= 13.0.0 (Next.js only)
12
15
 
13
- ### Automatic Config Loading
16
+ ---
14
17
 
15
- The easiest way to use the SDK is with `ElevateProvider`. It automatically fetches all active experiments from the CDN and makes them available throughout your app:
18
+ ## Hydrogen Setup
16
19
 
17
- ```typescript
18
- import { ElevateProvider, Experiment, VariantDisplay } from "@elevateab/sdk";
20
+ Hydrogen uses automatic analytics tracking via Shopify's `useAnalytics()` hook.
21
+
22
+ ### 1. Add the Provider
23
+
24
+ ```tsx
25
+ // app/root.tsx
26
+ import { ElevateProvider, ElevateAnalytics } from "@elevateab/sdk";
27
+ import { Analytics } from "@shopify/hydrogen";
28
+
29
+ export default function Root() {
30
+ const data = useLoaderData<typeof loader>();
19
31
 
20
- function App() {
21
32
  return (
22
- <ElevateProvider storeId="mystore.myshopify.com">
23
- <ProductPage />
24
- </ElevateProvider>
33
+ <Analytics.Provider cart={data.cart} shop={data.shop} consent={data.consent}>
34
+ <ElevateProvider
35
+ storeId="mystore.myshopify.com"
36
+ storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
37
+ >
38
+ <ElevateAnalytics />
39
+ <Outlet />
40
+ </ElevateProvider>
41
+ </Analytics.Provider>
25
42
  );
26
43
  }
44
+ ```
45
+
46
+ That's it. Analytics events are tracked automatically when users view pages, products, add to cart, etc.
47
+
48
+ ### 2. Add Anti-Flicker (Recommended)
27
49
 
28
- function ProductPage() {
50
+ Prevents content flash while tests load. Add this script to `<head>` before any other scripts:
51
+
52
+ ```tsx
53
+ // app/root.tsx
54
+ export default function Root() {
29
55
  return (
30
- <div>
31
- {/* Price test - Only the assigned variant will render */}
32
- <Experiment testId="exp-123" userId="user-123">
33
- <VariantDisplay variantId="control">
34
- <Price amount={99.99} />
35
- </VariantDisplay>
36
- <VariantDisplay variantId="variant-a">
37
- <Price amount={89.99} />
38
- </VariantDisplay>
39
- </Experiment>
40
-
41
- {/* Headline test */}
42
- <Experiment testId="exp-456" userId="user-123">
43
- <VariantDisplay variantId="control">
44
- <h1>Original Headline</h1>
45
- </VariantDisplay>
46
- <VariantDisplay variantId="variant-b">
47
- <h1>New Headline</h1>
48
- </VariantDisplay>
49
- </Experiment>
50
- </div>
56
+ <html>
57
+ <head>
58
+ <script
59
+ dangerouslySetInnerHTML={{
60
+ __html: `(function(){var d=document.documentElement;d.style.opacity='0';d.style.transition='opacity 0.2s';window.__eabReveal=function(){d.style.opacity='1'};setTimeout(window.__eabReveal,2000)})();`,
61
+ }}
62
+ />
63
+ {/* other head elements */}
64
+ </head>
65
+ <body>{/* ... */}</body>
66
+ </html>
51
67
  );
52
68
  }
53
69
  ```
54
70
 
55
- ### Using the Hook
71
+ Then pass `preventFlickering` to the provider:
72
+
73
+ ```tsx
74
+ <ElevateProvider
75
+ storeId="mystore.myshopify.com"
76
+ storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
77
+ preventFlickering={true}
78
+ >
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Next.js Setup
84
+
85
+ Next.js requires manual tracking since it doesn't have Shopify's analytics system.
56
86
 
57
- ```typescript
58
- import { ElevateProvider, useExperiment } from "@elevateab/sdk";
87
+ ### 1. Add the Provider
59
88
 
60
- function MyComponent() {
61
- const { variant, isLoading } = useExperiment("exp-123", "user-123");
89
+ ```tsx
90
+ // app/layout.tsx
91
+ import { ElevateNextProvider } from "@elevateab/sdk/next";
62
92
 
63
- if (isLoading) return <div>Loading...</div>;
93
+ export default function RootLayout({ children }) {
94
+ return (
95
+ <html>
96
+ <head>
97
+ {/* Anti-flicker script (optional but recommended) */}
98
+ <script
99
+ dangerouslySetInnerHTML={{
100
+ __html: `(function(){var d=document.documentElement;d.style.opacity='0';d.style.transition='opacity 0.2s';window.__eabReveal=function(){d.style.opacity='1'};setTimeout(window.__eabReveal,2000)})();`,
101
+ }}
102
+ />
103
+ </head>
104
+ <body>
105
+ <ElevateNextProvider
106
+ storeId="mystore.myshopify.com"
107
+ storefrontAccessToken={process.env.NEXT_PUBLIC_STOREFRONT_TOKEN}
108
+ preventFlickering={true}
109
+ >
110
+ {children}
111
+ </ElevateNextProvider>
112
+ </body>
113
+ </html>
114
+ );
115
+ }
116
+ ```
117
+
118
+ The `ElevateNextProvider` automatically:
119
+ - Tracks page views on route changes
120
+ - Initializes analytics globally
121
+ - Handles anti-flicker reveal
122
+
123
+ ### 2. Track Product Views
64
124
 
125
+ ```tsx
126
+ // app/product/[handle]/page.tsx
127
+ import { ProductViewTracker } from "@elevateab/sdk/next";
128
+
129
+ export default function ProductPage({ product }) {
65
130
  return (
66
- <div>
67
- {variant?.id === "control" ? (
68
- <Price amount={99.99} />
69
- ) : (
70
- <Price amount={89.99} />
71
- )}
72
- </div>
131
+ <>
132
+ <ProductViewTracker
133
+ productId={product.id}
134
+ productVendor={product.vendor}
135
+ productPrice={parseFloat(product.priceRange.minVariantPrice.amount)}
136
+ currency={product.priceRange.minVariantPrice.currencyCode}
137
+ />
138
+ {/* Product content */}
139
+ </>
73
140
  );
74
141
  }
75
142
  ```
76
143
 
77
- ### Utility Functions
144
+ ### 3. Track Add to Cart
145
+
146
+ ```tsx
147
+ import { trackAddToCart } from "@elevateab/sdk";
148
+
149
+ async function handleAddToCart() {
150
+ // Shopify GIDs are automatically converted to numeric IDs
151
+ await trackAddToCart({
152
+ productId: product.id, // "gid://shopify/Product/123" works
153
+ variantId: variant.id, // "gid://shopify/ProductVariant/456" works
154
+ productPrice: 99.99,
155
+ productQuantity: 1,
156
+ currency: "USD",
157
+ cartId: cart.id, // For cart attribute tagging
158
+ });
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Using A/B Tests
165
+
166
+ ### useExperiment Hook
167
+
168
+ ```tsx
169
+ import { useExperiment } from "@elevateab/sdk";
170
+
171
+ function PricingSection() {
172
+ const { variant, isLoading } = useExperiment("pricing-test");
173
+
174
+ if (isLoading) return <LoadingSkeleton />;
175
+
176
+ if (variant?.isControl) {
177
+ return <Price amount={99.99} />;
178
+ }
179
+
180
+ return <Price amount={79.99} />;
181
+ }
182
+ ```
183
+
184
+ ### Variant Properties
185
+
186
+ ```tsx
187
+ const { variant } = useExperiment("test-id");
188
+
189
+ variant?.isControl // true if control group
190
+ variant?.isA // true if variant A
191
+ variant?.isB // true if variant B
192
+ variant?.isC // true if variant C
193
+ variant?.isD // true if variant D
194
+ variant?.id // variant ID
195
+ variant?.name // variant name
196
+ ```
197
+
198
+ ### Multiple Variants
199
+
200
+ ```tsx
201
+ function LayoutTest() {
202
+ const { variant } = useExperiment("layout-test");
203
+
204
+ if (variant?.isA) return <LayoutA />;
205
+ if (variant?.isB) return <LayoutB />;
206
+ if (variant?.isC) return <LayoutC />;
207
+
208
+ return <DefaultLayout />;
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Preview Mode
215
+
216
+ Test specific variants without affecting live traffic. Add URL parameters:
217
+
218
+ ```
219
+ https://yourstore.com/?eabUserPreview=true&abtid=<test-id>&eab_tests=<short-id>_<variant-id>
220
+ ```
221
+
222
+ Example:
223
+ ```
224
+ https://yourstore.com/products/shirt?eabUserPreview=true&abtid=abc123&eab_tests=c123_12345
225
+ ```
226
+
227
+ Check if in preview mode:
228
+
229
+ ```tsx
230
+ import { isPreviewMode } from "@elevateab/sdk";
231
+
232
+ if (isPreviewMode()) {
233
+ // Show preview indicator
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Geo-Targeting
240
+
241
+ Get user's country for location-based tests:
242
+
243
+ ```tsx
244
+ import { getUserCountry } from "@elevateab/sdk";
245
+
246
+ const country = getUserCountry(); // "US", "CA", "GB", etc.
247
+ ```
248
+
249
+ Country is detected from:
250
+ 1. `localization` cookie (set by Shopify)
251
+ 2. Cloudflare `CF-IPCountry` header
252
+ 3. Falls back to `null` if unavailable
253
+
254
+ ---
255
+
256
+ ## Cart Attribute Tagging
257
+
258
+ Orders are attributed to A/B tests via cart attributes. This happens automatically when you provide `storefrontAccessToken` and `cartId`.
259
+
260
+ ### Automatic (Recommended)
78
261
 
79
- ```typescript
80
- import {
81
- assignVariant,
82
- validateConfig,
83
- generateExperimentId,
84
- calculateRevenueLift,
85
- } from "@elevateab/sdk";
262
+ For Hydrogen, pass `storefrontAccessToken` to the provider:
86
263
 
87
- // Assign a variant based on weighted distribution
88
- const variant = assignVariant(config.variants, "user-123");
264
+ ```tsx
265
+ <ElevateProvider
266
+ storeId="mystore.myshopify.com"
267
+ storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
268
+ />
269
+ ```
270
+
271
+ For Next.js, pass `cartId` to `trackAddToCart`:
272
+
273
+ ```tsx
274
+ trackAddToCart({
275
+ productId: "123",
276
+ variantId: "456",
277
+ cartId: cart.id,
278
+ // ...
279
+ });
280
+ ```
89
281
 
90
- // Validate experiment configuration
91
- const isValid = validateConfig(config);
282
+ ### Manual (If Needed)
92
283
 
93
- // Generate unique experiment ID
94
- const expId = generateExperimentId("My Test");
284
+ ```tsx
285
+ import { updateCartAttributes } from "@elevateab/sdk";
95
286
 
96
- // Calculate revenue lift
97
- const { lift, confidence } = calculateRevenueLift(
98
- 10000, // control revenue
99
- 12000, // variant revenue
100
- 500, // control sample size
101
- 500 // variant sample size
102
- );
287
+ await updateCartAttributes(cartId, {
288
+ storefrontAccessToken: process.env.NEXT_PUBLIC_STOREFRONT_TOKEN,
289
+ });
103
290
  ```
104
291
 
105
- ## Features
292
+ The Storefront Access Token is safe to use client-side. It's a public token designed for browser use.
293
+
294
+ ---
295
+
296
+ ## Utility Functions
106
297
 
107
- - Zero-config TypeScript support
108
- - ✅ React Server Components compatible
109
- - ✅ ESM and CJS dual format
110
- - ✅ Tree-shakeable
111
- - ✅ Minified and optimized
112
- - ✅ Full type definitions
113
- - ✅ < 1KB gzipped
298
+ ### Shopify ID Helpers
114
299
 
115
- ## Compatibility
300
+ ```tsx
301
+ import { extractShopifyId, isShopifyGid } from "@elevateab/sdk";
116
302
 
117
- - **Shopify Hydrogen**: Full support
118
- - **Remix**: ✅ Full support (ESM + CJS)
119
- - **React**: 18.0.0+
120
- - **TypeScript**: 5.0.0+
121
- - **Node.js**: 18.0.0+
303
+ // Extract numeric ID from GID
304
+ extractShopifyId("gid://shopify/Product/123456"); // "123456"
305
+ extractShopifyId("123456"); // "123456"
306
+
307
+ // Check if string is a GID
308
+ isShopifyGid("gid://shopify/Product/123"); // true
309
+ isShopifyGid("123456"); // false
310
+ ```
311
+
312
+ Note: Tracking functions automatically extract IDs, so you rarely need these directly.
313
+
314
+ ### Visitor & Session
315
+
316
+ ```tsx
317
+ import { getVisitorId, getSessionId, getTestList } from "@elevateab/sdk";
318
+
319
+ getVisitorId(); // Persistent visitor UUID
320
+ getSessionId(); // Session UUID
321
+ getTestList(); // { "test-1": "variant-a", "test-2": "control" }
322
+ ```
323
+
324
+ ### Device & Traffic Detection
325
+
326
+ ```tsx
327
+ import { getDeviceType, getTrafficSource } from "@elevateab/sdk";
328
+
329
+ getDeviceType(); // "desktop" | "tablet" | "mobile"
330
+ getTrafficSource(); // "facebook" | "google" | "instagram" | "direct" | "other"
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Analytics Events
336
+
337
+ The SDK tracks these events:
338
+
339
+ | Event | Hydrogen | Next.js |
340
+ |-------|----------|---------|
341
+ | `page_viewed` | Automatic | Automatic (via provider) |
342
+ | `product_viewed` | Automatic | `ProductViewTracker` or `trackProductView()` |
343
+ | `product_added_to_cart` | Automatic | `trackAddToCart()` |
344
+ | `product_removed_from_cart` | Automatic | `trackRemoveFromCart()` |
345
+ | `cart_viewed` | Automatic | `trackCartView()` |
346
+ | `search_submitted` | Automatic | `trackSearchSubmitted()` |
347
+ | `checkout_started` | Automatic | `trackCheckoutStarted()` |
348
+ | `checkout_completed` | Automatic | `trackCheckoutCompleted()` |
349
+
350
+ ---
122
351
 
123
352
  ## API Reference
124
353
 
125
- ### Types
354
+ ### ElevateProvider Props
126
355
 
127
- - `ExperimentConfig`: Configuration for an experiment
128
- - `Variant`: Definition of a test variant
129
- - `TrackingEvent`: Event tracking data structure
130
- - `ExperimentStatus`: Experiment lifecycle status
356
+ ```tsx
357
+ interface ElevateProviderProps {
358
+ storeId: string; // Required: your-store.myshopify.com
359
+ storefrontAccessToken?: string; // For cart attribute tagging
360
+ preventFlickering?: boolean; // Enable anti-flicker (default: false)
361
+ hasLocalizedPaths?: boolean; // True if homepage is at /en-us/, /fr-ca/, etc.
362
+ workerUrl?: string; // Custom analytics endpoint
363
+ children: React.ReactNode;
364
+ }
365
+ ```
131
366
 
132
- ### Components
367
+ ### Tracking Functions (Next.js)
368
+
369
+ All tracking functions accept Shopify GIDs directly - they're converted automatically.
370
+
371
+ ```tsx
372
+ // Page view (auto-tracked by ElevateNextProvider)
373
+ trackPageView();
374
+
375
+ // Product view
376
+ trackProductView({
377
+ productId: "gid://shopify/Product/123",
378
+ productPrice: 99.99,
379
+ currency: "USD",
380
+ });
381
+
382
+ // Add to cart
383
+ trackAddToCart({
384
+ productId: "123",
385
+ variantId: "456",
386
+ productPrice: 99.99,
387
+ productQuantity: 1,
388
+ currency: "USD",
389
+ cartId: "gid://shopify/Cart/abc", // Optional: enables cart tagging
390
+ });
391
+
392
+ // Other events
393
+ trackRemoveFromCart({ productId, variantId, ... });
394
+ trackCartView({ cartTotalPrice, cartItems, ... });
395
+ trackSearchSubmitted({ searchQuery });
396
+ trackCheckoutStarted({ cartTotalPrice, cartItems, ... });
397
+ trackCheckoutCompleted({ orderId, cartTotalPrice, cartItems, ... });
398
+ ```
133
399
 
134
- - `ElevateProvider`: Provider component that fetches configs from CDN
135
- - `Experiment`: Main experiment wrapper component
136
- - `VariantDisplay`: Conditional rendering based on variant
137
- - `useExperiment`: React hook for experiment logic
138
- - `useElevateConfig`: Hook to access config from context
400
+ ---
139
401
 
140
- ### Utilities
402
+ ## Cookies & Storage
141
403
 
142
- - `assignVariant()`: Assign variant based on distribution
143
- - `hashString()`: Hash function for consistent assignment
144
- - `validateConfig()`: Validate experiment configuration
145
- - `generateExperimentId()`: Generate unique experiment IDs
146
- - `calculateRevenueLift()`: Calculate A/B test metrics
404
+ **Cookies (1 year):**
405
+ - `eabUserId` - Visitor ID
406
+ - `ABTL` - Test assignments
407
+ - `ABAU` - Unique test views
408
+ - `eabUserPreview` - Preview mode flag
147
409
 
148
- ## License
410
+ **Session Storage:**
411
+ - `eabSessionId` - Session ID
412
+ - `ABAV` - Session test views
149
413
 
150
- MIT
414
+ ---
151
415
 
152
- ## Support
416
+ ## License
153
417
 
154
- For issues and questions, please visit [GitHub Issues](https://github.com/elevateab/sdk/issues).
418
+ MIT
@@ -0,0 +1,48 @@
1
+ /**
2
+ * ElevateAnalytics Component
3
+ *
4
+ * Internal component used by ElevateHydrogenAnalytics.
5
+ * Subscribes to Hydrogen analytics events and sends them to Elevate.
6
+ *
7
+ * For Hydrogen stores, use ElevateHydrogenAnalytics instead - it's zero-config!
8
+ * For Next.js/Remix, use manual tracking functions (trackPageView, trackAddToCart, etc.)
9
+ */
10
+ /**
11
+ * Type for useAnalytics hook signature (matches @shopify/hydrogen)
12
+ */
13
+ type UseAnalyticsHook = () => {
14
+ subscribe: (event: string, callback: (data: unknown) => void) => void;
15
+ register: (name: string) => {
16
+ ready: () => void;
17
+ };
18
+ };
19
+ /**
20
+ * Props for ElevateHydrogenAnalytics (public API)
21
+ */
22
+ interface ElevateHydrogenAnalyticsProps {
23
+ /** Override storeId from context (optional) */
24
+ storeId?: string;
25
+ /** Override storefrontAccessToken from context (optional) */
26
+ storefrontAccessToken?: string;
27
+ /** Enable localized path detection (e.g., /en-us/, /fr-ca/) */
28
+ hasLocalizedPaths?: boolean;
29
+ /** Custom CloudFlare Worker URL */
30
+ workerUrl?: string;
31
+ /** Custom orders worker URL */
32
+ ordersWorkerUrl?: string;
33
+ }
34
+ /**
35
+ * Internal props for ElevateAnalytics (requires useAnalytics hook)
36
+ */
37
+ interface ElevateAnalyticsProps extends ElevateHydrogenAnalyticsProps {
38
+ /** The useAnalytics hook from @shopify/hydrogen (required) */
39
+ useAnalytics: UseAnalyticsHook;
40
+ }
41
+ /**
42
+ * ElevateAnalytics - Subscribes to Hydrogen analytics events
43
+ *
44
+ * @internal Used by ElevateHydrogenAnalytics. For Hydrogen stores, use ElevateHydrogenAnalytics instead!
45
+ */
46
+ declare function ElevateAnalytics({ storeId: storeIdProp, storefrontAccessToken: tokenProp, hasLocalizedPaths, workerUrl, ordersWorkerUrl, useAnalytics, }: ElevateAnalyticsProps): null;
47
+
48
+ export { type ElevateHydrogenAnalyticsProps as E, type UseAnalyticsHook as U, ElevateAnalytics as a };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * ElevateAnalytics Component
3
+ *
4
+ * Internal component used by ElevateHydrogenAnalytics.
5
+ * Subscribes to Hydrogen analytics events and sends them to Elevate.
6
+ *
7
+ * For Hydrogen stores, use ElevateHydrogenAnalytics instead - it's zero-config!
8
+ * For Next.js/Remix, use manual tracking functions (trackPageView, trackAddToCart, etc.)
9
+ */
10
+ /**
11
+ * Type for useAnalytics hook signature (matches @shopify/hydrogen)
12
+ */
13
+ type UseAnalyticsHook = () => {
14
+ subscribe: (event: string, callback: (data: unknown) => void) => void;
15
+ register: (name: string) => {
16
+ ready: () => void;
17
+ };
18
+ };
19
+ /**
20
+ * Props for ElevateHydrogenAnalytics (public API)
21
+ */
22
+ interface ElevateHydrogenAnalyticsProps {
23
+ /** Override storeId from context (optional) */
24
+ storeId?: string;
25
+ /** Override storefrontAccessToken from context (optional) */
26
+ storefrontAccessToken?: string;
27
+ /** Enable localized path detection (e.g., /en-us/, /fr-ca/) */
28
+ hasLocalizedPaths?: boolean;
29
+ /** Custom CloudFlare Worker URL */
30
+ workerUrl?: string;
31
+ /** Custom orders worker URL */
32
+ ordersWorkerUrl?: string;
33
+ }
34
+ /**
35
+ * Internal props for ElevateAnalytics (requires useAnalytics hook)
36
+ */
37
+ interface ElevateAnalyticsProps extends ElevateHydrogenAnalyticsProps {
38
+ /** The useAnalytics hook from @shopify/hydrogen (required) */
39
+ useAnalytics: UseAnalyticsHook;
40
+ }
41
+ /**
42
+ * ElevateAnalytics - Subscribes to Hydrogen analytics events
43
+ *
44
+ * @internal Used by ElevateHydrogenAnalytics. For Hydrogen stores, use ElevateHydrogenAnalytics instead!
45
+ */
46
+ declare function ElevateAnalytics({ storeId: storeIdProp, storefrontAccessToken: tokenProp, hasLocalizedPaths, workerUrl, ordersWorkerUrl, useAnalytics, }: ElevateAnalyticsProps): null;
47
+
48
+ export { type ElevateHydrogenAnalyticsProps as E, type UseAnalyticsHook as U, ElevateAnalytics as a };
@@ -0,0 +1,2 @@
1
+ import{C as a,D as b,E as c}from"./chunk-VUGOZ5MR.js";export{c as cleanupCartAttributes,a as getCartAttributesPayload,b as updateCartAttributes};
2
+ //# sourceMappingURL=cartAttributes-4XA3JSEP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ import{p as a,q as b,r as c}from"./chunk-4D5I75NE.js";export{c as cleanupCartAttributes,a as getCartAttributesPayload,b as updateCartAttributes};
3
+ //# sourceMappingURL=cartAttributes-NW2TWOYC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ var C=Object.create;var b=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var N=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var E=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of I(e))!U.call(t,n)&&n!==r&&b(t,n,{get:()=>e[n],enumerable:!(s=k(e,n))||s.enumerable});return t};var D=(t,e,r)=>(r=t!=null?C(S(t)):{},E(e||!t||!t.__esModule?b(r,"default",{value:t,enumerable:!0}):r,t));function y(){try{if(typeof window<"u"&&window.crypto&&window.crypto.getRandomValues){let t=new Uint8Array(16);window.crypto.getRandomValues(t),t[6]=t[6]&15|64,t[8]=t[8]&63|128;let e=Array.from(t).map(r=>r.toString(16).padStart(2,"0"));return[e.slice(0,4).join(""),e.slice(4,6).join(""),e.slice(6,8).join(""),e.slice(8,10).join(""),e.slice(10,16).join("")].join("-")}}catch(t){console.warn("[ElevateAB] Crypto UUID generation failed, falling back to Math.random()",t)}return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}function h(t,e){if(typeof document>"u")return;let r=new Date;r.setTime(r.getTime()+365*24*60*60*1e3);let s="; expires="+r.toUTCString();document.cookie=t+"="+(e||"")+s+"; path=/"}function l(t){if(typeof document>"u")return null;let e=t+"=",r=document.cookie.split(";");for(let s=0;s<r.length;s++){let n=r[s];for(;n.charAt(0)===" ";)n=n.substring(1,n.length);if(n.indexOf(e)===0)return n.substring(e.length,n.length)}return null}function g(t){let e=l(t);if(e)try{return JSON.parse(e)}catch(r){return console.error("[ElevateAB] Error parsing JSON from cookie",r),null}return null}function V(t,e){typeof sessionStorage>"u"||sessionStorage.setItem(t,JSON.stringify(e))}function d(t){return typeof sessionStorage>"u"?null:sessionStorage.getItem(t)}function R(t){let e=d(t);return e?JSON.parse(e):null}function P(){let t=l("eabUserId");return t||(t=y(),h("eabUserId",t)),t}function J(){return P()}function L(){let t=d("eabSessionId");return t||(t=y(),V("eabSessionId",t)),t}function _(){return L()}function $(){return{referrer:d("eabReferrer")||"",entryPage:d("eabEntry")||""}}function T(t){let e=0;for(let r=0;r<t.length;r++){let s=t.charCodeAt(r);e=(e<<5)-e+s,e=e&e}return Math.abs(e)}function W(t){let e=[];for(let[r,s]of Object.entries(t.allTests)){if(!s.data.isLive)continue;let n=[],i=0;for(let[o,a]of Object.entries(s)){if(o==="data")continue;let c=a;if(typeof c=="object"&&c!==null&&"variationName"in c){let u=!!c.isControl;n.push({id:o,name:c.variationName,weight:c.trafficPercentage,isControl:u,productId:c.id,handle:c.handle,price:c.price,isA:!u&&i===0,isB:!u&&i===1,isC:!u&&i===2,isD:!u&&i===3}),u||i++}}n.length>0&&e.push({testId:r,name:s.data.name,enabled:s.data.isLive,type:s.data.type,variations:n})}return{tests:e,selectors:t.selectors}}function p(){return g("ABTL")||{}}function O(t){h("ABTL",JSON.stringify(t))}function A(t){return p()[t]||null}function v(t,e){if(t[e]?.isControl)return-1;let s=0;for(let n=0;n<e;n++)t[n]?.isControl||s++;return s}function B(t,e){let r=t.every(f=>typeof f.weight=="number"),s=[],n=0;if(r)for(let f of t)n+=f.weight,s.push(n);else{let f=100/t.length;for(let m=0;m<t.length;m++)n+=f,s.push(n)}let o=T(e)%1e4/100,a=0;for(let f=0;f<s.length;f++)if(o<s[f]){a=f;break}let c=t[a]||t[0],u=v(t,a);return{...c,isA:u===0,isB:u===1,isC:u===2,isD:u===3}}function X(t,e,r){let s=A(t);if(s){let c=e.variations.findIndex(u=>u.id===s);if(c!==-1){let u=e.variations[c],f=v(e.variations,c);return{...u,isA:f===0,isB:f===1,isC:f===2,isD:f===3}}}let n=e.variations.filter(c=>!c.isDone);if(n.length===0)return null;let i=e.isPersonalization,o;i?o=n.find(u=>!u.isControl)||n[0]:o=B(n,r);let a=p();return a[t]=o.id,O(a),o}function Q(t){let e=t.testTrafficPercentage;return typeof e!="number"||A(t.testId)?!0:Math.random()*100<e}var x=`#graphql
3
+ mutation cartAttributesUpdate($cartId: ID!, $attributes: [AttributeInput!]!) {
4
+ cartAttributesUpdate(cartId: $cartId, attributes: $attributes) {
5
+ cart {
6
+ id
7
+ attributes {
8
+ key
9
+ value
10
+ }
11
+ }
12
+ userErrors {
13
+ field
14
+ message
15
+ }
16
+ }
17
+ }
18
+ `;function M(){let t=p(),e=g("ABAU")||{},r=l("eabUserId"),s=l("eabUserPreview")==="true",n=l("abtid"),i={};for(let a in t)e[a]&&(i[a]=t[a]);let o=[{key:"_eabTestsList",value:JSON.stringify(i)},{key:"_eabUserId",value:r||""},{key:"_eabList",value:JSON.stringify(t)}];return s&&n&&o.push({key:"_eabPreviewTest",value:n}),o}async function rt(t,e={}){if(typeof window>"u")return{success:!1,error:"Not in browser environment"};if(!e.storefrontAccessToken&&!e.storefrontApiUrl)return console.warn("[ElevateAB] Missing storefrontAccessToken. For Next.js/non-Hydrogen stores, you must provide a Storefront Access Token. This token is SAFE to use client-side - it's a public token with limited permissions. Get it from: Shopify Admin \u2192 Settings \u2192 Apps \u2192 Develop apps"),{success:!1,error:"Missing storefrontAccessToken. Required for direct Storefront API calls."};try{let r=M(),s=t.startsWith("gid://")?t:`gid://shopify/Cart/${t}`,n=e.storefrontApiUrl||`https://${w()}/api/2025-01/graphql.json`,i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":e.storefrontAccessToken},body:JSON.stringify({query:x,variables:{cartId:s,attributes:r}})});if(!i.ok)throw new Error(`HTTP error! status: ${i.status}`);let o=await i.json();if(o.errors||o.data?.cartAttributesUpdate?.userErrors?.length>0){let a=o.errors?.[0]?.message||o.data?.cartAttributesUpdate?.userErrors?.[0]?.message||"Unknown error";return console.error("[ElevateAB] Cart attributes update failed:",a),{success:!1,error:a}}return{success:!0}}catch(r){return console.error("[ElevateAB] Error updating cart attributes:",r),{success:!1,error:r instanceof Error?r.message:"Unknown error"}}}async function nt(t,e={}){if(typeof window>"u")return{success:!1,error:"Not in browser environment"};if(!e.storefrontAccessToken&&!e.storefrontApiUrl)return{success:!1,error:"Missing storefrontAccessToken. Required for direct Storefront API calls."};try{let r=t.startsWith("gid://")?t:`gid://shopify/Cart/${t}`,s=[{key:"_eabTestsList",value:""},{key:"_eabUserId",value:""},{key:"_eabList",value:""},{key:"_eabPreviewTest",value:""}],n=e.storefrontApiUrl||`https://${w()}/api/2025-01/graphql.json`,i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":e.storefrontAccessToken},body:JSON.stringify({query:x,variables:{cartId:r,attributes:s}})});if(!i.ok)throw new Error(`HTTP error! status: ${i.status}`);let o=await i.json();if(o.errors||o.data?.cartAttributesUpdate?.userErrors?.length>0){let a=o.errors?.[0]?.message||o.data?.cartAttributesUpdate?.userErrors?.[0]?.message||"Unknown error";return console.error("[ElevateAB] Cart attributes cleanup failed:",a),{success:!1,error:a}}return{success:!0}}catch(r){return console.error("[ElevateAB] Error cleaning up cart attributes:",r),{success:!1,error:r instanceof Error?r.message:"Unknown error"}}}function w(){return typeof window>"u"?"":window.location.hostname}export{N as a,D as b,W as c,y as d,h as e,l as f,g,V as h,d as i,R as j,J as k,_ as l,$ as m,X as n,Q as o,M as p,rt as q,nt as r};
19
+ //# sourceMappingURL=chunk-4D5I75NE.js.map