@elevateab/sdk 1.1.2 → 1.2.1
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 +453 -76
- 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
|
-
Elevate AB Testing SDK for Hydrogen and
|
|
3
|
+
Elevate AB Testing SDK for Shopify Hydrogen and Next.js with built-in analytics tracking and cart attribute tagging.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,142 +8,519 @@ Elevate AB Testing SDK for Hydrogen and Remix frameworks.
|
|
|
8
8
|
npm install @elevateab/sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**Peer Dependencies:**
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
- `react` >= 18.0.0
|
|
14
|
+
- `@shopify/hydrogen` >= 2023.10.0 (optional - only for Hydrogen)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Framework Support
|
|
17
|
+
|
|
18
|
+
| Framework | Support | Analytics Mode |
|
|
19
|
+
| -------------------- | ------- | ------------------------------ |
|
|
20
|
+
| **Shopify Hydrogen** | ✅ Full | Automatic via `useAnalytics()` |
|
|
21
|
+
| **Next.js** | ✅ Full | Manual tracking functions |
|
|
22
|
+
| **Remix** | ✅ Full | Manual tracking functions |
|
|
23
|
+
| **Other React** | ✅ Full | Manual tracking functions |
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### 1. Setup ElevateProvider
|
|
28
|
+
|
|
29
|
+
Wrap your app with `ElevateProvider` - it handles everything (config fetching, analytics tracking, and event sending):
|
|
16
30
|
|
|
17
31
|
```typescript
|
|
18
|
-
import { ElevateProvider
|
|
32
|
+
import { ElevateProvider } from "@elevateab/sdk";
|
|
33
|
+
|
|
34
|
+
export default function Root() {
|
|
35
|
+
const data = useLoaderData<typeof loader>();
|
|
19
36
|
|
|
20
|
-
function App() {
|
|
21
37
|
return (
|
|
22
|
-
<ElevateProvider
|
|
23
|
-
|
|
38
|
+
<ElevateProvider
|
|
39
|
+
storeId="mystore.myshopify.com"
|
|
40
|
+
cart={data.cart}
|
|
41
|
+
shop={data.shop}
|
|
42
|
+
consent={data.consent}
|
|
43
|
+
storefrontUrl={data.shop.primaryDomain.url}
|
|
44
|
+
>
|
|
45
|
+
<Outlet />
|
|
24
46
|
</ElevateProvider>
|
|
25
47
|
);
|
|
26
48
|
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**That's it!** The `ElevateProvider` automatically:
|
|
52
|
+
|
|
53
|
+
- ✅ Fetches test configurations from CDN
|
|
54
|
+
- ✅ Sets up Shopify Analytics tracking
|
|
55
|
+
- ✅ Tracks all analytics events with A/B test data
|
|
56
|
+
- ✅ Sends events to CloudFlare Worker
|
|
57
|
+
- ✅ Manages visitor IDs and session tracking
|
|
58
|
+
|
|
59
|
+
### 2. Use A/B Tests in Your Components
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { useExperiment } from "@elevateab/sdk";
|
|
27
63
|
|
|
28
64
|
function ProductPage() {
|
|
65
|
+
const { variant, isLoading } = useExperiment("test-price-1");
|
|
66
|
+
|
|
67
|
+
if (isLoading) return <div>Loading...</div>;
|
|
68
|
+
|
|
29
69
|
return (
|
|
30
70
|
<div>
|
|
31
|
-
{
|
|
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>
|
|
71
|
+
{variant?.isControl ? <Price amount={99.99} /> : <Price amount={89.99} />}
|
|
50
72
|
</div>
|
|
51
73
|
);
|
|
52
74
|
}
|
|
53
75
|
```
|
|
54
76
|
|
|
77
|
+
## How It Works
|
|
78
|
+
|
|
79
|
+
### Automatic Analytics Tracking
|
|
80
|
+
|
|
81
|
+
The `ElevateProvider` automatically tracks the following Shopify analytics events and enriches them with A/B test data:
|
|
82
|
+
|
|
83
|
+
- `page_viewed` - Page views with test assignments
|
|
84
|
+
- `product_viewed` - Product views
|
|
85
|
+
- `product_added_to_cart` - Add to cart events
|
|
86
|
+
- `product_removed_from_cart` - Remove from cart events
|
|
87
|
+
- `cart_viewed` - Cart page views
|
|
88
|
+
|
|
89
|
+
### Event Data Structure
|
|
90
|
+
|
|
91
|
+
Events are sent to CloudFlare Worker with:
|
|
92
|
+
|
|
93
|
+
- **Test Assignments** (`ab_test_assignments`) - All tests the user is assigned to
|
|
94
|
+
- **Test Views** (`ab_test_views`) - Tests the user has actually viewed
|
|
95
|
+
- **UTM Parameters** - Campaign tracking data
|
|
96
|
+
- **Device/Browser Info** - User agent, device type, OS
|
|
97
|
+
- **Referrer Source** - Traffic source (Google, Facebook, Direct, etc.)
|
|
98
|
+
- **Product/Cart Data** - Product IDs, prices, quantities
|
|
99
|
+
|
|
100
|
+
**No additional setup needed** - just use `ElevateProvider` and everything works automatically!
|
|
101
|
+
|
|
102
|
+
## Cart Attribute Tagging
|
|
103
|
+
|
|
104
|
+
Carts are **automatically tagged** with A/B test data for order attribution.
|
|
105
|
+
|
|
106
|
+
### For Hydrogen Stores
|
|
107
|
+
|
|
108
|
+
Cart attributes are automatically updated when `product_added_to_cart` event fires, **if you provide the `storefrontAccessToken`**:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
<ElevateProvider
|
|
112
|
+
storeId="mystore.myshopify.com"
|
|
113
|
+
cart={data.cart}
|
|
114
|
+
shop={data.shop}
|
|
115
|
+
consent={data.consent}
|
|
116
|
+
storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN} // ← Add this!
|
|
117
|
+
>
|
|
118
|
+
<Outlet />
|
|
119
|
+
</ElevateProvider>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**That's it!** When users add items to cart, we automatically:
|
|
123
|
+
|
|
124
|
+
1. Track the `product_added_to_cart` event
|
|
125
|
+
2. Update cart attributes with A/B test data
|
|
126
|
+
|
|
127
|
+
### For Next.js / Other Stores
|
|
128
|
+
|
|
129
|
+
Pass `cartId` and `storefrontAccessToken` to `trackAddToCart()`:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
await trackAddToCart({
|
|
133
|
+
storeId: "mystore.myshopify.com",
|
|
134
|
+
productId: "123456789",
|
|
135
|
+
variantId: "987654321",
|
|
136
|
+
productPrice: 99.99,
|
|
137
|
+
productQuantity: 1,
|
|
138
|
+
currency: "USD",
|
|
139
|
+
// Add these to auto-update cart attributes:
|
|
140
|
+
cartId: cart.id,
|
|
141
|
+
storefrontAccessToken: process.env.NEXT_PUBLIC_STOREFRONT_TOKEN,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Manual Cart Tagging (If Needed)
|
|
146
|
+
|
|
147
|
+
You can also manually update cart attributes:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { updateCartAttributes } from "@elevateab/sdk";
|
|
151
|
+
|
|
152
|
+
await updateCartAttributes(cart.id, {
|
|
153
|
+
storefrontAccessToken: process.env.NEXT_PUBLIC_STOREFRONT_TOKEN,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> ✅ **The Storefront Access Token is SAFE to use client-side!**
|
|
158
|
+
> It's a PUBLIC token that Shopify designed for browser use. It can only read products and manage carts - it cannot do anything destructive.
|
|
159
|
+
|
|
160
|
+
### Cart Attributes GraphQL Mutation
|
|
161
|
+
|
|
162
|
+
If you want to handle cart attributes in your own GraphQL calls:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import {
|
|
166
|
+
CART_ATTRIBUTES_UPDATE_MUTATION,
|
|
167
|
+
getCartAttributesPayload,
|
|
168
|
+
} from "@elevateab/sdk";
|
|
169
|
+
|
|
170
|
+
const attributes = getCartAttributesPayload();
|
|
171
|
+
|
|
172
|
+
// Use in your GraphQL mutation
|
|
173
|
+
const result = await storefrontClient.mutate({
|
|
174
|
+
mutation: CART_ATTRIBUTES_UPDATE_MUTATION,
|
|
175
|
+
variables: {
|
|
176
|
+
cartId: "gid://shopify/Cart/...",
|
|
177
|
+
attributes,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Usage Examples
|
|
183
|
+
|
|
55
184
|
### Using the Hook
|
|
56
185
|
|
|
57
186
|
```typescript
|
|
58
|
-
import {
|
|
187
|
+
import { useExperiment } from "@elevateab/sdk";
|
|
59
188
|
|
|
60
|
-
function
|
|
61
|
-
const { variant, isLoading } = useExperiment("
|
|
189
|
+
function PriceTest() {
|
|
190
|
+
const { variant, isLoading } = useExperiment("test-price-1");
|
|
62
191
|
|
|
63
192
|
if (isLoading) return <div>Loading...</div>;
|
|
64
193
|
|
|
194
|
+
// Use convenience flags
|
|
195
|
+
if (variant?.isA) return <Price amount={99.99} label="Original" />;
|
|
196
|
+
if (variant?.isB) return <Price amount={89.99} label="Sale Price" />;
|
|
197
|
+
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Conditional Rendering
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
function ContentTest() {
|
|
206
|
+
const { variant } = useExperiment("test-headline-1");
|
|
207
|
+
|
|
65
208
|
return (
|
|
66
209
|
<div>
|
|
67
|
-
{variant?.
|
|
68
|
-
<
|
|
210
|
+
{variant?.isControl ? (
|
|
211
|
+
<h1>Buy Now and Save!</h1>
|
|
69
212
|
) : (
|
|
70
|
-
<
|
|
213
|
+
<h1>Limited Time Offer - 20% Off</h1>
|
|
71
214
|
)}
|
|
72
215
|
</div>
|
|
73
216
|
);
|
|
74
217
|
}
|
|
75
218
|
```
|
|
76
219
|
|
|
220
|
+
### Multiple Variations
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
function MultiVariantTest() {
|
|
224
|
+
const { variant } = useExperiment("test-layout-1");
|
|
225
|
+
|
|
226
|
+
if (variant?.isA) return <LayoutA />;
|
|
227
|
+
if (variant?.isB) return <LayoutB />;
|
|
228
|
+
if (variant?.isC) return <LayoutC />;
|
|
229
|
+
if (variant?.isD) return <LayoutD />;
|
|
230
|
+
|
|
231
|
+
return <LayoutDefault />;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Advanced Usage
|
|
236
|
+
|
|
237
|
+
### Cookie & Storage Management
|
|
238
|
+
|
|
239
|
+
The SDK uses cookies and session storage for state management:
|
|
240
|
+
|
|
241
|
+
**Cookies (1 year expiration):**
|
|
242
|
+
|
|
243
|
+
- `eabUserId` - Persistent visitor ID
|
|
244
|
+
- `ABTL` - Test assignments (which variant each test is assigned to)
|
|
245
|
+
- `ABAU` - Unique test views (which tests have been viewed)
|
|
246
|
+
- `_shopify_y` - Shopify client ID (read-only)
|
|
247
|
+
|
|
248
|
+
**Session Storage:**
|
|
249
|
+
|
|
250
|
+
- `eabSessionId` - Session identifier
|
|
251
|
+
- `ABAV` - Session test views
|
|
252
|
+
- `eabReferrer` - Entry referrer
|
|
253
|
+
- `eabEntry` - Entry page URL
|
|
254
|
+
|
|
77
255
|
### Utility Functions
|
|
78
256
|
|
|
79
257
|
```typescript
|
|
80
258
|
import {
|
|
81
259
|
assignVariant,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
260
|
+
getTestList,
|
|
261
|
+
getVisitorId,
|
|
262
|
+
getSessionId,
|
|
263
|
+
parseAddViewData,
|
|
85
264
|
} from "@elevateab/sdk";
|
|
86
265
|
|
|
87
|
-
//
|
|
88
|
-
const
|
|
266
|
+
// Get current test assignments
|
|
267
|
+
const assignments = getTestList(); // { "test-1": "variant-a", "test-2": "control" }
|
|
89
268
|
|
|
90
|
-
//
|
|
91
|
-
const
|
|
269
|
+
// Get visitor ID
|
|
270
|
+
const visitorId = getVisitorId(); // "uuid-v4"
|
|
92
271
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
272
|
+
// Get session ID
|
|
273
|
+
const sessionId = getSessionId(); // "uuid-v4"
|
|
274
|
+
|
|
275
|
+
// Parse analytics data
|
|
276
|
+
const analyticsData = parseAddViewData({
|
|
277
|
+
referrer: document.referrer,
|
|
278
|
+
entryPage: window.location.href,
|
|
279
|
+
userAgent: navigator.userAgent,
|
|
280
|
+
});
|
|
281
|
+
// Returns: UTM params, device type, browser info, referrer source, etc.
|
|
282
|
+
```
|
|
95
283
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
284
|
+
### Device & Traffic Detection
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import {
|
|
288
|
+
getDeviceType,
|
|
289
|
+
getTrafficSource,
|
|
290
|
+
checkFacebookBrowser,
|
|
291
|
+
checkInstagramBrowser,
|
|
292
|
+
} from "@elevateab/sdk";
|
|
293
|
+
|
|
294
|
+
const device = getDeviceType(); // "desktop" | "tablet" | "mobile"
|
|
295
|
+
const source = getTrafficSource(); // "facebook" | "google" | "direct" | etc.
|
|
296
|
+
const isFB = checkFacebookBrowser(); // boolean
|
|
297
|
+
const isIG = checkInstagramBrowser(); // boolean
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Custom Event Tracking
|
|
301
|
+
|
|
302
|
+
For advanced use cases, access the analytics utilities directly:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
import {
|
|
306
|
+
extractProductId,
|
|
307
|
+
extractProductVariantId,
|
|
308
|
+
cleanCartToken,
|
|
309
|
+
sanitizeString,
|
|
310
|
+
roundToTwo,
|
|
311
|
+
} from "@elevateab/sdk";
|
|
312
|
+
|
|
313
|
+
// Extract IDs from Shopify GIDs
|
|
314
|
+
const productId = extractProductId("gid://shopify/Product/123456");
|
|
315
|
+
// Returns: "123456"
|
|
316
|
+
|
|
317
|
+
const variantId = extractProductVariantId(
|
|
318
|
+
"gid://shopify/ProductVariant/789012"
|
|
102
319
|
);
|
|
320
|
+
// Returns: "789012"
|
|
321
|
+
|
|
322
|
+
// Clean and sanitize data
|
|
323
|
+
const cleanToken = cleanCartToken("token?param=value");
|
|
324
|
+
const safeString = sanitizeString(userInput);
|
|
325
|
+
const price = roundToTwo(99.999); // 100.00
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Test Types Support
|
|
329
|
+
|
|
330
|
+
The SDK provides infrastructure for multiple test types:
|
|
331
|
+
|
|
332
|
+
- **Split URL Tests** - Redirect tests between different URLs
|
|
333
|
+
- **Price Tests** - A/B test different pricing strategies
|
|
334
|
+
- **Content Tests** - Test different copy, headlines, CTAs
|
|
335
|
+
- **Custom Code Tests** - Run custom JavaScript for advanced tests
|
|
336
|
+
|
|
337
|
+
### Example: Split URL Test
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
function SplitURLTest() {
|
|
341
|
+
const { variant } = useExperiment("test-split-url-1");
|
|
342
|
+
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
if (
|
|
345
|
+
variant?.handle &&
|
|
346
|
+
window.location.pathname !== `/products/${variant.handle}`
|
|
347
|
+
) {
|
|
348
|
+
window.location.href = `/products/${variant.handle}`;
|
|
349
|
+
}
|
|
350
|
+
}, [variant]);
|
|
351
|
+
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
103
354
|
```
|
|
104
355
|
|
|
356
|
+
### Example: Price Test
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
function PriceTest({ originalPrice }: { originalPrice: number }) {
|
|
360
|
+
const { variant } = useExperiment("test-price-1");
|
|
361
|
+
|
|
362
|
+
const displayPrice = variant?.price
|
|
363
|
+
? parseFloat(variant.price)
|
|
364
|
+
: originalPrice;
|
|
365
|
+
|
|
366
|
+
return <Price amount={displayPrice} />;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## API Reference
|
|
371
|
+
|
|
372
|
+
### Components
|
|
373
|
+
|
|
374
|
+
#### `<ElevateProvider>`
|
|
375
|
+
|
|
376
|
+
The main component that handles everything - config fetching, analytics tracking, and event sending.
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
interface ElevateProviderProps {
|
|
380
|
+
storeId: string; // Your Shopify store ID (required)
|
|
381
|
+
cart?: any; // Cart object from Shopify (Hydrogen only)
|
|
382
|
+
shop?: any; // Shop object from Shopify (Hydrogen only)
|
|
383
|
+
consent?: any; // Consent object from Shopify (Hydrogen only)
|
|
384
|
+
hasLocalizedPaths?: boolean; // Set true if homepage is at /en-us/, /fr-ca/, etc.
|
|
385
|
+
workerUrl?: string; // Custom CloudFlare Worker endpoint
|
|
386
|
+
ordersWorkerUrl?: string; // Custom orders endpoint for checkout_completed
|
|
387
|
+
children: React.ReactNode;
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Basic Usage:**
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
<ElevateProvider
|
|
395
|
+
storeId="mystore.myshopify.com"
|
|
396
|
+
cart={data.cart}
|
|
397
|
+
shop={data.shop}
|
|
398
|
+
consent={data.consent}
|
|
399
|
+
>
|
|
400
|
+
<Outlet />
|
|
401
|
+
</ElevateProvider>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**With Localized Paths:**
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
<ElevateProvider
|
|
408
|
+
storeId="mystore.myshopify.com"
|
|
409
|
+
cart={data.cart}
|
|
410
|
+
shop={data.shop}
|
|
411
|
+
consent={data.consent}
|
|
412
|
+
hasLocalizedPaths={true} // Only if homepage is at /en-us/, /fr-ca/, etc.
|
|
413
|
+
>
|
|
414
|
+
<Outlet />
|
|
415
|
+
</ElevateProvider>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Hooks
|
|
419
|
+
|
|
420
|
+
#### `useExperiment(testId: string)`
|
|
421
|
+
|
|
422
|
+
Get assigned variant for a test.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
const { variant, isLoading } = useExperiment("test-id");
|
|
426
|
+
|
|
427
|
+
// variant properties:
|
|
428
|
+
// - id: string - Variant ID
|
|
429
|
+
// - name: string - Variant name
|
|
430
|
+
// - weight: number - Traffic percentage
|
|
431
|
+
// - isControl: boolean - Is this the control variant
|
|
432
|
+
// - isA, isB, isC, isD: boolean - Position flags
|
|
433
|
+
// - handle?: string - Product handle (for product tests)
|
|
434
|
+
// - price?: string - Price override (for price tests)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `useElevateConfig()`
|
|
438
|
+
|
|
439
|
+
Access the full configuration context.
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const { config } = useElevateConfig();
|
|
443
|
+
// config.tests: Test[] - All active tests
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Functions
|
|
447
|
+
|
|
448
|
+
#### Cart Attributes
|
|
449
|
+
|
|
450
|
+
- `updateCartAttributes(cartId, options)` - Update cart with test data
|
|
451
|
+
- `cleanupCartAttributes(cartId, options)` - Remove test data from cart
|
|
452
|
+
- `getCartAttributesPayload()` - Get current test data as attributes array
|
|
453
|
+
|
|
454
|
+
#### Storage
|
|
455
|
+
|
|
456
|
+
- `getVisitorId()` - Get or create visitor ID
|
|
457
|
+
- `getSessionId()` - Get or create session ID
|
|
458
|
+
- `getTestList()` - Get current test assignments
|
|
459
|
+
- `setCookie(name, value)` - Set a cookie (1 year expiration)
|
|
460
|
+
- `getCookie(name)` - Get a cookie value
|
|
461
|
+
|
|
462
|
+
#### Analytics
|
|
463
|
+
|
|
464
|
+
- `parseAddViewData(params)` - Parse UTM params, device info, referrer
|
|
465
|
+
- `getPageTypeFromPathname(path)` - Detect page type
|
|
466
|
+
- `extractProductId(gid)` - Extract product ID from Shopify GID
|
|
467
|
+
- `extractProductVariantId(gid)` - Extract variant ID from Shopify GID
|
|
468
|
+
|
|
105
469
|
## Features
|
|
106
470
|
|
|
107
|
-
- ✅
|
|
108
|
-
- ✅ React Server Components
|
|
109
|
-
- ✅
|
|
110
|
-
- ✅
|
|
111
|
-
- ✅
|
|
112
|
-
- ✅
|
|
113
|
-
- ✅
|
|
471
|
+
- ✅ **TypeScript** - Full type safety with zero config
|
|
472
|
+
- ✅ **React Server Components** - Compatible with RSC
|
|
473
|
+
- ✅ **Analytics Tracking** - Automatic event tracking via @shopify/hydrogen
|
|
474
|
+
- ✅ **Cart Tagging** - Attribute orders to A/B tests
|
|
475
|
+
- ✅ **Device Detection** - Mobile, tablet, desktop detection
|
|
476
|
+
- ✅ **Traffic Source Detection** - Facebook, Instagram, Google, etc.
|
|
477
|
+
- ✅ **UTM Parameter Tracking** - Campaign attribution
|
|
478
|
+
- ✅ **ESM & CJS** - Dual format for maximum compatibility
|
|
479
|
+
- ✅ **Tree-shakeable** - Import only what you need
|
|
480
|
+
- ✅ **Minified** - Optimized bundle size
|
|
114
481
|
|
|
115
482
|
## Compatibility
|
|
116
483
|
|
|
117
|
-
- **Shopify Hydrogen**: ✅ Full support
|
|
118
|
-
- **
|
|
484
|
+
- **Shopify Hydrogen**: ✅ Full support (2023.10.0+) - Automatic analytics
|
|
485
|
+
- **Next.js**: ✅ Full support (13+, 14+, 15+) - Manual tracking ([See guide](./NEXTJS_EXAMPLE.md))
|
|
486
|
+
- **Remix**: ✅ Full support - Manual tracking
|
|
119
487
|
- **React**: 18.0.0+
|
|
120
488
|
- **TypeScript**: 5.0.0+
|
|
121
489
|
- **Node.js**: 18.0.0+
|
|
122
490
|
|
|
123
|
-
##
|
|
491
|
+
## Quick Start Guides
|
|
124
492
|
|
|
125
|
-
|
|
493
|
+
- **Hydrogen**: See below for automatic analytics setup
|
|
494
|
+
- **Next.js**: See [Next.js Example](./NEXTJS_EXAMPLE.md) for manual tracking
|
|
126
495
|
|
|
127
|
-
|
|
128
|
-
- `Variant`: Definition of a test variant
|
|
129
|
-
- `TrackingEvent`: Event tracking data structure
|
|
130
|
-
- `ExperimentStatus`: Experiment lifecycle status
|
|
496
|
+
## 🧪 Sandbox Stores for Testing
|
|
131
497
|
|
|
132
|
-
|
|
498
|
+
We've set up complete sandbox Shopify stores to help you test the Elevate SDK:
|
|
499
|
+
|
|
500
|
+
### Hydrogen Sandbox Store
|
|
501
|
+
|
|
502
|
+
**Location:** `hydrogen-shopify-sandbox/`
|
|
503
|
+
|
|
504
|
+
A fully functional Shopify Hydrogen storefront with Elevate SDK integration.
|
|
505
|
+
|
|
506
|
+
**Quick Start:**
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
cd hydrogen-shopify-sandbox
|
|
510
|
+
npm install
|
|
511
|
+
npm run dev
|
|
512
|
+
```
|
|
133
513
|
|
|
134
|
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
514
|
+
**Features:**
|
|
515
|
+
- ✅ Complete ElevateProvider integration
|
|
516
|
+
- ✅ Automatic analytics tracking
|
|
517
|
+
- ✅ Cart attribute tagging
|
|
518
|
+
- ✅ Multiple A/B test examples
|
|
519
|
+
- ✅ Real Shopify products and cart
|
|
139
520
|
|
|
140
|
-
|
|
521
|
+
[View Hydrogen Sandbox Documentation →](./hydrogen-shopify-sandbox/README.md)
|
|
141
522
|
|
|
142
|
-
|
|
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
|
|
523
|
+
[View Complete Sandbox Setup Guide →](./SANDBOX_SETUP.md)
|
|
147
524
|
|
|
148
525
|
## License
|
|
149
526
|
|
|
@@ -151,4 +528,4 @@ MIT
|
|
|
151
528
|
|
|
152
529
|
## Support
|
|
153
530
|
|
|
154
|
-
For issues and questions, please visit [GitHub Issues](https://github.com/
|
|
531
|
+
For issues and questions, please visit [GitHub Issues](https://github.com/abshop/headless-npm/issues).
|
|
@@ -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 };
|