mixpanel-browser 2.76.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 (52) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.github/dependabot.yml +8 -0
  3. package/.github/workflows/integration-tests.yml +2 -2
  4. package/.github/workflows/unit-tests.yml +2 -2
  5. package/CHANGELOG.md +8 -0
  6. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js +2 -0
  7. package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +1 -0
  8. package/dist/async-modules/{mixpanel-recorder-bIS4LMGd.js → mixpanel-recorder-zMBXIyeG.js} +84 -10
  9. package/dist/async-modules/{mixpanel-targeting-VOeN7RWY.min.js → mixpanel-targeting-BSHal4N9.min.js} +2 -2
  10. package/dist/async-modules/{mixpanel-targeting-VOeN7RWY.min.js.map → mixpanel-targeting-BSHal4N9.min.js.map} +1 -1
  11. package/dist/async-modules/{mixpanel-targeting-BcAPS-Mz.js → mixpanel-targeting-UHf4eBfC.js} +1 -1
  12. package/dist/mixpanel-core.cjs.d.ts +3 -1
  13. package/dist/mixpanel-core.cjs.js +292 -130
  14. package/dist/mixpanel-recorder.js +84 -10
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +1 -1
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +3 -1
  21. package/dist/mixpanel-with-async-modules.cjs.js +294 -132
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +3 -1
  23. package/dist/mixpanel-with-async-recorder.cjs.js +294 -132
  24. package/dist/mixpanel-with-recorder.d.ts +3 -1
  25. package/dist/mixpanel-with-recorder.js +381 -168
  26. package/dist/mixpanel-with-recorder.min.d.ts +3 -1
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +3 -1
  29. package/dist/mixpanel.amd.js +381 -168
  30. package/dist/mixpanel.cjs.d.ts +3 -1
  31. package/dist/mixpanel.cjs.js +381 -168
  32. package/dist/mixpanel.globals.js +294 -132
  33. package/dist/mixpanel.min.js +191 -186
  34. package/dist/mixpanel.module.d.ts +3 -1
  35. package/dist/mixpanel.module.js +381 -168
  36. package/dist/mixpanel.umd.d.ts +3 -1
  37. package/dist/mixpanel.umd.js +381 -168
  38. package/dist/rrweb-bundled.js +61 -9
  39. package/dist/rrweb-compiled.js +56 -9
  40. package/package.json +6 -5
  41. package/src/config.js +1 -1
  42. package/src/flags/CLAUDE.md +24 -0
  43. package/src/flags/index.js +109 -80
  44. package/src/index.d.ts +3 -1
  45. package/src/mixpanel-core.js +4 -2
  46. package/src/recorder/session-recording.js +5 -1
  47. package/src/recorder/utils.js +27 -1
  48. package/src/recorder-manager.js +110 -2
  49. package/testServer.js +16 -1
  50. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +0 -2
  51. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +0 -1
  52. /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
@@ -3,7 +3,7 @@
3
3
 
4
4
  var Config = {
5
5
  DEBUG: false,
6
- LIB_VERSION: '2.76.0'
6
+ LIB_VERSION: '2.78.0'
7
7
  };
8
8
 
9
9
  // Window global names for async modules
@@ -11,8 +11,8 @@
11
11
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
12
12
 
13
13
  // Constants that are injected at build-time for the names of async modules.
14
- var RECORDER_FILENAME = 'mixpanel-recorder-bIS4LMGd.js';
15
- var TARGETING_FILENAME = 'mixpanel-targeting-BcAPS-Mz.js';
14
+ var RECORDER_FILENAME = 'mixpanel-recorder-zMBXIyeG.js';
15
+ var TARGETING_FILENAME = 'mixpanel-targeting-UHf4eBfC.js';
16
16
 
17
17
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
18
18
  var win;
@@ -2268,7 +2268,7 @@
2268
2268
 
2269
2269
  var MAX_DEPTH = 5;
2270
2270
 
2271
- var logger$4 = console_with_prefix('autocapture');
2271
+ var logger$5 = console_with_prefix('autocapture');
2272
2272
 
2273
2273
 
2274
2274
  function getClasses(el) {
@@ -2532,7 +2532,7 @@
2532
2532
  return false;
2533
2533
  }
2534
2534
  } catch (err) {
2535
- logger$4.critical('Error while checking element in allowElementCallback', err);
2535
+ logger$5.critical('Error while checking element in allowElementCallback', err);
2536
2536
  return false;
2537
2537
  }
2538
2538
  }
@@ -2549,7 +2549,7 @@
2549
2549
  return true;
2550
2550
  }
2551
2551
  } catch (err) {
2552
- logger$4.critical('Error while checking selector: ' + sel, err);
2552
+ logger$5.critical('Error while checking selector: ' + sel, err);
2553
2553
  }
2554
2554
  }
