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