mixpanel-browser 2.72.0 → 2.74.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 +5 -2
- package/.eslintrc.json +7 -4
- package/.github/workflows/integration-tests.yml +52 -0
- package/.github/workflows/unit-tests.yml +40 -0
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/build.sh +1 -5
- package/dist/mixpanel-core.cjs.d.ts +49 -4
- package/dist/mixpanel-core.cjs.js +244 -26
- package/dist/mixpanel-recorder.js +5258 -688
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +49 -4
- package/dist/mixpanel-with-async-recorder.cjs.js +244 -26
- package/dist/mixpanel-with-recorder.d.ts +49 -4
- package/dist/mixpanel-with-recorder.js +6858 -2099
- package/dist/mixpanel-with-recorder.min.d.ts +49 -4
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +49 -4
- package/dist/mixpanel.amd.js +6858 -2099
- package/dist/mixpanel.cjs.d.ts +49 -4
- package/dist/mixpanel.cjs.js +6858 -2099
- package/dist/mixpanel.globals.js +244 -26
- package/dist/mixpanel.min.js +175 -171
- package/dist/mixpanel.module.d.ts +49 -4
- package/dist/mixpanel.module.js +6858 -2099
- package/dist/mixpanel.umd.d.ts +49 -4
- package/dist/mixpanel.umd.js +6858 -2099
- package/dist/rrweb-bundled.js +4315 -591
- package/dist/rrweb-compiled.js +4962 -641
- package/package.json +30 -5
- package/rollup.config.mjs +254 -224
- package/src/autocapture/utils.js +15 -7
- package/src/config.js +1 -1
- package/src/index.d.ts +49 -4
- package/src/mixpanel-core.js +215 -15
- package/src/recorder/masking.js +197 -0
- package/src/recorder/rrweb-entrypoint.js +2 -1
- package/src/recorder/session-recording.js +43 -4
- package/src/recorder/utils.js +5 -1
- package/src/utils.js +11 -2
- package/src/window.js +3 -1
- package/testServer.js +51 -7
- package/.github/workflows/tests.yml +0 -25
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.74.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
|
|
@@ -22,7 +22,9 @@ if (typeof(window) === 'undefined') {
|
|
|
22
22
|
screen: { width: 0, height: 0 },
|
|
23
23
|
location: loc,
|
|
24
24
|
addEventListener: function() {},
|
|
25
|
-
removeEventListener: function() {}
|
|
25
|
+
removeEventListener: function() {},
|
|
26
|
+
dispatchEvent: function() {},
|
|
27
|
+
CustomEvent: function () {}
|
|
26
28
|
};
|
|
27
29
|
} else {
|
|
28
30
|
win = window;
|
|
@@ -1502,8 +1504,17 @@ function _storageWrapper(storage, name, is_supported_fn) {
|
|
|
1502
1504
|
};
|
|
1503
1505
|
}
|
|
1504
1506
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
+
// Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
|
|
1508
|
+
// so create dummy storage wrappers that silently fail as a fallback.
|
|
1509
|
+
var windowLocalStorage = null, windowSessionStorage = null;
|
|
1510
|
+
try {
|
|
1511
|
+
windowLocalStorage = win.localStorage;
|
|
1512
|
+
windowSessionStorage = win.sessionStorage;
|
|
1513
|
+
// eslint-disable-next-line no-empty
|
|
1514
|
+
} catch (_err) {}
|
|
1515
|
+
|
|
1516
|
+
_.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
|
|
1517
|
+
_.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
1507
1518
|
|
|
1508
1519
|
_.register_event = (function() {
|
|
1509
1520
|
// written by Dean Edwards, 2005
|
|
@@ -2653,6 +2664,18 @@ function shouldTrackDomEvent(el, ev) {
|
|
|
2653
2664
|
}
|
|
2654
2665
|
}
|
|
2655
2666
|
|
|
2667
|
+
function elementLooksSensitive(el) {
|
|
2668
|
+
var name = (el.name || el.id || '').toString().toLowerCase();
|
|
2669
|
+
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
2670
|
+
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
2671
|
+
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
2672
|
+
return true;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
return false;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2656
2679
|
/*
|
|
2657
2680
|
* Check whether a DOM element should be "tracked" or if it may contain sensitive data
|
|
2658
2681
|
* using a variety of heuristics.
|
|
@@ -2705,13 +2728,8 @@ function shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors)
|
|
|
2705
2728
|
}
|
|
2706
2729
|
}
|
|
2707
2730
|
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if (typeof name === 'string') { // it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
2711
|
-
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
2712
|
-
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
2713
|
-
return false;
|
|
2714
|
-
}
|
|
2731
|
+
if (elementLooksSensitive(el)) {
|
|
2732
|
+
return false;
|
|
2715
2733
|
}
|
|
2716
2734
|
|
|
2717
2735
|
return true;
|
|
@@ -6835,12 +6853,13 @@ var mixpanel_master; // main mixpanel instance / object
|
|
|
6835
6853
|
var INIT_MODULE = 0;
|
|
6836
6854
|
var INIT_SNIPPET = 1;
|
|
6837
6855
|
|
|
6838
|
-
var IDENTITY_FUNC = function(x) {return x;};
|
|
6839
|
-
|
|
6840
6856
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
6841
6857
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
6842
6858
|
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
6843
6859
|
/** @const */ var DEVICE_ID_PREFIX = '$device:';
|
|
6860
|
+
/** @const */ var SETTING_STRICT = 'strict';
|
|
6861
|
+
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
6862
|
+
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
6844
6863
|
|
|
6845
6864
|
|
|
6846
6865
|
/*
|
|
@@ -6869,7 +6888,8 @@ var DEFAULT_API_ROUTES = {
|
|
|
6869
6888
|
'engage': 'engage/',
|
|
6870
6889
|
'groups': 'groups/',
|
|
6871
6890
|
'record': 'record/',
|
|
6872
|
-
'flags': 'flags/'
|
|
6891
|
+
'flags': 'flags/',
|
|
6892
|
+
'settings': 'settings/'
|
|
6873
6893
|
};
|
|
6874
6894
|
|
|
6875
6895
|
/*
|
|
@@ -6933,12 +6953,12 @@ var DEFAULT_CONFIG = {
|
|
|
6933
6953
|
'record_console': true,
|
|
6934
6954
|
'record_heatmap_data': false,
|
|
6935
6955
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
6936
|
-
'
|
|
6937
|
-
'record_mask_text_selector': '*',
|
|
6956
|
+
'record_mask_inputs': true,
|
|
6938
6957
|
'record_max_ms': MAX_RECORDING_MS,
|
|
6939
6958
|
'record_min_ms': 0,
|
|
6940
6959
|
'record_sessions_percent': 0,
|
|
6941
|
-
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
|
|
6960
|
+
'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
|
|
6961
|
+
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
6942
6962
|
};
|
|
6943
6963
|
|
|
6944
6964
|
var DOM_LOADED = false;
|
|
@@ -7002,6 +7022,17 @@ var create_mplib = function(token, config, name) {
|
|
|
7002
7022
|
// global debug to be true
|
|
7003
7023
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
7004
7024
|
|
|
7025
|
+
var source = init_type === INIT_MODULE ? 'module' : 'snippet';
|
|
7026
|
+
win.dispatchEvent(new win.CustomEvent('$mp_sdk_to_extension_event', {
|
|
7027
|
+
'detail': {
|
|
7028
|
+
'instance': instance,
|
|
7029
|
+
'source': source,
|
|
7030
|
+
'token': token,
|
|
7031
|
+
'name': name,
|
|
7032
|
+
'info': _.info
|
|
7033
|
+
}
|
|
7034
|
+
}));
|
|
7035
|
+
|
|
7005
7036
|
// if target is not defined, we called init after the lib already
|
|
7006
7037
|
// loaded, so there won't be an array of things to execute
|
|
7007
7038
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -7072,6 +7103,8 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7072
7103
|
}
|
|
7073
7104
|
}
|
|
7074
7105
|
|
|
7106
|
+
this.hooks = {};
|
|
7107
|
+
|
|
7075
7108
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
7076
7109
|
'name': name,
|
|
7077
7110
|
'token': token,
|
|
@@ -7163,7 +7196,16 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7163
7196
|
this.autocapture.init();
|
|
7164
7197
|
|
|
7165
7198
|
this._init_tab_id();
|
|
7166
|
-
|
|
7199
|
+
|
|
7200
|
+
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
7201
|
+
var mode = this.get_config('remote_settings_mode');
|
|
7202
|
+
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
7203
|
+
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
7204
|
+
this._check_and_start_session_recording();
|
|
7205
|
+
}, this));
|
|
7206
|
+
} else {
|
|
7207
|
+
this._check_and_start_session_recording();
|
|
7208
|
+
}
|
|
7167
7209
|
};
|
|
7168
7210
|
|
|
7169
7211
|
/**
|
|
@@ -7588,6 +7630,77 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
7588
7630
|
return succeeded;
|
|
7589
7631
|
};
|
|
7590
7632
|
|
|
7633
|
+
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
7634
|
+
var disableRecordingIfStrict = function() {
|
|
7635
|
+
if (mode === 'strict') {
|
|
7636
|
+
self.set_config({'record_sessions_percent': 0});
|
|
7637
|
+
}
|
|
7638
|
+
};
|
|
7639
|
+
|
|
7640
|
+
if (!win['AbortController']) {
|
|
7641
|
+
console.critical('Remote settings unavailable: missing minimum required APIs');
|
|
7642
|
+
disableRecordingIfStrict();
|
|
7643
|
+
return Promise.resolve();
|
|
7644
|
+
}
|
|
7645
|
+
|
|
7646
|
+
var settings_endpoint = this.get_api_host('settings') + '/' + this.get_config('api_routes')['settings'];
|
|
7647
|
+
var request_params = {
|
|
7648
|
+
'$lib_version': Config.LIB_VERSION,
|
|
7649
|
+
'mp_lib': 'web',
|
|
7650
|
+
'sdk_config': '1',
|
|
7651
|
+
};
|
|
7652
|
+
var query_string = _.HTTPBuildQuery(request_params);
|
|
7653
|
+
var full_url = settings_endpoint + '?' + query_string;
|
|
7654
|
+
var self = this;
|
|
7655
|
+
|
|
7656
|
+
var abortController = new AbortController();
|
|
7657
|
+
var timeout_id = setTimeout(function() {
|
|
7658
|
+
abortController.abort();
|
|
7659
|
+
}, 500);
|
|
7660
|
+
var fetchOptions = {
|
|
7661
|
+
'method': 'GET',
|
|
7662
|
+
'headers': {
|
|
7663
|
+
'Authorization': 'Basic ' + btoa(self.get_config('token') + ':'),
|
|
7664
|
+
},
|
|
7665
|
+
'signal': abortController.signal
|
|
7666
|
+
};
|
|
7667
|
+
|
|
7668
|
+
return win['fetch'](full_url, fetchOptions).then(function(response) {
|
|
7669
|
+
clearTimeout(timeout_id);
|
|
7670
|
+
if (!response['ok']) {
|
|
7671
|
+
console.critical('Network response was not ok');
|
|
7672
|
+
disableRecordingIfStrict();
|
|
7673
|
+
return;
|
|
7674
|
+
}
|
|
7675
|
+
return response.json();
|
|
7676
|
+
}).then(function(result) {
|
|
7677
|
+
if (result && result['sdk_config'] && result['sdk_config']['config']) {
|
|
7678
|
+
var remote_config = result['sdk_config']['config'];
|
|
7679
|
+
|
|
7680
|
+
// Verify that remote config contains only valid keys from DEFAULT_CONFIG
|
|
7681
|
+
var valid_config = {};
|
|
7682
|
+
_.each(remote_config, function(value, key) {
|
|
7683
|
+
if (DEFAULT_CONFIG.hasOwnProperty(key)) {
|
|
7684
|
+
valid_config[key] = value;
|
|
7685
|
+
}
|
|
7686
|
+
});
|
|
7687
|
+
|
|
7688
|
+
if (_.isEmptyObject(valid_config)) {
|
|
7689
|
+
console.critical('No valid config keys found in remote settings.');
|
|
7690
|
+
disableRecordingIfStrict();
|
|
7691
|
+
} else {
|
|
7692
|
+
self.set_config(valid_config);
|
|
7693
|
+
}
|
|
7694
|
+
} else {
|
|
7695
|
+
disableRecordingIfStrict();
|
|
7696
|
+
}
|
|
7697
|
+
}).catch(function(err) {
|
|
7698
|
+
clearTimeout(timeout_id);
|
|
7699
|
+
console.critical('Failed to fetch remote settings', err);
|
|
7700
|
+
disableRecordingIfStrict();
|
|
7701
|
+
});
|
|
7702
|
+
};
|
|
7703
|
+
|
|
7591
7704
|
/**
|
|
7592
7705
|
* _execute_array() deals with processing any mixpanel function
|
|
7593
7706
|
* calls that were called before the Mixpanel library were loaded
|
|
@@ -7672,7 +7785,12 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
7672
7785
|
);
|
|
7673
7786
|
}, this),
|
|
7674
7787
|
beforeSendHook: _.bind(function(item) {
|
|
7675
|
-
|
|
7788
|
+
var ret = this._run_hook('before_send_' + attrs.type, item);
|
|
7789
|
+
if (ret) {
|
|
7790
|
+
return ret[0];
|
|
7791
|
+
} else {
|
|
7792
|
+
return null;
|
|
7793
|
+
}
|
|
7676
7794
|
}, this),
|
|
7677
7795
|
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
|
|
7678
7796
|
usePersistence: true,
|
|
@@ -7765,6 +7883,9 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
7765
7883
|
var send_request_immediately = _.bind(function() {
|
|
7766
7884
|
if (!send_request_options.skip_hooks) {
|
|
7767
7885
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
7886
|
+
if (truncated_data) {
|
|
7887
|
+
truncated_data = truncated_data[0];
|
|
7888
|
+
}
|
|
7768
7889
|
}
|
|
7769
7890
|
if (truncated_data) {
|
|
7770
7891
|
console.log('MIXPANEL REQUEST:');
|
|
@@ -7819,6 +7940,17 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
7819
7940
|
* with the tracking payload sent to the API server is returned; otherwise false.
|
|
7820
7941
|
*/
|
|
7821
7942
|
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) {
|
|
7943
|
+
var ret;
|
|
7944
|
+
if (!(options && options.skip_hooks)) {
|
|
7945
|
+
ret = this._run_hook('before_track', event_name, properties);
|
|
7946
|
+
if (ret === null) {
|
|
7947
|
+
return;
|
|
7948
|
+
} else {
|
|
7949
|
+
event_name = ret[0];
|
|
7950
|
+
properties = ret[1];
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
|
|
7822
7954
|
if (!callback && typeof options === 'function') {
|
|
7823
7955
|
callback = options;
|
|
7824
7956
|
options = null;
|
|
@@ -7888,7 +8020,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
7888
8020
|
'event': event_name,
|
|
7889
8021
|
'properties': properties
|
|
7890
8022
|
};
|
|
7891
|
-
|
|
8023
|
+
ret = this._track_or_batch({
|
|
7892
8024
|
type: 'events',
|
|
7893
8025
|
data: data,
|
|
7894
8026
|
endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
|
|
@@ -8234,6 +8366,14 @@ var options_for_register = function(days_or_options) {
|
|
|
8234
8366
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8235
8367
|
*/
|
|
8236
8368
|
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
8369
|
+
var ret = this._run_hook('before_register', props, days_or_options);
|
|
8370
|
+
if (ret === null) {
|
|
8371
|
+
return;
|
|
8372
|
+
} else {
|
|
8373
|
+
props = ret[0];
|
|
8374
|
+
days_or_options = ret[1];
|
|
8375
|
+
}
|
|
8376
|
+
|
|
8237
8377
|
var options = options_for_register(days_or_options);
|
|
8238
8378
|
if (options['persistent']) {
|
|
8239
8379
|
this['persistence'].register(props, options['days']);
|
|
@@ -8270,6 +8410,15 @@ MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
|
8270
8410
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8271
8411
|
*/
|
|
8272
8412
|
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
8413
|
+
var ret = this._run_hook('before_register_once', props, default_value, days_or_options);
|
|
8414
|
+
if (ret === null) {
|
|
8415
|
+
return;
|
|
8416
|
+
} else {
|
|
8417
|
+
props = ret[0];
|
|
8418
|
+
default_value = ret[1];
|
|
8419
|
+
days_or_options = ret[2];
|
|
8420
|
+
}
|
|
8421
|
+
|
|
8273
8422
|
var options = options_for_register(days_or_options);
|
|
8274
8423
|
if (options['persistent']) {
|
|
8275
8424
|
this['persistence'].register_once(props, default_value, options['days']);
|
|
@@ -8293,6 +8442,14 @@ MixpanelLib.prototype.register_once = function(props, default_value, days_or_opt
|
|
|
8293
8442
|
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
8294
8443
|
*/
|
|
8295
8444
|
MixpanelLib.prototype.unregister = function(property, options) {
|
|
8445
|
+
var ret = this._run_hook('before_unregister', property, options);
|
|
8446
|
+
if (ret === null) {
|
|
8447
|
+
return;
|
|
8448
|
+
} else {
|
|
8449
|
+
property = ret[0];
|
|
8450
|
+
options = ret[1];
|
|
8451
|
+
}
|
|
8452
|
+
|
|
8296
8453
|
options = options_for_register(options);
|
|
8297
8454
|
if (options['persistent']) {
|
|
8298
8455
|
this['persistence'].unregister(property);
|
|
@@ -8341,6 +8498,13 @@ MixpanelLib.prototype.identify = function(
|
|
|
8341
8498
|
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
|
|
8342
8499
|
// _union_callback:function A callback to be run if and when the People union queue is flushed
|
|
8343
8500
|
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
|
|
8501
|
+
var ret = this._run_hook('before_identify', new_distinct_id);
|
|
8502
|
+
|
|
8503
|
+
if (ret === null) {
|
|
8504
|
+
return -1;
|
|
8505
|
+
} else {
|
|
8506
|
+
new_distinct_id = ret[0];
|
|
8507
|
+
}
|
|
8344
8508
|
|
|
8345
8509
|
var previous_distinct_id = this.get_distinct_id();
|
|
8346
8510
|
if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
|
|
@@ -8665,6 +8829,25 @@ MixpanelLib.prototype.set_config = function(config) {
|
|
|
8665
8829
|
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
8666
8830
|
this.autocapture.init();
|
|
8667
8831
|
}
|
|
8832
|
+
|
|
8833
|
+
if (_.isObject(config['hooks'])) {
|
|
8834
|
+
this.hooks = {};
|
|
8835
|
+
_.each(config['hooks'], function(hook_value, hook_name) {
|
|
8836
|
+
if (_.isFunction(hook_value)) {
|
|
8837
|
+
this.hooks[hook_name] = [hook_value];
|
|
8838
|
+
} else if (_.isArray(hook_value)) {
|
|
8839
|
+
this.hooks[hook_name] = [];
|
|
8840
|
+
for (var i = 0; i < hook_value.length; i++) {
|
|
8841
|
+
if (!_.isFunction(hook_value[i])) {
|
|
8842
|
+
console.critical('Invalid hook added. Hook is not a function');
|
|
8843
|
+
}
|
|
8844
|
+
this.hooks[hook_name].push(hook_value[i]);
|
|
8845
|
+
}
|
|
8846
|
+
} else {
|
|
8847
|
+
console.critical('Invalid hooks added. Ensure that the hook values passed into config.hooks are functions or arrays of functions.');
|
|
8848
|
+
}
|
|
8849
|
+
}, this);
|
|
8850
|
+
}
|
|
8668
8851
|
}
|
|
8669
8852
|
};
|
|
8670
8853
|
|
|
@@ -8682,12 +8865,26 @@ MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
8682
8865
|
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
8683
8866
|
*/
|
|
8684
8867
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
8685
|
-
var
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8868
|
+
var hook_data = slice.call(arguments, 1);
|
|
8869
|
+
_.each(this.hooks[hook_name], function(hook) {
|
|
8870
|
+
if (hook_data === null) {
|
|
8871
|
+
return null;
|
|
8872
|
+
}
|
|
8873
|
+
|
|
8874
|
+
var ret = hook.apply(this, hook_data);
|
|
8875
|
+
|
|
8876
|
+
if (typeof ret === 'undefined') {
|
|
8877
|
+
this.report_error(hook_name + ' hook did not return a valid value');
|
|
8878
|
+
hook_data = null;
|
|
8879
|
+
} else {
|
|
8880
|
+
if (!_.isArray(ret)) {
|
|
8881
|
+
ret = [ret];
|
|
8882
|
+
}
|
|
8883
|
+
hook_data.splice.apply(hook_data, [0, ret.length].concat(ret));
|
|
8884
|
+
}
|
|
8885
|
+
}, this);
|
|
8886
|
+
|
|
8887
|
+
return hook_data;
|
|
8691
8888
|
};
|
|
8692
8889
|
|
|
8693
8890
|
/**
|
|
@@ -8998,6 +9195,25 @@ MixpanelLib.prototype.report_error = function(msg, err) {
|
|
|
8998
9195
|
}
|
|
8999
9196
|
};
|
|
9000
9197
|
|
|
9198
|
+
MixpanelLib.prototype.add_hook = function(hook_name, hook_fn) {
|
|
9199
|
+
if (!this.hooks[hook_name]) {
|
|
9200
|
+
this.hooks[hook_name] = [];
|
|
9201
|
+
}
|
|
9202
|
+
this.hooks[hook_name].push(hook_fn);
|
|
9203
|
+
};
|
|
9204
|
+
|
|
9205
|
+
MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
9206
|
+
var fn_index;
|
|
9207
|
+
if (this.hooks[hook_name]) {
|
|
9208
|
+
fn_index = this.hooks[hook_name].indexOf(hook_fn);
|
|
9209
|
+
if (fn_index !== -1) {
|
|
9210
|
+
this.hooks[hook_name].splice(fn_index, 1);
|
|
9211
|
+
} else {
|
|
9212
|
+
console.log('remove_hook failed. Matching hook was not found');
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
};
|
|
9216
|
+
|
|
9001
9217
|
// EXPORTS (for closure compiler)
|
|
9002
9218
|
|
|
9003
9219
|
// MixpanelLib Exports
|
|
@@ -9030,6 +9246,8 @@ MixpanelLib.prototype['get_group'] = MixpanelLib.protot
|
|
|
9030
9246
|
MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group;
|
|
9031
9247
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
9032
9248
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
9249
|
+
MixpanelLib.prototype['add_hook'] = MixpanelLib.prototype.add_hook;
|
|
9250
|
+
MixpanelLib.prototype['remove_hook'] = MixpanelLib.prototype.remove_hook;
|
|
9033
9251
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
9034
9252
|
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
9035
9253
|
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|