mixpanel-browser 2.67.0 → 2.69.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/CHANGELOG.md +5 -0
- package/dist/mixpanel-core.cjs.js +132 -6
- package/dist/mixpanel-recorder.js +186 -47
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +132 -6
- package/dist/mixpanel-with-recorder.js +308 -51
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.js +308 -51
- package/dist/mixpanel.cjs.js +308 -51
- package/dist/mixpanel.globals.js +132 -6
- package/dist/mixpanel.min.js +148 -146
- package/dist/mixpanel.module.js +308 -51
- package/dist/mixpanel.umd.js +308 -51
- package/dist/rrweb-compiled.js +190 -59
- package/package.json +14 -2
- package/rollup.config.mjs +2 -2
- package/src/autocapture/index.js +59 -1
- package/src/autocapture/rageclick.js +38 -0
- package/src/config.js +1 -1
- package/src/flags/index.js +22 -1
- package/src/index.d.ts +19 -0
- package/src/mixpanel-core.js +10 -2
- package/src/recorder/recorder.js +1 -1
- package/src/recorder/recording-registry.js +37 -2
- package/src/recorder/session-recording.js +2 -3
- package/src/request-queue.js +1 -1
- package/src/storage/indexed-db.js +4 -0
- package/src/storage/local-storage.js +4 -0
- package/src/storage/wrapper.js +1 -0
package/src/index.d.ts
CHANGED
|
@@ -40,6 +40,17 @@ export interface OutTrackingOptions extends ClearOptOutInOutOptions {
|
|
|
40
40
|
delete_user: boolean;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type RageClickConfig =
|
|
44
|
+
| boolean
|
|
45
|
+
| {
|
|
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
|
+
};
|
|
53
|
+
|
|
43
54
|
export interface RegisterOptions {
|
|
44
55
|
persistent: boolean;
|
|
45
56
|
}
|
|
@@ -66,6 +77,12 @@ export interface AutocaptureConfig {
|
|
|
66
77
|
* @default 'full-url'
|
|
67
78
|
*/
|
|
68
79
|
pageview?: TrackPageView;
|
|
80
|
+
/**
|
|
81
|
+
* When set to `true`, Mixpanel will track rage clicks (multiple clicks in a short time on the same element).
|
|
82
|
+
* Can also be configured as an object to customize rage click detection parameters.
|
|
83
|
+
* @default true
|
|
84
|
+
*/
|
|
85
|
+
rage_click?: RageClickConfig;
|
|
69
86
|
/**
|
|
70
87
|
* When set, Mixpanel will collect page scrolls at specified scroll intervals.
|
|
71
88
|
* @default true
|
|
@@ -181,9 +198,11 @@ export interface Config {
|
|
|
181
198
|
record_inline_images: boolean;
|
|
182
199
|
record_mask_text_class: string;
|
|
183
200
|
record_mask_text_selector: string;
|
|
201
|
+
record_min_ms: number;
|
|
184
202
|
record_max_ms: number;
|
|
185
203
|
record_sessions_percent: number;
|
|
186
204
|
record_canvas: boolean;
|
|
205
|
+
record_heatmap_data: boolean;
|
|
187
206
|
}
|
|
188
207
|
|
|
189
208
|
export type VerboseResponse =
|
package/src/mixpanel-core.js
CHANGED
|
@@ -149,7 +149,7 @@ var DEFAULT_CONFIG = {
|
|
|
149
149
|
'batch_autostart': true,
|
|
150
150
|
'hooks': {},
|
|
151
151
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
152
|
-
'record_block_selector': 'img, video',
|
|
152
|
+
'record_block_selector': 'img, video, audio',
|
|
153
153
|
'record_canvas': false,
|
|
154
154
|
'record_collect_fonts': false,
|
|
155
155
|
'record_heatmap_data': false,
|
|
@@ -373,6 +373,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
373
373
|
return this.get_api_host('flags') + '/' + this.get_config('api_routes')['flags'];
|
|
374
374
|
}, this),
|
|
375
375
|
getConfigFunc: _.bind(this.get_config, this),
|
|
376
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
376
377
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
377
378
|
trackingFunc: _.bind(this.track, this)
|
|
378
379
|
});
|
|
@@ -391,7 +392,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
391
392
|
* This is primarily used for session recording, where data must be isolated to the current tab.
|
|
392
393
|
*/
|
|
393
394
|
MixpanelLib.prototype._init_tab_id = function() {
|
|
394
|
-
if (
|
|
395
|
+
if (this.get_config('disable_persistence')) {
|
|
396
|
+
console.log('Tab ID initialization skipped due to disable_persistence config');
|
|
397
|
+
} else if (_.sessionStorage.is_supported()) {
|
|
395
398
|
try {
|
|
396
399
|
var key_suffix = this.get_config('name') + '_' + this.get_config('token');
|
|
397
400
|
var tab_id_key = 'mp_tab_id_' + key_suffix;
|
|
@@ -425,6 +428,11 @@ MixpanelLib.prototype.get_tab_id = function () {
|
|
|
425
428
|
};
|
|
426
429
|
|
|
427
430
|
MixpanelLib.prototype._should_load_recorder = function () {
|
|
431
|
+
if (this.get_config('disable_persistence')) {
|
|
432
|
+
console.log('Load recorder check skipped due to disable_persistence config');
|
|
433
|
+
return Promise.resolve(false);
|
|
434
|
+
}
|
|
435
|
+
|
|
428
436
|
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
429
437
|
var tab_id = this.get_tab_id();
|
|
430
438
|
return recording_registry_idb.init()
|
package/src/recorder/recorder.js
CHANGED
|
@@ -8,12 +8,17 @@ import { isRecordingExpired } from './utils';
|
|
|
8
8
|
* Makes sure that only one tab can be recording at a time.
|
|
9
9
|
*/
|
|
10
10
|
var RecordingRegistry = function (options) {
|
|
11
|
+
/** @type {IDBStorageWrapper} */
|
|
11
12
|
this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
12
13
|
this.errorReporter = options.errorReporter;
|
|
13
14
|
this.mixpanelInstance = options.mixpanelInstance;
|
|
14
15
|
this.sharedLockStorage = options.sharedLockStorage;
|
|
15
16
|
};
|
|
16
17
|
|
|
18
|
+
RecordingRegistry.prototype.isPersistenceEnabled = function() {
|
|
19
|
+
return !this.mixpanelInstance.get_config('disable_persistence');
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
RecordingRegistry.prototype.handleError = function (err) {
|
|
18
23
|
this.errorReporter('IndexedDB error: ', err);
|
|
19
24
|
};
|
|
@@ -22,6 +27,10 @@ RecordingRegistry.prototype.handleError = function (err) {
|
|
|
22
27
|
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
23
28
|
*/
|
|
24
29
|
RecordingRegistry.prototype.setActiveRecording = function (serializedRecording) {
|
|
30
|
+
if (!this.isPersistenceEnabled()) {
|
|
31
|
+
return Promise.resolve();
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
var tabId = serializedRecording['tabId'];
|
|
26
35
|
if (!tabId) {
|
|
27
36
|
console.warn('No tab ID is set, cannot persist recording metadata.');
|
|
@@ -39,6 +48,10 @@ RecordingRegistry.prototype.setActiveRecording = function (serializedRecording)
|
|
|
39
48
|
* @returns {Promise<import('./session-recording').SerializedRecording>}
|
|
40
49
|
*/
|
|
41
50
|
RecordingRegistry.prototype.getActiveRecording = function () {
|
|
51
|
+
if (!this.isPersistenceEnabled()) {
|
|
52
|
+
return Promise.resolve(null);
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
return this.idb.init()
|
|
43
56
|
.then(function () {
|
|
44
57
|
return this.idb.getItem(this.mixpanelInstance.get_tab_id());
|
|
@@ -50,8 +63,16 @@ RecordingRegistry.prototype.getActiveRecording = function () {
|
|
|
50
63
|
};
|
|
51
64
|
|
|
52
65
|
RecordingRegistry.prototype.clearActiveRecording = function () {
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
if (this.isPersistenceEnabled()) {
|
|
67
|
+
// mark recording as expired instead of deleting it in case the page unloads mid-flush and doesn't make it to ingestion.
|
|
68
|
+
// this will ensure the next pageload will flush the remaining events, but not try to continue the recording.
|
|
69
|
+
return this.markActiveRecordingExpired();
|
|
70
|
+
} else {
|
|
71
|
+
return this.deleteActiveRecording();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
RecordingRegistry.prototype.markActiveRecordingExpired = function () {
|
|
55
76
|
return this.getActiveRecording()
|
|
56
77
|
.then(function (serializedRecording) {
|
|
57
78
|
if (serializedRecording) {
|
|
@@ -62,11 +83,25 @@ RecordingRegistry.prototype.clearActiveRecording = function () {
|
|
|
62
83
|
.catch(this.handleError.bind(this));
|
|
63
84
|
};
|
|
64
85
|
|
|
86
|
+
RecordingRegistry.prototype.deleteActiveRecording = function () {
|
|
87
|
+
// avoid initializing IDB if this registry instance hasn't already written a recording
|
|
88
|
+
if (this.idb.isInitialized()) {
|
|
89
|
+
return this.idb.removeItem(this.mixpanelInstance.get_tab_id())
|
|
90
|
+
.catch(this.handleError.bind(this));
|
|
91
|
+
} else {
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
65
96
|
/**
|
|
66
97
|
* Flush any inactive recordings from the registry to minimize data loss.
|
|
67
98
|
* The main idea here is that we can flush remaining rrweb events on the next page load if a tab is closed mid-batch.
|
|
68
99
|
*/
|
|
69
100
|
RecordingRegistry.prototype.flushInactiveRecordings = function () {
|
|
101
|
+
if (!this.isPersistenceEnabled()) {
|
|
102
|
+
return Promise.resolve([]);
|
|
103
|
+
}
|
|
104
|
+
|
|
70
105
|
return this.idb.init()
|
|
71
106
|
.then(function() {
|
|
72
107
|
return this.idb.getAll();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { window } from '../window';
|
|
2
|
-
import { IncrementalSource, EventType } from 'rrweb';
|
|
2
|
+
import { IncrementalSource, EventType } from '@mixpanel/rrweb';
|
|
3
3
|
import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported} from '../utils'; // eslint-disable-line camelcase
|
|
4
4
|
import { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME } from '../storage/indexed-db';
|
|
5
5
|
import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
|
|
@@ -101,10 +101,9 @@ var SessionRecording = function(options) {
|
|
|
101
101
|
|
|
102
102
|
// disable persistence if localStorage is not supported
|
|
103
103
|
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
104
|
-
var usePersistence = localStorageSupported(options.sharedLockStorage, true);
|
|
104
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
|
|
105
105
|
|
|
106
106
|
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
107
|
-
// this will be important when persistence is introduced
|
|
108
107
|
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
109
108
|
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
110
109
|
this.batcher = new RequestBatcher(this.batcherKey, {
|
package/src/request-queue.js
CHANGED
|
@@ -61,6 +61,10 @@ IDBStorageWrapper.prototype.init = function () {
|
|
|
61
61
|
});
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
IDBStorageWrapper.prototype.isInitialized = function () {
|
|
65
|
+
return !!this.dbPromise;
|
|
66
|
+
};
|
|
67
|
+
|
|
64
68
|
/**
|
|
65
69
|
* @param {IDBTransactionMode} mode
|
|
66
70
|
* @param {function(IDBObjectStore): void} storeCb
|
|
@@ -13,6 +13,10 @@ LocalStorageWrapper.prototype.init = function () {
|
|
|
13
13
|
return Promise.resolve();
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
LocalStorageWrapper.prototype.isInitialized = function () {
|
|
17
|
+
return true;
|
|
18
|
+
};
|
|
19
|
+
|
|
16
20
|
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
17
21
|
return new Promise(_.bind(function (resolve, reject) {
|
|
18
22
|
try {
|
package/src/storage/wrapper.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Object} StorageWrapper
|
|
8
8
|
* @property {function():Promise<void>} init - Initializes the wrapper, async storage like IDB needs to create a table and upgrade if needed.
|
|
9
|
+
* @property {function():boolean} isInitialized - Checks if the storage is initialized.
|
|
9
10
|
* @property {function(string, any):Promise<void>} setItem - Sets an item in storage.
|
|
10
11
|
* @property {function(string):Promise<any>} getItem - Retrieves an item from storage.
|
|
11
12
|
* @property {function(string):Promise<void>} removeItem - Removes an item from storage.
|