2555
2555
  return false;
@@ -2564,7 +2564,7 @@
2564
2564
  return true;
2565
2565
  }
2566
2566
  } catch (err) {
2567
- logger$4.critical('Error while checking element in blockElementCallback', err);
2567
+ logger$5.critical('Error while checking element in blockElementCallback', err);
2568
2568
  return true;
2569
2569
  }
2570
2570
  }
@@ -2578,7 +2578,7 @@
2578
2578
  return true;
2579
2579
  }
2580
2580
  } catch (err) {
2581
- logger$4.critical('Error while checking selector: ' + sel, err);
2581
+ logger$5.critical('Error while checking selector: ' + sel, err);
2582
2582
  }
2583
2583
  }
2584
2584
  }
@@ -3042,7 +3042,7 @@
3042
3042
  observer.observe(shadowRoot, this.observerConfig);
3043
3043
  this.shadowObservers.push(observer);
3044
3044
  } catch (e) {
3045
- logger$4.critical('Error while observing shadow root', e);
3045
+ logger$5.critical('Error while observing shadow root', e);
3046
3046
  }
3047
3047
  };
3048
3048
 
@@ -3053,7 +3053,7 @@
3053
3053
  }
3054
3054
 
3055
3055
  if (!weakSetSupported()) {
3056
- logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
3056
+ logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
3057
3057
  return;
3058
3058
  }
3059
3059
 
@@ -3069,7 +3069,7 @@
3069
3069
  try {
3070
3070
  this.shadowObservers[i].disconnect();
3071
3071
  } catch (e) {
3072
- logger$4.critical('Error while disconnecting shadow DOM observer', e);
3072
+ logger$5.critical('Error while disconnecting shadow DOM observer', e);
3073
3073
  }
3074
3074
  }
3075
3075
  this.shadowObservers = [];
@@ -3257,7 +3257,7 @@
3257
3257
 
3258
3258
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
3259
3259
  } catch (e) {
3260
- logger$4.critical('Error while setting up mutation observer', e);
3260
+ logger$5.critical('Error while setting up mutation observer', e);
3261
3261
  }
3262
3262
  }
3263
3263
 
@@ -3272,7 +3272,7 @@
3272
3272
  );
3273
3273
  this.shadowDOMObserver.start();
3274
3274
  } catch (e) {
3275
- logger$4.critical('Error while setting up shadow DOM observer', e);
3275
+ logger$5.critical('Error while setting up shadow DOM observer', e);
3276
3276
  this.shadowDOMObserver = null;
3277
3277
  }
3278
3278
  }
@@ -3299,7 +3299,7 @@
3299
3299
  try {
3300
3300
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
3301
3301
  } catch (e) {
3302
- logger$4.critical('Error while removing event listener', e);
3302
+ logger$5.critical('Error while removing event listener', e);
3303
3303
  }
3304
3304
  }
3305
3305
  this.eventListeners = [];
@@ -3308,7 +3308,7 @@
3308
3308
  try {
3309
3309
  this.mutationObserver.disconnect();
3310
3310
  } catch (e) {
3311
- logger$4.critical('Error while disconnecting mutation observer', e);
3311
+ logger$5.critical('Error while disconnecting mutation observer', e);
3312
3312
  }
3313
3313
  this.mutationObserver = null;
3314
3314
  }
@@ -3317,7 +3317,7 @@
3317
3317
  try {
3318
3318
  this.shadowDOMObserver.stop();
3319
3319
  } catch (e) {
3320
- logger$4.critical('Error while stopping shadow DOM observer', e);
3320
+ logger$5.critical('Error while stopping shadow DOM observer', e);
3321
3321
  }
3322
3322
  this.shadowDOMObserver = null;
3323
3323
  }
@@ -3395,7 +3395,7 @@
3395
3395
 
3396
3396
  Autocapture.prototype.init = function() {
3397
3397
  if (!minDOMApisSupported()) {
3398
- logger$4.critical('Autocapture unavailable: missing required DOM APIs');
3398
+ logger$5.critical('Autocapture unavailable: missing required DOM APIs');
3399
3399
  return;
3400
3400
  }
3401
3401
  this.initPageListeners();
@@ -3435,7 +3435,7 @@
3435
3435
  try {
3436
3436
  return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
3437
3437
  } catch (err) {
3438
- logger$4.critical('Error while checking block URL regexes: ', err);
3438
+ logger$5.critical('Error while checking block URL regexes: ', err);
3439
3439
  return true;
3440
3440
  }
3441
3441
  }
@@ -3448,7 +3448,7 @@
3448
3448
  try {
3449
3449
  return urlMatchesRegexList(currentUrl, blockUrlRegexes);
3450
3450
  } catch (err) {
3451
- logger$4.critical('Error while checking block URL regexes: ', err);
3451
+ logger$5.critical('Error while checking block URL regexes: ', err);
3452
3452
  return true;
3453
3453
  }
