mixpanel-browser 2.77.0 → 2.78.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.
Files changed (41) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/CHANGELOG.md +4 -0
  3. package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js → mixpanel-recorder-BjSlYaNJ.min.js} +2 -2
  4. package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js.map → mixpanel-recorder-BjSlYaNJ.min.js.map} +1 -1
  5. package/dist/async-modules/{mixpanel-recorder-DLKbUIEE.js → mixpanel-recorder-zMBXIyeG.js} +1 -1
  6. package/dist/async-modules/{mixpanel-targeting-CTcftSJC.min.js → mixpanel-targeting-BSHal4N9.min.js} +2 -2
  7. package/dist/async-modules/{mixpanel-targeting-CTcftSJC.min.js.map → mixpanel-targeting-BSHal4N9.min.js.map} +1 -1
  8. package/dist/async-modules/{mixpanel-targeting-CmVvUyFM.js → mixpanel-targeting-UHf4eBfC.js} +1 -1
  9. package/dist/mixpanel-core.cjs.d.ts +1 -0
  10. package/dist/mixpanel-core.cjs.js +111 -80
  11. package/dist/mixpanel-recorder.js +1 -1
  12. package/dist/mixpanel-recorder.min.js +1 -1
  13. package/dist/mixpanel-recorder.min.js.map +1 -1
  14. package/dist/mixpanel-targeting.js +1 -1
  15. package/dist/mixpanel-targeting.min.js +1 -1
  16. package/dist/mixpanel-targeting.min.js.map +1 -1
  17. package/dist/mixpanel-with-async-modules.cjs.d.ts +1 -0
  18. package/dist/mixpanel-with-async-modules.cjs.js +113 -82
  19. package/dist/mixpanel-with-async-recorder.cjs.d.ts +1 -0
  20. package/dist/mixpanel-with-async-recorder.cjs.js +113 -82
  21. package/dist/mixpanel-with-recorder.d.ts +1 -0
  22. package/dist/mixpanel-with-recorder.js +111 -80
  23. package/dist/mixpanel-with-recorder.min.d.ts +1 -0
  24. package/dist/mixpanel-with-recorder.min.js +1 -1
  25. package/dist/mixpanel.amd.d.ts +1 -0
  26. package/dist/mixpanel.amd.js +111 -80
  27. package/dist/mixpanel.cjs.d.ts +1 -0
  28. package/dist/mixpanel.cjs.js +111 -80
  29. package/dist/mixpanel.globals.js +113 -82
  30. package/dist/mixpanel.min.js +180 -179
  31. package/dist/mixpanel.module.d.ts +1 -0
  32. package/dist/mixpanel.module.js +111 -80
  33. package/dist/mixpanel.umd.d.ts +1 -0
  34. package/dist/mixpanel.umd.js +111 -80
  35. package/package.json +1 -1
  36. package/src/config.js +1 -1
  37. package/src/flags/CLAUDE.md +24 -0
  38. package/src/flags/index.js +109 -80
  39. package/src/index.d.ts +1 -0
  40. package/src/mixpanel-core.js +3 -1
  41. package/testServer.js +2 -0
