@probat/react 0.2.1 → 0.3.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 +33 -344
- package/dist/index.d.mts +76 -247
- package/dist/index.d.ts +76 -247
- package/dist/index.js +395 -1357
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +392 -1341
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -11
- package/src/__tests__/Experiment.test.tsx +764 -0
- package/src/__tests__/setup.ts +63 -0
- package/src/__tests__/utils.test.ts +79 -0
- package/src/components/Experiment.tsx +291 -0
- package/src/components/ProbatProviderClient.tsx +19 -7
- package/src/context/ProbatContext.tsx +30 -152
- package/src/hooks/useProbatMetrics.ts +18 -134
- package/src/index.ts +9 -32
- package/src/utils/api.ts +96 -577
- package/src/utils/dedupeStorage.ts +40 -0
- package/src/utils/eventContext.ts +94 -0
- package/src/utils/stableInstanceId.ts +113 -0
- package/src/utils/storage.ts +18 -60
- package/src/hoc/itrt-frontend.code-workspace +0 -10
- package/src/hoc/withExperiment.tsx +0 -311
- package/src/hooks/useExperiment.ts +0 -188
- package/src/utils/documentClickTracker.ts +0 -215
- package/src/utils/heatmapTracker.ts +0 -665
package/README.md
CHANGED
|
@@ -1,379 +1,68 @@
|
|
|
1
1
|
# @probat/react
|
|
2
2
|
|
|
3
|
-
React
|
|
3
|
+
React SDK for Probat A/B testing. Automatic impression and click tracking with zero boilerplate.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install @probat/react
|
|
9
|
-
# or
|
|
10
|
-
yarn add @probat/react
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @probat/react
|
|
13
9
|
```
|
|
14
10
|
|
|
15
11
|
## Quick Start
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Wrap your app root with the provider (Next.js App Router example):
|
|
18
14
|
|
|
19
15
|
```tsx
|
|
20
|
-
|
|
16
|
+
// app/providers.tsx
|
|
17
|
+
"use client";
|
|
18
|
+
import { ProbatProviderClient } from "@probat/react";
|
|
21
19
|
|
|
22
|
-
function
|
|
20
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
23
21
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
</
|
|
22
|
+
<ProbatProviderClient userId="your-user-uuid">
|
|
23
|
+
{children}
|
|
24
|
+
</ProbatProviderClient>
|
|
27
25
|
);
|
|
28
26
|
}
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#### Option A: Using the `useExperiment` hook
|
|
29
|
+
Create a thin experiment wrapper — one per experiment:
|
|
34
30
|
|
|
35
31
|
```tsx
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (isLoading) return <div>Loading...</div>;
|
|
32
|
+
// components/CTAButton/CTAButton.experiment.tsx
|
|
33
|
+
"use client";
|
|
34
|
+
import { Experiment } from "@probat/react";
|
|
35
|
+
import { CTAButton } from "./CTAButton";
|
|
36
|
+
import { CTAButtonAI } from "./CTAButton.ai-v1";
|
|
42
37
|
|
|
38
|
+
export function CTAButtonExperiment() {
|
|
43
39
|
return (
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)}
|
|
50
|
-
</div>
|
|
40
|
+
<Experiment
|
|
41
|
+
id="cta-copy-test"
|
|
42
|
+
control={<CTAButton />}
|
|
43
|
+
variants={{ ai_v1: <CTAButtonAI /> }}
|
|
44
|
+
/>
|
|
51
45
|
);
|
|
52
46
|
}
|
|
53
47
|
```
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
import { withExperiment } from '@probat/react';
|
|
59
|
-
|
|
60
|
-
const ControlComponent = () => <div>Control</div>;
|
|
61
|
-
const VariantA = () => <div>Variant A</div>;
|
|
62
|
-
const VariantB = () => <div>Variant B</div>;
|
|
63
|
-
|
|
64
|
-
const ExperimentedComponent = withExperiment(ControlComponent, {
|
|
65
|
-
proposalId: 'proposal-id',
|
|
66
|
-
registry: {
|
|
67
|
-
'variant-a': VariantA,
|
|
68
|
-
'variant-b': VariantB,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
```
|
|
49
|
+
That's it. Impressions (50% visible for 250ms) and clicks are tracked automatically.
|
|
72
50
|
|
|
73
|
-
|
|
51
|
+
## Custom Metrics
|
|
74
52
|
|
|
75
53
|
```tsx
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function Button() {
|
|
79
|
-
const { trackClick, trackMetric } = useProbatMetrics();
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<button
|
|
83
|
-
onClick={(e) => {
|
|
84
|
-
trackClick(e, {
|
|
85
|
-
proposalId: 'proposal-id',
|
|
86
|
-
variantLabel: 'control',
|
|
87
|
-
});
|
|
88
|
-
}}
|
|
89
|
-
>
|
|
90
|
-
Click me
|
|
91
|
-
</button>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
54
|
+
const { capture } = useProbatMetrics();
|
|
55
|
+
capture("purchase", { revenue: 42 });
|
|
94
56
|
```
|
|
95
57
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
Heatmap tracking is automatically enabled when you use `ProbatProvider`. It tracks:
|
|
99
|
-
- **Click events**: User clicks across your website
|
|
100
|
-
- **Cursor movements**: Mouse movement patterns for engagement analysis
|
|
101
|
-
|
|
102
|
-
The tracking automatically associates data with the active experiment (`proposalId`) and variant (`variantLabel`) based on the experiment context. No additional configuration needed!
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
// Heatmap tracking is automatically initialized by ProbatProvider
|
|
106
|
-
<ProbatProvider clientKey="your-key" environment="dev">
|
|
107
|
-
<YourApp />
|
|
108
|
-
</ProbatProvider>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
To manually control heatmap tracking:
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
import { initHeatmapTracking, stopHeatmapTracking } from '@probat/react';
|
|
115
|
-
|
|
116
|
-
// Initialize with custom config
|
|
117
|
-
initHeatmapTracking({
|
|
118
|
-
apiBaseUrl: 'https://api.probat.com',
|
|
119
|
-
batchSize: 10,
|
|
120
|
-
batchInterval: 5000,
|
|
121
|
-
trackCursor: true, // Enable cursor movement tracking
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// Stop tracking when needed
|
|
125
|
-
stopHeatmapTracking();
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## API Reference
|
|
129
|
-
|
|
130
|
-
### `<ProbatProvider>`
|
|
131
|
-
|
|
132
|
-
Wraps your app and provides Probat configuration to all child components.
|
|
133
|
-
|
|
134
|
-
#### Props
|
|
135
|
-
|
|
136
|
-
- `apiBaseUrl?: string` - The base URL for the Probat API. If not provided, will try to read from:
|
|
137
|
-
- `VITE_PROBAT_API` (Vite)
|
|
138
|
-
- `NEXT_PUBLIC_PROBAT_API` (Next.js)
|
|
139
|
-
- `window.__PROBAT_API`
|
|
140
|
-
- Default: `"https://gushi.onrender.com"`
|
|
141
|
-
- `clientKey?: string` - Client key for identification (optional)
|
|
142
|
-
- `environment?: "dev" | "prod"` - Explicitly set environment. If not provided, will auto-detect based on hostname.
|
|
143
|
-
- `proposalId?: string` - Optional proposal/experiment ID for heatmap tracking segregation
|
|
144
|
-
- `variantLabel?: string` - Optional variant label for heatmap tracking segregation
|
|
145
|
-
- `children: React.ReactNode` - Your app components
|
|
146
|
-
|
|
147
|
-
**Note**: `proposalId` and `variantLabel` are automatically set from localStorage when experiments are active. You typically don't need to pass these manually.
|
|
148
|
-
|
|
149
|
-
#### Example
|
|
150
|
-
|
|
151
|
-
```tsx
|
|
152
|
-
<ProbatProvider
|
|
153
|
-
apiBaseUrl="https://api.probat.com"
|
|
154
|
-
clientKey="your-key"
|
|
155
|
-
environment="prod"
|
|
156
|
-
>
|
|
157
|
-
<App />
|
|
158
|
-
</ProbatProvider>
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### `useExperiment(proposalId, options?)`
|
|
162
|
-
|
|
163
|
-
Hook for fetching and applying experiment variants.
|
|
164
|
-
|
|
165
|
-
#### Parameters
|
|
166
|
-
|
|
167
|
-
- `proposalId: string` - The proposal ID for the experiment
|
|
168
|
-
- `options?: { autoTrackImpression?: boolean }` - Optional configuration
|
|
169
|
-
- `autoTrackImpression` - Automatically track impression when variant is loaded (default: `true`)
|
|
170
|
-
|
|
171
|
-
#### Returns
|
|
172
|
-
|
|
173
|
-
- `variantLabel: string` - The current variant label (e.g., "control", "variant-a")
|
|
174
|
-
- `experimentId: string | null` - The experiment ID
|
|
175
|
-
- `isLoading: boolean` - Whether the experiment decision is still loading
|
|
176
|
-
- `error: Error | null` - Any error that occurred while fetching the experiment
|
|
177
|
-
- `trackClick: (event?: React.MouseEvent | null) => void` - Manually track a click for this experiment
|
|
178
|
-
|
|
179
|
-
#### Example
|
|
180
|
-
|
|
181
|
-
```tsx
|
|
182
|
-
const { variantLabel, isLoading, trackClick } = useExperiment('proposal-id', {
|
|
183
|
-
autoTrackImpression: true,
|
|
184
|
-
});
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### `useProbatMetrics()`
|
|
188
|
-
|
|
189
|
-
Hook for tracking Probat metrics (clicks, impressions, custom metrics).
|
|
190
|
-
|
|
191
|
-
#### Returns
|
|
192
|
-
|
|
193
|
-
- `trackClick(event?, options?)` - Track a click event
|
|
194
|
-
- `event?: React.MouseEvent` - Optional React mouse event (will extract metadata automatically)
|
|
195
|
-
- `options?: { force?: boolean; proposalId?: string; variantLabel?: string; dimensions?: Record<string, any> }`
|
|
196
|
-
- `trackMetric(metricName, proposalId, variantLabel?, dimensions?)` - Track a custom metric
|
|
197
|
-
- `trackImpression(proposalId, variantLabel?, experimentId?)` - Track an impression/view
|
|
198
|
-
|
|
199
|
-
#### Example
|
|
200
|
-
|
|
201
|
-
```tsx
|
|
202
|
-
const { trackClick, trackMetric, trackImpression } = useProbatMetrics();
|
|
203
|
-
|
|
204
|
-
// Track click
|
|
205
|
-
<button onClick={(e) => trackClick(e, { proposalId: 'id', variantLabel: 'control' })}>
|
|
206
|
-
Click
|
|
207
|
-
</button>
|
|
208
|
-
|
|
209
|
-
// Track custom metric
|
|
210
|
-
trackMetric('purchase', 'proposal-id', 'variant-a', { amount: 100 });
|
|
211
|
-
|
|
212
|
-
// Track impression
|
|
213
|
-
trackImpression('proposal-id', 'control', 'experiment-id');
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Heatmap Tracking Functions
|
|
217
|
-
|
|
218
|
-
#### `initHeatmapTracking(config)`
|
|
58
|
+
## Run the Example
|
|
219
59
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
- `config: HeatmapConfig` - Configuration object:
|
|
225
|
-
- `apiBaseUrl: string` - Base URL for the heatmap API (default: from ProbatProvider)
|
|
226
|
-
- `batchSize?: number` - Number of clicks to batch before sending (default: `10`)
|
|
227
|
-
- `batchInterval?: number` - Time in ms to wait before sending batch (default: `5000`)
|
|
228
|
-
- `trackCursor?: boolean` - Enable cursor movement tracking (default: `true`)
|
|
229
|
-
- `cursorThrottle?: number` - Throttle cursor events in ms (default: `100`)
|
|
230
|
-
- `cursorBatchSize?: number` - Number of cursor movements to batch (default: `50`)
|
|
231
|
-
- `enabled?: boolean` - Enable/disable tracking (default: `true`)
|
|
232
|
-
- `excludeSelectors?: string[]` - CSS selectors to exclude from tracking
|
|
233
|
-
- `excludedOrigins?: string[]` - Origins to exclude from tracking
|
|
234
|
-
|
|
235
|
-
##### Example
|
|
236
|
-
|
|
237
|
-
```tsx
|
|
238
|
-
import { initHeatmapTracking } from '@probat/react';
|
|
239
|
-
|
|
240
|
-
initHeatmapTracking({
|
|
241
|
-
apiBaseUrl: 'https://api.probat.com',
|
|
242
|
-
batchSize: 20,
|
|
243
|
-
trackCursor: true,
|
|
244
|
-
cursorThrottle: 100,
|
|
245
|
-
});
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
#### `stopHeatmapTracking()`
|
|
249
|
-
|
|
250
|
-
Stop heatmap tracking and send any pending batches.
|
|
251
|
-
|
|
252
|
-
##### Example
|
|
253
|
-
|
|
254
|
-
```tsx
|
|
255
|
-
import { stopHeatmapTracking } from '@probat/react';
|
|
256
|
-
|
|
257
|
-
stopHeatmapTracking();
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
#### `getHeatmapTracker()`
|
|
261
|
-
|
|
262
|
-
Get the current heatmap tracker instance (for advanced usage).
|
|
263
|
-
|
|
264
|
-
##### Returns
|
|
265
|
-
|
|
266
|
-
- `HeatmapTracker | null` - The tracker instance or null if not initialized
|
|
267
|
-
|
|
268
|
-
### `withExperiment(Control, options)`
|
|
269
|
-
|
|
270
|
-
Higher-Order Component for wrapping components with experiment variants (backward compatible with old injection pattern).
|
|
271
|
-
|
|
272
|
-
#### Parameters
|
|
273
|
-
|
|
274
|
-
- `Control: React.ComponentType` - The control/default component
|
|
275
|
-
- `options: { proposalId: string; registry: Record<string, React.ComponentType> }`
|
|
276
|
-
- `proposalId` - The proposal ID
|
|
277
|
-
- `registry` - Map of variant labels to component types
|
|
278
|
-
|
|
279
|
-
#### Returns
|
|
280
|
-
|
|
281
|
-
A wrapped component that automatically selects and renders the correct variant.
|
|
282
|
-
|
|
283
|
-
#### Example
|
|
284
|
-
|
|
285
|
-
```tsx
|
|
286
|
-
const ExperimentedComponent = withExperiment(ControlComponent, {
|
|
287
|
-
proposalId: 'proposal-id',
|
|
288
|
-
registry: {
|
|
289
|
-
'variant-a': VariantA,
|
|
290
|
-
'variant-b': VariantB,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## Environment Detection
|
|
296
|
-
|
|
297
|
-
The library automatically detects whether you're running in development or production based on the hostname:
|
|
298
|
-
|
|
299
|
-
- **Development**: `localhost`, `127.0.0.1`, or local IP addresses (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
|
|
300
|
-
- **Production**: Everything else
|
|
301
|
-
|
|
302
|
-
You can override this by passing the `environment` prop to `ProbatProvider`.
|
|
303
|
-
|
|
304
|
-
## Configuration
|
|
305
|
-
|
|
306
|
-
### Environment Variables
|
|
307
|
-
|
|
308
|
-
You can configure the API base URL using environment variables:
|
|
309
|
-
|
|
310
|
-
- **Vite**: `VITE_PROBAT_API=https://api.probat.com`
|
|
311
|
-
- **Next.js**: `NEXT_PUBLIC_PROBAT_API=https://api.probat.com`
|
|
312
|
-
- **Browser**: `window.__PROBAT_API = 'https://api.probat.com'`
|
|
313
|
-
|
|
314
|
-
### TypeScript
|
|
315
|
-
|
|
316
|
-
The package includes TypeScript definitions. No additional `@types` package needed.
|
|
317
|
-
|
|
318
|
-
## Testing Locally
|
|
319
|
-
|
|
320
|
-
1. Build the package:
|
|
321
|
-
```bash
|
|
322
|
-
cd packages/probat-react
|
|
323
|
-
npm run build
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
2. Link it locally (optional, for testing in other projects):
|
|
327
|
-
```bash
|
|
328
|
-
npm link
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
3. In your test project:
|
|
332
|
-
```bash
|
|
333
|
-
npm link @probat/react
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Migration from Injected Files
|
|
337
|
-
|
|
338
|
-
If you're currently using the injected file pattern, you can migrate gradually:
|
|
339
|
-
|
|
340
|
-
1. Install the package: `npm install @probat/react`
|
|
341
|
-
2. Wrap your app with `<ProbatProvider>`
|
|
342
|
-
3. Replace `withExperiment` imports:
|
|
343
|
-
```tsx
|
|
344
|
-
// Old
|
|
345
|
-
import { withExperiment } from '../../probat/runtime';
|
|
346
|
-
|
|
347
|
-
// New
|
|
348
|
-
import { withExperiment } from '@probat/react';
|
|
349
|
-
```
|
|
350
|
-
4. Remove the `probat/` directory from your repo
|
|
351
|
-
5. Update your component exports to use the new import
|
|
352
|
-
|
|
353
|
-
## Next.js Support
|
|
354
|
-
|
|
355
|
-
The package is fully compatible with Next.js 13+ App Router and Pages Router. See [NEXTJS_GUIDE.md](./NEXTJS_GUIDE.md) for detailed Next.js integration instructions.
|
|
356
|
-
|
|
357
|
-
### Quick Next.js Example
|
|
358
|
-
|
|
359
|
-
```tsx
|
|
360
|
-
// app/layout.tsx (App Router)
|
|
361
|
-
import { ProbatProvider } from '@probat/react';
|
|
362
|
-
|
|
363
|
-
export default function RootLayout({ children }) {
|
|
364
|
-
return (
|
|
365
|
-
<html>
|
|
366
|
-
<body>
|
|
367
|
-
<ProbatProvider clientKey="..." environment="dev">
|
|
368
|
-
{children}
|
|
369
|
-
</ProbatProvider>
|
|
370
|
-
</body>
|
|
371
|
-
</html>
|
|
372
|
-
);
|
|
373
|
-
}
|
|
60
|
+
```bash
|
|
61
|
+
cd packages/probat-react/example
|
|
62
|
+
node mocks/server.ts & # mock API on :3001
|
|
63
|
+
npx next dev # app on :3000
|
|
374
64
|
```
|
|
375
65
|
|
|
376
|
-
##
|
|
377
|
-
|
|
378
|
-
MIT
|
|
66
|
+
## Docs
|
|
379
67
|
|
|
68
|
+
See [SPEC.md](./SPEC.md) for the full event schemas, assignment rules, dedupe logic, and edge cases.
|