3454
3454
  };
@@ -3586,7 +3586,7 @@
3586
3586
  return;
3587
3587
  }
3588
3588
 
3589
- logger$4.log('Initializing scroll depth tracking');
3589
+ logger$5.log('Initializing scroll depth tracking');
3590
3590
 
3591
3591
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
3592
3592
 
@@ -3612,7 +3612,7 @@
3612
3612
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
3613
3613
  return;
3614
3614
  }
3615
- logger$4.log('Initializing click tracking');
3615
+ logger$5.log('Initializing click tracking');
3616
3616
 
3617
3617
  this.listenerClick = function(ev) {
3618
3618
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -3631,7 +3631,7 @@
3631
3631
  return;
3632
3632
  }
3633
3633
 
3634
- logger$4.log('Initializing dead click tracking');
3634
+ logger$5.log('Initializing dead click tracking');
3635
3635
  if (!this._deadClickTracker) {
3636
3636
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
3637
3637
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -3665,7 +3665,7 @@
3665
3665
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
3666
3666
  return;
3667
3667
  }
3668
- logger$4.log('Initializing input tracking');
3668
+ logger$5.log('Initializing input tracking');
3669
3669
 
3670
3670
  this.listenerChange = function(ev) {
3671
3671
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -3682,7 +3682,7 @@
3682
3682
  if (!this.pageviewTrackingConfig()) {
3683
3683
  return;
3684
3684
  }
3685
- logger$4.log('Initializing pageview tracking');
3685
+ logger$5.log('Initializing pageview tracking');
3686
3686
 
3687
3687
  var previousTrackedUrl = '';
3688
3688
  var tracked = false;
@@ -3717,7 +3717,7 @@
3717
3717
  }
3718
3718
  if (didPathChange) {
3719
3719
  this.lastScrollCheckpoint = 0;
3720
- logger$4.log('Path change: re-initializing scroll depth checkpoints');
3720
+ logger$5.log('Path change: re-initializing scroll depth checkpoints');
3721
3721
  }
3722
3722
  }
3723
3723
  }.bind(this));
@@ -3732,7 +3732,7 @@
3732
3732
  return;
3733
3733
  }
3734
3734
 
3735
- logger$4.log('Initializing rage click tracking');
3735
+ logger$5.log('Initializing rage click tracking');
3736
3736
  if (!this._rageClickTracker) {
3737
3737
  this._rageClickTracker = new RageClickTracker();
3738
3738
  }
@@ -3762,7 +3762,7 @@
3762
3762
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
3763
3763
  return;
3764
3764
  }
3765
- logger$4.log('Initializing scroll tracking');
3765
+ logger$5.log('Initializing scroll tracking');
3766
3766
  this.lastScrollCheckpoint = 0;
3767
3767
 
3768
3768
  var scrollTrackFunction = function() {
@@ -3799,7 +3799,7 @@
3799
3799
  }
3800
3800
  }
3801
3801
  } catch (err) {
3802
- logger$4.critical('Error while calculating scroll percentage', err);
3802
+ logger$5.critical('Error while calculating scroll percentage', err);
3803
3803
  }
3804
3804
  if (shouldTrack) {
3805
3805
  this.mp.track(MP_EV_SCROLL, props);
@@ -3817,7 +3817,7 @@
3817
3817
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
3818
3818
  return;
3819
3819
  }
3820
- logger$4.log('Initializing submit tracking');
3820
+ logger$5.log('Initializing submit tracking');
3821
3821
 
3822
3822
  this.listenerSubmit = function(ev) {
3823
3823
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -3839,7 +3839,7 @@
3839
3839
  return;
3840
3840
  }
3841
3841
 
3842
- logger$4.log('Initializing page visibility tracking.');
3842
+ logger$5.log('Initializing page visibility tracking.');
3843
3843
  this._initScrollDepthTracking();
3844
3844
  var previousTrackedUrl = _.info.currentUrl();
3845
3845
 
@@ -3924,7 +3924,7 @@
3924
3924
  return win[TARGETING_GLOBAL_NAME];
3925
3925
  };
3926
3926
 
3927
- var logger$3 = console_with_prefix('flags');
3927
+ var logger$4 = console_with_prefix('flags');
3928
3928
  var FLAGS_CONFIG_KEY = 'flags';
3929
3929
 
3930
3930
  var CONFIG_CONTEXT = 'context';
@@ -3967,12 +3967,14 @@
3967
3967
 
