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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixpanel-browser",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.73.0",
|
|
4
4
|
"description": "The official Mixpanel JavaScript browser client library",
|
|
5
5
|
"main": "dist/mixpanel.cjs.js",
|
|
6
6
|
"module": "dist/mixpanel.module.js",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"webpack": "1.12.2"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@mixpanel/rrweb": "2.0.0-alpha.18.2"
|
|
69
|
+
"@mixpanel/rrweb": "2.0.0-alpha.18.2",
|
|
70
|
+
"@mixpanel/rrweb-plugin-console-record": "2.0.0-alpha.18.2"
|
|
70
71
|
}
|
|
71
72
|
}
|
package/rollup.config.mjs
CHANGED
|
@@ -7,10 +7,11 @@ import fs from 'fs';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
|
|
9
9
|
const COMPILED_RRWEB_PATH = 'build/rrweb-compiled.js';
|
|
10
|
+
const BUNDLED_RRWEB_PATH = 'build/rrweb-bundled.js';
|
|
10
11
|
|
|
11
12
|
const aliasRrweb = () => alias({
|
|
12
13
|
entries: [
|
|
13
|
-
{ find:
|
|
14
|
+
{ find: /rrweb-entrypoint(?:\.js)?$/, replacement: COMPILED_RRWEB_PATH },
|
|
14
15
|
]
|
|
15
16
|
});
|
|
16
17
|
|
|
@@ -42,15 +43,25 @@ const MINIFY = process.env.MINIFY || process.env.FULL;
|
|
|
42
43
|
|
|
43
44
|
// Main builds used to develop / iterate quickly
|
|
44
45
|
const MAIN_BUILDS = [
|
|
45
|
-
// compile rrweb first to es5 with swc, we'll replace the import later on
|
|
46
46
|
{
|
|
47
|
-
'input': '
|
|
47
|
+
'input': 'src/recorder/rrweb-entrypoint.js',
|
|
48
|
+
'output': [
|
|
49
|
+
{
|
|
50
|
+
file: BUNDLED_RRWEB_PATH,
|
|
51
|
+
format: 'es',
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
plugins: [nodeResolve({browser: true})]
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
'input': BUNDLED_RRWEB_PATH,
|
|
48
58
|
'output': [
|
|
49
59
|
{
|
|
50
60
|
file: COMPILED_RRWEB_PATH,
|
|
61
|
+
format: 'es',
|
|
51
62
|
}
|
|
52
63
|
],
|
|
53
|
-
plugins: [
|
|
64
|
+
plugins: [swc({swc: {jsc: {target: 'es5'}}})]
|
|
54
65
|
},
|
|
55
66
|
|
|
56
67
|
// IIFE recorder bundle that is loaded asynchronously
|
package/src/autocapture/index.js
CHANGED
|
@@ -449,7 +449,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
449
449
|
return;
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
if (this._rageClickTracker.isRageClick(ev
|
|
452
|
+
if (this._rageClickTracker.isRageClick(ev, currentRageClickConfig)) {
|
|
453
453
|
this.trackDomEvent(ev, MP_EV_RAGE_CLICK);
|
|
454
454
|
}
|
|
455
455
|
}.bind(this);
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
+
import { getClickEventTargetElement, isDefinitelyNonInteractive } from './utils';
|
|
2
|
+
|
|
1
3
|
/** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
|
|
2
4
|
/** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
|
|
3
5
|
/** @const */ var DEFAULT_RAGE_CLICK_CLICK_COUNT = 4;
|
|
6
|
+
/** @const */ var DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY = false;
|
|
4
7
|
|
|
5
8
|
function RageClickTracker() {
|
|
6
9
|
this.clicks = [];
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Determines if a click event is part of a rage click sequence.
|
|
14
|
+
* @param {Event} event - the original click event.
|
|
15
|
+
* @param {import('../index.d.ts').RageClickConfig} options - configuration options for rage click detection.
|
|
16
|
+
* @returns {boolean} - true if the click is considered a rage click, false otherwise.
|
|
17
|
+
*/
|
|
18
|
+
RageClickTracker.prototype.isRageClick = function(event, options) {
|
|
10
19
|
options = options || {};
|
|
11
20
|
var thresholdPx = options['threshold_px'] || DEFAULT_RAGE_CLICK_THRESHOLD_PX;
|
|
12
21
|
var timeoutMs = options['timeout_ms'] || DEFAULT_RAGE_CLICK_TIMEOUT_MS;
|
|
13
22
|
var clickCount = options['click_count'] || DEFAULT_RAGE_CLICK_CLICK_COUNT;
|
|
23
|
+
var interactiveElementsOnly = options['interactive_elements_only'] || DEFAULT_RAGE_CLICK_INTERACTIVE_ELEMENTS_ONLY;
|
|
24
|
+
|
|
25
|
+
if (interactiveElementsOnly) {
|
|
26
|
+
var target = getClickEventTargetElement(event);
|
|
27
|
+
if (!target || isDefinitelyNonInteractive(target)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
var timestamp = Date.now();
|
|
33
|
+
var x = event['pageX'], y = event['pageY'];
|
|
15
34
|
|
|
16
35
|
var lastClick = this.clicks[this.clicks.length - 1];
|
|
17
36
|
if (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger, weakSetSupported } from './utils';
|
|
1
|
+
import { getClickEventComposedPath, getClickEventTargetElement, logger, weakSetSupported } from './utils';
|
|
2
2
|
|
|
3
3
|
function ShadowDOMObserver(changeCallback, observerConfig) {
|
|
4
4
|
this.changeCallback = changeCallback || function() {};
|
|
@@ -12,28 +12,16 @@ ShadowDOMObserver.prototype.getEventTarget = function(event) {
|
|
|
12
12
|
if (!this.observedShadowRoots) {
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
var path = this.getComposedPath(event);
|
|
16
|
-
if (path && path.length) {
|
|
17
|
-
return path[0];
|
|
18
|
-
}
|
|
19
15
|
|
|
20
|
-
return event
|
|
16
|
+
return getClickEventTargetElement(event);
|
|
21
17
|
};
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
ShadowDOMObserver.prototype.getComposedPath = function(event) {
|
|
25
|
-
if ('composedPath' in event) {
|
|
26
|
-
return event['composedPath']();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return [];
|
|
30
|
-
};
|
|
31
19
|
ShadowDOMObserver.prototype.observeFromEvent = function(event) {
|
|
32
20
|
if (!this.observedShadowRoots) {
|
|
33
21
|
return;
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
var path =
|
|
24
|
+
var path = getClickEventComposedPath(event);
|
|
37
25
|
|
|
38
26
|
// Check each element in path for shadow roots
|
|
39
27
|
for (var i = 0; i < path.length; i++) {
|
package/src/autocapture/utils.js
CHANGED
|
@@ -744,9 +744,39 @@ function isDefinitelyNonInteractive(element) {
|
|
|
744
744
|
return false;
|
|
745
745
|
}
|
|
746
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Get the composed path of a click event for elements embedded in shadow DOM.
|
|
749
|
+
* @param {Event} event - event to get the composed path from
|
|
750
|
+
* @returns {Array} the composed path of the click event
|
|
751
|
+
*/
|
|
752
|
+
function getClickEventComposedPath(event) {
|
|
753
|
+
if ('composedPath' in event) {
|
|
754
|
+
return event['composedPath']();
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return [];
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Get the element from a click event, accounting for elements embedded in shadow DOM.
|
|
762
|
+
* @param {Event} event - event to get the target from
|
|
763
|
+
* @returns {Element | null} the element that was the target of the click event
|
|
764
|
+
*/
|
|
765
|
+
function getClickEventTargetElement(event) {
|
|
766
|
+
var path = getClickEventComposedPath(event);
|
|
767
|
+
|
|
768
|
+
if (path && path.length > 0) {
|
|
769
|
+
return path[0];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return event['target'] || event['srcElement'];
|
|
773
|
+
}
|
|
774
|
+
|
|
747
775
|
export {
|
|
748
776
|
EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_INPUT, EV_LOAD,EV_MP_LOCATION_CHANGE, EV_POPSTATE,
|
|
749
777
|
EV_SCROLL, EV_SCROLLEND, EV_SELECT, EV_SUBMIT, EV_TOGGLE, EV_VISIBILITYCHANGE,
|
|
778
|
+
getClickEventComposedPath,
|
|
779
|
+
getClickEventTargetElement,
|
|
750
780
|
getPolyfillScrollEndFunction,
|
|
751
781
|
getPropsForDOMEvent,
|
|
752
782
|
getSafeText,
|
package/src/config.js
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -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 } | {};
|
package/src/mixpanel-core.js
CHANGED
|
@@ -57,8 +57,6 @@ var mixpanel_master; // main mixpanel instance / object
|
|
|
57
57
|
var INIT_MODULE = 0;
|
|
58
58
|
var INIT_SNIPPET = 1;
|
|
59
59
|
|
|
60
|
-
var IDENTITY_FUNC = function(x) {return x;};
|
|
61
|
-
|
|
62
60
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
63
61
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
64
62
|
/** @const */ var PAYLOAD_TYPE_JSON = 'json';
|
|
@@ -152,6 +150,7 @@ var DEFAULT_CONFIG = {
|
|
|
152
150
|
'record_block_selector': 'img, video, audio',
|
|
153
151
|
'record_canvas': false,
|
|
154
152
|
'record_collect_fonts': false,
|
|
153
|
+
'record_console': true,
|
|
155
154
|
'record_heatmap_data': false,
|
|
156
155
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
157
156
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
@@ -223,6 +222,17 @@ var create_mplib = function(token, config, name) {
|
|
|
223
222
|
// global debug to be true
|
|
224
223
|
Config.DEBUG = Config.DEBUG || instance.get_config('debug');
|
|
225
224
|
|
|
225
|
+
var source = init_type === INIT_MODULE ? 'module' : 'snippet';
|
|
226
|
+
window.dispatchEvent(new window.CustomEvent('$mp_sdk_to_extension_event', {
|
|
227
|
+
'detail': {
|
|
228
|
+
'instance': instance,
|
|
229
|
+
'source': source,
|
|
230
|
+
'token': token,
|
|
231
|
+
'name': name,
|
|
232
|
+
'info': _.info
|
|
233
|
+
}
|
|
234
|
+
}));
|
|
235
|
+
|
|
226
236
|
// if target is not defined, we called init after the lib already
|
|
227
237
|
// loaded, so there won't be an array of things to execute
|
|
228
238
|
if (!_.isUndefined(target) && _.isArray(target)) {
|
|
@@ -293,6 +303,8 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
293
303
|
}
|
|
294
304
|
}
|
|
295
305
|
|
|
306
|
+
this.hooks = {};
|
|
307
|
+
|
|
296
308
|
this.set_config(_.extend({}, DEFAULT_CONFIG, variable_features, config, {
|
|
297
309
|
'name': name,
|
|
298
310
|
'token': token,
|
|
@@ -893,7 +905,12 @@ MixpanelLib.prototype.init_batchers = function() {
|
|
|
893
905
|
);
|
|
894
906
|
}, this),
|
|
895
907
|
beforeSendHook: _.bind(function(item) {
|
|
896
|
-
|
|
908
|
+
var ret = this._run_hook('before_send_' + attrs.type, item);
|
|
909
|
+
if (ret) {
|
|
910
|
+
return ret[0];
|
|
911
|
+
} else {
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
897
914
|
}, this),
|
|
898
915
|
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
|
|
899
916
|
usePersistence: true,
|
|
@@ -986,6 +1003,9 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
986
1003
|
var send_request_immediately = _.bind(function() {
|
|
987
1004
|
if (!send_request_options.skip_hooks) {
|
|
988
1005
|
truncated_data = this._run_hook('before_send_' + options.type, truncated_data);
|
|
1006
|
+
if (truncated_data) {
|
|
1007
|
+
truncated_data = truncated_data[0];
|
|
1008
|
+
}
|
|
989
1009
|
}
|
|
990
1010
|
if (truncated_data) {
|
|
991
1011
|
console.log('MIXPANEL REQUEST:');
|
|
@@ -1040,6 +1060,17 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
1040
1060
|
* with the tracking payload sent to the API server is returned; otherwise false.
|
|
1041
1061
|
*/
|
|
1042
1062
|
MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, properties, options, callback) {
|
|
1063
|
+
var ret;
|
|
1064
|
+
if (!(options && options.skip_hooks)) {
|
|
1065
|
+
ret = this._run_hook('before_track', event_name, properties);
|
|
1066
|
+
if (ret === null) {
|
|
1067
|
+
return;
|
|
1068
|
+
} else {
|
|
1069
|
+
event_name = ret[0];
|
|
1070
|
+
properties = ret[1];
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1043
1074
|
if (!callback && typeof options === 'function') {
|
|
1044
1075
|
callback = options;
|
|
1045
1076
|
options = null;
|
|
@@ -1109,7 +1140,7 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
1109
1140
|
'event': event_name,
|
|
1110
1141
|
'properties': properties
|
|
1111
1142
|
};
|
|
1112
|
-
|
|
1143
|
+
ret = this._track_or_batch({
|
|
1113
1144
|
type: 'events',
|
|
1114
1145
|
data: data,
|
|
1115
1146
|
endpoint: this.get_api_host('events') + '/' + this.get_config('api_routes')['track'],
|
|
@@ -1455,6 +1486,14 @@ var options_for_register = function(days_or_options) {
|
|
|
1455
1486
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
1456
1487
|
*/
|
|
1457
1488
|
MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
1489
|
+
var ret = this._run_hook('before_register', props, days_or_options);
|
|
1490
|
+
if (ret === null) {
|
|
1491
|
+
return;
|
|
1492
|
+
} else {
|
|
1493
|
+
props = ret[0];
|
|
1494
|
+
days_or_options = ret[1];
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1458
1497
|
var options = options_for_register(days_or_options);
|
|
1459
1498
|
if (options['persistent']) {
|
|
1460
1499
|
this['persistence'].register(props, options['days']);
|
|
@@ -1491,6 +1530,15 @@ MixpanelLib.prototype.register = function(props, days_or_options) {
|
|
|
1491
1530
|
* @param {boolean} [days_or_options.persistent=true] - whether to put in persistent storage (cookie/localStorage)
|
|
1492
1531
|
*/
|
|
1493
1532
|
MixpanelLib.prototype.register_once = function(props, default_value, days_or_options) {
|
|
1533
|
+
var ret = this._run_hook('before_register_once', props, default_value, days_or_options);
|
|
1534
|
+
if (ret === null) {
|
|
1535
|
+
return;
|
|
1536
|
+
} else {
|
|
1537
|
+
props = ret[0];
|
|
1538
|
+
default_value = ret[1];
|
|
1539
|
+
days_or_options = ret[2];
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1494
1542
|
var options = options_for_register(days_or_options);
|
|
1495
1543
|
if (options['persistent']) {
|
|
1496
1544
|
this['persistence'].register_once(props, default_value, options['days']);
|
|
@@ -1514,6 +1562,14 @@ MixpanelLib.prototype.register_once = function(props, default_value, days_or_opt
|
|
|
1514
1562
|
* @param {boolean} [options.persistent=true] - whether to look in persistent storage (cookie/localStorage)
|
|
1515
1563
|
*/
|
|
1516
1564
|
MixpanelLib.prototype.unregister = function(property, options) {
|
|
1565
|
+
var ret = this._run_hook('before_unregister', property, options);
|
|
1566
|
+
if (ret === null) {
|
|
1567
|
+
return;
|
|
1568
|
+
} else {
|
|
1569
|
+
property = ret[0];
|
|
1570
|
+
options = ret[1];
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1517
1573
|
options = options_for_register(options);
|
|
1518
1574
|
if (options['persistent']) {
|
|
1519
1575
|
this['persistence'].unregister(property);
|
|
@@ -1562,6 +1618,13 @@ MixpanelLib.prototype.identify = function(
|
|
|
1562
1618
|
// _set_once_callback:function A callback to be run if and when the People set_once queue is flushed
|
|
1563
1619
|
// _union_callback:function A callback to be run if and when the People union queue is flushed
|
|
1564
1620
|
// _unset_callback:function A callback to be run if and when the People unset queue is flushed
|
|
1621
|
+
var ret = this._run_hook('before_identify', new_distinct_id);
|
|
1622
|
+
|
|
1623
|
+
if (ret === null) {
|
|
1624
|
+
return -1;
|
|
1625
|
+
} else {
|
|
1626
|
+
new_distinct_id = ret[0];
|
|
1627
|
+
}
|
|
1565
1628
|
|
|
1566
1629
|
var previous_distinct_id = this.get_distinct_id();
|
|
1567
1630
|
if (new_distinct_id && previous_distinct_id !== new_distinct_id) {
|
|
@@ -1886,6 +1949,25 @@ MixpanelLib.prototype.set_config = function(config) {
|
|
|
1886
1949
|
if (('autocapture' in config || 'record_heatmap_data' in config) && this.autocapture) {
|
|
1887
1950
|
this.autocapture.init();
|
|
1888
1951
|
}
|
|
1952
|
+
|
|
1953
|
+
if (_.isObject(config['hooks'])) {
|
|
1954
|
+
this.hooks = {};
|
|
1955
|
+
_.each(config['hooks'], function(hook_value, hook_name) {
|
|
1956
|
+
if (_.isFunction(hook_value)) {
|
|
1957
|
+
this.hooks[hook_name] = [hook_value];
|
|
1958
|
+
} else if (_.isArray(hook_value)) {
|
|
1959
|
+
this.hooks[hook_name] = [];
|
|
1960
|
+
for (var i = 0; i < hook_value.length; i++) {
|
|
1961
|
+
if (!_.isFunction(hook_value[i])) {
|
|
1962
|
+
console.critical('Invalid hook added. Hook is not a function');
|
|
1963
|
+
}
|
|
1964
|
+
this.hooks[hook_name].push(hook_value[i]);
|
|
1965
|
+
}
|
|
1966
|
+
} else {
|
|
1967
|
+
console.critical('Invalid hooks added. Ensure that the hook values passed into config.hooks are functions or arrays of functions.');
|
|
1968
|
+
}
|
|
1969
|
+
}, this);
|
|
1970
|
+
}
|
|
1889
1971
|
}
|
|
1890
1972
|
};
|
|
1891
1973
|
|
|
@@ -1903,12 +1985,26 @@ MixpanelLib.prototype.get_config = function(prop_name) {
|
|
|
1903
1985
|
* @returns {any|null} return value of user-provided hook, or null if nothing was returned
|
|
1904
1986
|
*/
|
|
1905
1987
|
MixpanelLib.prototype._run_hook = function(hook_name) {
|
|
1906
|
-
var
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1988
|
+
var hook_data = slice.call(arguments, 1);
|
|
1989
|
+
_.each(this.hooks[hook_name], function(hook) {
|
|
1990
|
+
if (hook_data === null) {
|
|
1991
|
+
return null;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
var ret = hook.apply(this, hook_data);
|
|
1995
|
+
|
|
1996
|
+
if (typeof ret === 'undefined') {
|
|
1997
|
+
this.report_error(hook_name + ' hook did not return a valid value');
|
|
1998
|
+
hook_data = null;
|
|
1999
|
+
} else {
|
|
2000
|
+
if (!_.isArray(ret)) {
|
|
2001
|
+
ret = [ret];
|
|
2002
|
+
}
|
|
2003
|
+
hook_data.splice.apply(hook_data, [0, ret.length].concat(ret));
|
|
2004
|
+
}
|
|
2005
|
+
}, this);
|
|
2006
|
+
|
|
2007
|
+
return hook_data;
|
|
1912
2008
|
};
|
|
1913
2009
|
|
|
1914
2010
|
/**
|
|
@@ -2219,6 +2315,25 @@ MixpanelLib.prototype.report_error = function(msg, err) {
|
|
|
2219
2315
|
}
|
|
2220
2316
|
};
|
|
2221
2317
|
|
|
2318
|
+
MixpanelLib.prototype.add_hook = function(hook_name, hook_fn) {
|
|
2319
|
+
if (!this.hooks[hook_name]) {
|
|
2320
|
+
this.hooks[hook_name] = [];
|
|
2321
|
+
}
|
|
2322
|
+
this.hooks[hook_name].push(hook_fn);
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
2326
|
+
var fn_index;
|
|
2327
|
+
if (this.hooks[hook_name]) {
|
|
2328
|
+
fn_index = this.hooks[hook_name].indexOf(hook_fn);
|
|
2329
|
+
if (fn_index !== -1) {
|
|
2330
|
+
this.hooks[hook_name].splice(fn_index, 1);
|
|
2331
|
+
} else {
|
|
2332
|
+
console.log('remove_hook failed. Matching hook was not found');
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2222
2337
|
// EXPORTS (for closure compiler)
|
|
2223
2338
|
|
|
2224
2339
|
// MixpanelLib Exports
|
|
@@ -2251,6 +2366,8 @@ MixpanelLib.prototype['get_group'] = MixpanelLib.protot
|
|
|
2251
2366
|
MixpanelLib.prototype['set_group'] = MixpanelLib.prototype.set_group;
|
|
2252
2367
|
MixpanelLib.prototype['add_group'] = MixpanelLib.prototype.add_group;
|
|
2253
2368
|
MixpanelLib.prototype['remove_group'] = MixpanelLib.prototype.remove_group;
|
|
2369
|
+
MixpanelLib.prototype['add_hook'] = MixpanelLib.prototype.add_hook;
|
|
2370
|
+
MixpanelLib.prototype['remove_hook'] = MixpanelLib.prototype.remove_hook;
|
|
2254
2371
|
MixpanelLib.prototype['track_with_groups'] = MixpanelLib.prototype.track_with_groups;
|
|
2255
2372
|
MixpanelLib.prototype['start_batch_senders'] = MixpanelLib.prototype.start_batch_senders;
|
|
2256
2373
|
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|
package/src/recorder/recorder.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// this file exists as an entry point to be able to transpile rrweb packages to es5
|
|
2
|
+
// compatible code without needing to transpile the entire mixpanel-js codebase
|
|
3
|
+
import {record, EventType, IncrementalSource} from '@mixpanel/rrweb';
|
|
4
|
+
import {getRecordConsolePlugin} from '@mixpanel/rrweb-plugin-console-record';
|
|
5
|
+
|
|
6
|
+
export { record, EventType, IncrementalSource, getRecordConsolePlugin };
|