mixpanel-react-native 3.2.0-beta.1 → 3.2.0-beta.3

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.
@@ -1,8 +1,69 @@
1
1
  import { MixpanelFlagsJS } from './mixpanel-flags-js';
2
+ import { MixpanelLogger } from './mixpanel-logger';
2
3
 
3
4
  /**
4
- * Flags class for managing Feature Flags functionality
5
- * This class handles both native and JavaScript fallback implementations
5
+ * Core class for using Mixpanel Feature Flags.
6
+ *
7
+ * <p>The Flags class provides access to Mixpanel's Feature Flags functionality, enabling
8
+ * dynamic feature control, A/B testing, and personalized user experiences. Feature flags
9
+ * allow you to remotely configure your app's features without deploying new code.
10
+ *
11
+ * <p>This class is accessed through the {@link Mixpanel#flags} property and is lazy-loaded
12
+ * to minimize performance impact until feature flags are actually used.
13
+ *
14
+ * <p><b>Platform Support:</b>
15
+ * <ul>
16
+ * <li><b>Native Mode (iOS/Android):</b> Fully supported with automatic experiment tracking</li>
17
+ * <li><b>JavaScript Mode (Expo/React Native Web):</b> Planned for future release</li>
18
+ * </ul>
19
+ *
20
+ * <p><b>Key Concepts:</b>
21
+ * <ul>
22
+ * <li><b>Feature Name:</b> The unique identifier for your feature flag (e.g., "new-checkout")</li>
23
+ * <li><b>Variant:</b> An object containing both a key and value representing the feature configuration</li>
24
+ * <li><b>Variant Key:</b> The identifier for the specific variation (e.g., "control", "treatment")</li>
25
+ * <li><b>Variant Value:</b> The actual configuration value (can be any JSON-serializable type)</li>
26
+ * <li><b>Fallback:</b> Default value returned when a flag is not available or not loaded</li>
27
+ * </ul>
28
+ *
29
+ * <p><b>Automatic Experiment Tracking:</b> When a feature flag is evaluated for the first time,
30
+ * Mixpanel automatically tracks a "$experiment_started" event with relevant metadata.
31
+ *
32
+ * @example
33
+ * // Initialize with feature flags enabled
34
+ * const mixpanel = new Mixpanel('YOUR_TOKEN', true);
35
+ * await mixpanel.init(false, {}, 'https://api.mixpanel.com', false, {
36
+ * enabled: true,
37
+ * context: { platform: 'mobile' }
38
+ * });
39
+ *
40
+ * @example
41
+ * // Synchronous access (when flags are ready)
42
+ * if (mixpanel.flags.areFlagsReady()) {
43
+ * const isEnabled = mixpanel.flags.isEnabledSync('new-feature', false);
44
+ * const color = mixpanel.flags.getVariantValueSync('button-color', 'blue');
45
+ * const variant = mixpanel.flags.getVariantSync('checkout-flow', {
46
+ * key: 'control',
47
+ * value: 'standard'
48
+ * });
49
+ * }
50
+ *
51
+ * @example
52
+ * // Asynchronous access with Promise pattern
53
+ * const variant = await mixpanel.flags.getVariant('pricing-test', {
54
+ * key: 'control',
55
+ * value: { price: 9.99, currency: 'USD' }
56
+ * });
57
+ *
58
+ * @example
59
+ * // Asynchronous access with callback pattern
60
+ * mixpanel.flags.isEnabled('beta-features', false, (isEnabled) => {
61
+ * if (isEnabled) {
62
+ * // Enable beta features
63
+ * }
64
+ * });
65
+ *
66
+ * @see Mixpanel#flags
6
67
  */
