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
@@ -53,7 +53,9 @@ FeatureFlagManager.prototype.init = function() {
53
53
  }
54
54
 
55
55
  this.flags = null;
56
- this.fetchFlags();
56
+ this.fetchFlags().catch(function() {
57
+ logger.error('Error fetching flags during init');
58
+ });
57
59
 
58
60
  this.trackedFeatures = new Set();
59
61
  this.pendingFirstTimeEvents = {};
@@ -94,8 +96,12 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
94
96
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
95
97
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
96
98
 
97
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
98
- return this.fetchFlags();
99
+ var configUpdate = {};
100
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
101
+ this.setMpConfig(configUpdate);
102
+ return this.fetchFlags().catch(function() {
103
+ logger.error('Error fetching flags during updateContext');
104
+ });
99
105
  };
100
106
 
101
107
  FeatureFlagManager.prototype.areFlagsReady = function() {
@@ -132,96 +138,110 @@ FeatureFlagManager.prototype.fetchFlags = function() {
132
138
  }
133
139
  }).then(function(response) {
134
140
  this.markFetchComplete();
135
- return response.json().then(function(responseBody) {
136
- var responseFlags = responseBody['flags'];
137
- if (!responseFlags) {
138
- throw new Error('No flags in API response');
139
- }
140
- var flags = new Map();
141
- var pendingFirstTimeEvents = {};
142
-
143
- // Process flags from response
144
- _.each(responseFlags, function(data, key) {
145
- // Check if this flag has any activated first-time events this session
146
- var hasActivatedEvent = false;
147
- var prefix = key + ':';
148
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
149
- if (eventKey.startsWith(prefix)) {
150
- hasActivatedEvent = true;
151
- }
152
- });
153
-
154
- if (hasActivatedEvent) {
155
- // Preserve the activated variant, don't overwrite with server's current variant
156
- var currentFlag = this.flags && this.flags.get(key);
157
- if (currentFlag) {
158
- flags.set(key, currentFlag);
159
- }
160
- } else {
161
- // Use server's current variant
162
- flags.set(key, {
163
- 'key': data['variant_key'],
164
- 'value': data['variant_value'],
165
- 'experiment_id': data['experiment_id'],
166
- 'is_experiment_active': data['is_experiment_active'],
167
- 'is_qa_tester': data['is_qa_tester']
168
- });
141
+ return response.json();
142
+ }.bind(this)).then(function(responseBody) {
143
+ var responseFlags = responseBody['flags'];
144
+ if (!responseFlags) {
145
+ throw new Error('No flags in API response');
146
+ }
147
+ var flags = new Map();
148
+ var pendingFirstTimeEvents = {};
149
+
150
+ // Process flags from response
151
+ _.each(responseFlags, function(data, key) {
152
+ // Check if this flag has any activated first-time events this session
153
+ var hasActivatedEvent = false;
154
+ var prefix = key + ':';
155
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
156
+ if (eventKey.startsWith(prefix)) {
157
+ hasActivatedEvent = true;
169
158
  }
170
- }, this);
159
+ });
171
160
 
172
- // Process top-level pending_first_time_events array
173
- var topLevelDefinitions = responseBody['pending_first_time_events'];
174
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
175
- _.each(topLevelDefinitions, function(def) {
176
- var flagKey = def['flag_key'];
177
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
178
-
179
- // Skip if this specific event has already been activated this session
180
- if (this.activatedFirstTimeEvents[eventKey]) {
181
- return;
182
- }
183
-
184
- // Store pending event definition using composite key
185
- pendingFirstTimeEvents[eventKey] = {
186
- 'flag_key': flagKey,
187
- 'flag_id': def['flag_id'],
188
- 'project_id': def['project_id'],
189
- 'first_time_event_hash': def['first_time_event_hash'],
190
- 'event_name': def['event_name'],
191
- 'property_filters': def['property_filters'],
192
- 'pending_variant': def['pending_variant']
193
- };
194
- }, this);
161
+ if (hasActivatedEvent) {
162
+ // Preserve the activated variant, don't overwrite with server's current variant
163
+ var currentFlag = this.flags && this.flags.get(key);
164
+ if (currentFlag) {
165
+ flags.set(key, currentFlag);
166
+ }
167
+ } else {
168
+ // Use server's current variant
169
+ flags.set(key, {
170
+ 'key': data['variant_key'],
171
+ 'value': data['variant_value'],
172
+ 'experiment_id': data['experiment_id'],
173
+ 'is_experiment_active': data['is_experiment_active'],
174
+ 'is_qa_tester': data['is_qa_tester']
175
+ });
195
176
  }
177
+ }, this);
178
+
179
+ // Process top-level pending_first_time_events array
180
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
181
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
182
+ _.each(topLevelDefinitions, function(def) {
183
+ var flagKey = def['flag_key'];
184
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
185
+
186
+ // Skip if this specific event has already been activated this session
187
+ if (this.activatedFirstTimeEvents[eventKey]) {
188
+ return;
189
+ }
196
190
 
197
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
198
- if (this.activatedFirstTimeEvents) {
199
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
200
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
201
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
202
- // Keep the activated flag even though it's not in the new response
203
- flags.set(flagKey, this.flags.get(flagKey));
204
- }
205
- }, this);
206
- }
191
+ // Store pending event definition using composite key
192
+ pendingFirstTimeEvents[eventKey] = {
193
+ 'flag_key': flagKey,
194
+ 'flag_id': def['flag_id'],
195
+ 'project_id': def['project_id'],
196
+ 'first_time_event_hash': def['first_time_event_hash'],
197
+ 'event_name': def['event_name'],
198
+ 'property_filters': def['property_filters'],
199
+ 'pending_variant': def['pending_variant']
200
+ };
201
+ }, this);
202
+ }
207
203
 