@@ -357,6 +357,7 @@ export interface FlagsUpdateContextOptions {
357
357
 
358
358
  export interface FlagsManager {
359
359
  are_flags_ready(): boolean;
360
+ load_flags(): Promise<void>;
360
361
  get_variant(
361
362
  featureName: string,
362
363
  fallback: FlagsVariant
@@ -25,7 +25,7 @@ if (typeof(window) === 'undefined') {
25
25
 
26
26
  var Config = {
27
27
  DEBUG: false,
28
- LIB_VERSION: '2.77.0'
28
+ LIB_VERSION: '2.78.0'
29
29
  };
30
30
 
31
31
  // Window global names for async modules
@@ -26191,7 +26191,9 @@ FeatureFlagManager.prototype.init = function() {
26191
26191
  }
26192
26192
 
26193
26193
  this.flags = null;
26194
- this.fetchFlags();
26194
+ this.fetchFlags().catch(function() {
26195
+ logger$1.error('Error fetching flags during init');
26196
+ });
26195
26197
 
26196
26198
  this.trackedFeatures = new Set();
26197
26199
  this.pendingFirstTimeEvents = {};
@@ -26232,8 +26234,12 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
26232
26234
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
26233
26235
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
26234
26236
 
26235
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
26236
- return this.fetchFlags();
26237
+ var configUpdate = {};
26238
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
26239
+ this.setMpConfig(configUpdate);
26240
+ return this.fetchFlags().catch(function() {
26241
+ logger$1.error('Error fetching flags during updateContext');
26242
+ });
26237
26243
  };
26238
26244
 
26239
26245
  FeatureFlagManager.prototype.areFlagsReady = function() {
@@ -26270,96 +26276,110 @@ FeatureFlagManager.prototype.fetchFlags = function() {
26270
26276
  }
26271
26277
  }).then(function(response) {
26272
26278
  this.markFetchComplete();
26273
- return response.json().then(function(responseBody) {
26274
- var responseFlags = responseBody['flags'];
26275
- if (!responseFlags) {
26276
- throw new Error('No flags in API response');
26277
- }
26278
- var flags = new Map();
26279
- var pendingFirstTimeEvents = {};
26280
-
26281
- // Process flags from response
26282
- _.each(responseFlags, function(data, key) {
26283
- // Check if this flag has any activated first-time events this session
26284
- var hasActivatedEvent = false;
26285
- var prefix = key + ':';
26286
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26287
- if (eventKey.startsWith(prefix)) {
26288
- hasActivatedEvent = true;
26289
- }
26290
- });
26279
+ return response.json();
26280
+ }.bind(this)).then(function(responseBody) {
26281
+ var responseFlags = responseBody['flags'];
26282
+ if (!responseFlags) {
26283
+ throw new Error('No flags in API response');
26284
+ }
26285
+ var flags = new Map();
26286
+ var pendingFirstTimeEvents = {};
26287
+
26288
+ // Process flags from response
26289
+ _.each(responseFlags, function(data, key) {
26290
+ // Check if this flag has any activated first-time events this session
26291
+ var hasActivatedEvent = false;
26292
+ var prefix = key + ':';
26293
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26294
+ if (eventKey.startsWith(prefix)) {
26295
+ hasActivatedEvent = true;
26296
+ }
26297
+ });
26291
26298
 
26292
- if (hasActivatedEvent) {
26293
- // Preserve the activated variant, don't overwrite with server's current variant
26294
- var currentFlag = this.flags && this.flags.get(key);
26295
- if (currentFlag) {
26296
- flags.set(key, currentFlag);
26297
- }
26298
- } else {
26299
- // Use server's current variant
26300
- flags.set(key, {
26301
- 'key': data['variant_key'],
26302
- 'value': data['variant_value'],
26303
- 'experiment_id': data['experiment_id'],
26304
- 'is_experiment_active': data['is_experiment_active'],
26305
- 'is_qa_tester': data['is_qa_tester']
26306
- });
26299
+ if (hasActivatedEvent) {
26300
+ // Preserve the activated variant, don't overwrite with server's current variant
26301
+ var currentFlag = this.flags && this.flags.get(key);
26302
+ if (currentFlag) {
26303
+ flags.set(key, currentFlag);
26307
26304
  }
26308
- }, this);
26305
+ } else {
26306
+ // Use server's current variant
26307
+ flags.set(key, {
26308
+ 'key': data['variant_key'],
26309
+ 'value': data['variant_value'],
26310
+ 'experiment_id': data['experiment_id'],
26311
+ 'is_experiment_active': data['is_experiment_active'],
26312
+ 'is_qa_tester': data['is_qa_tester']
26313
+ });
26314
+ }
26315
+ }, this);
26309
26316
 
26310
- // Process top-level pending_first_time_events array
26311
- var topLevelDefinitions = responseBody['pending_first_time_events'];
26312
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26313
- _.each(topLevelDefinitions, function(def) {
26314
- var flagKey = def['flag_key'];
26315
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26317
+ // Process top-level pending_first_time_events array
26318
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
26319
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26320
+ _.each(topLevelDefinitions, function(def) {
26321
+ var flagKey = def['flag_key'];
26322
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26316
26323
 
26317
- // Skip if this specific event has already been activated this session
26318
- if (this.activatedFirstTimeEvents[eventKey]) {
26319
- return;
26320
- }
26324
+ // Skip if this specific event has already been activated this session
26325
+ if (this.activatedFirstTimeEvents[eventKey]) {
26326
+ return;
26327
+ }
26321
26328
 
26322
- // Store pending event definition using composite key
26323
- pendingFirstTimeEvents[eventKey] = {
26324
- 'flag_key': flagKey,
26325
- 'flag_id': def['flag_id'],
26326
- 'project_id': def['project_id'],
26327
- 'first_time_event_hash': def['first_time_event_hash'],
26328
- 'event_name': def['event_name'],
26329
- 'property_filters': def['property_filters'],
26330
- 'pending_variant': def['pending_variant']
26331
- };
26332
- }, this);
26333
- }
26329
+ // Store pending event definition using composite key
26330
+ pendingFirstTimeEvents[eventKey] = {
26331
+ 'flag_key': flagKey,
26332
+ 'flag_id': def['flag_id'],
26333
+ 'project_id': def['project_id'],
26334
+ 'first_time_event_hash': def['first_time_event_hash'],
26335
+ 'event_name': def['event_name'],
26336
+ 'property_filters': def['property_filters'],
26337
+ 'pending_variant': def['pending_variant']
26338
+ };
26339
+ }, this);
26340
+ }
26334
26341
 
26335
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26336
- if (this.activatedFirstTimeEvents) {
26337
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26338
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26339
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26340
- // Keep the activated flag even though it's not in the new response
26341
- flags.set(flagKey, this.flags.get(flagKey));
26342
- }
26343
- }, this);
26344
- }
26342
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26343
+ if (this.activatedFirstTimeEvents) {
26344
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26345
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26346
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26347
+ // Keep the activated flag even though it's not in the new response
26348
+ flags.set(flagKey, this.flags.get(flagKey));
26349
+ }
26350
+ }, this);
26351
+ }
26345
26352
 