3968
3968
  FeatureFlagManager.prototype.init = function() {
3969
3969
  if (!this.minApisSupported()) {
3970
- logger$3.critical('Feature Flags unavailable: missing minimum required APIs');
3970
+ logger$4.critical('Feature Flags unavailable: missing minimum required APIs');
3971
3971
  return;
3972
3972
  }
3973
3973
 
3974
3974
  this.flags = null;
3975
- this.fetchFlags();
3975
+ this.fetchFlags().catch(function() {
3976
+ logger$4.error('Error fetching flags during init');
3977
+ });
3976
3978
 
3977
3979
  this.trackedFeatures = new Set();
3978
3980
  this.pendingFirstTimeEvents = {};
@@ -4002,7 +4004,7 @@
4002
4004
 
4003
4005
  FeatureFlagManager.prototype.updateContext = function(newContext, options) {
4004
4006
  if (!this.isSystemEnabled()) {
4005
- logger$3.critical('Feature Flags not enabled, cannot update context');
4007
+ logger$4.critical('Feature Flags not enabled, cannot update context');
4006
4008
  return Promise.resolve();
4007
4009
  }
4008
4010
 
@@ -4013,13 +4015,17 @@
4013
4015
  var oldContext = (options && options['replace']) ? {} : this.getConfig(CONFIG_CONTEXT);
4014
4016
  ffConfig[CONFIG_CONTEXT] = _.extend({}, oldContext, newContext);
4015
4017
 
4016
- this.setMpConfig(FLAGS_CONFIG_KEY, ffConfig);
4017
- return this.fetchFlags();
4018
+ var configUpdate = {};
4019
+ configUpdate[FLAGS_CONFIG_KEY] = ffConfig;
4020
+ this.setMpConfig(configUpdate);
4021
+ return this.fetchFlags().catch(function() {
4022
+ logger$4.error('Error fetching flags during updateContext');
4023
+ });
4018
4024
  };
4019
4025
 
4020
4026
  FeatureFlagManager.prototype.areFlagsReady = function() {
4021
4027
  if (!this.isSystemEnabled()) {
4022
- logger$3.error('Feature Flags not enabled');
4028
+ logger$4.error('Feature Flags not enabled');
4023
4029
  }
4024
4030
  return !!this.flags;
4025
4031
  };
@@ -4032,7 +4038,7 @@
4032
4038
  var distinctId = this.getMpProperty('distinct_id');
4033
4039
  var deviceId = this.getMpProperty('$device_id');
4034
4040
  var traceparent = generateTraceparent();
4035
- logger$3.log('Fetching flags for distinct ID: ' + distinctId);
4041
+ logger$4.log('Fetching flags for distinct ID: ' + distinctId);
4036
4042
 
4037
4043
  var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
4038
4044
  var searchParams = new URLSearchParams();
@@ -4051,99 +4057,113 @@
4051
4057
  }
