mixpanel-browser 2.77.0 → 2.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +6 -9
- package/.eslintrc.json +12 -0
- package/.github/workflows/openfeature-provider-tests.yml +31 -0
- package/CHANGELOG.md +11 -0
- package/build.sh +2 -2
- package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js → mixpanel-recorder-D5HJyV2E.min.js} +2 -2
- package/dist/async-modules/mixpanel-recorder-D5HJyV2E.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-recorder-DLKbUIEE.js → mixpanel-recorder-P6SEnnPV.js} +57 -33
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-1L9FyetZ.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-targeting-CmVvUyFM.js → mixpanel-targeting-BBMVbgJF.js} +24 -13
- package/dist/mixpanel-core.cjs.d.ts +46 -1
- package/dist/mixpanel-core.cjs.js +671 -272
- package/dist/mixpanel-recorder.js +57 -33
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +24 -13
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +46 -1
- package/dist/mixpanel-with-async-modules.cjs.js +673 -274
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +46 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +673 -274
- package/dist/mixpanel-with-recorder.d.ts +46 -1
- package/dist/mixpanel-with-recorder.js +596 -197
- package/dist/mixpanel-with-recorder.min.d.ts +46 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +46 -1
- package/dist/mixpanel.amd.js +596 -197
- package/dist/mixpanel.cjs.d.ts +46 -1
- package/dist/mixpanel.cjs.js +596 -197
- package/dist/mixpanel.globals.js +673 -274
- package/dist/mixpanel.min.js +200 -189
- package/dist/mixpanel.module.d.ts +46 -1
- package/dist/mixpanel.module.js +596 -197
- package/dist/mixpanel.umd.d.ts +46 -1
- package/dist/mixpanel.umd.js +596 -197
- package/package.json +1 -1
- package/packages/openfeature-web-provider/README.md +357 -0
- package/packages/openfeature-web-provider/package-lock.json +1636 -0
- package/packages/openfeature-web-provider/package.json +51 -0
- package/packages/openfeature-web-provider/rollup.config.browser.mjs +26 -0
- package/packages/openfeature-web-provider/src/MixpanelProvider.ts +302 -0
- package/packages/openfeature-web-provider/src/index.ts +1 -0
- package/packages/openfeature-web-provider/src/types.ts +72 -0
- package/packages/openfeature-web-provider/test/MixpanelProvider.spec.ts +484 -0
- package/packages/openfeature-web-provider/tsconfig.json +15 -0
- package/src/autocapture/index.js +7 -2
- package/src/config.js +1 -1
- package/src/flags/CLAUDE.md +24 -0
- package/src/flags/flags-persistence.js +176 -0
- package/src/flags/index.js +278 -98
- package/src/index.d.ts +46 -1
- package/src/mixpanel-core.js +27 -8
- package/src/recorder/idb-config.js +16 -0
- package/src/recorder/recording-registry.js +7 -2
- package/src/recorder/session-recording.js +9 -4
- package/src/recorder-manager.js +7 -2
- package/src/request-queue.js +1 -2
- package/src/shared-lock.js +2 -3
- package/src/storage/indexed-db.js +16 -15
- package/src/storage/local-storage.js +5 -3
- package/src/utils.js +25 -12
- package/testServer.js +2 -0
- package/tsconfig.base.json +9 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +0 -1
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +0 -2
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# @mixpanel/openfeature-web-provider
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@mixpanel/openfeature-web-provider)
|
|
4
|
+
[](https://openfeature.dev/)
|
|
5
|
+
[](https://github.com/mixpanel/mixpanel-js/blob/master/LICENSE)
|
|
6
|
+
|
|
7
|
+
An [OpenFeature](https://openfeature.dev/) provider that wraps Mixpanel's feature flags for use with the OpenFeature Web SDK. This allows you to use Mixpanel's feature flagging capabilities through OpenFeature's standardized, vendor-agnostic API.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This package provides a bridge between Mixpanel's native feature flags implementation and the OpenFeature specification. By using this provider, you can:
|
|
12
|
+
|
|
13
|
+
- Leverage Mixpanel's powerful feature flag and experimentation platform
|
|
14
|
+
- Use OpenFeature's standardized API for flag evaluation
|
|
15
|
+
- Easily switch between feature flag providers without changing your application code
|
|
16
|
+
- Integrate with OpenFeature's ecosystem of tools and frameworks
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @mixpanel/openfeature-web-provider @openfeature/web-sdk mixpanel-browser
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or with yarn:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add @mixpanel/openfeature-web-provider @openfeature/web-sdk mixpanel-browser
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import mixpanel from 'mixpanel-browser';
|
|
34
|
+
import { OpenFeature } from '@openfeature/web-sdk';
|
|
35
|
+
import { MixpanelProvider } from '@mixpanel/openfeature-web-provider';
|
|
36
|
+
|
|
37
|
+
// 1. Initialize Mixpanel with feature flags and context
|
|
38
|
+
mixpanel.init('YOUR_PROJECT_TOKEN', {
|
|
39
|
+
flags: {
|
|
40
|
+
context: { plan: 'premium' }
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 2. Create and register the Mixpanel provider
|
|
45
|
+
const provider = new MixpanelProvider(mixpanel.flags);
|
|
46
|
+
await OpenFeature.setProviderAndWait(provider);
|
|
47
|
+
|
|
48
|
+
// 3. Get a client and evaluate flags
|
|
49
|
+
const client = OpenFeature.getClient();
|
|
50
|
+
const showNewFeature = client.getBooleanValue('new-feature-flag', false);
|
|
51
|
+
|
|
52
|
+
if (showNewFeature) {
|
|
53
|
+
console.log('New feature is enabled!');
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage Examples
|
|
58
|
+
|
|
59
|
+
### Basic Boolean Flag
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const client = OpenFeature.getClient();
|
|
63
|
+
|
|
64
|
+
// Get a boolean flag with a default value
|
|
65
|
+
const isFeatureEnabled = client.getBooleanValue('my-feature', false);
|
|
66
|
+
|
|
67
|
+
if (isFeatureEnabled) {
|
|
68
|
+
// Show the new feature
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Mixpanel Flag Types and OpenFeature Evaluation Methods
|
|
73
|
+
|
|
74
|
+
Mixpanel feature flags support three flag types. Use the corresponding OpenFeature evaluation method based on your flag's variant values:
|
|
75
|
+
|
|
76
|
+
| Mixpanel Flag Type | Variant Values | OpenFeature Method |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Feature Gate | `true` / `false` | `getBooleanValue()` |
|
|
79
|
+
| Experiment | boolean, string, number, or JSON object | `getBooleanValue()`, `getStringValue()`, `getNumberValue()`, or `getObjectValue()` |
|
|
80
|
+
| Dynamic Config | JSON object | `getObjectValue()` |
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const client = OpenFeature.getClient();
|
|
84
|
+
|
|
85
|
+
// Feature Gate — boolean variants
|
|
86
|
+
const isFeatureOn = client.getBooleanValue('new-checkout', false);
|
|
87
|
+
|
|
88
|
+
// Experiment with string variants
|
|
89
|
+
const buttonColor = client.getStringValue('button-color-test', 'blue');
|
|
90
|
+
|
|
91
|
+
// Experiment with number variants
|
|
92
|
+
const maxItems = client.getNumberValue('max-items', 10);
|
|
93
|
+
|
|
94
|
+
// Dynamic Config — JSON object variants
|
|
95
|
+
const featureConfig = client.getObjectValue('homepage-layout', {
|
|
96
|
+
layout: 'grid',
|
|
97
|
+
itemsPerRow: 3
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Getting Full Resolution Details
|
|
102
|
+
|
|
103
|
+
If you need additional metadata about the flag evaluation:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const client = OpenFeature.getClient();
|
|
107
|
+
|
|
108
|
+
const details = client.getBooleanDetails('my-feature', false);
|
|
109
|
+
|
|
110
|
+
console.log(details.value); // The resolved value
|
|
111
|
+
console.log(details.variant); // The variant key from Mixpanel
|
|
112
|
+
console.log(details.reason); // Why this value was returned
|
|
113
|
+
console.log(details.errorCode); // Error code if evaluation failed
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Setting Context
|
|
117
|
+
|
|
118
|
+
You can pass evaluation context that will be sent to Mixpanel for flag evaluation using `OpenFeature.setContext()`:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
await OpenFeature.setContext({
|
|
122
|
+
email: 'user@example.com',
|
|
123
|
+
plan: 'premium'
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **Note:** Per-evaluation context (the optional third argument to `getBooleanValue`, `getStringValue`, etc.) is **not supported** by this provider. Context must be set globally via `OpenFeature.setContext()`, which triggers a re-fetch of flag values from Mixpanel.
|
|
128
|
+
|
|
129
|
+
### Using custom_properties for Runtime Properties
|
|
130
|
+
|
|
131
|
+
You can pass `custom_properties` in the evaluation context for use with Mixpanel's [Runtime Properties](https://docs.mixpanel.com/docs/feature-flags/runtime-properties) targeting rules. Values must be flat key-value pairs (no nested objects):
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
await OpenFeature.setContext({
|
|
135
|
+
custom_properties: {
|
|
136
|
+
tier: 'enterprise',
|
|
137
|
+
seats: 50,
|
|
138
|
+
industry: 'technology'
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### React Integration
|
|
144
|
+
|
|
145
|
+
Using OpenFeature with React via the `@openfeature/react-sdk`:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { OpenFeatureProvider, useBooleanFlagValue } from '@openfeature/react-sdk';
|
|
149
|
+
import { OpenFeature } from '@openfeature/web-sdk';
|
|
150
|
+
import mixpanel from 'mixpanel-browser';
|
|
151
|
+
import { MixpanelProvider } from '@mixpanel/openfeature-web-provider';
|
|
152
|
+
|
|
153
|
+
// Initialize outside of component
|
|
154
|
+
mixpanel.init('YOUR_PROJECT_TOKEN', {
|
|
155
|
+
flags: {
|
|
156
|
+
context: { plan: 'premium' }
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
const provider = new MixpanelProvider(mixpanel.flags);
|
|
160
|
+
OpenFeature.setProvider(provider);
|
|
161
|
+
|
|
162
|
+
function App() {
|
|
163
|
+
return (
|
|
164
|
+
<OpenFeatureProvider>
|
|
165
|
+
<MyComponent />
|
|
166
|
+
</OpenFeatureProvider>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function MyComponent() {
|
|
171
|
+
// Use the hook to get flag values reactively
|
|
172
|
+
const showBanner = useBooleanFlagValue('show-banner', false);
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
{showBanner && <Banner message="Welcome to our new feature!" />}
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Context Mapping
|
|
183
|
+
|
|
184
|
+
Understanding how OpenFeature context maps to Mixpanel:
|
|
185
|
+
|
|
186
|
+
### All Properties Passed Directly
|
|
187
|
+
|
|
188
|
+
All properties in the OpenFeature `EvaluationContext` are passed directly to Mixpanel's feature flag evaluation. There is no transformation or filtering of properties.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// This OpenFeature context...
|
|
192
|
+
await OpenFeature.setContext({
|
|
193
|
+
targetingKey: 'user-123',
|
|
194
|
+
email: 'user@example.com',
|
|
195
|
+
plan: 'premium',
|
|
196
|
+
beta_tester: true
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// ...is passed to Mixpanel as-is for flag evaluation
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### targetingKey is Not Special
|
|
203
|
+
|
|
204
|
+
Unlike some feature flag providers, `targetingKey` is **not** used as a special bucketing key in Mixpanel. It is simply passed as another context property. Mixpanel's server-side configuration determines which properties are used for:
|
|
205
|
+
|
|
206
|
+
- **Targeting rules**: Which users see which variants
|
|
207
|
+
- **Bucketing**: How users are consistently assigned to variants
|
|
208
|
+
|
|
209
|
+
### User Identity is Managed Separately
|
|
210
|
+
|
|
211
|
+
**Important**: This provider does **not** call `mixpanel.identify()`. User identity should be managed separately through your normal Mixpanel integration:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Manage identity through Mixpanel directly
|
|
215
|
+
mixpanel.identify('user-123');
|
|
216
|
+
|
|
217
|
+
// The provider will use Mixpanel's current distinct_id automatically
|
|
218
|
+
const client = OpenFeature.getClient();
|
|
219
|
+
const value = client.getBooleanValue('my-flag', false);
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## API Reference
|
|
223
|
+
|
|
224
|
+
### MixpanelProvider
|
|
225
|
+
|
|
226
|
+
The main provider class that implements the OpenFeature `Provider` interface.
|
|
227
|
+
|
|
228
|
+
#### Constructor
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
constructor(flagsManager: FlagsManager)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Parameters:**
|
|
235
|
+
|
|
236
|
+
- `flagsManager`: The Mixpanel FlagsManager instance (accessed via `mixpanel.flags`)
|
|
237
|
+
|
|
238
|
+
**Note:** Pass `mixpanel.flags` (the FlagsManager) to the provider, not the entire mixpanel instance. This reduces coupling and makes the provider only depend on the flags interface.
|
|
239
|
+
|
|
240
|
+
#### Properties
|
|
241
|
+
|
|
242
|
+
| Property | Type | Description |
|
|
243
|
+
|----------|------|-------------|
|
|
244
|
+
| `metadata` | `{ name: string }` | Provider metadata with the name "mixpanel-provider" |
|
|
245
|
+
| `runsOn` | `'client'` | Indicates this is a client-side provider |
|
|
246
|
+
|
|
247
|
+
#### Methods
|
|
248
|
+
|
|
249
|
+
| Method | Description |
|
|
250
|
+
|--------|-------------|
|
|
251
|
+
| `initialize(context?)` | Called when the provider is registered. Waits for Mixpanel flags to be ready. |
|
|
252
|
+
| `onClose()` | Called when the provider is shut down. |
|
|
253
|
+
| `resolveBooleanEvaluation(flagKey, defaultValue, context, logger)` | Evaluates a boolean flag |
|
|
254
|
+
| `resolveStringEvaluation(flagKey, defaultValue, context, logger)` | Evaluates a string flag |
|
|
255
|
+
| `resolveNumberEvaluation(flagKey, defaultValue, context, logger)` | Evaluates a number flag |
|
|
256
|
+
| `resolveObjectEvaluation(flagKey, defaultValue, context, logger)` | Evaluates an object flag |
|
|
257
|
+
|
|
258
|
+
## Error Handling
|
|
259
|
+
|
|
260
|
+
The provider uses OpenFeature's standard error codes to indicate issues during flag evaluation:
|
|
261
|
+
|
|
262
|
+
### PROVIDER_NOT_READY
|
|
263
|
+
|
|
264
|
+
Returned when flags are evaluated before the provider has finished initializing.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// To avoid this error, use setProviderAndWait
|
|
268
|
+
await OpenFeature.setProviderAndWait(provider);
|
|
269
|
+
|
|
270
|
+
// Or listen for the READY event
|
|
271
|
+
OpenFeature.addHandler(ProviderEvents.Ready, () => {
|
|
272
|
+
// Now safe to evaluate flags
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### FLAG_NOT_FOUND
|
|
277
|
+
|
|
278
|
+
Returned when the requested flag does not exist in Mixpanel.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
const details = client.getBooleanDetails('nonexistent-flag', false);
|
|
282
|
+
|
|
283
|
+
if (details.errorCode === 'FLAG_NOT_FOUND') {
|
|
284
|
+
console.log('Flag does not exist, using default value');
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### TYPE_MISMATCH
|
|
289
|
+
|
|
290
|
+
Returned when the flag value type does not match the requested type.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// If 'my-flag' is configured as a string in Mixpanel...
|
|
294
|
+
const details = client.getBooleanDetails('my-flag', false);
|
|
295
|
+
|
|
296
|
+
if (details.errorCode === 'TYPE_MISMATCH') {
|
|
297
|
+
console.log('Flag is not a boolean, using default value');
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Troubleshooting
|
|
302
|
+
|
|
303
|
+
### Flags Always Return Default Values
|
|
304
|
+
|
|
305
|
+
**Possible causes:**
|
|
306
|
+
|
|
307
|
+
1. **Feature flags not enabled**: Ensure you initialized Mixpanel with `flags` enabled:
|
|
308
|
+
```typescript
|
|
309
|
+
mixpanel.init('YOUR_TOKEN', { flags: { context: { plan: 'premium' } } });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
2. **Provider not ready**: Make sure to wait for the provider to initialize:
|
|
313
|
+
```typescript
|
|
314
|
+
await OpenFeature.setProviderAndWait(provider);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
3. **Network issues**: Check the browser console for failed requests to Mixpanel's flags API.
|
|
318
|
+
|
|
319
|
+
4. **Flag not configured**: Verify the flag exists in your Mixpanel project and is enabled.
|
|
320
|
+
|
|
321
|
+
### Type Mismatch Errors
|
|
322
|
+
|
|
323
|
+
If you are getting `TYPE_MISMATCH` errors:
|
|
324
|
+
|
|
325
|
+
1. **Check flag configuration**: Verify the flag's value type in Mixpanel matches how you are evaluating it:
|
|
326
|
+
```typescript
|
|
327
|
+
// If flag value is a string like "true", use getStringValue, not getBooleanValue
|
|
328
|
+
const value = client.getStringValue('my-flag', 'default');
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
2. **Use getObjectValue for complex types**: For JSON objects or arrays, use `getObjectValue`.
|
|
332
|
+
|
|
333
|
+
### Exposure Events Not Tracking
|
|
334
|
+
|
|
335
|
+
If `$experiment_started` events are not appearing in Mixpanel:
|
|
336
|
+
|
|
337
|
+
1. **Verify Mixpanel tracking is working**: Test that other Mixpanel events are being tracked successfully.
|
|
338
|
+
|
|
339
|
+
2. **Check for duplicate evaluations**: Mixpanel only tracks the first exposure per flag per session to avoid duplicate events.
|
|
340
|
+
|
|
341
|
+
### Flags Not Updating After Context Change
|
|
342
|
+
|
|
343
|
+
When you update the OpenFeature context, the provider needs to fetch new flag values:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Update context and wait for new flags
|
|
347
|
+
await OpenFeature.setContext({ plan: 'premium' });
|
|
348
|
+
|
|
349
|
+
// Now evaluate with new context
|
|
350
|
+
const value = client.getBooleanValue('premium-feature', false);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
If flags still are not updating, check that your targeting rules in Mixpanel are configured to use the context properties you are setting.
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
Apache-2.0
|