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.
- package/.claude/settings.local.json +3 -1
- package/CHANGELOG.md +4 -0
- package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js → mixpanel-recorder-BjSlYaNJ.min.js} +2 -2
- package/dist/async-modules/{mixpanel-recorder-wIWnMDLA.min.js.map → mixpanel-recorder-BjSlYaNJ.min.js.map} +1 -1
- package/dist/async-modules/{mixpanel-recorder-DLKbUIEE.js → mixpanel-recorder-zMBXIyeG.js} +1 -1
- package/dist/async-modules/{mixpanel-targeting-CTcftSJC.min.js → mixpanel-targeting-BSHal4N9.min.js} +2 -2
- package/dist/async-modules/{mixpanel-targeting-CTcftSJC.min.js.map → mixpanel-targeting-BSHal4N9.min.js.map} +1 -1
- package/dist/async-modules/{mixpanel-targeting-CmVvUyFM.js → mixpanel-targeting-UHf4eBfC.js} +1 -1
- package/dist/mixpanel-core.cjs.d.ts +1 -0
- package/dist/mixpanel-core.cjs.js +111 -80
- package/dist/mixpanel-recorder.js +1 -1
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +1 -1
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +1 -0
- package/dist/mixpanel-with-async-modules.cjs.js +113 -82
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +1 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +113 -82
- package/dist/mixpanel-with-recorder.d.ts +1 -0
- package/dist/mixpanel-with-recorder.js +111 -80
- package/dist/mixpanel-with-recorder.min.d.ts +1 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +1 -0
- package/dist/mixpanel.amd.js +111 -80
- package/dist/mixpanel.cjs.d.ts +1 -0
- package/dist/mixpanel.cjs.js +111 -80
- package/dist/mixpanel.globals.js +113 -82
- package/dist/mixpanel.min.js +180 -179
- package/dist/mixpanel.module.d.ts +1 -0
- package/dist/mixpanel.module.js +111 -80
- package/dist/mixpanel.umd.d.ts +1 -0
- package/dist/mixpanel.umd.js +111 -80
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/flags/CLAUDE.md +24 -0
- package/src/flags/index.js +109 -80
- package/src/index.d.ts +1 -0
- package/src/mixpanel-core.js +3 -1
- package/testServer.js +2 -0
package/src/flags/index.js
CHANGED
|
@@ -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
|
-
|
|
98
|
-
|
|
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()
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
}
|
|
159
|
+
});
|
|
171
160
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
this.
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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.
|
|
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
package/src/mixpanel-core.js
CHANGED
|
@@ -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
|
});
|