4052
4058
  }).then(function(response) {
4053
4059
  this.markFetchComplete();
4054
- return response.json().then(function(responseBody) {
4055
- var responseFlags = responseBody['flags'];
4056
- if (!responseFlags) {
4057
- throw new Error('No flags in API response');
4058
- }
4059
- var flags = new Map();
4060
- var pendingFirstTimeEvents = {};
4061
-
4062
- // Process flags from response
4063
- _.each(responseFlags, function(data, key) {
4064
- // Check if this flag has any activated first-time events this session
4065
- var hasActivatedEvent = false;
4066
- var prefix = key + ':';
4067
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
4068
- if (eventKey.startsWith(prefix)) {
4069
- hasActivatedEvent = true;
4070
- }
4071
- });
4060
+ return response.json();
4061
+ }.bind(this)).then(function(responseBody) {
4062
+ var responseFlags = responseBody['flags'];
4063
+ if (!responseFlags) {
4064
+ throw new Error('No flags in API response');
4065
+ }
4066
+ var flags = new Map();
4067
+ var pendingFirstTimeEvents = {};
4068
+
4069
+ // Process flags from response
4070
+ _.each(responseFlags, function(data, key) {
4071
+ // Check if this flag has any activated first-time events this session
4072
+ var hasActivatedEvent = false;
4073
+ var prefix = key + ':';
4074
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
4075
+ if (eventKey.startsWith(prefix)) {
4076
+ hasActivatedEvent = true;
4077
+ }
4078
+ });
4072
4079
 
4073
- if (hasActivatedEvent) {
4074
- // Preserve the activated variant, don't overwrite with server's current variant
4075
- var currentFlag = this.flags && this.flags.get(key);
4076
- if (currentFlag) {
4077
- flags.set(key, currentFlag);
4078
- }
4079
- } else {
4080
- // Use server's current variant
4081
- flags.set(key, {
4082
- 'key': data['variant_key'],
4083
- 'value': data['variant_value'],
4084
- 'experiment_id': data['experiment_id'],
4085
- 'is_experiment_active': data['is_experiment_active'],
4086
- 'is_qa_tester': data['is_qa_tester']
4087
- });
4080
+ if (hasActivatedEvent) {
4081
+ // Preserve the activated variant, don't overwrite with server's current variant
4082
+ var currentFlag = this.flags && this.flags.get(key);
4083
+ if (currentFlag) {
4084
+ flags.set(key, currentFlag);
4088
4085
  }
4089
- }, this);
4086
+ } else {
4087
+ // Use server's current variant
4088
+ flags.set(key, {
4089
+ 'key': data['variant_key'],
4090
+ 'value': data['variant_value'],
4091
+ 'experiment_id': data['experiment_id'],
4092
+ 'is_experiment_active': data['is_experiment_active'],
4093
+ 'is_qa_tester': data['is_qa_tester']
4094
+ });
4095
+ }
4096
+ }, this);
4090
4097
 
4091
- // Process top-level pending_first_time_events array
4092
- var topLevelDefinitions = responseBody['pending_first_time_events'];
4093
- if (topLevelDefinitions && topLevelDefinitions.length > 0) {
4094
- _.each(topLevelDefinitions, function(def) {
4095
- var flagKey = def['flag_key'];
4096
- var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
4098
+ // Process top-level pending_first_time_events array
4099
+ var topLevelDefinitions = responseBody['pending_first_time_events'];
4100
+ if (topLevelDefinitions && topLevelDefinitions.length > 0) {
4101
+ _.each(topLevelDefinitions, function(def) {
4102
+ var flagKey = def['flag_key'];
4103
+ var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
4097
4104
 
4098
- // Skip if this specific event has already been activated this session
4099
- if (this.activatedFirstTimeEvents[eventKey]) {
4100
- return;
4101
- }
4105
+ // Skip if this specific event has already been activated this session
4106
+ if (this.activatedFirstTimeEvents[eventKey]) {
4107
+ return;
4108
+ }
4102
4109
 
4103
- // Store pending event definition using composite key
4104
- pendingFirstTimeEvents[eventKey] = {
4105
- 'flag_key': flagKey,
4106
- 'flag_id': def['flag_id'],
4107
- 'project_id': def['project_id'],
4108
- 'first_time_event_hash': def['first_time_event_hash'],
4109
- 'event_name': def['event_name'],
4110
- 'property_filters': def['property_filters'],
4111
- 'pending_variant': def['pending_variant']
4112
- };
4113
- }, this);
4114
- }
4110
+ // Store pending event definition using composite key
4111
+ pendingFirstTimeEvents[eventKey] = {
4112
+ 'flag_key': flagKey,
4113
+ 'flag_id': def['flag_id'],
4114
+ 'project_id': def['project_id'],
4115
+ 'first_time_event_hash': def['first_time_event_hash'],
4116
+ 'event_name': def['event_name'],
4117
+ 'property_filters': def['property_filters'],
4118
+ 'pending_variant': def['pending_variant']
4119
+ };
4120
+ }, this);
4121
+ }
4115
4122
 
4116
- // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
4117
- if (this.activatedFirstTimeEvents) {
4118
- _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
4119
- var flagKey = getFlagKeyFromPendingEventKey(eventKey);
4120
- if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
4121
- // Keep the activated flag even though it's not in the new response
4122
- flags.set(flagKey, this.flags.get(flagKey));
4123
- }
4124
- }, this);
4125
- }
4123
+ // Preserve any activated orphaned flags (flags that were activated but are no longer in response)
4124
+ if (this.activatedFirstTimeEvents) {
4125
+ _.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
4126
+ var flagKey = getFlagKeyFromPendingEventKey(eventKey);
4127
+ if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
4128
+ // Keep the activated flag even though it's not in the new response
4129
+ flags.set(flagKey, this.flags.get(flagKey));
4130
+ }
4131
+ }, this);
4132
+ }
4126
4133
 
4127
- this.flags = flags;
4128
- this.pendingFirstTimeEvents = pendingFirstTimeEvents;
4129
- this._traceparent = traceparent;
4134
+ this.flags = flags;
4135
+ this.pendingFirstTimeEvents = pendingFirstTimeEvents;
4136
+ this._traceparent = traceparent;
4130
4137
 
4131
- this._loadTargetingIfNeeded();
4132
- }.bind(this)).catch(function(error) {
4133
- this.markFetchComplete();
4134
- logger$3.error(error);
4135
- }.bind(this));
4138
+ this._loadTargetingIfNeeded();
4136
4139
  }.bind(this)).catch(function(error) {
4137
- this.markFetchComplete();
4138
- logger$3.error(error);
4140
+ if (this._fetchInProgressStartTime) {
4141
+ this.markFetchComplete();
4142
+ }
4143
+ logger$4.error(error);
4144
+ throw error;
4139
4145
  }.bind(this));
4140
4146
 
4141
4147
  return this.fetchPromise;
4142
4148
  };
4143
4149
 