26346
- this.flags = flags;
26347
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26348
- this._traceparent = traceparent;
26353
+ this.flags = flags;
26354
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26355
+ this._traceparent = traceparent;
26349
26356
 
26350
- this._loadTargetingIfNeeded();
26351
- }.bind(this)).catch(function(error) {
26352
- this.markFetchComplete();
26353
- logger$1.error(error);
26354
- }.bind(this));
26357
+ this._loadTargetingIfNeeded();
26355
26358
  }.bind(this)).catch(function(error) {
26356
- this.markFetchComplete();
26359
+ if (this._fetchInProgressStartTime) {
26360
+ this.markFetchComplete();
26361
+ }
26357
26362
  logger$1.error(error);
26363
+ throw error;
26358
26364
  }.bind(this));
26359
26365
 
26360
26366
  return this.fetchPromise;
26361
26367
  };
26362
26368
 
26369
+ FeatureFlagManager.prototype.loadFlags = function() {
26370
+ if (!this.isSystemEnabled()) {
26371
+ return Promise.resolve();
26372
+ }
26373
+ if (!this.trackedFeatures) {
26374
+ logger$1.error('loadFlags called before init');
26375
+ return Promise.resolve();
26376
+ }
26377
+ if (this._fetchInProgressStartTime) {
26378
+ return this.fetchPromise;
26379
+ }
26380
+ return this.fetchFlags();
26381
+ };
26382
+
26363
26383
  FeatureFlagManager.prototype.markFetchComplete = function() {
26364
26384
  if (!this._fetchInProgressStartTime) {
26365
26385
  logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
@@ -26639,6 +26659,13 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
26639
26659
  this.track('$experiment_started', trackingProperties);
26640
26660
  };
26641
26661
 
26662
+ FeatureFlagManager.prototype.whenReady = function() {
26663
+ if (this.fetchPromise) {
26664
+ return this.fetchPromise;
26665
+ }
26666
+ return Promise.resolve();
26667
+ };
26668
+
26642
26669
  FeatureFlagManager.prototype.minApisSupported = function() {
26643
26670
  return !!this.fetch &&
26644
26671
  typeof Promise !== 'undefined' &&
@@ -26655,7 +26682,9 @@ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype
26655
26682
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26656
26683
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
26657
26684
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
26685
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
26658
26686
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
26687
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
26659
26688
 
26660
26689
  // Deprecated method
26661
26690
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -30011,7 +30040,9 @@ MixpanelLib.prototype.identify = function(
30011
30040
 
30012
30041
  // check feature flags again if distinct id has changed
30013
30042
  if (new_distinct_id !== previous_distinct_id) {
30014
- this.flags.fetchFlags();
30043
+ this.flags.fetchFlags().catch(function() {
30044
+ console$1.error('[flags] Error fetching flags during identify');
30045
+ });
30015
30046
  }
30016
30047
  };
30017
30048
 
@@ -357,6 +357,7 @@ export interface FlagsUpdateContextOptions {
357
357
 
358
358
  export interface FlagsManager {
359
359
  are_flags_ready(): boolean;
360
+ load_flags(): Promise<void>;
360
361
  get_variant(
361
362
  featureName: string,
362
363
  fallback: FlagsVariant
@@ -31,7 +31,7 @@
31
31
 
32
32
  var Config = {
33
33
  DEBUG: false,
34
- LIB_VERSION: '2.77.0'
34
+ LIB_VERSION: '2.78.0'
35
35
  };
36
36
 
37
37
  // Window global names for async modules
@@ -26197,7 +26197,9 @@
26197
26197
  }
26198
26198
 
26199
26199
  this.flags = null;
26200
- this.fetchFlags();
26200
+ this.fetchFlags().catch(function() {
26201
+ logger$1.error('Error fetching flags during init');
26202
+ });
26201
26203
 
26202
26204
  this.trackedFeatures = new Set();
26203
26205
  this.pendingFirstTimeEvents = {};
@@ -26238,8 +26240,12 @@
26238
26240
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
26239
26241
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
26240
26242
 
26241
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
26242
- return this.fetchFlags();
26243
+ var configUpdate = {};
26244
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
26245
+ this.setMpConfig(configUpdate);
26246
+ return this.fetchFlags().catch(function() {
26247
+ logger$1.error('Error fetching flags during updateContext');
26248
+ });
26243
26249
  };
26244
26250
 
26245
26251
  FeatureFlagManager.prototype.areFlagsReady = function() {
@@ -26276,96 +26282,110 @@
26276
26282
  }
26277
26283
  }).then(function(response) {
26278
26284
  this.markFetchComplete();
26279
- return response.json().then(function(responseBody) {
26280
- var responseFlags = responseBody['flags'];
26281
- if (!responseFlags) {
26282
- throw new Error('No flags in API response');
26283
- }
26284
- var flags = new Map();
26285
- var pendingFirstTimeEvents = {};
26286
-
26287
- // Process flags from response
26288
- _.each(responseFlags, function(data, key) {
26289
- // Check if this flag has any activated first-time events this session
26290
- var hasActivatedEvent = false;
26291
- var prefix = key + ':';
26292
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26293
- if (eventKey.startsWith(prefix)) {
26294
- hasActivatedEvent = true;
26295
- }
26296
- });
26285
+ return response.json();
26286
+ }.bind(this)).then(function(responseBody) {
26287
+ var responseFlags = responseBody['flags'];
26288
+ if (!responseFlags) {
26289
+ throw new Error('No flags in API response');
26290
+ }
26291
+ var flags = new Map();
26292
+ var pendingFirstTimeEvents = {};
26293
+
26294
+ // Process flags from response
26295
+ _.each(responseFlags, function(data, key) {
26296
+ // Check if this flag has any activated first-time events this session
26297
+ var hasActivatedEvent = false;
26298
+ var prefix = key + ':';
26299
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26300
+ if (eventKey.startsWith(prefix)) {
26301
+ hasActivatedEvent = true;
26302
+ }
26303
+ });
26297
26304
 
26298
- if (hasActivatedEvent) {
26299
- // Preserve the activated variant, don't overwrite with server's current variant
26300
- var currentFlag = this.flags && this.flags.get(key);
26301
- if (currentFlag) {
26302
- flags.set(key, currentFlag);
26303
- }
26304
- } else {
26305
- // Use server's current variant
26306
- flags.set(key, {
26307
- 'key': data['variant_key'],
26308
- 'value': data['variant_value'],
26309
- 'experiment_id': data['experiment_id'],
26310
- 'is_experiment_active': data['is_experiment_active'],
26311
- 'is_qa_tester': data['is_qa_tester']
26312
- });
26305
+ if (hasActivatedEvent) {
26306
+ // Preserve the activated variant, don't overwrite with server's current variant
26307
+ var currentFlag = this.flags && this.flags.get(key);
26308
+ if (currentFlag) {
26309
+ flags.set(key, currentFlag);
26313
26310
  }
26314
- }, this);
26311
+ } else {
26312
+ // Use server's current variant
26313
+ flags.set(key, {
26314
+ 'key': data['variant_key'],
26315
+ 'value': data['variant_value'],
26316
+ 'experiment_id': data['experiment_id'],
26317
+ 'is_experiment_active': data['is_experiment_active'],
26318
+ 'is_qa_tester': data['is_qa_tester']
26319
+ });
26320
+ }
26321
+ }, this);
26315
26322
 
26316
- // Process top-level pending_first_time_events array
26317
- var topLevelDefinitions = responseBody['pending_first_time_events'];
26318
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26319
- _.each(topLevelDefinitions, function(def) {
26320
- var flagKey = def['flag_key'];
26321
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26323
+ // Process top-level pending_first_time_events array
26324
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
26325
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
26326
+ _.each(topLevelDefinitions, function(def) {
26327
+ var flagKey = def['flag_key'];
26328
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
26322
26329
 
26323
- // Skip if this specific event has already been activated this session
26324
- if (this.activatedFirstTimeEvents[eventKey]) {
26325
- return;
26326
- }
26330
+ // Skip if this specific event has already been activated this session
26331
+ if (this.activatedFirstTimeEvents[eventKey]) {
26332
+ return;
26333
+ }
26327
26334
 
26328
- // Store pending event definition using composite key
26329
- pendingFirstTimeEvents[eventKey] = {
26330
- 'flag_key': flagKey,
26331
- 'flag_id': def['flag_id'],
26332
- 'project_id': def['project_id'],
26333
- 'first_time_event_hash': def['first_time_event_hash'],
26334
- 'event_name': def['event_name'],
26335
- 'property_filters': def['property_filters'],
26336
- 'pending_variant': def['pending_variant']
26337
- };
26338
- }, this);
26339
- }
26335
+ // Store pending event definition using composite key
26336
+ pendingFirstTimeEvents[eventKey] = {
26337
+ 'flag_key': flagKey,
26338
+ 'flag_id': def['flag_id'],
26339
+ 'project_id': def['project_id'],
26340
+ 'first_time_event_hash': def['first_time_event_hash'],
26341
+ 'event_name': def['event_name'],
26342
+ 'property_filters': def['property_filters'],
26343
+ 'pending_variant': def['pending_variant']
26344
+ };
26345
+ }, this);
26346
+ }
26340
26347
 
