mixpanel-react-native 3.2.0-beta.1 → 3.2.0-beta.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/FEATURE_FLAGS_QUICKSTART.md +348 -0
- package/README.md +29 -0
- package/index.js +64 -10
- package/javascript/mixpanel-flags.js +421 -41
- package/package.json +2 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Feature Flags Quick Start Guide (Beta)
|
|
2
|
+
|
|
3
|
+
> **Beta Version:** `3.2.0-beta.1`
|
|
4
|
+
> **Native Mode Only:** This beta release supports iOS and Android native implementations. JavaScript mode (Expo/React Native Web) support coming in future release.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Install the beta version:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install mixpanel-react-native@3.2.0-beta.1
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For iOS, update native dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd ios && pod install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Basic Setup
|
|
21
|
+
|
|
22
|
+
### 1. Initialize with Feature Flags Enabled
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import { Mixpanel } from 'mixpanel-react-native';
|
|
26
|
+
|
|
27
|
+
const mixpanel = new Mixpanel('YOUR_TOKEN');
|
|
28
|
+
|
|
29
|
+
// Enable Feature Flags during initialization
|
|
30
|
+
await mixpanel.init(
|
|
31
|
+
false, // optOutTrackingDefault
|
|
32
|
+
{}, // superProperties
|
|
33
|
+
'https://api.mixpanel.com', // serverURL
|
|
34
|
+
true, // useGzipCompression
|
|
35
|
+
{
|
|
36
|
+
enabled: true, // Enable Feature Flags
|
|
37
|
+
context: { // Optional: Add targeting context
|
|
38
|
+
platform: 'mobile',
|
|
39
|
+
app_version: '2.1.0'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Check Flag Availability
|
|
46
|
+
|
|
47
|
+
Before accessing flags, verify they're loaded:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
if (mixpanel.flags.areFlagsReady()) {
|
|
51
|
+
// Flags are ready to use
|
|
52
|
+
console.log('Feature flags loaded!');
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Using Feature Flags
|
|
57
|
+
|
|
58
|
+
### Synchronous API (Recommended for UI)
|
|
59
|
+
|
|
60
|
+
Use sync methods when flags are ready (e.g., in render methods):
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
// Check if feature is enabled
|
|
64
|
+
const showNewUI = mixpanel.flags.isEnabledSync('new-checkout-flow', false);
|
|
65
|
+
|
|
66
|
+
// Get variant value directly
|
|
67
|
+
const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
68
|
+
|
|
69
|
+
// Get full variant object with metadata
|
|
70
|
+
const variant = mixpanel.flags.getVariantSync('pricing-tier', {
|
|
71
|
+
key: 'control',
|
|
72
|
+
value: 'standard'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
console.log(`Variant: ${variant.key}, Value: ${variant.value}`);
|
|
76
|
+
if (variant.experiment_id) {
|
|
77
|
+
console.log(`Part of experiment: ${variant.experiment_id}`);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Asynchronous API (Promise Pattern)
|
|
82
|
+
|
|
83
|
+
Use async methods for event handlers or initialization:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Promise pattern
|
|
87
|
+
const variant = await mixpanel.flags.getVariant('checkout-flow', {
|
|
88
|
+
key: 'control',
|
|
89
|
+
value: 'standard'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const enabled = await mixpanel.flags.isEnabled('dark-mode', false);
|
|
93
|
+
|
|
94
|
+
const colorValue = await mixpanel.flags.getVariantValue('theme-color', '#0000FF');
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Asynchronous API (Callback Pattern)
|
|
98
|
+
|
|
99
|
+
Alternative callback style for compatibility:
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
// Callback pattern
|
|
103
|
+
mixpanel.flags.getVariant('feature-name', { key: 'control', value: 'off' }, (variant) => {
|
|
104
|
+
console.log(`Feature variant: ${variant.key}`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
mixpanel.flags.isEnabled('new-feature', false, (isEnabled) => {
|
|
108
|
+
if (isEnabled) {
|
|
109
|
+
// Show new feature
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Real-World Examples
|
|
115
|
+
|
|
116
|
+
### Example 1: Feature Toggle
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const NewCheckoutButton = () => {
|
|
120
|
+
const [showNewCheckout, setShowNewCheckout] = useState(false);
|
|
121
|
+
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
// Load flags on mount
|
|
124
|
+
if (mixpanel.flags.areFlagsReady()) {
|
|
125
|
+
const enabled = mixpanel.flags.isEnabledSync('new-checkout', false);
|
|
126
|
+
setShowNewCheckout(enabled);
|
|
127
|
+
}
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
return showNewCheckout ? <NewCheckout /> : <LegacyCheckout />;
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Example 2: A/B Test with Variants
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
const ProductCard = ({ product }) => {
|
|
138
|
+
// Get button color variant (A/B test)
|
|
139
|
+
const buttonColor = mixpanel.flags.areFlagsReady()
|
|
140
|
+
? mixpanel.flags.getVariantValueSync('button-color', 'blue')
|
|
141
|
+
: 'blue';
|
|
142
|
+
|
|
143
|
+
// Get pricing display variant
|
|
144
|
+
const pricingVariant = mixpanel.flags.areFlagsReady()
|
|
145
|
+
? mixpanel.flags.getVariantSync('pricing-display', {
|
|
146
|
+
key: 'control',
|
|
147
|
+
value: 'standard'
|
|
148
|
+
})
|
|
149
|
+
: { key: 'control', value: 'standard' };
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<View>
|
|
153
|
+
<Text>{product.name}</Text>
|
|
154
|
+
{pricingVariant.value === 'bold' ? (
|
|
155
|
+
<Text style={styles.boldPrice}>${product.price}</Text>
|
|
156
|
+
) : (
|
|
157
|
+
<Text>${product.price}</Text>
|
|
158
|
+
)}
|
|
159
|
+
<Button
|
|
160
|
+
title="Add to Cart"
|
|
161
|
+
color={buttonColor}
|
|
162
|
+
onPress={handleAddToCart}
|
|
163
|
+
/>
|
|
164
|
+
</View>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Example 3: Gradual Rollout with Fallback
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const App = () => {
|
|
173
|
+
const [uiVersion, setUiVersion] = useState('v1');
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
const loadFlags = async () => {
|
|
177
|
+
try {
|
|
178
|
+
// Manually trigger flag load
|
|
179
|
+
await mixpanel.flags.loadFlags();
|
|
180
|
+
|
|
181
|
+
// Check which UI version to show
|
|
182
|
+
const variant = mixpanel.flags.getVariantSync('ui-redesign', {
|
|
183
|
+
key: 'control',
|
|
184
|
+
value: 'v1'
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
setUiVersion(variant.value);
|
|
188
|
+
|
|
189
|
+
// Track if user is in experiment
|
|
190
|
+
if (variant.experiment_id) {
|
|
191
|
+
console.log(`User in experiment: ${variant.experiment_id}`);
|
|
192
|
+
}
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Failed to load flags:', error);
|
|
195
|
+
// Fallback to default
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
loadFlags();
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
return uiVersion === 'v2' ? <NewUI /> : <LegacyUI />;
|
|
203
|
+
};
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Example 4: Targeting with Context
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
// Set user context for targeting
|
|
210
|
+
await mixpanel.init(
|
|
211
|
+
false,
|
|
212
|
+
{},
|
|
213
|
+
'https://api.mixpanel.com',
|
|
214
|
+
true,
|
|
215
|
+
{
|
|
216
|
+
enabled: true,
|
|
217
|
+
context: {
|
|
218
|
+
user_tier: 'premium',
|
|
219
|
+
device_type: Platform.OS,
|
|
220
|
+
app_version: '2.1.0',
|
|
221
|
+
custom_properties: {
|
|
222
|
+
beta_tester: true,
|
|
223
|
+
region: 'US'
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// Flags will be evaluated based on context
|
|
230
|
+
const hasAccess = mixpanel.flags.isEnabledSync('premium-feature', false);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## API Reference
|
|
234
|
+
|
|
235
|
+
### Methods
|
|
236
|
+
|
|
237
|
+
| Method | Type | Description |
|
|
238
|
+
|--------|------|-------------|
|
|
239
|
+
| `areFlagsReady()` | Sync | Returns `true` if flags are loaded |
|
|
240
|
+
| `loadFlags()` | Async | Manually fetch flags from server |
|
|
241
|
+
| `isEnabledSync(name, fallback)` | Sync | Check if feature is enabled |
|
|
242
|
+
| `isEnabled(name, fallback)` | Async | Async version of isEnabledSync |
|
|
243
|
+
| `getVariantValueSync(name, fallback)` | Sync | Get variant value only |
|
|
244
|
+
| `getVariantValue(name, fallback)` | Async | Async version of getVariantValueSync |
|
|
245
|
+
| `getVariantSync(name, fallback)` | Sync | Get full variant object |
|
|
246
|
+
| `getVariant(name, fallback)` | Async | Async version of getVariantSync |
|
|
247
|
+
|
|
248
|
+
### Snake Case Aliases
|
|
249
|
+
|
|
250
|
+
All methods have snake_case aliases for consistency with mixpanel-js:
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
// These are equivalent
|
|
254
|
+
mixpanel.flags.areFlagsReady()
|
|
255
|
+
mixpanel.flags.are_flags_ready()
|
|
256
|
+
|
|
257
|
+
mixpanel.flags.getVariantSync('feature', fallback)
|
|
258
|
+
mixpanel.flags.get_variant_sync('feature', fallback)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Automatic Experiment Tracking
|
|
262
|
+
|
|
263
|
+
When a user is evaluated for a flag that's part of an A/B test, Mixpanel automatically tracks an `$experiment_started` event. No additional code required!
|
|
264
|
+
|
|
265
|
+
## Important Notes
|
|
266
|
+
|
|
267
|
+
### Platform Support
|
|
268
|
+
|
|
269
|
+
- ✅ **iOS**: Full support via native Swift SDK
|
|
270
|
+
- ✅ **Android**: Full support via native Android SDK
|
|
271
|
+
- ❌ **Expo/React Native Web**: Not supported in this beta (coming soon)
|
|
272
|
+
|
|
273
|
+
### Fallback Values
|
|
274
|
+
|
|
275
|
+
Always provide fallback values for graceful degradation:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// Good - provides fallback
|
|
279
|
+
const color = mixpanel.flags.getVariantValueSync('color', 'blue');
|
|
280
|
+
|
|
281
|
+
// Bad - could return undefined if flag not found
|
|
282
|
+
const color = mixpanel.flags.getVariantValueSync('color');
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Performance
|
|
286
|
+
|
|
287
|
+
- Flags are lazy-loaded on first access
|
|
288
|
+
- Sync methods are preferred for UI rendering (no async overhead)
|
|
289
|
+
- Check `areFlagsReady()` before using sync methods
|
|
290
|
+
|
|
291
|
+
### Error Handling
|
|
292
|
+
|
|
293
|
+
Flags fail gracefully and return fallback values:
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
try {
|
|
297
|
+
await mixpanel.flags.loadFlags();
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error('Flag loading failed:', error);
|
|
300
|
+
// App continues with fallback values
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Troubleshooting
|
|
305
|
+
|
|
306
|
+
### Flags Not Loading
|
|
307
|
+
|
|
308
|
+
1. Verify Feature Flags are enabled in your Mixpanel project
|
|
309
|
+
2. Check initialization includes `featureFlagsOptions: { enabled: true }`
|
|
310
|
+
3. Enable logging: `mixpanel.setLoggingEnabled(true)`
|
|
311
|
+
4. Verify network connectivity to Mixpanel API
|
|
312
|
+
|
|
313
|
+
### Native Module Not Found
|
|
314
|
+
|
|
315
|
+
This beta requires native modules. If you see "Native module not found":
|
|
316
|
+
|
|
317
|
+
1. Run `cd ios && pod install` (iOS)
|
|
318
|
+
2. Rebuild the app completely
|
|
319
|
+
3. Verify you're not using Expo Go (use dev client or eject)
|
|
320
|
+
|
|
321
|
+
### Getting Fallback Values
|
|
322
|
+
|
|
323
|
+
If flags always return fallbacks:
|
|
324
|
+
|
|
325
|
+
1. Check `mixpanel.flags.areFlagsReady()` returns `true`
|
|
326
|
+
2. Verify flag names match exactly (case-sensitive)
|
|
327
|
+
3. Confirm flags are published in Mixpanel dashboard
|
|
328
|
+
4. Check context targeting rules
|
|
329
|
+
|
|
330
|
+
## Feedback & Issues
|
|
331
|
+
|
|
332
|
+
This is a beta release. Please report issues at:
|
|
333
|
+
https://github.com/mixpanel/mixpanel-react-native/issues
|
|
334
|
+
|
|
335
|
+
Reference PR #331 for technical details:
|
|
336
|
+
https://github.com/mixpanel/mixpanel-react-native/pull/331
|
|
337
|
+
|
|
338
|
+
## What's Next
|
|
339
|
+
|
|
340
|
+
Coming in future releases:
|
|
341
|
+
|
|
342
|
+
- JavaScript mode support (Expo/React Native Web)
|
|
343
|
+
- Runtime context updates via `updateContext()`
|
|
344
|
+
- Performance optimizations
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
**Questions?** Visit [Mixpanel Documentation](https://mixpanel.com) or reach out to support.
|
package/README.md
CHANGED
|
@@ -115,6 +115,35 @@ const SampleApp = () => {
|
|
|
115
115
|
export default SampleApp;
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
+
### Feature Flags (Beta - 3.2.0-beta.1+)
|
|
119
|
+
|
|
120
|
+
Control features dynamically and run A/B tests with Mixpanel Feature Flags. **Native mode only** (iOS/Android) in beta.
|
|
121
|
+
|
|
122
|
+
#### Quick Start
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
// Enable during initialization
|
|
126
|
+
await mixpanel.init(false, {}, 'https://api.mixpanel.com', true, {
|
|
127
|
+
enabled: true,
|
|
128
|
+
context: { platform: 'mobile' } // Optional targeting context
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Check if feature is enabled
|
|
132
|
+
if (mixpanel.flags.areFlagsReady()) {
|
|
133
|
+
const showNewUI = mixpanel.flags.isEnabledSync('new-feature', false);
|
|
134
|
+
const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Key Methods
|
|
139
|
+
|
|
140
|
+
- `areFlagsReady()` - Check if flags are loaded
|
|
141
|
+
- `isEnabledSync(name, fallback)` - Check if feature is enabled
|
|
142
|
+
- `getVariantValueSync(name, fallback)` - Get variant value
|
|
143
|
+
- `getVariantSync(name, fallback)` - Get full variant object with metadata
|
|
144
|
+
|
|
145
|
+
All methods support async versions and snake_case aliases. See **[Feature Flags Quick Start Guide](FEATURE_FLAGS_QUICKSTART.md)** for complete documentation.
|
|
146
|
+
|
|
118
147
|
### Expo and React Native for Web support (3.0.2 and above)
|
|
119
148
|
|
|
120
149
|
Starting from version 3.0.2, we have introduced support for Expo, React Native for Web, and other platforms utilizing React Native that do not support iOS and Android directly.
|
package/index.js
CHANGED
|
@@ -63,10 +63,27 @@ export class Mixpanel {
|
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Returns the Flags instance for feature flags operations.
|
|
66
|
-
* This property is lazy-loaded to avoid unnecessary initialization.
|
|
67
66
|
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* <p>Feature Flags enable dynamic feature control and A/B testing capabilities.
|
|
68
|
+
* This property is lazy-loaded to avoid unnecessary initialization until first access.
|
|
69
|
+
*
|
|
70
|
+
* <p><b>Native Mode Only:</b> Feature flags are currently only available when using native mode
|
|
71
|
+
* (iOS/Android). JavaScript mode (Expo/React Native Web) support is planned for a future release.
|
|
72
|
+
*
|
|
73
|
+
* @return {Flags} an instance of Flags that provides access to feature flag operations
|
|
74
|
+
* @throws {Error} if accessed in JavaScript mode (when native modules are not available)
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Check if flags are ready
|
|
78
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
79
|
+
* const isEnabled = mixpanel.flags.isEnabledSync('new-checkout', false);
|
|
80
|
+
* }
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Get a feature variant value
|
|
84
|
+
* const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
85
|
+
*
|
|
86
|
+
* @see Flags
|
|
70
87
|
*/
|
|
71
88
|
get flags() {
|
|
72
89
|
// Short circuit for JavaScript mode - flags not ready for public use
|
|
@@ -86,13 +103,50 @@ export class Mixpanel {
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
/**
|
|
89
|
-
* Initializes Mixpanel
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* @param {boolean}
|
|
95
|
-
*
|
|
106
|
+
* Initializes Mixpanel with optional configuration for tracking, super properties, and feature flags.
|
|
107
|
+
*
|
|
108
|
+
* <p>This method must be called before using any other Mixpanel functionality. It sets up
|
|
109
|
+
* the tracking environment, registers super properties, and optionally initializes feature flags.
|
|
110
|
+
*
|
|
111
|
+
* @param {boolean} [optOutTrackingDefault=false] Whether or not Mixpanel can start tracking by default.
|
|
112
|
+
* If true, no data will be tracked until optInTracking() is called. See optOutTracking()
|
|
113
|
+
* @param {object} [superProperties={}] A Map containing the key value pairs of the super properties to register.
|
|
114
|
+
* These properties will be sent with every event. Pass {} if no super properties needed.
|
|
115
|
+
* @param {string} [serverURL="https://api.mixpanel.com"] The base URL used for Mixpanel API requests.
|
|
116
|
+
* Use "https://api-eu.mixpanel.com" for EU data residency. See setServerURL()
|
|
117
|
+
* @param {boolean} [useGzipCompression=false] Whether to use gzip compression for network requests.
|
|
118
|
+
* Enabling this reduces bandwidth usage but adds slight CPU overhead.
|
|
119
|
+
* @param {object} [featureFlagsOptions={}] Feature flags configuration object with the following properties:
|
|
120
|
+
* @param {boolean} [featureFlagsOptions.enabled=false] Whether to enable feature flags functionality
|
|
121
|
+
* @param {object} [featureFlagsOptions.context={}] Context properties used for feature flag targeting.
|
|
122
|
+
* Can include user properties, device properties, or any custom properties for flag evaluation.
|
|
123
|
+
* Note: In native mode, context must be set during initialization and cannot be updated later.
|
|
124
|
+
* @returns {Promise<void>} A promise that resolves when initialization is complete
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Basic initialization
|
|
128
|
+
* const mixpanel = new Mixpanel('YOUR_TOKEN', true);
|
|
129
|
+
* await mixpanel.init();
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // Initialize with feature flags enabled
|
|
133
|
+
* const mixpanel = new Mixpanel('YOUR_TOKEN', true);
|
|
134
|
+
* await mixpanel.init(false, {}, 'https://api.mixpanel.com', false, {
|
|
135
|
+
* enabled: true,
|
|
136
|
+
* context: {
|
|
137
|
+
* platform: 'mobile',
|
|
138
|
+
* app_version: '2.1.0'
|
|
139
|
+
* }
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* // Initialize with EU data residency and super properties
|
|
144
|
+
* await mixpanel.init(
|
|
145
|
+
* false,
|
|
146
|
+
* { plan: 'premium', region: 'eu' },
|
|
147
|
+
* 'https://api-eu.mixpanel.com',
|
|
148
|
+
* true
|
|
149
|
+
* );
|
|
96
150
|
*/
|
|
97
151
|
async init(
|
|
98
152
|
optOutTrackingDefault = DEFAULT_OPT_OUT,
|
|
@@ -1,8 +1,68 @@
|
|
|
1
1
|
import { MixpanelFlagsJS } from './mixpanel-flags-js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Core class for using Mixpanel Feature Flags.
|
|
5
|
+
*
|
|
6
|
+
* <p>The Flags class provides access to Mixpanel's Feature Flags functionality, enabling
|
|
7
|
+
* dynamic feature control, A/B testing, and personalized user experiences. Feature flags
|
|
8
|
+
* allow you to remotely configure your app's features without deploying new code.
|
|
9
|
+
*
|
|
10
|
+
* <p>This class is accessed through the {@link Mixpanel#flags} property and is lazy-loaded
|
|
11
|
+
* to minimize performance impact until feature flags are actually used.
|
|
12
|
+
*
|
|
13
|
+
* <p><b>Platform Support:</b>
|
|
14
|
+
* <ul>
|
|
15
|
+
* <li><b>Native Mode (iOS/Android):</b> Fully supported with automatic experiment tracking</li>
|
|
16
|
+
* <li><b>JavaScript Mode (Expo/React Native Web):</b> Planned for future release</li>
|
|
17
|
+
* </ul>
|
|
18
|
+
*
|
|
19
|
+
* <p><b>Key Concepts:</b>
|
|
20
|
+
* <ul>
|
|
21
|
+
* <li><b>Feature Name:</b> The unique identifier for your feature flag (e.g., "new-checkout")</li>
|
|
22
|
+
* <li><b>Variant:</b> An object containing both a key and value representing the feature configuration</li>
|
|
23
|
+
* <li><b>Variant Key:</b> The identifier for the specific variation (e.g., "control", "treatment")</li>
|
|
24
|
+
* <li><b>Variant Value:</b> The actual configuration value (can be any JSON-serializable type)</li>
|
|
25
|
+
* <li><b>Fallback:</b> Default value returned when a flag is not available or not loaded</li>
|
|
26
|
+
* </ul>
|
|
27
|
+
*
|
|
28
|
+
* <p><b>Automatic Experiment Tracking:</b> When a feature flag is evaluated for the first time,
|
|
29
|
+
* Mixpanel automatically tracks a "$experiment_started" event with relevant metadata.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Initialize with feature flags enabled
|
|
33
|
+
* const mixpanel = new Mixpanel('YOUR_TOKEN', true);
|
|
34
|
+
* await mixpanel.init(false, {}, 'https://api.mixpanel.com', false, {
|
|
35
|
+
* enabled: true,
|
|
36
|
+
* context: { platform: 'mobile' }
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Synchronous access (when flags are ready)
|
|
41
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
42
|
+
* const isEnabled = mixpanel.flags.isEnabledSync('new-feature', false);
|
|
43
|
+
* const color = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
44
|
+
* const variant = mixpanel.flags.getVariantSync('checkout-flow', {
|
|
45
|
+
* key: 'control',
|
|
46
|
+
* value: 'standard'
|
|
47
|
+
* });
|
|
48
|
+
* }
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* // Asynchronous access with Promise pattern
|
|
52
|
+
* const variant = await mixpanel.flags.getVariant('pricing-test', {
|
|
53
|
+
* key: 'control',
|
|
54
|
+
* value: { price: 9.99, currency: 'USD' }
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Asynchronous access with callback pattern
|
|
59
|
+
* mixpanel.flags.isEnabled('beta-features', false, (isEnabled) => {
|
|
60
|
+
* if (isEnabled) {
|
|
61
|
+
* // Enable beta features
|
|
62
|
+
* }
|
|
63
|
+
* });
|
|
64
|
+
*
|
|
65
|
+
* @see Mixpanel#flags
|
|
6
66
|
*/
|
|
7
67
|
export class Flags {
|
|
8
68
|
constructor(token, mixpanelImpl, storage) {
|
|
@@ -18,8 +78,26 @@ export class Flags {
|
|
|
18
78
|
}
|
|
19
79
|
|
|
20
80
|
/**
|
|
21
|
-
* Manually
|
|
22
|
-
*
|
|
81
|
+
* Manually fetch feature flags from the Mixpanel servers.
|
|
82
|
+
*
|
|
83
|
+
* <p>Feature flags are automatically loaded during initialization when feature flags are enabled.
|
|
84
|
+
* This method allows you to manually trigger a refresh of the flags, which is useful when:
|
|
85
|
+
* <ul>
|
|
86
|
+
* <li>You want to reload flags after a user property change</li>
|
|
87
|
+
* <li>You need to ensure you have the latest flag configuration</li>
|
|
88
|
+
* <li>Initial automatic load failed and you want to retry</li>
|
|
89
|
+
* </ul>
|
|
90
|
+
*
|
|
91
|
+
* <p>After successfully loading flags, {@link areFlagsReady} will return true and synchronous
|
|
92
|
+
* methods can be used to access flag values.
|
|
93
|
+
*
|
|
94
|
+
* @returns {Promise<void>} A promise that resolves when flags have been fetched and loaded
|
|
95
|
+
* @throws {Error} if feature flags are not initialized
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Manually reload flags after user identification
|
|
99
|
+
* await mixpanel.identify('user123');
|
|
100
|
+
* await mixpanel.flags.loadFlags();
|
|
23
101
|
*/
|
|
24
102
|
async loadFlags() {
|
|
25
103
|
if (this.isNativeMode) {
|
|
@@ -31,8 +109,31 @@ export class Flags {
|
|
|
31
109
|
}
|
|
32
110
|
|
|
33
111
|
/**
|
|
34
|
-
* Check if feature flags have been fetched and are ready to use.
|
|
35
|
-
*
|
|
112
|
+
* Check if feature flags have been fetched from the server and are ready to use.
|
|
113
|
+
*
|
|
114
|
+
* <p>This method returns true after feature flags have been successfully loaded via {@link loadFlags}
|
|
115
|
+
* or during initialization. When flags are ready, you can safely use the synchronous methods
|
|
116
|
+
* ({@link getVariantSync}, {@link getVariantValueSync}, {@link isEnabledSync}) without waiting.
|
|
117
|
+
*
|
|
118
|
+
* <p>It's recommended to check this before using synchronous methods to ensure you're not
|
|
119
|
+
* getting fallback values due to flags not being loaded yet.
|
|
120
|
+
*
|
|
121
|
+
* @returns {boolean} true if flags have been loaded and are ready to use, false otherwise
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Check before using synchronous methods
|
|
125
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
126
|
+
* const isEnabled = mixpanel.flags.isEnabledSync('new-feature', false);
|
|
127
|
+
* } else {
|
|
128
|
+
* console.log('Flags not ready yet, using fallback');
|
|
129
|
+
* }
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* // Wait for flags to be ready
|
|
133
|
+
* await mixpanel.flags.loadFlags();
|
|
134
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
135
|
+
* // Now safe to use sync methods
|
|
136
|
+
* }
|
|
36
137
|
*/
|
|
37
138
|
areFlagsReady() {
|
|
38
139
|
if (this.isNativeMode) {
|
|
@@ -44,10 +145,51 @@ export class Flags {
|
|
|
44
145
|
}
|
|
45
146
|
|
|
46
147
|
/**
|
|
47
|
-
* Get a feature flag variant synchronously.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
148
|
+
* Get a feature flag variant synchronously.
|
|
149
|
+
*
|
|
150
|
+
* <p>Returns the complete variant object for a feature flag, including both the variant key
|
|
151
|
+
* (e.g., "control", "treatment") and the variant value (the actual configuration data).
|
|
152
|
+
*
|
|
153
|
+
* <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
|
|
154
|
+
* Always check {@link areFlagsReady} first, or use the asynchronous {@link getVariant} method instead.
|
|
155
|
+
*
|
|
156
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
157
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
160
|
+
* @param {object} fallback The fallback variant object to return if the flag is not available.
|
|
161
|
+
* Must include both 'key' and 'value' properties.
|
|
162
|
+
* @returns {object} The flag variant object with the following structure:
|
|
163
|
+
* - key: {string} The variant key (e.g., "control", "treatment")
|
|
164
|
+
* - value: {any} The variant value (can be any JSON-serializable type)
|
|
165
|
+
* - experiment_id: {string|number} (optional) The experiment ID if this is an experiment
|
|
166
|
+
* - is_experiment_active: {boolean} (optional) Whether the experiment is currently active
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* // Get a checkout flow variant
|
|
170
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
171
|
+
* const variant = mixpanel.flags.getVariantSync('checkout-flow', {
|
|
172
|
+
* key: 'control',
|
|
173
|
+
* value: 'standard'
|
|
174
|
+
* });
|
|
175
|
+
* console.log(`Using variant: ${variant.key}`);
|
|
176
|
+
* console.log(`Configuration: ${JSON.stringify(variant.value)}`);
|
|
177
|
+
* }
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Get a complex configuration variant
|
|
181
|
+
* const defaultConfig = {
|
|
182
|
+
* key: 'default',
|
|
183
|
+
* value: {
|
|
184
|
+
* theme: 'light',
|
|
185
|
+
* layout: 'grid',
|
|
186
|
+
* itemsPerPage: 20
|
|
187
|
+
* }
|
|
188
|
+
* };
|
|
189
|
+
* const config = mixpanel.flags.getVariantSync('ui-config', defaultConfig);
|
|
190
|
+
*
|
|
191
|
+
* @see getVariant for asynchronous access
|
|
192
|
+
* @see getVariantValueSync to get only the value (not the full variant object)
|
|
51
193
|
*/
|
|
52
194
|
getVariantSync(featureName, fallback) {
|
|
53
195
|
if (!this.areFlagsReady()) {
|
|
@@ -63,10 +205,45 @@ export class Flags {
|
|
|
63
205
|
}
|
|
64
206
|
|
|
65
207
|
/**
|
|
66
|
-
* Get a feature flag variant value synchronously.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
208
|
+
* Get a feature flag variant value synchronously.
|
|
209
|
+
*
|
|
210
|
+
* <p>Returns only the value portion of a feature flag variant, without the variant key or metadata.
|
|
211
|
+
* This is useful when you only care about the configuration data, not which variant was selected.
|
|
212
|
+
*
|
|
213
|
+
* <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
|
|
214
|
+
* Always check {@link areFlagsReady} first, or use the asynchronous {@link getVariantValue} method instead.
|
|
215
|
+
*
|
|
216
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
217
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
220
|
+
* @param {any} fallbackValue The fallback value to return if the flag is not available.
|
|
221
|
+
* Can be any JSON-serializable type (string, number, boolean, object, array, etc.)
|
|
222
|
+
* @returns {any} The flag's value, or the fallback if the flag is not available.
|
|
223
|
+
* The return type matches the type of value configured in your Mixpanel project.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // Get a simple string value
|
|
227
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
228
|
+
* const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
|
|
229
|
+
* applyButtonColor(buttonColor);
|
|
230
|
+
* }
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* // Get a complex object value
|
|
234
|
+
* const defaultPricing = { price: 9.99, currency: 'USD', trial_days: 7 };
|
|
235
|
+
* const pricing = mixpanel.flags.getVariantValueSync('pricing-config', defaultPricing);
|
|
236
|
+
* console.log(`Price: ${pricing.price} ${pricing.currency}`);
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* // Get a boolean value
|
|
240
|
+
* const showPromo = mixpanel.flags.getVariantValueSync('show-promo', false);
|
|
241
|
+
* if (showPromo) {
|
|
242
|
+
* displayPromotionalBanner();
|
|
243
|
+
* }
|
|
244
|
+
*
|
|
245
|
+
* @see getVariantValue for asynchronous access
|
|
246
|
+
* @see getVariantSync to get the full variant object including key and metadata
|
|
70
247
|
*/
|
|
71
248
|
getVariantValueSync(featureName, fallbackValue) {
|
|
72
249
|
if (!this.areFlagsReady()) {
|
|
@@ -89,10 +266,39 @@ export class Flags {
|
|
|
89
266
|
}
|
|
90
267
|
|
|
91
268
|
/**
|
|
92
|
-
* Check if a feature flag is enabled synchronously.
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
269
|
+
* Check if a feature flag is enabled synchronously.
|
|
270
|
+
*
|
|
271
|
+
* <p>This is a convenience method for boolean feature flags. It checks if a feature is enabled
|
|
272
|
+
* by evaluating the variant value as a boolean. A feature is considered "enabled" when its
|
|
273
|
+
* variant value evaluates to true.
|
|
274
|
+
*
|
|
275
|
+
* <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
|
|
276
|
+
* Always check {@link areFlagsReady} first, or use the asynchronous {@link isEnabled} method instead.
|
|
277
|
+
*
|
|
278
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
279
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
280
|
+
*
|
|
281
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
282
|
+
* @param {boolean} [fallbackValue=false] The fallback value to return if the flag is not available.
|
|
283
|
+
* Defaults to false if not provided.
|
|
284
|
+
* @returns {boolean} true if the feature is enabled, false otherwise
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* // Simple feature toggle
|
|
288
|
+
* if (mixpanel.flags.areFlagsReady()) {
|
|
289
|
+
* if (mixpanel.flags.isEnabledSync('new-checkout', false)) {
|
|
290
|
+
* showNewCheckout();
|
|
291
|
+
* } else {
|
|
292
|
+
* showLegacyCheckout();
|
|
293
|
+
* }
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* // With explicit fallback
|
|
298
|
+
* const enableBetaFeatures = mixpanel.flags.isEnabledSync('beta-features', true);
|
|
299
|
+
*
|
|
300
|
+
* @see isEnabled for asynchronous access
|
|
301
|
+
* @see getVariantValueSync for non-boolean flag values
|
|
96
302
|
*/
|
|
97
303
|
isEnabledSync(featureName, fallbackValue = false) {
|
|
98
304
|
if (!this.areFlagsReady()) {
|
|
@@ -109,11 +315,47 @@ export class Flags {
|
|
|
109
315
|
|
|
110
316
|
/**
|
|
111
317
|
* Get a feature flag variant asynchronously.
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
318
|
+
*
|
|
319
|
+
* <p>Returns the complete variant object for a feature flag, including both the variant key
|
|
320
|
+
* and the variant value. This method works regardless of whether flags are ready, making it
|
|
321
|
+
* safe to use at any time.
|
|
322
|
+
*
|
|
323
|
+
* <p>Supports both Promise and callback patterns for maximum flexibility.
|
|
324
|
+
*
|
|
325
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
326
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
327
|
+
*
|
|
328
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
329
|
+
* @param {object} fallback The fallback variant object to return if the flag is not available.
|
|
330
|
+
* Must include both 'key' and 'value' properties.
|
|
331
|
+
* @param {function} [callback] Optional callback function that receives the variant object.
|
|
332
|
+
* If provided, the method returns void. If omitted, the method returns a Promise.
|
|
333
|
+
* @returns {Promise<object>|void} Promise that resolves to the variant object if no callback provided,
|
|
334
|
+
* void if callback is provided. The variant object has the following structure:
|
|
335
|
+
* - key: {string} The variant key (e.g., "control", "treatment")
|
|
336
|
+
* - value: {any} The variant value (can be any JSON-serializable type)
|
|
337
|
+
* - experiment_id: {string|number} (optional) The experiment ID if this is an experiment
|
|
338
|
+
* - is_experiment_active: {boolean} (optional) Whether the experiment is currently active
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* // Promise pattern (recommended)
|
|
342
|
+
* const variant = await mixpanel.flags.getVariant('checkout-flow', {
|
|
343
|
+
* key: 'control',
|
|
344
|
+
* value: 'standard'
|
|
345
|
+
* });
|
|
346
|
+
* console.log(`Using ${variant.key}: ${variant.value}`);
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* // Callback pattern
|
|
350
|
+
* mixpanel.flags.getVariant('pricing-test', {
|
|
351
|
+
* key: 'default',
|
|
352
|
+
* value: { price: 9.99 }
|
|
353
|
+
* }, (variant) => {
|
|
354
|
+
* console.log(`Price: ${variant.value.price}`);
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* @see getVariantSync for synchronous access when flags are ready
|
|
358
|
+
* @see getVariantValue to get only the value without the variant key
|
|
117
359
|
*/
|
|
118
360
|
getVariant(featureName, fallback, callback) {
|
|
119
361
|
// If callback provided, use callback pattern
|
|
@@ -150,11 +392,45 @@ export class Flags {
|
|
|
150
392
|
|
|
151
393
|
/**
|
|
152
394
|
* Get a feature flag variant value asynchronously.
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
395
|
+
*
|
|
396
|
+
* <p>Returns only the value portion of a feature flag variant. This method works regardless
|
|
397
|
+
* of whether flags are ready, making it safe to use at any time.
|
|
398
|
+
*
|
|
399
|
+
* <p>Supports both Promise and callback patterns for maximum flexibility.
|
|
400
|
+
*
|
|
401
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
402
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
403
|
+
*
|
|
404
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
405
|
+
* @param {any} fallbackValue The fallback value to return if the flag is not available.
|
|
406
|
+
* Can be any JSON-serializable type (string, number, boolean, object, array, etc.)
|
|
407
|
+
* @param {function} [callback] Optional callback function that receives the flag value.
|
|
408
|
+
* If provided, the method returns void. If omitted, the method returns a Promise.
|
|
409
|
+
* @returns {Promise<any>|void} Promise that resolves to the flag value if no callback provided,
|
|
410
|
+
* void if callback is provided. The return type matches the type of value configured in
|
|
411
|
+
* your Mixpanel project.
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* // Promise pattern (recommended)
|
|
415
|
+
* const buttonColor = await mixpanel.flags.getVariantValue('button-color', 'blue');
|
|
416
|
+
* applyButtonColor(buttonColor);
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* // Promise pattern with object value
|
|
420
|
+
* const pricing = await mixpanel.flags.getVariantValue('pricing-config', {
|
|
421
|
+
* price: 9.99,
|
|
422
|
+
* currency: 'USD'
|
|
423
|
+
* });
|
|
424
|
+
* displayPrice(pricing.price, pricing.currency);
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* // Callback pattern
|
|
428
|
+
* mixpanel.flags.getVariantValue('theme', 'light', (theme) => {
|
|
429
|
+
* applyTheme(theme);
|
|
430
|
+
* });
|
|
431
|
+
*
|
|
432
|
+
* @see getVariantValueSync for synchronous access when flags are ready
|
|
433
|
+
* @see getVariant to get the full variant object including key and metadata
|
|
158
434
|
*/
|
|
159
435
|
getVariantValue(featureName, fallbackValue, callback) {
|
|
160
436
|
// If callback provided, use callback pattern
|
|
@@ -191,11 +467,47 @@ export class Flags {
|
|
|
191
467
|
|
|
192
468
|
/**
|
|
193
469
|
* Check if a feature flag is enabled asynchronously.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
470
|
+
*
|
|
471
|
+
* <p>This is a convenience method for boolean feature flags. It checks if a feature is enabled
|
|
472
|
+
* by evaluating the variant value as a boolean. This method works regardless of whether flags
|
|
473
|
+
* are ready, making it safe to use at any time.
|
|
474
|
+
*
|
|
475
|
+
* <p>Supports both Promise and callback patterns for maximum flexibility.
|
|
476
|
+
*
|
|
477
|
+
* <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
|
|
478
|
+
* "$experiment_started" event with relevant experiment metadata.
|
|
479
|
+
*
|
|
480
|
+
* @param {string} featureName The unique identifier for the feature flag
|
|
481
|
+
* @param {boolean} [fallbackValue=false] The fallback value to return if the flag is not available.
|
|
482
|
+
* Defaults to false if not provided.
|
|
483
|
+
* @param {function} [callback] Optional callback function that receives the boolean result.
|
|
484
|
+
* If provided, the method returns void. If omitted, the method returns a Promise.
|
|
485
|
+
* @returns {Promise<boolean>|void} Promise that resolves to true if enabled, false otherwise
|
|
486
|
+
* (when no callback provided). Returns void if callback is provided.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* // Promise pattern (recommended)
|
|
490
|
+
* const isEnabled = await mixpanel.flags.isEnabled('new-checkout', false);
|
|
491
|
+
* if (isEnabled) {
|
|
492
|
+
* showNewCheckout();
|
|
493
|
+
* } else {
|
|
494
|
+
* showLegacyCheckout();
|
|
495
|
+
* }
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* // Callback pattern
|
|
499
|
+
* mixpanel.flags.isEnabled('beta-features', false, (isEnabled) => {
|
|
500
|
+
* if (isEnabled) {
|
|
501
|
+
* enableBetaFeatures();
|
|
502
|
+
* }
|
|
503
|
+
* });
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* // Default fallback (false)
|
|
507
|
+
* const showPromo = await mixpanel.flags.isEnabled('show-promo');
|
|
508
|
+
*
|
|
509
|
+
* @see isEnabledSync for synchronous access when flags are ready
|
|
510
|
+
* @see getVariantValue for non-boolean flag values
|
|
199
511
|
*/
|
|
200
512
|
isEnabled(featureName, fallbackValue = false, callback) {
|
|
201
513
|
// If callback provided, use callback pattern
|
|
@@ -231,16 +543,50 @@ export class Flags {
|
|
|
231
543
|
}
|
|
232
544
|
|
|
233
545
|
/**
|
|
234
|
-
* Update the context used for feature flag evaluation
|
|
235
|
-
*
|
|
546
|
+
* Update the context used for feature flag evaluation.
|
|
547
|
+
*
|
|
548
|
+
* <p>Context properties are used to determine which feature flag variants a user should receive
|
|
549
|
+
* based on targeting rules configured in your Mixpanel project. This allows for personalized
|
|
550
|
+
* feature experiences based on user attributes, device properties, or custom criteria.
|
|
551
|
+
*
|
|
552
|
+
* <p><b>IMPORTANT LIMITATION:</b> This method is <b>only available in JavaScript mode</b>
|
|
553
|
+
* (Expo/React Native Web). In native mode (iOS/Android), context must be set during initialization
|
|
554
|
+
* via {@link Mixpanel#init} and cannot be updated at runtime.
|
|
555
|
+
*
|
|
556
|
+
* <p>By default, the new context properties are merged with existing context. Set
|
|
557
|
+
* <code>options.replace = true</code> to completely replace the context instead.
|
|
236
558
|
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
559
|
+
* @param {object} newContext New context properties to add or update. Can include any
|
|
560
|
+
* JSON-serializable properties that are used in your feature flag targeting rules.
|
|
561
|
+
* Common examples include user tier, region, platform version, etc.
|
|
562
|
+
* @param {object} [options={replace: false}] Configuration options for the update
|
|
563
|
+
* @param {boolean} [options.replace=false] If true, replaces the entire context instead of merging.
|
|
564
|
+
* If false (default), merges new properties with existing context.
|
|
565
|
+
* @returns {Promise<void>} A promise that resolves when the context has been updated and
|
|
566
|
+
* flags have been re-evaluated with the new context
|
|
567
|
+
* @throws {Error} if called in native mode (iOS/Android)
|
|
239
568
|
*
|
|
240
|
-
* @
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
569
|
+
* @example
|
|
570
|
+
* // Merge new properties into existing context (JavaScript mode only)
|
|
571
|
+
* await mixpanel.flags.updateContext({
|
|
572
|
+
* user_tier: 'premium',
|
|
573
|
+
* region: 'us-west'
|
|
574
|
+
* });
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* // Replace entire context (JavaScript mode only)
|
|
578
|
+
* await mixpanel.flags.updateContext({
|
|
579
|
+
* device_type: 'tablet',
|
|
580
|
+
* os_version: '14.0'
|
|
581
|
+
* }, { replace: true });
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* // This will throw an error in native mode
|
|
585
|
+
* try {
|
|
586
|
+
* await mixpanel.flags.updateContext({ tier: 'premium' });
|
|
587
|
+
* } catch (error) {
|
|
588
|
+
* console.error('Context updates not supported in native mode');
|
|
589
|
+
* }
|
|
244
590
|
*/
|
|
245
591
|
async updateContext(newContext, options = { replace: false }) {
|
|
246
592
|
if (this.isNativeMode) {
|
|
@@ -256,34 +602,68 @@ export class Flags {
|
|
|
256
602
|
}
|
|
257
603
|
|
|
258
604
|
// snake_case aliases for API consistency with mixpanel-js
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Alias for {@link areFlagsReady}. Provided for API consistency with mixpanel-js.
|
|
608
|
+
* @see areFlagsReady
|
|
609
|
+
*/
|
|
259
610
|
are_flags_ready() {
|
|
260
611
|
return this.areFlagsReady();
|
|
261
612
|
}
|
|
262
613
|
|
|
614
|
+
/**
|
|
615
|
+
* Alias for {@link getVariant}. Provided for API consistency with mixpanel-js.
|
|
616
|
+
* @see getVariant
|
|
617
|
+
*/
|
|
263
618
|
get_variant(featureName, fallback, callback) {
|
|
264
619
|
return this.getVariant(featureName, fallback, callback);
|
|
265
620
|
}
|
|
266
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Alias for {@link getVariantSync}. Provided for API consistency with mixpanel-js.
|
|
624
|
+
* @see getVariantSync
|
|
625
|
+
*/
|
|
267
626
|
get_variant_sync(featureName, fallback) {
|
|
268
627
|
return this.getVariantSync(featureName, fallback);
|
|
269
628
|
}
|
|
270
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Alias for {@link getVariantValue}. Provided for API consistency with mixpanel-js.
|
|
632
|
+
* @see getVariantValue
|
|
633
|
+
*/
|
|
271
634
|
get_variant_value(featureName, fallbackValue, callback) {
|
|
272
635
|
return this.getVariantValue(featureName, fallbackValue, callback);
|
|
273
636
|
}
|
|
274
637
|
|
|
638
|
+
/**
|
|
639
|
+
* Alias for {@link getVariantValueSync}. Provided for API consistency with mixpanel-js.
|
|
640
|
+
* @see getVariantValueSync
|
|
641
|
+
*/
|
|
275
642
|
get_variant_value_sync(featureName, fallbackValue) {
|
|
276
643
|
return this.getVariantValueSync(featureName, fallbackValue);
|
|
277
644
|
}
|
|
278
645
|
|
|
646
|
+
/**
|
|
647
|
+
* Alias for {@link isEnabled}. Provided for API consistency with mixpanel-js.
|
|
648
|
+
* @see isEnabled
|
|
649
|
+
*/
|
|
279
650
|
is_enabled(featureName, fallbackValue, callback) {
|
|
280
651
|
return this.isEnabled(featureName, fallbackValue, callback);
|
|
281
652
|
}
|
|
282
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Alias for {@link isEnabledSync}. Provided for API consistency with mixpanel-js.
|
|
656
|
+
* @see isEnabledSync
|
|
657
|
+
*/
|
|
283
658
|
is_enabled_sync(featureName, fallbackValue) {
|
|
284
659
|
return this.isEnabledSync(featureName, fallbackValue);
|
|
285
660
|
}
|
|
286
661
|
|
|
662
|
+
/**
|
|
663
|
+
* Alias for {@link updateContext}. Provided for API consistency with mixpanel-js.
|
|
664
|
+
* JavaScript mode only.
|
|
665
|
+
* @see updateContext
|
|
666
|
+
*/
|
|
287
667
|
update_context(newContext, options) {
|
|
288
668
|
return this.updateContext(newContext, options);
|
|
289
669
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixpanel-react-native",
|
|
3
|
-
"version": "3.2.0-beta.
|
|
3
|
+
"version": "3.2.0-beta.2",
|
|
4
4
|
"description": "Official React Native Tracking Library for Mixpanel Analytics",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"eslint": "^7.11.0",
|
|
39
39
|
"jest": "^26.6.3",
|
|
40
40
|
"jest-fetch-mock": "^3.0.3",
|
|
41
|
+
"jsdoc": "^4.0.5",
|
|
41
42
|
"metro-react-native-babel-preset": "^0.63.0",
|
|
42
43
|
"react-native": "^0.63.3",
|
|
43
44
|
"react-test-renderer": "16.13.1"
|