4150
+ FeatureFlagManager.prototype.loadFlags = function() {
4151
+ if (!this.isSystemEnabled()) {
4152
+ return Promise.resolve();
4153
+ }
4154
+ if (!this.trackedFeatures) {
4155
+ logger$4.error('loadFlags called before init');
4156
+ return Promise.resolve();
4157
+ }
4158
+ if (this._fetchInProgressStartTime) {
4159
+ return this.fetchPromise;
4160
+ }
4161
+ return this.fetchFlags();
4162
+ };
4163
+
4144
4164
  FeatureFlagManager.prototype.markFetchComplete = function() {
4145
4165
  if (!this._fetchInProgressStartTime) {
4146
- logger$3.error('Fetch in progress started time not set, cannot mark fetch complete');
4166
+ logger$4.error('Fetch in progress started time not set, cannot mark fetch complete');
4147
4167
  return;
4148
4168
  }
4149
4169
  this._fetchStartTime = this._fetchInProgressStartTime;
@@ -4165,7 +4185,7 @@
4165
4185
 
4166
4186
  if (hasPropertyFilters) {
4167
4187
  this.getTargeting().then(function() {
4168
- logger$3.log('targeting loaded for property filter evaluation');
4188
+ logger$4.log('targeting loaded for property filter evaluation');
4169
4189
  });
4170
4190
  }
4171
4191
  };
@@ -4180,7 +4200,7 @@
4180
4200
  this.loadExtraBundle.bind(this),
4181
4201
  this.targetingSrc
4182
4202
  ).catch(function(error) {
4183
- logger$3.error('Failed to load targeting: ' + error);
4203
+ logger$4.error('Failed to load targeting: ' + error);
4184
4204
  }.bind(this));
4185
4205
  };
4186
4206
 
@@ -4234,7 +4254,7 @@
4234
4254
 
4235
4255
  // If no targeting library and event has property filters, skip it
4236
4256
  if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
4237
- logger$3.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
4257
+ logger$4.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
4238
4258
  return;
4239
4259
  }
4240
4260
 
@@ -4257,7 +4277,7 @@
4257
4277
  }
4258
4278
 
4259
4279
  if (matchResult.error) {
4260
- logger$3.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
4280
+ logger$4.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
4261
4281
  return;
4262
4282
  }
4263
4283
 
@@ -4265,7 +4285,7 @@
4265
4285
  return;
4266
4286
  }
4267
4287
 
4268
- logger$3.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
4288
+ logger$4.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
4269
4289
 
4270
4290
  var newVariant = {
4271
4291
  'key': pendingEvent['pending_variant']['variant_key'],
@@ -4306,7 +4326,7 @@
4306
4326
  'first_time_event_hash': firstTimeEventHash
4307
4327
  };
4308
4328
 
4309
- logger$3.log('Recording first-time event for flag: ' + flagId);
4329
+ logger$4.log('Recording first-time event for flag: ' + flagId);
4310
4330
 
4311
4331
  // Fire-and-forget POST request
4312
4332
  this.fetch.call(win, url, {
@@ -4319,14 +4339,14 @@
4319
4339
  'body': JSON.stringify(payload)
4320
4340
  }).catch(function(error) {
4321
4341
  // Silent failure - cohort sync will catch up
4322
- logger$3.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
4342
+ logger$4.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
4323
4343
  });
4324
4344
  };
4325
4345
 
4326
4346
  FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
4327
4347
  if (!this.fetchPromise) {
4328
4348
  return new Promise(function(resolve) {
4329
- logger$3.critical('Feature Flags not initialized');
4349
+ logger$4.critical('Feature Flags not initialized');
4330
4350
  resolve(fallback);
4331
4351
  });
4332
4352
  }
@@ -4334,19 +4354,19 @@
4334
4354
  return this.fetchPromise.then(function() {
4335
4355
  return this.getVariantSync(featureName, fallback);
4336
4356
  }.bind(this)).catch(function(error) {
4337
- logger$3.error(error);
4357
+ logger$4.error(error);
4338
4358
  return fallback;
4339
4359
  });
4340
4360
  };
4341
4361
 
4342
4362
  FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
4343
4363
  if (!this.areFlagsReady()) {
4344
- logger$3.log('Flags not loaded yet');
4364
+ logger$4.log('Flags not loaded yet');
4345
4365
  return fallback;
4346
4366
  }
4347
4367
  var feature = this.flags.get(featureName);
4348
4368
  if (!feature) {
4349
- logger$3.log('No flag found: "' + featureName + '"');
4369
+ logger$4.log('No flag found: "' + featureName + '"');
4350
4370
  return fallback;
4351
4371
  }
4352
4372
  this.trackFeatureCheck(featureName, feature);
@@ -4357,14 +4377,14 @@
4357
4377
  return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
4358
4378
  return feature['value'];
