mixpanel-browser 2.71.1 → 2.73.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/tests.yml +1 -0
- package/CHANGELOG.md +12 -0
- package/dist/mixpanel-core.cjs.d.ts +84 -13
- package/dist/mixpanel-core.cjs.js +180 -28
- package/dist/mixpanel-recorder.js +684 -114
- 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 +84 -13
- package/dist/mixpanel-with-async-recorder.cjs.js +180 -28
- package/dist/mixpanel-with-recorder.d.ts +84 -13
- package/dist/mixpanel-with-recorder.js +860 -140
- package/dist/mixpanel-with-recorder.min.d.ts +84 -13
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +84 -13
- package/dist/mixpanel.amd.js +860 -140
- package/dist/mixpanel.cjs.d.ts +84 -13
- package/dist/mixpanel.cjs.js +860 -140
- package/dist/mixpanel.globals.js +180 -28
- package/dist/mixpanel.min.js +172 -170
- package/dist/mixpanel.module.d.ts +84 -13
- package/dist/mixpanel.module.js +860 -140
- package/dist/mixpanel.umd.d.ts +84 -13
- package/dist/mixpanel.umd.js +860 -140
- package/dist/rrweb-bundled.js +12760 -0
- package/dist/rrweb-compiled.js +2496 -7176
- package/package.json +3 -2
- package/rollup.config.mjs +15 -4
- package/src/autocapture/index.js +1 -1
- package/src/autocapture/rageclick.js +20 -1
- package/src/autocapture/shadow-dom-observer.js +3 -15
- package/src/autocapture/utils.js +30 -0
- package/src/config.js +1 -1
- package/src/index.d.ts +84 -13
- package/src/mixpanel-core.js +127 -10
- package/src/recorder/recorder.js +1 -1
- package/src/recorder/rrweb-entrypoint.js +6 -0
- package/src/recorder/session-recording.js +69 -12
- package/src/utils.js +24 -0
- package/src/window.js +3 -1
- package/.claude/settings.local.json +0 -9
|
@@ -2,7 +2,7 @@ export type Persistence = "cookie" | "localStorage";
|
|
|
2
2
|
|
|
3
3
|
export type ApiPayloadFormat = "base64" | "json";
|
|
4
4
|
|
|
5
|
-
export type PushItem = Array<string | Dict>;
|
|
5
|
+
export type PushItem = Array<string | Dict | ((this: Mixpanel) => void)>;
|
|
6
6
|
|
|
7
7
|
export type Query = string | Element | Element[];
|
|
8
8
|
|
|
@@ -43,20 +43,22 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
|
|
|
43
43
|
export type RageClickConfig =
|
|
44
44
|
| boolean
|
|
45
45
|
| {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
/** Distance threshold in pixels for clicks to be considered within the same area (default: 30) */
|
|
47
|
+
threshold_px?: number;
|
|
48
|
+
/** Time window in milliseconds for clicks to be considered rapid (default: 1000) */
|
|
49
|
+
timeout_ms?: number;
|
|
50
|
+
/** Number of clicks required to trigger a rage click event (default: 3) */
|
|
51
|
+
click_count?: number;
|
|
52
|
+
/** Whether to only track rage clicks on interactive elements like buttons, links, inputs (default: false) */
|
|
53
|
+
interactive_elements_only?: boolean;
|
|
54
|
+
};
|
|
53
55
|
|
|
54
56
|
export type DeadClickConfig =
|
|
55
57
|
| boolean
|
|
56
58
|
| {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
/** Time in milliseconds to wait after a click before qualifying it as dead (default: 500) */
|
|
60
|
+
timeout_ms?: number;
|
|
61
|
+
};
|
|
60
62
|
|
|
61
63
|
export interface RegisterOptions {
|
|
62
64
|
persistent: boolean;
|
|
@@ -149,6 +151,15 @@ export interface AutocaptureConfig {
|
|
|
149
151
|
block_element_callback?: (element: Element, event: Event) => boolean;
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
export interface FlagsConfig {
|
|
155
|
+
context: Dict;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface BeforeSendHookPayload {
|
|
159
|
+
event: string;
|
|
160
|
+
properties: Record<string, any>;
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
export interface Config {
|
|
153
164
|
api_host: string;
|
|
154
165
|
api_routes: {
|
|
@@ -161,10 +172,14 @@ export interface Config {
|
|
|
161
172
|
app_host: string;
|
|
162
173
|
api_payload_format: ApiPayloadFormat;
|
|
163
174
|
autotrack: boolean;
|
|
175
|
+
batch_autostart: boolean;
|
|
176
|
+
batch_requests: boolean;
|
|
164
177
|
cdn: string;
|
|
165
178
|
cookie_domain: string;
|
|
166
179
|
cross_site_cookie: boolean;
|
|
167
180
|
cross_subdomain_cookie: boolean;
|
|
181
|
+
error_reporter: (msg: string, err?: Error) => void;
|
|
182
|
+
flags: boolean | FlagsConfig;
|
|
168
183
|
persistence: Persistence;
|
|
169
184
|
persistence_name: string;
|
|
170
185
|
cookie_name: string;
|
|
@@ -200,10 +215,10 @@ export interface Config {
|
|
|
200
215
|
inapp_protocol: string;
|
|
201
216
|
inapp_link_new_window: boolean;
|
|
202
217
|
ignore_dnt: boolean;
|
|
203
|
-
batch_requests: boolean;
|
|
204
218
|
batch_size: number;
|
|
205
219
|
batch_flush_interval_ms: number;
|
|
206
220
|
batch_request_timeout_ms: number;
|
|
221
|
+
recorder_src: string;
|
|
207
222
|
record_block_class: string | RegExp;
|
|
208
223
|
record_block_selector: string;
|
|
209
224
|
record_collect_fonts: boolean;
|
|
@@ -216,6 +231,29 @@ export interface Config {
|
|
|
216
231
|
record_sessions_percent: number;
|
|
217
232
|
record_canvas: boolean;
|
|
218
233
|
record_heatmap_data: boolean;
|
|
234
|
+
hooks: {
|
|
235
|
+
before_identify?: (new_distinct_id: string) => string | null;
|
|
236
|
+
before_register?: (
|
|
237
|
+
props: Dict,
|
|
238
|
+
days_or_options?: number | Partial<RegisterOptions>
|
|
239
|
+
) => Dict | Array<Dict | number | Partial<RegisterOptions>> | null;
|
|
240
|
+
before_register_once?: (
|
|
241
|
+
props: Dict,
|
|
242
|
+
default_value?: any,
|
|
243
|
+
days_or_options?: number | Partial<RegisterOptions>
|
|
244
|
+
) => Dict | Array<any | Dict | number | Partial<RegisterOptions>> | null;
|
|
245
|
+
before_send_events?: (
|
|
246
|
+
event: BeforeSendHookPayload
|
|
247
|
+
) => BeforeSendHookPayload | null;
|
|
248
|
+
before_track?: (
|
|
249
|
+
event_name: string,
|
|
250
|
+
properties: Dict
|
|
251
|
+
) => string | Array<string | Dict> | null;
|
|
252
|
+
before_unregister?: (
|
|
253
|
+
property: string,
|
|
254
|
+
options?: Partial<RegisterOptions>
|
|
255
|
+
) => string | Partial<RegisterOptions> | null;
|
|
256
|
+
};
|
|
219
257
|
}
|
|
220
258
|
|
|
221
259
|
export type VerboseResponse =
|
|
@@ -277,19 +315,50 @@ export interface Group {
|
|
|
277
315
|
unset(prop: string, callback?: Callback): void;
|
|
278
316
|
}
|
|
279
317
|
|
|
318
|
+
export interface FlagsVariant {
|
|
319
|
+
key: string;
|
|
320
|
+
value: any;
|
|
321
|
+
experiment_id?: string;
|
|
322
|
+
is_experiment_active?: boolean;
|
|
323
|
+
is_qa_tester?: boolean;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface FlagsUpdateContextOptions {
|
|
327
|
+
replace?: boolean;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface FlagsManager {
|
|
331
|
+
are_flags_ready(): boolean;
|
|
332
|
+
get_variant(
|
|
333
|
+
featureName: string,
|
|
334
|
+
fallback: FlagsVariant
|
|
335
|
+
): Promise<FlagsVariant>;
|
|
336
|
+
get_variant_sync(featureName: string, fallback: FlagsVariant): FlagsVariant;
|
|
337
|
+
get_variant_value(featureName: string, fallbackValue: any): Promise<any>;
|
|
338
|
+
get_variant_value_sync(featureName: string, fallbackValue: any): any;
|
|
339
|
+
is_enabled(featureName: string, fallbackValue?: boolean): Promise<boolean>;
|
|
340
|
+
is_enabled_sync(featureName: string, fallbackValue?: boolean): boolean;
|
|
341
|
+
update_context(
|
|
342
|
+
context: Dict,
|
|
343
|
+
options?: FlagsUpdateContextOptions
|
|
344
|
+
): Promise<void>;
|
|
345
|
+
}
|
|
346
|
+
|
|
280
347
|
export interface Mixpanel {
|
|
281
348
|
add_group(group_key: string, group_id: string, callback?: Callback): void;
|
|
282
349
|
alias(alias: string, original?: string): void;
|
|
283
350
|
clear_opt_in_out_tracking(options?: Partial<ClearOptOutInOutOptions>): void;
|
|
284
351
|
disable(events?: string[]): void;
|
|
352
|
+
flags: FlagsManager;
|
|
285
353
|
get_config(prop_name?: string): any;
|
|
286
354
|
get_distinct_id(): any;
|
|
287
355
|
get_group(group_key: string, group_id: string): Group;
|
|
288
356
|
get_property(property_name: string): any;
|
|
357
|
+
get_session_replay_url(): string;
|
|
289
358
|
has_opted_in_tracking(options?: Partial<HasOptedInOutOptions>): boolean;
|
|
290
359
|
has_opted_out_tracking(options?: Partial<HasOptedInOutOptions>): boolean;
|
|
291
360
|
identify(unique_id?: string): any;
|
|
292
|
-
init(token: string, config: Partial<Config>, name
|
|
361
|
+
init(token: string, config: Partial<Config>, name?: string): Mixpanel;
|
|
293
362
|
opt_in_tracking(options?: Partial<InTrackingOptions>): void;
|
|
294
363
|
opt_out_tracking(options?: Partial<OutTrackingOptions>): void;
|
|
295
364
|
push(item: PushItem): void;
|
|
@@ -314,6 +383,7 @@ export interface Mixpanel {
|
|
|
314
383
|
group_ids: string | string[] | number | number[],
|
|
315
384
|
callback?: Callback
|
|
316
385
|
): void;
|
|
386
|
+
start_batch_senders(): void;
|
|
317
387
|
time_event(event_name: string): void;
|
|
318
388
|
track(
|
|
319
389
|
event_name: string,
|
|
@@ -343,6 +413,7 @@ export interface Mixpanel {
|
|
|
343
413
|
): void;
|
|
344
414
|
unregister(property: string, options?: Partial<RegisterOptions>): void;
|
|
345
415
|
people: People;
|
|
416
|
+
start_batch_senders(): void;
|
|
346
417
|
start_session_recording(): void;
|
|
347
418
|
stop_session_recording(): void;
|
|
348
419
|
get_session_recording_properties(): { $mp_replay_id?: string } | {};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.73.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;
|
|
@@ -2892,20 +2894,65 @@ function isDefinitelyNonInteractive(element) {
|
|
|
2892
2894
|
return false;
|
|
2893
2895
|
}
|
|
2894
2896
|
|
|
2897
|
+
/**
|
|
2898
|
+
* Get the composed path of a click event for elements embedded in shadow DOM.
|
|
2899
|
+
* @param {Event} event - event to get the composed path from
|
|
2900
|
+
* @returns {Array} the composed path of the click event
|
|
2901
|
+
*/
|
|
2902
|
+
function getClickEventComposedPath(event) {
|
|
2903
|
+
if ('composedPath' in event) {
|
|
2904
|
+
return event['composedPath']();
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
return [];
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
/**
|
|
2911
|
+
* Get the element from a click event, accounting for elements embedded in shadow DOM.
|
|
2912
|
+
* @param {Event} event - event to get the target from
|
|
2913
|
+
* @returns {Element | null} the element that was the target of the click event
|
|
2914
|
+
*/
|
|
2915
|
+
function getClickEventTargetElement(event) {
|
|
2916
|
+
var path = getClickEventComposedPath(event);
|
|
2917
|
+
|
|
2918
|
+
if (path && path.length > 0) {
|
|
2919
|
+
return path[0];
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
return event['target'] || event['srcElement'];
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2895
2925
|
/** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
|
|
2896
2926
|
/** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
|
|
2897
2927
|
/** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
|
|
2928
|
+
/** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
|
|
2898
2929
|
|
|
2899
2930
|
function RageClickTracker() {
|
|
2900
2931
|
this.clicks = [];
|
|
2901
2932
|
}
|
|
2902
2933
|
|
|
2903
|
-
|
|
2934
|
+
/**
|
|
2935
|
+
* Determines if a click event is part of a rage click sequence.
|
|
2936
|
+
* @param {Event} event - the original click event.
|
|
2937
|
+
* @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
|
|
2938
|
+
* @returns {boolean} - true if the click is considered a rage click, false otherwise.
|
|
2939
|
+
*/
|
|
2940
|
+
RageClickTracker.prototype.isRageClick = function(event, options) {
|
|
2904
2941
|
options = options || {};
|
|
2905
2942
|
var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
|
|
2906
2943
|
var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
|
|
2907
2944
|
var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
|
|
2945
|
+
var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
|
|
2946
|
+
|
|
2947
|
+
if (interactiveElementsOnly) {
|
|
2948
|
+
var target = getClickEventTargetElement(event);
|
|
2949
|
+
if (!target || isDefinitelyNonInteractive(target)) {
|
|
2950
|
+
return false;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2908
2954
|
var timestamp = Date.now();
|
|
2955
|
+
var x = event['pageX'], y = event['pageY'];
|
|
2909
2956
|
|
|
2910
2957
|
var lastClick = this.clicks[this.clicks.length - 1];
|
|
2911
2958
|
if (
|
|
@@ -2936,28 +2983,16 @@ ShadowDOMObserver.prototype.getEventTarget = function(event) {
|
|
|
2936
2983
|
if (!this.observedShadowRoots) {
|
|
2937
2984
|
return;
|
|
2938
2985
|
}
|
|
2939
|
-
var path = this.getComposedPath(event);
|
|
2940
|
-
if (path && path.length) {
|
|
2941
|
-
return path[0];
|
|
2942
|
-
}
|
|
2943
2986
|
|
|
2944
|
-
return event
|
|
2987
|
+
return getClickEventTargetElement(event);
|
|
2945
2988
|
};
|
|
2946
2989
|
|
|
2947
|
-
|
|
2948
|
-
ShadowDOMObserver.prototype.getComposedPath = function(event) {
|
|
2949
|
-
if ('composedPath' in event) {
|
|
2950
|
-
return event['composedPath']();
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
return [];
|
|
2954
|
-
};
|
|
2955
2990
|
ShadowDOMObserver.prototype.observeFromEvent = function(event) {
|
|
2956
2991
|
if (!this.observedShadowRoots) {
|
|
2957
2992
|
return;
|
|
2958
2993
|
}
|
|
2959
2994
|
|
|
2960
|
-
var path =
|
|
2995
|
+
var path = getClickEventComposedPath(event);
|
|
2961
2996
|
|
|
2962
2997
|
// Check each element in path for shadow roots
|
|
2963
2998
|
for (var i = 0; i < path.length; i++) {
|
|
@@ -3709,7 +3744,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
3709
3744
|
return;
|
|
3710
3745
|
}
|
|
3711
3746
|
|
|
3712
|
-
if (this._rageClickTracker.isRageClick(ev
|
|
3747
|
+
if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
|
|
3713
3748
|
this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
|
|
3714
3749
|
}
|
|
3715
3750
|
}.bind(this);
|
|
@@ -6802,8 +6837,6 @@ var mixpanel_master; // main mixpanel instance / object
|
|
|
6802
6837
|
var INIT_MODULE = 0;
|
|
6803
6838
|
var INIT_SNIPPET = 1;
|
|
6804
6839
|
|
|
6805
|
-
var IDENTITY_FUNC = function(x) {return x;};
|
|
6806
|
-
|
|
6807
6840
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
6808
6841
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
6809
6842
|
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
@@ -6897,6 +6930,7 @@ var DEFAULT_CONFIG = {
|
|
|
6897
6930
|
'record_block_selector': 'img, video, audio',
|
|
6898
6931
|
'record_canvas': false,
|
|
6899
6932
|
'record_collect_fonts': false,
|
|
6933
|
+
'record_console': true,
|
|
6900
6934
|
'record_heatmap_data': false,
|
|
6901
6935
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
6902
6936
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
@@ -6968,6 +7002,17 @@ var create_mplib = function(token, config, name) {
|
|
|
6968
7002
|
// global debug to be true
|
|
6969
7003
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
6970
7004
|
|
|
7005
|
+
var source = init_type === INIT_MODULE ? 'module' : 'snippet';
|
|
7006
|
+
win.dispatchEvent(new win.CustomEvent('$mp_sdk_to_extension_event', {
|
|
7007
|
+
'detail': {
|
|
7008
|
+
'instance': instance,
|
|
7009
|
+
'source': source,
|
|
7010
|
+
'token': token,
|
|
7011
|
+
'name': name,
|
|
7012
|
+
'info': _.info
|
|
7013
|
+
}
|
|
7014
|
+
}));
|
|
7015
|
+
|
|
6971
7016
|
// if target is not defined, we called init after the lib already
|
|
6972
7017
|
// loaded, so there won't be an array of things to execute
|
|
6973
7018
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -7038,6 +7083,8 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
7038
7083
|
}
|
|
7039
7084
|
}
|
|
7040
7085
|
|
|
7086
|
+
this.hooks = {};
|
|
7087
|
+
|
|
7041
7088
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
7042
7089
|
'name': name,
|
|
7043
7090
|
'token': token,
|
|
@@ -7638,7 +7685,12 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
7638
7685
|
);
|
|
7639
7686
|
}, this),
|
|
7640
7687
|
beforeSendHook: _.bind(function(item) {
|
|
7641
|
-
|
|
7688
|
+
var ret = this._run_hook('before_send_' + attrs.type, item);
|
|
7689
|
+
if (ret) {
|
|
7690
|
+
return ret[0];
|
|
7691
|
+
} else {
|
|
7692
|
+
return null;
|
|
7693
|
+
}
|
|
7642
7694
|
}, this),
|
|
7643
7695
|
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
|
|
7644
7696
|
usePersistence: true,
|
|
@@ -7731,6 +7783,9 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
7731
7783
|
var send_request_immediately = _.bind(function() {
|
|
7732
7784
|
if (!send_request_options.skip_hooks) {
|
|
7733
7785
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
7786
|
+
if (truncated_data) {
|
|
7787
|
+
truncated_data = truncated_data[0];
|
|
7788
|
+
}
|
|
7734
7789
|
}
|
|
7735
7790
|
if (truncated_data) {
|
|
7736
7791
|
console.log('MIXPANEL REQUEST:');
|
|
@@ -7785,6 +7840,17 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
7785
7840
|
* with the tracking payload sent to the API server is returned; otherwise false.
|
|
7786
7841
|
*/
|
|
7787
7842
|
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) {
|
|
7843
|
+
var ret;
|
|
7844
|
+
if (!(options && options.skip_hooks)) {
|
|
7845
|
+
ret = this._run_hook('before_track', event_name, properties);
|
|
7846
|
+
if (ret === null) {
|
|
7847
|
+
return;
|
|
7848
|
+
} else {
|
|
7849
|
+
event_name = ret[0];
|
|
7850
|
+
properties = ret[1];
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7853
|
+
|
|
7788
7854
|
if (!callback && typeof options === 'function') {
|
|
7789
7855
|
callback = options;
|
|
7790
7856
|
options = null;
|
|
@@ -7854,7 +7920,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
7854
7920
|
'event': event_name,
|
|
7855
7921
|
'properties': properties
|
|
7856
7922
|
};
|
|
7857
|
-
|
|
7923
|
+
ret = this._track_or_batch({
|
|
7858
7924
|
type: 'events',
|
|
7859
7925
|
data: data,
|
|
7860
7926
|
endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
|
|
@@ -8200,6 +8266,14 @@ var options_for_register = function(days_or_options) {
|
|
|
8200
8266
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8201
8267
|
*/
|
|
8202
8268
|
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
8269
|
+
var ret = this._run_hook('before_register', props, days_or_options);
|
|
8270
|
+
if (ret === null) {
|
|
8271
|
+
return;
|
|
8272
|
+
} else {
|
|
8273
|
+
props = ret[0];
|
|
8274
|
+
days_or_options = ret[1];
|
|
8275
|
+
}
|
|
8276
|
+
|
|
8203
8277
|
var options = options_for_register(days_or_options);
|
|
8204
8278
|
if (options['persistent']) {
|
|
8205
8279
|
this['persistence'].register(props, options['days']);
|
|
@@ -8236,6 +8310,15 @@ MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
|
8236
8310
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
8237
8311
|
*/
|
|
8238
8312
|
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
8313
|
+
var ret = this._run_hook('before_register_once', props, default_value, days_or_options);
|
|
8314
|
+
if (ret === null) {
|
|
8315
|
+
return;
|
|
8316
|
+
} else {
|
|
8317
|
+
props = ret[0];
|
|
8318
|
+
default_value = ret[1];
|
|
8319
|
+
days_or_options = ret[2];
|
|
8320
|
+
}
|
|
8321
|
+
|
|
8239
8322
|
var options = options_for_register(days_or_options);
|
|
8240
8323
|
if (options['persistent']) {
|
|
8241
8324
|
this['persistence'].register_once(props, default_value, options['days']);
|
|
@@ -8259,6 +8342,14 @@ MixpanelLib.prototype.register_once = function(props, default_value, days_or_opt
|
|
|
8259
8342
|
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
8260
8343
|
*/
|
|
8261
8344
|
MixpanelLib.prototype.unregister = function(property, options) {
|
|
8345
|
+
var ret = this._run_hook('before_unregister', property, options);
|
|
8346
|
+
if (ret === null) {
|
|
8347
|
+
return;
|
|
8348
|
+
} else {
|
|
8349
|
+
property = ret[0];
|
|
8350
|
+
options = ret[1];
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8262
8353
|
options = options_for_register(options);
|
|
8263
8354
|
if (options['persistent']) {
|
|
8264
8355
|
this['persistence'].unregister(property);
|
|
@@ -8307,6 +8398,13 @@ MixpanelLib.prototype.identify = function(
|
|
|
8307
8398
|
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
|
|
8308
8399
|
// _union_callback:function A callback to be run if and when the People union queue is flushed
|
|
8309
8400
|
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
|
|
8401
|
+
var ret = this._run_hook('before_identify', new_distinct_id);
|
|
8402
|
+
|
|
8403
|
+
if (ret === null) {
|
|
8404
|
+
return -1;
|
|
8405
|
+
} else {
|
|
8406
|
+
new_distinct_id = ret[0];
|
|
8407
|
+
}
|
|
8310
8408
|
|
|
8311
8409
|
var previous_distinct_id = this.get_distinct_id();
|
|
8312
8410
|
if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
|
|
@@ -8631,6 +8729,25 @@ MixpanelLib.prototype.set_config = function(config) {
|
|
|
8631
8729
|
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
8632
8730
|
this.autocapture.init();
|
|
8633
8731
|
}
|
|
8732
|
+
|
|
8733
|
+
if (_.isObject(config['hooks'])) {
|
|
8734
|
+
this.hooks = {};
|
|
8735
|
+
_.each(config['hooks'], function(hook_value, hook_name) {
|
|
8736
|
+
if (_.isFunction(hook_value)) {
|
|
8737
|
+
this.hooks[hook_name] = [hook_value];
|
|
8738
|
+
} else if (_.isArray(hook_value)) {
|
|
8739
|
+
this.hooks[hook_name] = [];
|
|
8740
|
+
for (var i = 0; i < hook_value.length; i++) {
|
|
8741
|
+
if (!_.isFunction(hook_value[i])) {
|
|
8742
|
+
console.critical('Invalid hook added. Hook is not a function');
|
|
8743
|
+
}
|
|
8744
|
+
this.hooks[hook_name].push(hook_value[i]);
|
|
8745
|
+
}
|
|
8746
|
+
} else {
|
|
8747
|
+
console.critical('Invalid hooks added. Ensure that the hook values passed into config.hooks are functions or arrays of functions.');
|
|
8748
|
+
}
|
|
8749
|
+
}, this);
|
|
8750
|
+
}
|
|
8634
8751
|
}
|
|
8635
8752
|
};
|
|
8636
8753
|
|
|
@@ -8648,12 +8765,26 @@ MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
8648
8765
|
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
8649
8766
|
*/
|
|
8650
8767
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
8651
|
-
var
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8768
|
+
var hook_data = slice.call(arguments, 1);
|
|
8769
|
+
_.each(this.hooks[hook_name], function(hook) {
|
|
8770
|
+
if (hook_data === null) {
|
|
8771
|
+
return null;
|
|
8772
|
+
}
|
|
8773
|
+
|
|
8774
|
+
var ret = hook.apply(this, hook_data);
|
|
8775
|
+
|
|
8776
|
+
if (typeof ret === 'undefined') {
|
|
8777
|
+
this.report_error(hook_name + ' hook did not return a valid value');
|
|
8778
|
+
hook_data = null;
|
|
8779
|
+
} else {
|
|
8780
|
+
if (!_.isArray(ret)) {
|
|
8781
|
+
ret = [ret];
|
|
8782
|
+
}
|
|
8783
|
+
hook_data.splice.apply(hook_data, [0, ret.length].concat(ret));
|
|
8784
|
+
}
|
|
8785
|
+
}, this);
|
|
8786
|
+
|
|
8787
|
+
return hook_data;
|
|
8657
8788
|
};
|
|
8658
8789
|
|
|
8659
8790
|
/**
|
|
@@ -8964,6 +9095,25 @@ MixpanelLib.prototype.report_error = function(msg, err) {
|
|
|
8964
9095
|
}
|
|
8965
9096
|
};
|
|
8966
9097
|
|
|
9098
|
+
MixpanelLib.prototype.add_hook = function(hook_name, hook_fn) {
|
|
9099
|
+
if (!this.hooks[hook_name]) {
|
|
9100
|
+
this.hooks[hook_name] = [];
|
|
9101
|
+
}
|
|
9102
|
+
this.hooks[hook_name].push(hook_fn);
|
|
9103
|
+
};
|
|
9104
|
+
|
|
9105
|
+
MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
9106
|
+
var fn_index;
|
|
9107
|
+
if (this.hooks[hook_name]) {
|
|
9108
|
+
fn_index = this.hooks[hook_name].indexOf(hook_fn);
|
|
9109
|
+
if (fn_index !== -1) {
|
|
9110
|
+
this.hooks[hook_name].splice(fn_index, 1);
|
|
9111
|
+
} else {
|
|
9112
|
+
console.log('remove_hook failed. Matching hook was not found');
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
};
|
|
9116
|
+
|
|
8967
9117
|
// EXPORTS (for closure compiler)
|
|
8968
9118
|
|
|
8969
9119
|
// MixpanelLib Exports
|
|
@@ -8996,6 +9146,8 @@ MixpanelLib.prototype['get_group'] = MixpanelLib.protot
|
|
|
8996
9146
|
MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group;
|
|
8997
9147
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
8998
9148
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
9149
|
+
MixpanelLib.prototype['add_hook'] = MixpanelLib.prototype.add_hook;
|
|
9150
|
+
MixpanelLib.prototype['remove_hook'] = MixpanelLib.prototype.remove_hook;
|
|
8999
9151
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
9000
9152
|
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
9001
9153
|
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|