@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 +365 -101
- package/dist/ElevateAnalytics-Cp5iR7dJ.d.cts +48 -0
- package/dist/ElevateAnalytics-Cp5iR7dJ.d.ts +48 -0
- package/dist/cartAttributes-4XA3JSEP.js +2 -0
- package/dist/cartAttributes-4XA3JSEP.js.map +1 -0
- package/dist/cartAttributes-NW2TWOYC.js +3 -0
- package/dist/cartAttributes-NW2TWOYC.js.map +1 -0
- package/dist/chunk-4D5I75NE.js +19 -0
- package/dist/chunk-4D5I75NE.js.map +1 -0
- package/dist/chunk-VUGOZ5MR.js +18 -0
- package/dist/chunk-VUGOZ5MR.js.map +1 -0
- package/dist/chunk-XXNIBCJ6.js +2 -0
- package/dist/chunk-XXNIBCJ6.js.map +1 -0
- package/dist/hydrogen.cjs +18 -0
- package/dist/hydrogen.cjs.map +1 -0
- package/dist/hydrogen.d.cts +48 -0
- package/dist/hydrogen.d.ts +48 -0
- package/dist/hydrogen.js +2 -0
- package/dist/hydrogen.js.map +1 -0
- package/dist/index.cjs +40 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1433 -33
- package/dist/index.d.ts +1433 -33
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/next.cjs +42 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +734 -0
- package/dist/next.d.ts +734 -0
- package/dist/next.js +26 -0
- package/dist/next.js.map +1 -0
- package/package.json +31 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @elevateab/sdk
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
---
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
## Hydrogen Setup
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
import { ElevateProvider, useExperiment } from "@elevateab/sdk";
|
|
87
|
+
### 1. Add the Provider
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
```tsx
|
|
90
|
+
// app/layout.tsx
|
|
91
|
+
import { ElevateNextProvider } from "@elevateab/sdk/next";
|
|
62
92
|
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
const isValid = validateConfig(config);
|
|
282
|
+
### Manual (If Needed)
|
|
92
283
|
|
|
93
|
-
|
|
94
|
-
|
|
284
|
+
```tsx
|
|
285
|
+
import { updateCartAttributes } from "@elevateab/sdk";
|
|
95
286
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
+
```tsx
|
|
301
|
+
import { extractShopifyId, isShopifyGid } from "@elevateab/sdk";
|
|
116
302
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
###
|
|
354
|
+
### ElevateProvider Props
|
|
126
355
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
402
|
+
## Cookies & Storage
|
|
141
403
|
|
|
142
|
-
|
|
143
|
-
- `
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
146
|
-
- `
|
|
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
|
-
|
|
410
|
+
**Session Storage:**
|
|
411
|
+
- `eabSessionId` - Session ID
|
|
412
|
+
- `ABAV` - Session test views
|
|
149
413
|
|
|
150
|
-
|
|
414
|
+
---
|
|
151
415
|
|
|
152
|
-
##
|
|
416
|
+
## License
|
|
153
417
|
|
|
154
|
-
|
|
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 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -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
|