4359
4379
  }).catch(function(error) {
4360
- logger$3.error(error);
4380
+ logger$4.error(error);
4361
4381
  return fallbackValue;
4362
4382
  });
4363
4383
  };
4364
4384
 
4365
4385
  // TODO remove deprecated method
4366
4386
  FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
4367
- logger$3.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
4387
+ logger$4.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
4368
4388
  return this.getVariantValue(featureName, fallbackValue);
4369
4389
  };
4370
4390
 
@@ -4376,7 +4396,7 @@
4376
4396
  return this.getVariantValue(featureName).then(function() {
4377
4397
  return this.isEnabledSync(featureName, fallbackValue);
4378
4398
  }.bind(this)).catch(function(error) {
4379
- logger$3.error(error);
4399
+ logger$4.error(error);
4380
4400
  return fallbackValue;
4381
4401
  });
4382
4402
  };
@@ -4385,7 +4405,7 @@
4385
4405
  fallbackValue = fallbackValue || false;
4386
4406
  var val = this.getVariantValueSync(featureName, fallbackValue);
4387
4407
  if (val !== true && val !== false) {
4388
- logger$3.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
4408
+ logger$4.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
4389
4409
  val = fallbackValue;
4390
4410
  }
4391
4411
  return val;
@@ -4420,6 +4440,13 @@
4420
4440
  this.track('$experiment_started', trackingProperties);
4421
4441
  };
4422
4442
 
4443
+ FeatureFlagManager.prototype.whenReady = function() {
4444
+ if (this.fetchPromise) {
4445
+ return this.fetchPromise;
4446
+ }
4447
+ return Promise.resolve();
4448
+ };
4449
+
4423
4450
  FeatureFlagManager.prototype.minApisSupported = function() {
4424
4451
  return !!this.fetch &&
4425
4452
  typeof Promise !== 'undefined' &&
@@ -4436,7 +4463,9 @@
4436
4463
  FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
4437
4464
  FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
4438
4465
  FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
4466
+ FeatureFlagManager.prototype['load_flags'] = FeatureFlagManager.prototype.loadFlags;
4439
4467
  FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
4468
+ FeatureFlagManager.prototype['when_ready'] = FeatureFlagManager.prototype.whenReady;
4440
4469
 
4441
4470
  // Deprecated method
4442
4471
  FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
@@ -4580,9 +4609,38 @@
4580
4609
  return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
4581
4610
  };
4582
4611
 
4612
+ var validateAllowedOrigins = function(origins, logger) {
4613
+ if (!_.isArray(origins)) {
4614
+ if (origins) {
4615
+ logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
4616
+ }
4617
+ return [];
4618
+ }
4619
+ var valid = [];
4620
+ for (var i = 0; i < origins.length; i++) {
4621
+ try {
4622
+ var origin = new URL(origins[i]).origin;
4623
+ if (origin === 'null') {
4624
+ logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
4625
+ continue;
4626
+ }
4627
+ valid.push(origin);
4628
+ } catch (e) {
4629
+ logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
4630
+ }
4631
+ }
4632
+ return valid;
4633
+ };
4634
+
4583
4635
  /* eslint camelcase: "off" */
4584
4636
 
4585
4637
 