26341
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26342
- if (this.activatedFirstTimeEvents) {
26343
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26344
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26345
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26346
- // Keep the activated flag even though it's not in the new response
26347
- flags.set(flagKey, this.flags.get(flagKey));
26348
- }
26349
- }, this);
26350
- }
26348
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
26349
+ if (this.activatedFirstTimeEvents) {
26350
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
26351
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
26352
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
26353
+ // Keep the activated flag even though it's not in the new response
26354
+ flags.set(flagKey, this.flags.get(flagKey));
26355
+ }
26356
+ }, this);
26357
+ }
26351
26358
 
26352
- this.flags = flags;
26353
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26354
- this._traceparent = traceparent;
26359
+ this.flags = flags;
26360
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
26361
+ this._traceparent = traceparent;
26355
26362
 
26356
- this._loadTargetingIfNeeded();
26357
- }.bind(this)).catch(function(error) {
26358
- this.markFetchComplete();
26359
- logger$1.error(error);
26360
- }.bind(this));
26363
+ this._loadTargetingIfNeeded();
26361
26364
  }.bind(this)).catch(function(error) {
26362
- this.markFetchComplete();
26365
+ if (this._fetchInProgressStartTime) {
26366
+ this.markFetchComplete();
26367
+ }
26363
26368
  logger$1.error(error);
26369
+ throw error;
26364
26370
  }.bind(this));