7
68
  export class Flags {
8
69
  constructor(token, mixpanelImpl, storage) {
@@ -13,13 +74,32 @@ export class Flags {
13
74
 
14
75
  // For JavaScript mode, create the JS implementation
15
76
  if (!this.isNativeMode && storage) {
16
- this.jsFlags = new MixpanelFlagsJS(token, mixpanelImpl, storage);
77
+ // Get the initial context from mixpanelImpl (always MixpanelMain in JS mode)
78
+ const initialContext = mixpanelImpl.getFeatureFlagsContext();
79
+ this.jsFlags = new MixpanelFlagsJS(token, mixpanelImpl, storage, initialContext);
17
80
  }
18
81
  }
19
82
 
20
83
  /**
21
- * Manually trigger a fetch of feature flags from the Mixpanel servers.
22
- * This is usually automatic but can be called manually if needed.
84
+ * Manually fetch feature flags from the Mixpanel servers.
85
+ *
86
+ * <p>Feature flags are automatically loaded during initialization when feature flags are enabled.
87
+ * This method allows you to manually trigger a refresh of the flags, which is useful when:
88
+ * <ul>
89
+ * <li>You want to reload flags after a user property change</li>
90
+ * <li>You need to ensure you have the latest flag configuration</li>
91
+ * <li>Initial automatic load failed and you want to retry</li>
92
+ * </ul>
93
+ *
94
+ * <p>After successfully loading flags, {@link areFlagsReady} will return true and synchronous
95
+ * methods can be used to access flag values.
96
+ *
97
+ * @returns {Promise<void>} A promise that resolves when flags have been fetched and loaded
98
+ *
99
+ * @example
100
+ * // Manually reload flags after user identification
101
+ * await mixpanel.identify('user123');
102
+ * await mixpanel.flags.loadFlags();
23
103
  */
24
104
  async loadFlags() {
25
105
  if (this.isNativeMode) {
@@ -27,12 +107,37 @@ export class Flags {
27
107
  } else if (this.jsFlags) {
28
108
  return await this.jsFlags.loadFlags();
29
109
  }
30
- throw new Error("Feature flags are not initialized");
110
+ // Log warning and return gracefully instead of throwing
111
+ MixpanelLogger.warn(this.token, "Feature flags are not initialized - cannot load flags");
112
+ return;
31
113
  }
32
114
 
33
115
  /**
34
- * Check if feature flags have been fetched and are ready to use.
35
- * @returns {boolean} True if flags are ready, false otherwise
116
+ * Check if feature flags have been fetched from the server and are ready to use.
117
+ *
118
+ * <p>This method returns true after feature flags have been successfully loaded via {@link loadFlags}
119
+ * or during initialization. When flags are ready, you can safely use the synchronous methods
120
+ * ({@link getVariantSync}, {@link getVariantValueSync}, {@link isEnabledSync}) without waiting.
121
+ *
122
+ * <p>It's recommended to check this before using synchronous methods to ensure you're not
123
+ * getting fallback values due to flags not being loaded yet.
124
+ *
125
+ * @returns {boolean} true if flags have been loaded and are ready to use, false otherwise
126
+ *
127
+ * @example
128
+ * // Check before using synchronous methods
129
+ * if (mixpanel.flags.areFlagsReady()) {
130
+ * const isEnabled = mixpanel.flags.isEnabledSync('new-feature', false);
131
+ * } else {
132
+ * console.log('Flags not ready yet, using fallback');
133
+ * }
134
+ *
135
+ * @example
136
+ * // Wait for flags to be ready
137
+ * await mixpanel.flags.loadFlags();
138
+ * if (mixpanel.flags.areFlagsReady()) {
139
+ * // Now safe to use sync methods
140
+ * }
36
141
  */
37
142
  areFlagsReady() {
38
143
  if (this.isNativeMode) {
@@ -44,10 +149,51 @@ export class Flags {
44
149
  }
45
150
 
46
151
  /**
47
- * Get a feature flag variant synchronously. Only works when flags are ready.
48
- * @param {string} featureName - Name of the feature flag
49
- * @param {object} fallback - Fallback variant if flag is not available
50
- * @returns {object} The flag variant with key and value properties
152
+ * Get a feature flag variant synchronously.
153
+ *
154
+ * <p>Returns the complete variant object for a feature flag, including both the variant key
155
+ * (e.g., "control", "treatment") and the variant value (the actual configuration data).
156
+ *
157
+ * <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
158
+ * Always check {@link areFlagsReady} first, or use the asynchronous {@link getVariant} method instead.
159
+ *
160
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
161
+ * "$experiment_started" event with relevant experiment metadata.
162
+ *
163
+ * @param {string} featureName The unique identifier for the feature flag
164
+ * @param {object} fallback The fallback variant object to return if the flag is not available.
165
+ * Must include both 'key' and 'value' properties.
166
+ * @returns {object} The flag variant object with the following structure:
167
+ * - key: {string} The variant key (e.g., "control", "treatment")
168
+ * - value: {any} The variant value (can be any JSON-serializable type)
169
+ * - experiment_id: {string|number} (optional) The experiment ID if this is an experiment
170
+ * - is_experiment_active: {boolean} (optional) Whether the experiment is currently active
171
+ *
172
+ * @example
173
+ * // Get a checkout flow variant
174
+ * if (mixpanel.flags.areFlagsReady()) {
175
+ * const variant = mixpanel.flags.getVariantSync('checkout-flow', {
176
+ * key: 'control',
177
+ * value: 'standard'
178
+ * });
179
+ * console.log(`Using variant: ${variant.key}`);
180
+ * console.log(`Configuration: ${JSON.stringify(variant.value)}`);
181
+ * }
182
+ *
183
+ * @example
184
+ * // Get a complex configuration variant
185
+ * const defaultConfig = {
186
+ * key: 'default',
187
+ * value: {
188
+ * theme: 'light',
189
+ * layout: 'grid',
190
+ * itemsPerPage: 20
191
+ * }
192
+ * };
193
+ * const config = mixpanel.flags.getVariantSync('ui-config', defaultConfig);
194
+ *
195
+ * @see getVariant for asynchronous access
196
+ * @see getVariantValueSync to get only the value (not the full variant object)
51
197
  */
52
198
  getVariantSync(featureName, fallback) {
53
199
  if (!this.areFlagsReady()) {
@@ -63,10 +209,45 @@ export class Flags {
63
209
  }
64
210
 
65
211
  /**
66
- * Get a feature flag variant value synchronously. Only works when flags are ready.
67
- * @param {string} featureName - Name of the feature flag
68
- * @param {any} fallbackValue - Fallback value if flag is not available
69
- * @returns {any} The flag value
212
+ * Get a feature flag variant value synchronously.
213
+ *
214
+ * <p>Returns only the value portion of a feature flag variant, without the variant key or metadata.
215
+ * This is useful when you only care about the configuration data, not which variant was selected.
216
+ *
217
+ * <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
218
+ * Always check {@link areFlagsReady} first, or use the asynchronous {@link getVariantValue} method instead.
219
+ *
220
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
221
+ * "$experiment_started" event with relevant experiment metadata.
222
+ *
223
+ * @param {string} featureName The unique identifier for the feature flag
224
+ * @param {any} fallbackValue The fallback value to return if the flag is not available.
225
+ * Can be any JSON-serializable type (string, number, boolean, object, array, etc.)
226
+ * @returns {any} The flag's value, or the fallback if the flag is not available.
227
+ * The return type matches the type of value configured in your Mixpanel project.
228
+ *
229
+ * @example
230
+ * // Get a simple string value
231
+ * if (mixpanel.flags.areFlagsReady()) {
232
+ * const buttonColor = mixpanel.flags.getVariantValueSync('button-color', 'blue');
233
+ * applyButtonColor(buttonColor);
234
+ * }
235
+ *
236
+ * @example
237
+ * // Get a complex object value
238
+ * const defaultPricing = { price: 9.99, currency: 'USD', trial_days: 7 };
239
+ * const pricing = mixpanel.flags.getVariantValueSync('pricing-config', defaultPricing);
240
+ * console.log(`Price: ${pricing.price} ${pricing.currency}`);
241
+ *
242
+ * @example
243
+ * // Get a boolean value
244
+ * const showPromo = mixpanel.flags.getVariantValueSync('show-promo', false);
245
+ * if (showPromo) {
246
+ * displayPromotionalBanner();
247
+ * }
248
+ *
249
+ * @see getVariantValue for asynchronous access
250
+ * @see getVariantSync to get the full variant object including key and metadata
70
251
  */
71
252
  getVariantValueSync(featureName, fallbackValue) {
72
253
  if (!this.areFlagsReady()) {
@@ -89,10 +270,39 @@ export class Flags {
89
270
  }
90
271
 
91
272
  /**
92
- * Check if a feature flag is enabled synchronously. Only works when flags are ready.
93
- * @param {string} featureName - Name of the feature flag
94
- * @param {boolean} fallbackValue - Fallback value if flag is not available
95
- * @returns {boolean} True if enabled, false otherwise
273
+ * Check if a feature flag is enabled synchronously.
274
+ *
275
+ * <p>This is a convenience method for boolean feature flags. It checks if a feature is enabled
276
+ * by evaluating the variant value as a boolean. A feature is considered "enabled" when its
277
+ * variant value evaluates to true.
278
+ *
279
+ * <p><b>Important:</b> This is a synchronous method that only works when flags are ready.
280
+ * Always check {@link areFlagsReady} first, or use the asynchronous {@link isEnabled} method instead.
281
+ *
282
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
283
+ * "$experiment_started" event with relevant experiment metadata.
284
+ *
285
+ * @param {string} featureName The unique identifier for the feature flag
286
+ * @param {boolean} [fallbackValue=false] The fallback value to return if the flag is not available.
287
+ * Defaults to false if not provided.
288
+ * @returns {boolean} true if the feature is enabled, false otherwise
289
+ *
290
+ * @example
291
+ * // Simple feature toggle
292
+ * if (mixpanel.flags.areFlagsReady()) {
293
+ * if (mixpanel.flags.isEnabledSync('new-checkout', false)) {
294
+ * showNewCheckout();
295
+ * } else {
296
+ * showLegacyCheckout();
297
+ * }
298
+ * }
299
+ *
300
+ * @example
301
+ * // With explicit fallback
302
+ * const enableBetaFeatures = mixpanel.flags.isEnabledSync('beta-features', true);
303
+ *
304
+ * @see isEnabled for asynchronous access
305
+ * @see getVariantValueSync for non-boolean flag values
96
306
  */
97
307
  isEnabledSync(featureName, fallbackValue = false) {
98
308
  if (!this.areFlagsReady()) {
@@ -109,11 +319,47 @@ export class Flags {
109
319
 
110
320
  /**
111
321
  * Get a feature flag variant asynchronously.
112
- * Supports both callback and Promise patterns.
113
- * @param {string} featureName - Name of the feature flag
114
- * @param {object} fallback - Fallback variant if flag is not available
115
- * @param {function} callback - Optional callback function
116
- * @returns {Promise|void} Promise if no callback provided, void otherwise
322
+ *
323
+ * <p>Returns the complete variant object for a feature flag, including both the variant key
324
+ * and the variant value. This method works regardless of whether flags are ready, making it
325
+ * safe to use at any time.
326
+ *
327
+ * <p>Supports both Promise and callback patterns for maximum flexibility.
328
+ *
329
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
330
+ * "$experiment_started" event with relevant experiment metadata.
331
+ *
332
+ * @param {string} featureName The unique identifier for the feature flag
333
+ * @param {object} fallback The fallback variant object to return if the flag is not available.
334
+ * Must include both 'key' and 'value' properties.
335
+ * @param {function} [callback] Optional callback function that receives the variant object.
336
+ * If provided, the method returns void. If omitted, the method returns a Promise.
337
+ * @returns {Promise<object>|void} Promise that resolves to the variant object if no callback provided,
338
+ * void if callback is provided. The variant object has the following structure:
339
+ * - key: {string} The variant key (e.g., "control", "treatment")
340
+ * - value: {any} The variant value (can be any JSON-serializable type)
341
+ * - experiment_id: {string|number} (optional) The experiment ID if this is an experiment
342
+ * - is_experiment_active: {boolean} (optional) Whether the experiment is currently active
343
+ *
344
+ * @example
345
+ * // Promise pattern (recommended)
346
+ * const variant = await mixpanel.flags.getVariant('checkout-flow', {
347
+ * key: 'control',
348
+ * value: 'standard'
349
+ * });
350
+ * console.log(`Using ${variant.key}: ${variant.value}`);
351
+ *
352
+ * @example
353
+ * // Callback pattern
354
+ * mixpanel.flags.getVariant('pricing-test', {
355
+ * key: 'default',
356
+ * value: { price: 9.99 }
357
+ * }, (variant) => {
358
+ * console.log(`Price: ${variant.value.price}`);
359
+ * });
360
+ *
361
+ * @see getVariantSync for synchronous access when flags are ready
362
+ * @see getVariantValue to get only the value without the variant key
117
363
  */
118
364
  getVariant(featureName, fallback, callback) {
119
365
  // If callback provided, use callback pattern
@@ -121,11 +367,17 @@ export class Flags {
121
367
  if (this.isNativeMode) {
122
368
  this.mixpanelImpl.getVariant(this.token, featureName, fallback)
123
369
  .then(result => callback(result))
124
- .catch(() => callback(fallback));
370
+ .catch((error) => {
371
+ MixpanelLogger.error(this.token, `Failed to get variant for ${featureName}:`, error);
372
+ callback(fallback);
373
+ });
125
374
  } else if (this.jsFlags) {
126
375
  this.jsFlags.getVariant(featureName, fallback)
127
376
  .then(result => callback(result))
128
- .catch(() => callback(fallback));
377
+ .catch((error) => {
378
+ MixpanelLogger.error(this.token, `Failed to get variant for ${featureName}:`, error);
379
+ callback(fallback);
380
+ });
129
381
  } else {
130
382
  callback(fallback);
131
383
  }
@@ -137,11 +389,17 @@ export class Flags {
137
389
  if (this.isNativeMode) {
138
390
  this.mixpanelImpl.getVariant(this.token, featureName, fallback)
139
391
  .then(resolve)
140
- .catch(() => resolve(fallback));
392
+ .catch((error) => {
393
+ MixpanelLogger.error(this.token, `Failed to get variant for ${featureName}:`, error);
394
+ resolve(fallback);
395
+ });
141
396
  } else if (this.jsFlags) {
142
397
  this.jsFlags.getVariant(featureName, fallback)
143
398
  .then(resolve)
144
- .catch(() => resolve(fallback));
399
+ .catch((error) => {
400
+ MixpanelLogger.error(this.token, `Failed to get variant for ${featureName}:`, error);
401
+ resolve(fallback);
402
+ });
145
403
  } else {
146
404
  resolve(fallback);
147
405
  }
@@ -150,11 +408,45 @@ export class Flags {
150
408
 
151
409
  /**
152
410
  * Get a feature flag variant value asynchronously.
153
- * Supports both callback and Promise patterns.
154
- * @param {string} featureName - Name of the feature flag
155
- * @param {any} fallbackValue - Fallback value if flag is not available
156
- * @param {function} callback - Optional callback function
157
- * @returns {Promise|void} Promise if no callback provided, void otherwise
411
+ *
412
+ * <p>Returns only the value portion of a feature flag variant. This method works regardless
413
+ * of whether flags are ready, making it safe to use at any time.
414
+ *
415
+ * <p>Supports both Promise and callback patterns for maximum flexibility.
416
+ *
417
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
418
+ * "$experiment_started" event with relevant experiment metadata.
419
+ *
420
+ * @param {string} featureName The unique identifier for the feature flag
421
+ * @param {any} fallbackValue The fallback value to return if the flag is not available.
422
+ * Can be any JSON-serializable type (string, number, boolean, object, array, etc.)
423
+ * @param {function} [callback] Optional callback function that receives the flag value.
424
+ * If provided, the method returns void. If omitted, the method returns a Promise.
425
+ * @returns {Promise<any>|void} Promise that resolves to the flag value if no callback provided,
426
+ * void if callback is provided. The return type matches the type of value configured in
427
+ * your Mixpanel project.
428
+ *
429
+ * @example
430
+ * // Promise pattern (recommended)
431
+ * const buttonColor = await mixpanel.flags.getVariantValue('button-color', 'blue');
432
+ * applyButtonColor(buttonColor);
433
+ *
434
+ * @example
435
+ * // Promise pattern with object value
436
+ * const pricing = await mixpanel.flags.getVariantValue('pricing-config', {
437
+ * price: 9.99,
438
+ * currency: 'USD'
439
+ * });
440
+ * displayPrice(pricing.price, pricing.currency);
441
+ *
442
+ * @example
443
+ * // Callback pattern
444
+ * mixpanel.flags.getVariantValue('theme', 'light', (theme) => {
445
+ * applyTheme(theme);
446
+ * });
447
+ *
448
+ * @see getVariantValueSync for synchronous access when flags are ready
449
+ * @see getVariant to get the full variant object including key and metadata
158
450
  */
159
451
  getVariantValue(featureName, fallbackValue, callback) {
160
452
  // If callback provided, use callback pattern
@@ -162,11 +454,17 @@ export class Flags {
162
454
  if (this.isNativeMode) {
163
455
  this.mixpanelImpl.getVariantValue(this.token, featureName, fallbackValue)
164
456
  .then(result => callback(result))
165
- .catch(() => callback(fallbackValue));
457
+ .catch((error) => {
458
+ MixpanelLogger.error(this.token, `Failed to get variant value for ${featureName}:`, error);
459
+ callback(fallbackValue);
460
+ });
166
461
  } else if (this.jsFlags) {
167
462
  this.jsFlags.getVariantValue(featureName, fallbackValue)
168
463
  .then(result => callback(result))
169
- .catch(() => callback(fallbackValue));
464
+ .catch((error) => {
465
+ MixpanelLogger.error(this.token, `Failed to get variant value for ${featureName}:`, error);
466
+ callback(fallbackValue);
467
+ });
170
468
  } else {
171
469
  callback(fallbackValue);
172
470
  }
@@ -178,11 +476,17 @@ export class Flags {
178
476
  if (this.isNativeMode) {
179
477
  this.mixpanelImpl.getVariantValue(this.token, featureName, fallbackValue)
180
478
  .then(resolve)
181
- .catch(() => resolve(fallbackValue));
479
+ .catch((error) => {
480
+ MixpanelLogger.error(this.token, `Failed to get variant value for ${featureName}:`, error);
481
+ resolve(fallbackValue);
482
+ });
182
483
  } else if (this.jsFlags) {
183
484
  this.jsFlags.getVariantValue(featureName, fallbackValue)
184
485
  .then(resolve)
185
- .catch(() => resolve(fallbackValue));
486
+ .catch((error) => {
487
+ MixpanelLogger.error(this.token, `Failed to get variant value for ${featureName}:`, error);
488
+ resolve(fallbackValue);
489
+ });
186
490
  } else {
187
491
  resolve(fallbackValue);
188
492
  }
@@ -191,11 +495,47 @@ export class Flags {
191
495
 
192
496
  /**
193
497
  * Check if a feature flag is enabled asynchronously.
194
- * Supports both callback and Promise patterns.
195
- * @param {string} featureName - Name of the feature flag
196
- * @param {boolean} [fallbackValue=false] - Fallback value if flag is not available
197
- * @param {function} [callback] - Optional callback function
198
- * @returns {Promise<boolean>|void} Promise if no callback provided, void otherwise
498
+ *
499
+ * <p>This is a convenience method for boolean feature flags. It checks if a feature is enabled
500
+ * by evaluating the variant value as a boolean. This method works regardless of whether flags
501
+ * are ready, making it safe to use at any time.
502
+ *
503
+ * <p>Supports both Promise and callback patterns for maximum flexibility.
504
+ *
505
+ * <p>When a flag is evaluated for the first time, Mixpanel automatically tracks a
506
+ * "$experiment_started" event with relevant experiment metadata.
507
+ *
508
+ * @param {string} featureName The unique identifier for the feature flag
509
+ * @param {boolean} [fallbackValue=false] The fallback value to return if the flag is not available.
510
+ * Defaults to false if not provided.
511
+ * @param {function} [callback] Optional callback function that receives the boolean result.
512
+ * If provided, the method returns void. If omitted, the method returns a Promise.
513
+ * @returns {Promise<boolean>|void} Promise that resolves to true if enabled, false otherwise
514
+ * (when no callback provided). Returns void if callback is provided.
515
+ *
516
+ * @example
517
+ * // Promise pattern (recommended)
518
+ * const isEnabled = await mixpanel.flags.isEnabled('new-checkout', false);
519
+ * if (isEnabled) {
520
+ * showNewCheckout();
521
+ * } else {
522
+ * showLegacyCheckout();
523
+ * }
524
+ *
525
+ * @example
526
+ * // Callback pattern
527
+ * mixpanel.flags.isEnabled('beta-features', false, (isEnabled) => {
528
+ * if (isEnabled) {
529
+ * enableBetaFeatures();
530
+ * }
531
+ * });
532
+ *
533
+ * @example
534
+ * // Default fallback (false)
535
+ * const showPromo = await mixpanel.flags.isEnabled('show-promo');
536
+ *
537
+ * @see isEnabledSync for synchronous access when flags are ready
538
+ * @see getVariantValue for non-boolean flag values
199
539
  */
200
540
  isEnabled(featureName, fallbackValue = false, callback) {
201
541
  // If callback provided, use callback pattern
@@ -203,11 +543,17 @@ export class Flags {
203
543
  if (this.isNativeMode) {
204
544
  this.mixpanelImpl.isEnabled(this.token, featureName, fallbackValue)
205
545
  .then(result => callback(result))
206
- .catch(() => callback(fallbackValue));
546
+ .catch((error) => {
547
+ MixpanelLogger.error(this.token, `Failed to check if ${featureName} is enabled:`, error);
548
+ callback(fallbackValue);
549
+ });
207
550
  } else if (this.jsFlags) {
208
551
  this.jsFlags.isEnabled(featureName, fallbackValue)
209
552
  .then(result => callback(result))
210
- .catch(() => callback(fallbackValue));
553
+ .catch((error) => {
554
+ MixpanelLogger.error(this.token, `Failed to check if ${featureName} is enabled:`, error);
555
+ callback(fallbackValue);
556
+ });
211
557
  } else {
212
558
  callback(fallbackValue);
213
559
  }
@@ -219,11 +565,17 @@ export class Flags {
219
565
  if (this.isNativeMode) {
220
566
  this.mixpanelImpl.isEnabled(this.token, featureName, fallbackValue)
221
567
  .then(resolve)
222
- .catch(() => resolve(fallbackValue));
568
+ .catch((error) => {
569
+ MixpanelLogger.error(this.token, `Failed to check if ${featureName} is enabled:`, error);
570
+ resolve(fallbackValue);
571
+ });
223
572
  } else if (this.jsFlags) {
224
573
  this.jsFlags.isEnabled(featureName, fallbackValue)
225
574
  .then(resolve)
226
- .catch(() => resolve(fallbackValue));
575
+ .catch((error) => {
576
+ MixpanelLogger.error(this.token, `Failed to check if ${featureName} is enabled:`, error);
577
+ resolve(fallbackValue);
578
+ });
227
579
  } else {
228
580
  resolve(fallbackValue);
229
581
  }
@@ -231,16 +583,50 @@ export class Flags {
231
583
  }
232
584
 
233
585
  /**
234
- * Update the context used for feature flag evaluation
235
- * Aligned with mixpanel-js API
586
+ * Update the context used for feature flag evaluation.
587
+ *
588
+ * <p>Context properties are used to determine which feature flag variants a user should receive
589
+ * based on targeting rules configured in your Mixpanel project. This allows for personalized
590
+ * feature experiences based on user attributes, device properties, or custom criteria.
591
+ *
592
+ * <p><b>IMPORTANT LIMITATION:</b> This method is <b>only available in JavaScript mode</b>
593
+ * (Expo/React Native Web). In native mode (iOS/Android), context must be set during initialization
594
+ * via {@link Mixpanel#init} and cannot be updated at runtime.
595
+ *
596
+ * <p>By default, the new context properties are merged with existing context. Set
597
+ * <code>options.replace = true</code> to completely replace the context instead.
236
598
  *
237
- * NOTE: This method is only available in JavaScript mode (Expo/React Native Web).
238
- * In native mode, context must be set during initialization via FeatureFlagsOptions.
599
+ * @param {object} newContext New context properties to add or update. Can include any
600
+ * JSON-serializable properties that are used in your feature flag targeting rules.
601
+ * Common examples include user tier, region, platform version, etc.
602
+ * @param {object} [options={replace: false}] Configuration options for the update
603
+ * @param {boolean} [options.replace=false] If true, replaces the entire context instead of merging.
604
+ * If false (default), merges new properties with existing context.
605
+ * @returns {Promise<void>} A promise that resolves when the context has been updated and
606
+ * flags have been re-evaluated with the new context
607
+ * @throws {Error} if called in native mode (iOS/Android)
239
608
  *
240
- * @param {object} newContext - New context properties to add/update
241
- * @param {object} options - Options object
242
- * @param {boolean} options.replace - If true, replace entire context instead of merging
243
- * @returns {Promise<void>}
609
+ * @example
610
+ * // Merge new properties into existing context (JavaScript mode only)
611
+ * await mixpanel.flags.updateContext({
612
+ * user_tier: 'premium',
613
+ * region: 'us-west'
614
+ * });
615
+ *
616
+ * @example
617
+ * // Replace entire context (JavaScript mode only)
618
+ * await mixpanel.flags.updateContext({
619
+ * device_type: 'tablet',
620
+ * os_version: '14.0'
621
+ * }, { replace: true });
622
+ *
623
+ * @example
624
+ * // This will throw an error in native mode
625
+ * try {
626
+ * await mixpanel.flags.updateContext({ tier: 'premium' });
627
+ * } catch (error) {
628
+ * console.error('Context updates not supported in native mode');
629
+ * }
244
630
  */
245
631
  async updateContext(newContext, options = { replace: false }) {
246
632
  if (this.isNativeMode) {
@@ -256,34 +642,68 @@ export class Flags {
256
642
  }
257
643
 
258
644
  // snake_case aliases for API consistency with mixpanel-js
645
+
646
+ /**
647
+ * Alias for {@link areFlagsReady}. Provided for API consistency with mixpanel-js.
648
+ * @see areFlagsReady
649
+ */
259
650
  are_flags_ready() {
260
651
  return this.areFlagsReady();
261
652
  }
262
653
 
654
+ /**
655
+ * Alias for {@link getVariant}. Provided for API consistency with mixpanel-js.
656
+ * @see getVariant
657
+ */
263
658
  get_variant(featureName, fallback, callback) {
264
659
  return this.getVariant(featureName, fallback, callback);
265
660
  }
266
661
 
662
+ /**
663
+ * Alias for {@link getVariantSync}. Provided for API consistency with mixpanel-js.
664
+ * @see getVariantSync
665
+ */
267
666
  get_variant_sync(featureName, fallback) {
268
667
  return this.getVariantSync(featureName, fallback);
269
668
  }
270
669
 
670
+ /**
671
+ * Alias for {@link getVariantValue}. Provided for API consistency with mixpanel-js.
672
+ * @see getVariantValue
673
+ */
271
674
  get_variant_value(featureName, fallbackValue, callback) {
272
675
  return this.getVariantValue(featureName, fallbackValue, callback);
273
676
  }
274
677
 
678
+ /**
679
+ * Alias for {@link getVariantValueSync}. Provided for API consistency with mixpanel-js.
680
+ * @see getVariantValueSync
681
+ */
275
682
  get_variant_value_sync(featureName, fallbackValue) {
276
683
  return this.getVariantValueSync(featureName, fallbackValue);
277
684
  }
278
685
 
686
+ /**
687
+ * Alias for {@link isEnabled}. Provided for API consistency with mixpanel-js.
688
+ * @see isEnabled
689
+ */
279
690
  is_enabled(featureName, fallbackValue, callback) {
280
691
  return this.isEnabled(featureName, fallbackValue, callback);
281
692
  }
282
693
 
694
+ /**
695
+ * Alias for {@link isEnabledSync}. Provided for API consistency with mixpanel-js.
696
+ * @see isEnabledSync
697
+ */
283
698
  is_enabled_sync(featureName, fallbackValue) {
284
699
  return this.isEnabledSync(featureName, fallbackValue);
285
700
  }
286
701
 
702
+ /**
703
+ * Alias for {@link updateContext}. Provided for API consistency with mixpanel-js.
704
+ * JavaScript mode only.
705
+ * @see updateContext
706
+ */
287
707
  update_context(newContext, options) {
288
708
  return this.updateContext(newContext, options);
289
709
  }