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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// this file exists as an entry point to be able to transpile rrweb packages to es5
|
|
2
2
|
// compatible code without needing to transpile the entire mixpanel-js codebase
|
|
3
3
|
import {record, EventType, IncrementalSource} from '@mixpanel/rrweb';
|
|
4
|
+
import {classMatchesRegex} from '@mixpanel/rrweb-snapshot';
|
|
4
5
|
import {getRecordConsolePlugin} from '@mixpanel/rrweb-plugin-console-record';
|
|
5
6
|
|
|
6
|
-
export { record, EventType, IncrementalSource, getRecordConsolePlugin };
|
|
7
|
+
export { record, EventType, IncrementalSource, getRecordConsolePlugin, classMatchesRegex };
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { window } from '../window';
|
|
2
|
-
import {
|
|
6
|
+
import { EventType, getRecordConsolePlugin, IncrementalSource } from './rrweb-entrypoint';
|
|
3
7
|
import { MAX_RECORDING_MS, MAX_VALUE_FOR_MIN_RECORDING_MS, console_with_prefix, NOOP_FUNC, _, localStorageSupported, canUseCompressionStream, navigator, userAgent, windowOpera} from '../utils'; // eslint-disable-line camelcase
|
|
4
8
|
import { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME } from '../storage/indexed-db';
|
|
5
9
|
import { addOptOutCheckMixpanelLib } from '../gdpr-utils';
|
|
6
10
|
import { RequestBatcher } from '../request-batcher';
|
|
11
|
+
|
|
7
12
|
import Config from '../config';
|
|
8
13
|
import { RECORD_ENQUEUE_THROTTLE_MS } from './utils';
|
|
14
|
+
import { shouldMaskInput, shouldMaskText, getPrivacyConfig } from './masking';
|
|
9
15
|
|
|
10
16
|
var logger = console_with_prefix('recorder');
|
|
11
17
|
var CompressionStream = window['CompressionStream'];
|
|
@@ -52,7 +58,7 @@ function isUserEvent(ev) {
|
|
|
52
58
|
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
53
59
|
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
54
60
|
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
55
|
-
* @property {
|
|
61
|
+
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
56
62
|
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
57
63
|
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
58
64
|
* optional properties for deserialization:
|
|
@@ -233,6 +239,8 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
233
239
|
blockSelector = undefined;
|
|
234
240
|
}
|
|
235
241
|
|
|
242
|
+
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
243
|
+
|
|
236
244
|
try {
|
|
237
245
|
this._stopRecording = this._rrwebRecord({
|
|
238
246
|
'emit': function (ev) {
|
|
@@ -262,9 +270,11 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
262
270
|
'type': 'image/webp',
|
|
263
271
|
'quality': 0.6
|
|
264
272
|
},
|
|
273
|
+
// mask all inputs and text by default, unmasking is applied via callbacks maskInputFn and maskTextFn
|
|
265
274
|
'maskAllInputs': true,
|
|
266
|
-
'
|
|
267
|
-
'
|
|
275
|
+
'maskTextSelector': '*',
|
|
276
|
+
'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
|
|
277
|
+
'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
|
|
268
278
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
269
279
|
'sampling': {
|
|
270
280
|
'canvas': 15
|
|
@@ -536,4 +546,33 @@ SessionRecording.prototype._getRecordMinMs = function() {
|
|
|
536
546
|
return configValue;
|
|
537
547
|
};
|
|
538
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Creates a masking function for rrweb's maskInputFn or maskTextFn
|
|
551
|
+
* @param {(element: HTMLElement, privacyConfig: RecordPrivacyConfig) => boolean} shouldMaskFn - Function that determines if element should be masked
|
|
552
|
+
* @param {RecordPrivacyConfig} privacyConfig - Privacy configuration
|
|
553
|
+
* @returns {(text: string, element: HTMLElement) => string} Function that masks text based on privacy config
|
|
554
|
+
*/
|
|
555
|
+
SessionRecording.prototype._getMaskFn = function(shouldMaskFn, privacyConfig) {
|
|
556
|
+
return function(text, element) {
|
|
557
|
+
// prevent visual artifacts from random whitespace
|
|
558
|
+
if (!text.trim().length) {
|
|
559
|
+
return '';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
var shouldMask = true;
|
|
563
|
+
try {
|
|
564
|
+
shouldMask = shouldMaskFn(element, privacyConfig);
|
|
565
|
+
} catch (err) {
|
|
566
|
+
this.reportError('Error checking if text should be masked, defaulting to masked', err);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (shouldMask) {
|
|
570
|
+
var textLength = Math.min(text.length, 10000); // limit to 10000 chars to optimize performance
|
|
571
|
+
return '*'.repeat(textLength);
|
|
572
|
+
} else {
|
|
573
|
+
return text;
|
|
574
|
+
}
|
|
575
|
+
}.bind(this);
|
|
576
|
+
};
|
|
577
|
+
|
|
539
578
|
export { SessionRecording };
|
package/src/recorder/utils.js
CHANGED
|
@@ -7,6 +7,10 @@ var isRecordingExpired = function(serializedRecording) {
|
|
|
7
7
|
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
11
12
|
|
|
12
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
isRecordingExpired,
|
|
15
|
+
RECORD_ENQUEUE_THROTTLE_MS
|
|
16
|
+
};
|
package/src/utils.js
CHANGED
|
@@ -1115,8 +1115,17 @@ function _storageWrapper(storage, name, is_supported_fn) {
|
|
|
1115
1115
|
};
|
|
1116
1116
|
}
|
|
1117
1117
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1118
|
+
// Safari errors out accessing localStorage/sessionStorage when cookies are disabled,
|
|
1119
|
+
// so create dummy storage wrappers that silently fail as a fallback.
|
|
1120
|
+
var windowLocalStorage = null, windowSessionStorage = null;
|
|
1121
|
+
try {
|
|
1122
|
+
windowLocalStorage = window.localStorage;
|
|
1123
|
+
windowSessionStorage = window.sessionStorage;
|
|
1124
|
+
// eslint-disable-next-line no-empty
|
|
1125
|
+
} catch (_err) {}
|
|
1126
|
+
|
|
1127
|
+
_.localStorage = _storageWrapper(windowLocalStorage, 'localStorage', localStorageSupported);
|
|
1128
|
+
_.sessionStorage = _storageWrapper(windowSessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
1120
1129
|
|
|
1121
1130
|
_.register_event = (function() {
|
|
1122
1131
|
// written by Dean Edwards, 2005
|
package/src/window.js
CHANGED
|
@@ -15,7 +15,9 @@ if (typeof(window) === 'undefined') {
|
|
|
15
15
|
screen: { width: 0, height: 0 },
|
|
16
16
|
location: loc,
|
|
17
17
|
addEventListener: function() {},
|
|
18
|
-
removeEventListener: function() {}
|
|
18
|
+
removeEventListener: function() {},
|
|
19
|
+
dispatchEvent: function() {},
|
|
20
|
+
CustomEvent: function () {}
|
|
19
21
|
};
|
|
20
22
|
} else {
|
|
21
23
|
win = window;
|
package/testServer.js
CHANGED
|
@@ -1,14 +1,40 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const cookieParser = require('cookie-parser');
|
|
5
|
+
const logger = require('morgan');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const app = express();
|
|
8
|
+
|
|
9
|
+
// Test suite definitions
|
|
10
|
+
const TEST_SUITES = {
|
|
11
|
+
'dev': {
|
|
12
|
+
name: 'Development Build',
|
|
13
|
+
description: 'Unminified library for easier debugging',
|
|
14
|
+
customLibUrl: './static/build/mixpanel.js',
|
|
15
|
+
snippetUrl: './static/src/loaders/mixpanel-jslib-snippet.js',
|
|
16
|
+
testUrl: './static/build/test/browser/snippet-test.js'
|
|
17
|
+
},
|
|
18
|
+
'minified': {
|
|
19
|
+
name: 'Minified Build',
|
|
20
|
+
description: 'Production build (closure compiled, served via Mixpanel CDN)',
|
|
21
|
+
customLibUrl: './static/build/mixpanel.min.js',
|
|
22
|
+
snippetUrl: './static/build/mixpanel-jslib-snippet.min.test.js',
|
|
23
|
+
testUrl: './static/build/test/browser/snippet-test.js'
|
|
24
|
+
},
|
|
25
|
+
'module-cjs': {
|
|
26
|
+
name: 'CommonJS Module',
|
|
27
|
+
description: 'Node.js compatible module (served via npm install)',
|
|
28
|
+
testUrl: './static/build/test/browser/module-cjs-test.js'
|
|
29
|
+
}
|
|
30
|
+
};
|
|
8
31
|
|
|
9
32
|
app.use(cookieParser());
|
|
10
33
|
app.use(logger('dev'));
|
|
11
34
|
|
|
35
|
+
app.set('views', __dirname + '/tests');
|
|
36
|
+
app.set('view engine', 'pug');
|
|
37
|
+
|
|
12
38
|
app.use('/tests', express.static(__dirname + "/tests"));
|
|
13
39
|
app.get('/tests/cookie_included/:cookieName', function(req, res) {
|
|
14
40
|
if (req.cookies && req.cookies[req.params.cookieName]) {
|
|
@@ -19,9 +45,27 @@ app.get('/tests/cookie_included/:cookieName', function(req, res) {
|
|
|
19
45
|
});
|
|
20
46
|
app.use(express.static(__dirname));
|
|
21
47
|
app.get('/', function(req, res) {
|
|
22
|
-
res.redirect(301, '/tests/');
|
|
48
|
+
res.redirect(301, '/tests/new');
|
|
23
49
|
});
|
|
24
50
|
|
|
25
|
-
|
|
26
|
-
|
|
51
|
+
app.get('/tests/new', function(req, res) {
|
|
52
|
+
res.render('directory.pug', { testSuites: TEST_SUITES });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.use('/tests/new/static', express.static(__dirname));
|
|
56
|
+
|
|
57
|
+
// register routes for each test suite
|
|
58
|
+
for (const [suiteId, suite] of Object.entries(TEST_SUITES)) {
|
|
59
|
+
app.get('/tests/new/' + suiteId, function(req, res) {
|
|
60
|
+
res.render('integration.pug', {
|
|
61
|
+
suiteName: suite.name,
|
|
62
|
+
customLibUrl: suite.customLibUrl,
|
|
63
|
+
snippetUrl: suite.snippetUrl,
|
|
64
|
+
testUrl: suite.testUrl
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const server = app.listen(3001, function () {
|
|
70
|
+
console.log(`Mixpanel test app listening on port ${server.address().port}`);
|
|
27
71
|
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [20.x, 22.x]
|
|
16
|
-
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
-
uses: actions/setup-node@v4
|
|
21
|
-
with:
|
|
22
|
-
node-version: ${{ matrix.node-version }}
|
|
23
|
-
- run: npm ci
|
|
24
|
-
- run: npm test
|
|
25
|
-
- run: npm run build-dist
|