208
- this.flags = flags;
209
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
210
- this._traceparent = traceparent;
204
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
205
+ if (this.activatedFirstTimeEvents) {
206
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
207
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
208
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
209
+ // Keep the activated flag even though it's not in the new response
210
+ flags.set(flagKey, this.flags.get(flagKey));
211
+ }
212
+ }, this);
213
+ }
211
214
 
212
- this._loadTargetingIfNeeded();
213
- }.bind(this)).catch(function(error) {
214
- this.markFetchComplete();
215
- logger.error(error);
216
- }.bind(this));
215
+ this.flags = flags;
216
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
217
+ this._traceparent = traceparent;
218
+
219
+ this._loadTargetingIfNeeded();
217
220
  }.bind(this)).catch(function(error) {
218
- this.markFetchComplete();
221
+ if (this._fetchInProgressStartTime) {
222
+ this.markFetchComplete();
223
+ }
219
224
  logger.error(error);
225
+ throw error;
220
226
  }.bind(this));
221
227
 
222
228
  return this.fetchPromise;
223
229
  };
224
230
 
231
+ FeatureFlagManager.prototype.loadFlags = function() {
232
+ if (!this.isSystemEnabled()) {
233
+ return Promise.resolve();
234
+ }
235
+ if (!this.trackedFeatures) {
236
+ logger.error('loadFlags called before init');
237
+ return Promise.resolve();
238
+ }
239
+ if (this._fetchInProgressStartTime) {
240
+ return this.fetchPromise;
241
+ }
242
+ return this.fetchFlags();
243
+ };
244
+
225
245
  FeatureFlagManager.prototype.markFetchComplete = function() {
226
246
  if (!this._fetchInProgressStartTime) {
227
247
  logger.error('Fetch in progress started time not set, cannot mark fetch complete');
@@ -501,6 +521,13 @@ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature)
501
521
  this.track('$experiment_started', trackingProperties);
502
522
  };
503
523
 
524
+ FeatureFlagManager.prototype.whenReady = function() {
525
+ if (this.fetchPromise) {
526
+ return this.fetchPromise;
527
+ }
528
+ return Promise.resolve();
529
+ };
530
+
504
531
  FeatureFlagManager.prototype.minApisSupported = function() {
505
532
  return !!this.fetch &&
506
533
  typeof Promise !== 'undefined' &&
@@ -517,7 +544,9 @@ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype
517
544
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
518
545
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
519
546
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
547
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
520
548
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
549
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
521
550
 
522
551
  // Deprecated method
523
552
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
package/src/index.d.ts CHANGED
@@ -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
@@ -1724,7 +1724,9 @@ MixpanelLib.prototype.identify = function(
1724
1724
 
1725
1725
  // check feature flags again if distinct id has changed
1726
1726
  if (new_distinct_id !== previous_distinct_id) {
1727
- this.flags.fetchFlags();
1727
+ this.flags.fetchFlags().catch(function() {
1728
+ console.error('[flags] Error fetching flags during identify');
1729
+ });
1728
1730
  }
1729
1731
  };
1730
1732
 
package/testServer.js CHANGED
@@ -124,6 +124,8 @@ for (const [suiteId, suite] of Object.entries(TEST_SUITES)) {
124
124
  // Cross-origin child iframe page for session recording tests
125
125
  app.get('/tests/new/' + suiteId + '-cross-origin-page', function(req, res) {
126
126
  res.render('cross-origin-page.pug', {
127
+ customLibUrl: suite.customLibUrl || './static/build/mixpanel.js',
128
+ snippetUrl: suite.snippetUrl || './static/src/loaders/mixpanel-jslib-snippet.js',
127
129
  testUrl: './static/build/test/browser/cross-origin-page.js'
128
130
  });
129
131
  });