mixpanel-browser 2.74.0 → 2.75.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/.github/workflows/unit-tests.yml +1 -1
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/dist/mixpanel-core.cjs.js +318 -20
- package/dist/mixpanel-recorder.js +127 -15
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +2576 -0
- package/dist/mixpanel-targeting.min.js +2 -0
- package/dist/mixpanel-targeting.min.js.map +1 -0
- package/dist/mixpanel-with-async-modules.cjs.d.ts +522 -0
- package/dist/mixpanel-with-async-modules.cjs.js +9700 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +318 -20
- package/dist/mixpanel-with-recorder.js +435 -26
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +1020 -28
- package/dist/mixpanel.cjs.js +1020 -28
- package/dist/mixpanel.globals.js +318 -20
- package/dist/mixpanel.min.js +179 -172
- package/dist/mixpanel.module.js +1020 -28
- package/dist/mixpanel.umd.js +1020 -28
- package/dist/rrweb-bundled.js +119 -5
- package/dist/rrweb-compiled.js +116 -5
- package/package.json +4 -3
- package/rollup.config.mjs +34 -2
- package/src/config.js +1 -1
- package/src/flags/index.js +269 -8
- package/src/globals.js +14 -0
- package/src/loaders/loader-module.js +1 -0
- package/src/mixpanel-core.js +12 -3
- package/src/recorder/index.js +2 -1
- package/src/targeting/event-matcher.js +97 -0
- package/src/targeting/index.js +11 -0
- package/src/targeting/loader.js +36 -0
- package/src/utils.js +1 -8
- package/.claude/settings.local.json +0 -12
- /package/src/loaders/{loader-module-with-async-recorder.js → loader-module-with-async-modules.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
**2.75.0** (24 Feb 2026)
|
|
2
|
+
- Adds support for Feature Flags targeting based on events tracked during the current session (Runtime Targeting)
|
|
3
|
+
- Fixes memory leaks in Session Recording on sites which create/destroy iframes
|
|
4
|
+
- Fixes usage of arrow functions in `hooks` config
|
|
5
|
+
|
|
1
6
|
**2.74.0** (27 Jan 2026)
|
|
2
7
|
- New session recording masking configuration options, including the ability to unmask inputs and allowlist-based selector masking.
|
|
3
8
|
- Adds initial support for the remote settings API, allowing remote configuration of SDK config options.
|
package/README.md
CHANGED
|
@@ -30,9 +30,9 @@ To load the core SDK with no option of session recording:
|
|
|
30
30
|
import mixpanel from 'mixpanel-browser/src/loaders/loader-module-core';
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
To load the core SDK and optionally load session recording
|
|
33
|
+
To load the core SDK and optionally load session recording and targeting bundles asynchronously (via script tag):
|
|
34
34
|
```javascript
|
|
35
|
-
import mixpanel from 'mixpanel-browser/src/loaders/loader-module-with-async-
|
|
35
|
+
import mixpanel from 'mixpanel-browser/src/loaders/loader-module-with-async-modules';
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Use as a browser JavaScript module
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.75.0'
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -593,15 +593,8 @@ _.isArray = nativeIsArray || function(obj) {
|
|
|
593
593
|
return toString.call(obj) === '[object Array]';
|
|
594
594
|
};
|
|
595
595
|
|
|
596
|
-
// from a comment on http://dbj.org/dbj/?p=286
|
|
597
|
-
// fails on only one very rare and deliberate custom object:
|
|
598
|
-
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
599
596
|
_.isFunction = function(f) {
|
|
600
|
-
|
|
601
|
-
return /^\s*\bfunction\b/.test(f);
|
|
602
|
-
} catch (x) {
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
597
|
+
return typeof f === 'function';
|
|
605
598
|
};
|
|
606
599
|
|
|
607
600
|
_.isArguments = function(obj) {
|
|
@@ -2159,6 +2152,16 @@ var isRecordingExpired = function(serializedRecording) {
|
|
|
2159
2152
|
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
2160
2153
|
};
|
|
2161
2154
|
|
|
2155
|
+
/**
|
|
2156
|
+
* Shared global window property names used across modules
|
|
2157
|
+
*/
|
|
2158
|
+
|
|
2159
|
+
// Targeting library global (used by flags and targeting modules)
|
|
2160
|
+
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
2161
|
+
|
|
2162
|
+
// Recorder library global (used by recorder and mixpanel-core)
|
|
2163
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
2164
|
+
|
|
2162
2165
|
// stateless utils
|
|
2163
2166
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
2164
2167
|
|
|
@@ -3907,14 +3910,62 @@ Autocapture.prototype.stopDeadClickTracking = function() {
|
|
|
3907
3910
|
// TODO integrate error_reporter from mixpanel instance
|
|
3908
3911
|
safewrapClass(Autocapture);
|
|
3909
3912
|
|
|
3910
|
-
|
|
3913
|
+
/**
|
|
3914
|
+
* Get the promise-based targeting loader
|
|
3915
|
+
* @param {Function} loadExtraBundle - Function to load external bundle (callback-based)
|
|
3916
|
+
* @param {string} targetingSrc - URL to targeting bundle
|
|
3917
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
3918
|
+
*/
|
|
3919
|
+
var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
|
|
3920
|
+
// Return existing promise if already initialized or loading
|
|
3921
|
+
if (win[TARGETING_GLOBAL_NAME] && typeof win[TARGETING_GLOBAL_NAME].then === 'function') {
|
|
3922
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
// Create loading promise and set it as the global immediately
|
|
3926
|
+
// This makes minified build behavior consistent with dev/CJS builds
|
|
3927
|
+
win[TARGETING_GLOBAL_NAME] = new Promise(function (resolve) {
|
|
3928
|
+
loadExtraBundle(targetingSrc, resolve);
|
|
3929
|
+
}).then(function () {
|
|
3930
|
+
var p = win[TARGETING_GLOBAL_NAME];
|
|
3931
|
+
if (p && typeof p.then === 'function') {
|
|
3932
|
+
return p;
|
|
3933
|
+
}
|
|
3934
|
+
throw new Error('targeting failed to load');
|
|
3935
|
+
}).catch(function (err) {
|
|
3936
|
+
delete win[TARGETING_GLOBAL_NAME];
|
|
3937
|
+
throw err;
|
|
3938
|
+
});
|
|
3939
|
+
|
|
3940
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
3941
|
+
};
|
|
3911
3942
|
|
|
3943
|
+
var logger$3 = console_with_prefix('flags');
|
|
3912
3944
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
3913
3945
|
|
|
3914
3946
|
var CONFIG_CONTEXT = 'context';
|
|
3915
3947
|
var CONFIG_DEFAULTS = {};
|
|
3916
3948
|
CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
3917
3949
|
|
|
3950
|
+
/**
|
|
3951
|
+
* Generate a unique key for a pending first-time event
|
|
3952
|
+
* @param {string} flagKey - The flag key
|
|
3953
|
+
* @param {string} firstTimeEventHash - The first_time_event_hash from the pending event definition
|
|
3954
|
+
* @returns {string} Composite key in format "flagKey:firstTimeEventHash"
|
|
3955
|
+
*/
|
|
3956
|
+
var getPendingEventKey = function(flagKey, firstTimeEventHash) {
|
|
3957
|
+
return flagKey + ':' + firstTimeEventHash;
|
|
3958
|
+
};
|
|
3959
|
+
|
|
3960
|
+
/**
|
|
3961
|
+
* Extract the flag key from a pending event key
|
|
3962
|
+
* @param {string} eventKey - The composite event key in format "flagKey:firstTimeEventHash"
|
|
3963
|
+
* @returns {string} The flag key portion
|
|
3964
|
+
*/
|
|
3965
|
+
var getFlagKeyFromPendingEventKey = function(eventKey) {
|
|
3966
|
+
return eventKey.split(':')[0];
|
|
3967
|
+
};
|
|
3968
|
+
|
|
3918
3969
|
/**
|
|
3919
3970
|
* FeatureFlagManager: support for Mixpanel's feature flagging product
|
|
3920
3971
|
* @constructor
|
|
@@ -3926,6 +3977,8 @@ var FeatureFlagManager = function(initOptions) {
|
|
|
3926
3977
|
this.setMpConfig = initOptions.setConfigFunc;
|
|
3927
3978
|
this.getMpProperty = initOptions.getPropertyFunc;
|
|
3928
3979
|
this.track = initOptions.trackingFunc;
|
|
3980
|
+
this.loadExtraBundle = initOptions.loadExtraBundle || function() {};
|
|
3981
|
+
this.targetingSrc = initOptions.targetingSrc || '';
|
|
3929
3982
|
};
|
|
3930
3983
|
|
|
3931
3984
|
FeatureFlagManager.prototype.init = function() {
|
|
@@ -3938,6 +3991,8 @@ FeatureFlagManager.prototype.init = function() {
|
|
|
3938
3991
|
this.fetchFlags();
|
|
3939
3992
|
|
|
3940
3993
|
this.trackedFeatures = new Set();
|
|
3994
|
+
this.pendingFirstTimeEvents = {};
|
|
3995
|
+
this.activatedFirstTimeEvents = {};
|
|
3941
3996
|
};
|
|
3942
3997
|
|
|
3943
3998
|
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
@@ -4018,17 +4073,78 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
4018
4073
|
throw new Error('No flags in API response');
|
|
4019
4074
|
}
|
|
4020
4075
|
var flags = new Map();
|
|
4076
|
+
var pendingFirstTimeEvents = {};
|
|
4077
|
+
|
|
4078
|
+
// Process flags from response
|
|
4021
4079
|
_.each(responseFlags, function(data, key) {
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4080
|
+
// Check if this flag has any activated first-time events this session
|
|
4081
|
+
var hasActivatedEvent = false;
|
|
4082
|
+
var prefix = key + ':';
|
|
4083
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4084
|
+
if (eventKey.startsWith(prefix)) {
|
|
4085
|
+
hasActivatedEvent = true;
|
|
4086
|
+
}
|
|
4028
4087
|
});
|
|
4029
|
-
|
|
4088
|
+
|
|
4089
|
+
if (hasActivatedEvent) {
|
|
4090
|
+
// Preserve the activated variant, don't overwrite with server's current variant
|
|
4091
|
+
var currentFlag = this.flags && this.flags.get(key);
|
|
4092
|
+
if (currentFlag) {
|
|
4093
|
+
flags.set(key, currentFlag);
|
|
4094
|
+
}
|
|
4095
|
+
} else {
|
|
4096
|
+
// Use server's current variant
|
|
4097
|
+
flags.set(key, {
|
|
4098
|
+
'key': data['variant_key'],
|
|
4099
|
+
'value': data['variant_value'],
|
|
4100
|
+
'experiment_id': data['experiment_id'],
|
|
4101
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
4102
|
+
'is_qa_tester': data['is_qa_tester']
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
}, this);
|
|
4106
|
+
|
|
4107
|
+
// Process top-level pending_first_time_events array
|
|
4108
|
+
var topLevelDefinitions = responseBody['pending_first_time_events'];
|
|
4109
|
+
if (topLevelDefinitions && topLevelDefinitions.length > 0) {
|
|
4110
|
+
_.each(topLevelDefinitions, function(def) {
|
|
4111
|
+
var flagKey = def['flag_key'];
|
|
4112
|
+
var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
|
|
4113
|
+
|
|
4114
|
+
// Skip if this specific event has already been activated this session
|
|
4115
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
4116
|
+
return;
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
// Store pending event definition using composite key
|
|
4120
|
+
pendingFirstTimeEvents[eventKey] = {
|
|
4121
|
+
'flag_key': flagKey,
|
|
4122
|
+
'flag_id': def['flag_id'],
|
|
4123
|
+
'project_id': def['project_id'],
|
|
4124
|
+
'first_time_event_hash': def['first_time_event_hash'],
|
|
4125
|
+
'event_name': def['event_name'],
|
|
4126
|
+
'property_filters': def['property_filters'],
|
|
4127
|
+
'pending_variant': def['pending_variant']
|
|
4128
|
+
};
|
|
4129
|
+
}, this);
|
|
4130
|
+
}
|
|
4131
|
+
|
|
4132
|
+
// Preserve any activated orphaned flags (flags that were activated but are no longer in response)
|
|
4133
|
+
if (this.activatedFirstTimeEvents) {
|
|
4134
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
4135
|
+
var flagKey = getFlagKeyFromPendingEventKey(eventKey);
|
|
4136
|
+
if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
|
|
4137
|
+
// Keep the activated flag even though it's not in the new response
|
|
4138
|
+
flags.set(flagKey, this.flags.get(flagKey));
|
|
4139
|
+
}
|
|
4140
|
+
}, this);
|
|
4141
|
+
}
|
|
4142
|
+
|
|
4030
4143
|
this.flags = flags;
|
|
4144
|
+
this.pendingFirstTimeEvents = pendingFirstTimeEvents;
|
|
4031
4145
|
this._traceparent = traceparent;
|
|
4146
|
+
|
|
4147
|
+
this._loadTargetingIfNeeded();
|
|
4032
4148
|
}.bind(this)).catch(function(error) {
|
|
4033
4149
|
this.markFetchComplete();
|
|
4034
4150
|
logger$3.error(error);
|
|
@@ -4052,6 +4168,177 @@ FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
|
4052
4168
|
this._fetchInProgressStartTime = null;
|
|
4053
4169
|
};
|
|
4054
4170
|
|
|
4171
|
+
/**
|
|
4172
|
+
* Proactively load targeting bundle if any pending events have property filters
|
|
4173
|
+
*/
|
|
4174
|
+
FeatureFlagManager.prototype._loadTargetingIfNeeded = function() {
|
|
4175
|
+
var hasPropertyFilters = false;
|
|
4176
|
+
_.each(this.pendingFirstTimeEvents, function(evt) {
|
|
4177
|
+
if (evt['property_filters'] && !_.isEmptyObject(evt['property_filters'])) {
|
|
4178
|
+
hasPropertyFilters = true;
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
|
|
4182
|
+
if (hasPropertyFilters) {
|
|
4183
|
+
this.getTargeting().then(function() {
|
|
4184
|
+
logger$3.log('targeting loaded for property filter evaluation');
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
};
|
|
4188
|
+
|
|
4189
|
+
/**
|
|
4190
|
+
* Get the targeting library (initializes if not already loaded)
|
|
4191
|
+
* This method is primarily for testing - production code should rely on automatic loading
|
|
4192
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
4193
|
+
*/
|
|
4194
|
+
FeatureFlagManager.prototype.getTargeting = function() {
|
|
4195
|
+
return getTargetingPromise(
|
|
4196
|
+
this.loadExtraBundle.bind(this),
|
|
4197
|
+
this.targetingSrc
|
|
4198
|
+
).catch(function(error) {
|
|
4199
|
+
logger$3.error('Failed to load targeting: ' + error);
|
|
4200
|
+
}.bind(this));
|
|
4201
|
+
};
|
|
4202
|
+
|
|
4203
|
+
/**
|
|
4204
|
+
* Check if a tracked event matches any pending first-time events and activate the corresponding flag variant
|
|
4205
|
+
* @param {string} eventName - The name of the event being tracked
|
|
4206
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
4207
|
+
*
|
|
4208
|
+
* When a match is found (event name matches and property filters pass), this method:
|
|
4209
|
+
* - Switches the flag to the pending variant
|
|
4210
|
+
* - Marks the event as activated for this session
|
|
4211
|
+
* - Records the activation via the API (fire-and-forget)
|
|
4212
|
+
*/
|
|
4213
|
+
FeatureFlagManager.prototype.checkFirstTimeEvents = function(eventName, properties) {
|
|
4214
|
+
if (!this.pendingFirstTimeEvents || _.isEmptyObject(this.pendingFirstTimeEvents)) {
|
|
4215
|
+
return;
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
// Check if targeting promise exists (either bundled or async loaded)
|
|
4219
|
+
if (win[TARGETING_GLOBAL_NAME] && _.isFunction(win[TARGETING_GLOBAL_NAME].then)) {
|
|
4220
|
+
win[TARGETING_GLOBAL_NAME].then(function(library) {
|
|
4221
|
+
this._processFirstTimeEventCheck(eventName, properties, library);
|
|
4222
|
+
}.bind(this)).catch(function() {
|
|
4223
|
+
// If targeting failed to load, process with null
|
|
4224
|
+
// Events without property filters will still match
|
|
4225
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
4226
|
+
}.bind(this));
|
|
4227
|
+
} else {
|
|
4228
|
+
// No targeting available, process with null
|
|
4229
|
+
// Events without property filters will still match
|
|
4230
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
4231
|
+
}
|
|
4232
|
+
};
|
|
4233
|
+
|
|
4234
|
+
/**
|
|
4235
|
+
* Internal method to process first-time event checks with loaded targeting library
|
|
4236
|
+
* @param {string} eventName - The name of the event being tracked
|
|
4237
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
4238
|
+
* @param {Object} targeting - The loaded targeting library
|
|
4239
|
+
*/
|
|
4240
|
+
FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, properties, targeting) {
|
|
4241
|
+
_.each(this.pendingFirstTimeEvents, function(pendingEvent, eventKey) {
|
|
4242
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
4243
|
+
return;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
var flagKey = pendingEvent['flag_key'];
|
|
4247
|
+
|
|
4248
|
+
// Use targeting module to check if event matches
|
|
4249
|
+
var matchResult;
|
|
4250
|
+
|
|
4251
|
+
// If no targeting library and event has property filters, skip it
|
|
4252
|
+
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
4253
|
+
logger$3.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
// For simple events (no property filters), just check event name
|
|
4258
|
+
if (!targeting) {
|
|
4259
|
+
matchResult = {
|
|
4260
|
+
matches: eventName === pendingEvent['event_name'],
|
|
4261
|
+
error: null
|
|
4262
|
+
};
|
|
4263
|
+
} else {
|
|
4264
|
+
var criteria = {
|
|
4265
|
+
'event_name': pendingEvent['event_name'],
|
|
4266
|
+
'property_filters': pendingEvent['property_filters']
|
|
4267
|
+
};
|
|
4268
|
+
matchResult = targeting['eventMatchesCriteria'](
|
|
4269
|
+
eventName,
|
|
4270
|
+
properties,
|
|
4271
|
+
criteria
|
|
4272
|
+
);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
if (matchResult.error) {
|
|
4276
|
+
logger$3.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
if (!matchResult.matches) {
|
|
4281
|
+
return;
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
logger$3.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
4285
|
+
|
|
4286
|
+
var newVariant = {
|
|
4287
|
+
'key': pendingEvent['pending_variant']['variant_key'],
|
|
4288
|
+
'value': pendingEvent['pending_variant']['variant_value'],
|
|
4289
|
+
'experiment_id': pendingEvent['pending_variant']['experiment_id'],
|
|
4290
|
+
'is_experiment_active': pendingEvent['pending_variant']['is_experiment_active']
|
|
4291
|
+
};
|
|
4292
|
+
|
|
4293
|
+
this.flags.set(flagKey, newVariant);
|
|
4294
|
+
this.activatedFirstTimeEvents[eventKey] = true;
|
|
4295
|
+
|
|
4296
|
+
this.recordFirstTimeEvent(
|
|
4297
|
+
pendingEvent['flag_id'],
|
|
4298
|
+
pendingEvent['project_id'],
|
|
4299
|
+
pendingEvent['first_time_event_hash']
|
|
4300
|
+
);
|
|
4301
|
+
}, this);
|
|
4302
|
+
};
|
|
4303
|
+
|
|
4304
|
+
FeatureFlagManager.prototype.getFirstTimeEventApiRoute = function(flagId) {
|
|
4305
|
+
// Construct URL: {api_host}/flags/{flagId}/first-time-events
|
|
4306
|
+
return this.getFullApiRoute() + '/' + flagId + '/first-time-events';
|
|
4307
|
+
};
|
|
4308
|
+
|
|
4309
|
+
FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId, firstTimeEventHash) {
|
|
4310
|
+
var distinctId = this.getMpProperty('distinct_id');
|
|
4311
|
+
var traceparent = generateTraceparent();
|
|
4312
|
+
|
|
4313
|
+
// Build URL with query string parameters
|
|
4314
|
+
var searchParams = new URLSearchParams();
|
|
4315
|
+
searchParams.set('mp_lib', 'web');
|
|
4316
|
+
searchParams.set('$lib_version', Config.LIB_VERSION);
|
|
4317
|
+
var url = this.getFirstTimeEventApiRoute(flagId) + '?' + searchParams.toString();
|
|
4318
|
+
|
|
4319
|
+
var payload = {
|
|
4320
|
+
'distinct_id': distinctId,
|
|
4321
|
+
'project_id': projectId,
|
|
4322
|
+
'first_time_event_hash': firstTimeEventHash
|
|
4323
|
+
};
|
|
4324
|
+
|
|
4325
|
+
logger$3.log('Recording first-time event for flag: ' + flagId);
|
|
4326
|
+
|
|
4327
|
+
// Fire-and-forget POST request
|
|
4328
|
+
this.fetch.call(win, url, {
|
|
4329
|
+
'method': 'POST',
|
|
4330
|
+
'headers': {
|
|
4331
|
+
'Content-Type': 'application/json',
|
|
4332
|
+
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
4333
|
+
'traceparent': traceparent
|
|
4334
|
+
},
|
|
4335
|
+
'body': JSON.stringify(payload)
|
|
4336
|
+
}).catch(function(error) {
|
|
4337
|
+
// Silent failure - cohort sync will catch up
|
|
4338
|
+
logger$3.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
4339
|
+
});
|
|
4340
|
+
};
|
|
4341
|
+
|
|
4055
4342
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
4056
4343
|
if (!this.fetchPromise) {
|
|
4057
4344
|
return new Promise(function(resolve) {
|
|
@@ -4170,6 +4457,9 @@ FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.up
|
|
|
4170
4457
|
// Deprecated method
|
|
4171
4458
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
4172
4459
|
|
|
4460
|
+
// Exports intended only for testing
|
|
4461
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
4462
|
+
|
|
4173
4463
|
/* eslint camelcase: "off" */
|
|
4174
4464
|
|
|
4175
4465
|
|
|
@@ -6958,6 +7248,7 @@ var DEFAULT_CONFIG = {
|
|
|
6958
7248
|
'record_min_ms': 0,
|
|
6959
7249
|
'record_sessions_percent': 0,
|
|
6960
7250
|
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
|
|
7251
|
+
'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
|
|
6961
7252
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
6962
7253
|
};
|
|
6963
7254
|
|
|
@@ -7187,7 +7478,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7187
7478
|
getConfigFunc: _.bind(this.get_config, this),
|
|
7188
7479
|
setConfigFunc: _.bind(this.set_config, this),
|
|
7189
7480
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
7190
|
-
trackingFunc: _.bind(this.track, this)
|
|
7481
|
+
trackingFunc: _.bind(this.track, this),
|
|
7482
|
+
loadExtraBundle: load_extra_bundle,
|
|
7483
|
+
targetingSrc: this.get_config('targeting_src')
|
|
7191
7484
|
});
|
|
7192
7485
|
this.flags.init();
|
|
7193
7486
|
this['flags'] = this.flags;
|
|
@@ -7283,11 +7576,11 @@ MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpane
|
|
|
7283
7576
|
|
|
7284
7577
|
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
7285
7578
|
var handleLoadedRecorder = _.bind(function() {
|
|
7286
|
-
this._recorder = this._recorder || new win[
|
|
7579
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
7287
7580
|
this._recorder['resumeRecording'](startNewIfInactive);
|
|
7288
7581
|
}, this);
|
|
7289
7582
|
|
|
7290
|
-
if (_.isUndefined(win[
|
|
7583
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
7291
7584
|
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
7292
7585
|
} else {
|
|
7293
7586
|
handleLoadedRecorder();
|
|
@@ -8029,6 +8322,11 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
8029
8322
|
send_request_options: options
|
|
8030
8323
|
}, callback);
|
|
8031
8324
|
|
|
8325
|
+
// Check for first-time event matches
|
|
8326
|
+
if (this.flags && this.flags.checkFirstTimeEvents) {
|
|
8327
|
+
this.flags.checkFirstTimeEvents(event_name, properties);
|
|
8328
|
+
}
|
|
8329
|
+
|
|
8032
8330
|
return ret;
|
|
8033
8331
|
});
|
|
8034
8332
|
|