26365
26371
 
26366
26372
  return this.fetchPromise;
26367
26373
  };
26368
26374
 
26375
+ FeatureFlagManager.prototype.loadFlags = function() {
26376
+ if (!this.isSystemEnabled()) {
26377
+ return Promise.resolve();
26378
+ }
26379
+ if (!this.trackedFeatures) {
26380
+ logger$1.error('loadFlags called before init');
26381
+ return Promise.resolve();
26382
+ }
26383
+ if (this._fetchInProgressStartTime) {
26384
+ return this.fetchPromise;
26385
+ }
26386
+ return this.fetchFlags();
26387
+ };
26388
+
26369
26389
  FeatureFlagManager.prototype.markFetchComplete = function() {
26370
26390
  if (!this._fetchInProgressStartTime) {
26371
26391
  logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
@@ -26645,6 +26665,13 @@
26645
26665
  this.track('$experiment_started', trackingProperties);
26646
26666
  };
26647
26667
 
26668
+ FeatureFlagManager.prototype.whenReady = function() {
26669
+ if (this.fetchPromise) {
26670
+ return this.fetchPromise;
26671
+ }
26672
+ return Promise.resolve();
26673
+ };
26674
+
26648
26675
  FeatureFlagManager.prototype.minApisSupported = function() {
26649
26676
  return !!this.fetch &&
26650
26677
  typeof Promise !== 'undefined' &&
@@ -26661,7 +26688,9 @@
26661
26688
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26662
26689
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
26663
26690
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
26691
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
26664
26692
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
26693
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
26665
26694
 
26666
26695
  // Deprecated method
26667
26696
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -30017,7 +30046,9 @@
30017
30046
 
30018
30047
  // check feature flags again if distinct id has changed
30019
30048
  if (new_distinct_id !== previous_distinct_id) {
30020
- this.flags.fetchFlags();
30049
+ this.flags.fetchFlags().catch(function() {
30050
+ console$1.error('[flags] Error fetching flags during identify');
30051
+ });
30021
30052
  }
30022
30053
  };
30023
30054
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixpanel-browser",
3
- "version": "2.77.0",
3
+ "version": "2.78.0",
4
4
  "description": "The official Mixpanel JavaScript browser client library",
5
5
  "main": "dist/mixpanel.cjs.js",
6
6
  "module": "dist/mixpanel.module.js",
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export var Config = {
2
2
  DEBUG: false,
3
- LIB_VERSION: '2.77.0'
3
+ LIB_VERSION: '2.78.0'
4
4
  };
5
5
 
6
6
  // Window global names for async modules
@@ -0,0 +1,24 @@
1
+ # Flags Module
2
+
3
+ ## Testing
4
+ - Test runner is **mocha** (not jest): `BABEL_ENV=test npx mocha --require babel-core/register tests/unit/flags.js`
5
+ - Unit tests live in `tests/unit/flags.js`
6
+
7
+ ## Code style
8
+ - ES5 prototypal classes — no arrow functions, no ES6 classes
9
+ - Use `.bind(this)` for `this` context in promise `.then()` / `.catch()` callbacks
10
+ - Flat promise chains preferred over nested `.then()` inside `.then()`
11
+
12
+ ## Public API pattern
13
+ - Snake-case aliases are registered at the bottom of `index.js` (e.g., `prototype['load_flags'] = prototype.loadFlags`)
14
+ - New public methods need both the camelCase implementation and a snake_case alias
15
+
16
+ ## Error handling convention
17
+ - `fetchFlags()` always rejects on error (single `.catch` that logs and re-throws)
18
+ - Fire-and-forget callers (`init`, `updateContext`, `mixpanel-core.js` identify call) swallow errors at the call site with `.catch(function() {})`
19
+ - User-facing methods like `loadFlags` propagate rejections so the caller can handle them
20
+
21
+ ## Key files
22
+ - `src/flags/index.js` — `FeatureFlagManager` class (fetch, load, variants, first-time events)
23
+ - `src/mixpanel-core.js` — calls `fetchFlags()` on distinct_id change (~line 1764)
24
+ - `tests/unit/flags.js` — all flag unit tests