4638
+ var logger$3 = console_with_prefix('recorder');
4639
+
4640
+ var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
4641
+ var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
4642
+
4643
+
4586
4644
  /**
4587
4645
  * RecorderManager: manages session recording initialization, lifecycle and state
4588
4646
  * @constructor
@@ -4602,6 +4660,8 @@
4602
4660
  this.libBasePath = initOptions.libBasePath;
4603
4661
 
4604
4662
  this._recorder = null;
4663
+ this._parentReplayId = null;
4664
+ this._parentFrameRetryInterval = null;
4605
4665
  };
4606
4666
 
4607
4667
  RecorderManager.prototype.shouldLoadRecorder = function() {
@@ -4655,6 +4715,22 @@
4655
4715
  }, this));
4656
4716
  }, this);
4657
4717
 
4718
+ // Cross-origin iframe handling
4719
+ var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger$3);
4720
+ var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
4721
+
4722
+ if (isCrossOriginRecordingEnabled) {
4723
+ // listen for handshake requests from their own child iframes (including nested)
4724
+ this._setupParentFrameListener(allowedOrigins);
4725
+
4726
+ if (win.parent !== win) {
4727
+ // also wait for parent's replay ID
4728
+ this._setupChildFrameListener(allowedOrigins, loadRecorder);
4729
+ this._sendParentFrameRequestWithRetry(allowedOrigins);
4730
+ return PromisePolyfill.resolve();
4731
+ }
4732
+ }
4733
+
4658
4734
  /**
4659
4735
  * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
4660
4736
  * Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
@@ -4774,6 +4850,10 @@
4774
4850
  };
4775
4851
 
4776
4852
  RecorderManager.prototype.getSessionReplayId = function() {
4853
+ // Child iframe uses parent's replay ID
4854
+ if (this._parentReplayId) {
4855
+ return this._parentReplayId;
4856
+ }
4777
4857
  var replay_id = null;
4778
4858
  if (this._recorder) {
4779
4859
  replay_id = this._recorder['replayId'];
@@ -4786,6 +4866,86 @@
4786
4866
  return this._recorder;
4787
4867
  };
4788
4868
 
4869
+ RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
4870
+ if (this._childFrameMessageHandler) {
4871
+ return;
4872
+ }
4873
+ var self = this;
4874
+ this._childFrameMessageHandler = function(event) {
4875
+ if (allowedOrigins.indexOf(event.origin) === -1) return;
4876
+ var data = event.data;
4877
+ if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
4878
+ self._parentReplayId = data['replayId'];
4879
+ if (data['distinctId']) {
4880
+ self.mixpanelInstance['identify'](data['distinctId']);
4881
+ }
4882
+ self._parentFrameRetryActive = false;
4883
+ win.removeEventListener('message', self._childFrameMessageHandler);
4884
+ self._childFrameMessageHandler = null;
4885
+ loadRecorder(true);
4886
+ }
4887
+ };
4888
+ win.addEventListener('message', this._childFrameMessageHandler);
4889
+ };
4890
+
4891
+ RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
4892
+ var message = {};
4893
+ message['type'] = IFRAME_HANDSHAKE_REQUEST;
4894
+ message['token'] = this.getMpConfig('token');
4895
+ for (var i = 0; i < allowedOrigins.length; i++) {
4896
+ try {
4897
+ win.parent.postMessage(message, allowedOrigins[i]);
4898
+ } catch (e) {
4899
+ // origin mismatch - ignore
4900
+ }
4901
+ }
4902
+ };
4903
+
4904
+ RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
4905
+ var self = this;
4906
+ var maxRetries = 10;
4907
+ var retryCount = 0;
4908
+ var delay = 50;
4909
+ this._parentFrameRetryActive = true;
4910
+
4911
+ this._sendParentFrameRequest(allowedOrigins);
4912
+
4913
+ function scheduleRetry() {
4914
+ setTimeout(function() {
4915
+ if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
4916
+ return;
4917
+ }
4918
+ self._sendParentFrameRequest(allowedOrigins);
4919
+ delay *= 2;
4920
+ scheduleRetry();
4921
+ }, delay);
4922
+ }
4923
+ scheduleRetry();
4924
+ };
4925
+
4926
+ RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
4927
+ if (this._parentFrameMessageHandler) {
4928
+ return;
4929
+ }
4930
+ var self = this;
4931
+ this._parentFrameMessageHandler = function(event) {
4932
+ if (allowedOrigins.indexOf(event.origin) === -1) return;
4933
+ var data = event.data;
4934
+ if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
4935
+ var replayId = self.getSessionReplayId();
4936
+ if (replayId) {
4937
+ var response = {};
4938
+ response['type'] = IFRAME_HANDSHAKE_RESPONSE;
4939
+ response['token'] = self.getMpConfig('token');
4940
+ response['replayId'] = replayId;
4941
+ response['distinctId'] = self.getDistinctId();
4942
+ event.source.postMessage(response, event.origin);
4943
+ }
4944
+ }
4945
+ };
4946
+ win.addEventListener('message', this._parentFrameMessageHandler);
4947
+ };
4948
+
4789
4949
  safewrapClass(RecorderManager);
4790
4950
 
4791
4951
  /* eslint camelcase: "off" */
@@ -7352,7 +7512,6 @@
7352
7512
  /** @const */ var SETTING_FALLBACK = 'fallback';
7353
7513
  /** @const */ var SETTING_DISABLED = 'disabled';
7354
7514
 
7355
-
7356
7515
  /*
7357
7516
  * Dynamic... constants? Is that an oxymoron?
7358
7517
  */
@@ -7437,6 +7596,7 @@
7437
7596
  'batch_request_timeout_ms': 90000,
7438
7597
  'batch_autostart': true,
7439
7598
  'hooks': {},
7599
+ 'record_allowed_iframe_origins': [],
7440
7600
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
7441
7601
  'record_block_selector': 'img, video, audio',
7442
7602
  'record_canvas': false,
@@ -9012,7 +9172,9 @@
9012
9172
 
9013
9173
  // check feature flags again if distinct id has changed
9014
9174
  if (new_distinct_id !== previous_distinct_id) {
9015
- this.flags.fetchFlags();
9175
+ this.flags.fetchFlags().catch(function() {
9176
+ console.error('[flags] Error fetching flags during identify');
9177
+ });
9016
9178
  }
9017
9179
  };
9018
9180