mixpanel-browser 2.76.0 → 2.78.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 +3 -1
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/integration-tests.yml +2 -2
- package/.github/workflows/unit-tests.yml +2 -2
- package/CHANGELOG.md +8 -0
- package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-BjSlYaNJ.min.js.map +1 -0
- package/dist/async-modules/{mixpanel-recorder-bIS4LMGd.js → mixpanel-recorder-zMBXIyeG.js} +84 -10
- package/dist/async-modules/{mixpanel-targeting-VOeN7RWY.min.js → mixpanel-targeting-BSHal4N9.min.js} +2 -2
- package/dist/async-modules/{mixpanel-targeting-VOeN7RWY.min.js.map → mixpanel-targeting-BSHal4N9.min.js.map} +1 -1
- package/dist/async-modules/{mixpanel-targeting-BcAPS-Mz.js → mixpanel-targeting-UHf4eBfC.js} +1 -1
- package/dist/mixpanel-core.cjs.d.ts +3 -1
- package/dist/mixpanel-core.cjs.js +292 -130
- package/dist/mixpanel-recorder.js +84 -10
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +1 -1
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +3 -1
- package/dist/mixpanel-with-async-modules.cjs.js +294 -132
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +3 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +294 -132
- package/dist/mixpanel-with-recorder.d.ts +3 -1
- package/dist/mixpanel-with-recorder.js +381 -168
- package/dist/mixpanel-with-recorder.min.d.ts +3 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +3 -1
- package/dist/mixpanel.amd.js +381 -168
- package/dist/mixpanel.cjs.d.ts +3 -1
- package/dist/mixpanel.cjs.js +381 -168
- package/dist/mixpanel.globals.js +294 -132
- package/dist/mixpanel.min.js +191 -186
- package/dist/mixpanel.module.d.ts +3 -1
- package/dist/mixpanel.module.js +381 -168
- package/dist/mixpanel.umd.d.ts +3 -1
- package/dist/mixpanel.umd.js +381 -168
- package/dist/rrweb-bundled.js +61 -9
- package/dist/rrweb-compiled.js +56 -9
- package/package.json +6 -5
- package/src/config.js +1 -1
- package/src/flags/CLAUDE.md +24 -0
- package/src/flags/index.js +109 -80
- package/src/index.d.ts +3 -1
- package/src/mixpanel-core.js +4 -2
- package/src/recorder/session-recording.js +5 -1
- package/src/recorder/utils.js +27 -1
- package/src/recorder-manager.js +110 -2
- package/testServer.js +16 -1
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +0 -2
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +0 -1
- /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
package/src/recorder-manager.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/* eslint camelcase: "off" */
|
|
2
|
+
|
|
2
3
|
import {RECORDER_FILENAME, TARGETING_FILENAME, RECORDER_GLOBAL_NAME} from './config';
|
|
3
|
-
import { _, console, safewrap, safewrapClass } from './utils';
|
|
4
|
+
import { _, console, console_with_prefix, safewrap, safewrapClass } from './utils';
|
|
4
5
|
import { window } from './window';
|
|
5
6
|
import { Promise } from './promise-polyfill';
|
|
6
7
|
import { IDBStorageWrapper, RECORDING_REGISTRY_STORE_NAME } from './storage/indexed-db';
|
|
7
|
-
import { isRecordingExpired } from './recorder/utils';
|
|
8
|
+
import { isRecordingExpired, validateAllowedOrigins } from './recorder/utils';
|
|
8
9
|
import { getTargetingPromise } from './targeting/loader';
|
|
9
10
|
|
|
11
|
+
var logger = console_with_prefix('recorder');
|
|
12
|
+
|
|
13
|
+
var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
|
|
14
|
+
var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
@@ -27,6 +33,8 @@ var RecorderManager = function(initOptions) {
|
|
|
27
33
|
this.libBasePath = initOptions.libBasePath;
|
|
28
34
|
|
|
29
35
|
this._recorder = null;
|
|
36
|
+
this._parentReplayId = null;
|
|
37
|
+
this._parentFrameRetryInterval = null;
|
|
30
38
|
};
|
|
31
39
|
|
|
32
40
|
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
@@ -80,6 +88,22 @@ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start,
|
|
|
80
88
|
}, this));
|
|
81
89
|
}, this);
|
|
82
90
|
|
|
91
|
+
// Cross-origin iframe handling
|
|
92
|
+
var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger);
|
|
93
|
+
var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
|
|
94
|
+
|
|
95
|
+
if (isCrossOriginRecordingEnabled) {
|
|
96
|
+
// listen for handshake requests from their own child iframes (including nested)
|
|
97
|
+
this._setupParentFrameListener(allowedOrigins);
|
|
98
|
+
|
|
99
|
+
if (window.parent !== window) {
|
|
100
|
+
// also wait for parent's replay ID
|
|
101
|
+
this._setupChildFrameListener(allowedOrigins, loadRecorder);
|
|
102
|
+
this._sendParentFrameRequestWithRetry(allowedOrigins);
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
83
107
|
/**
|
|
84
108
|
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
85
109
|
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
@@ -199,6 +223,10 @@ RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
|
199
223
|
};
|
|
200
224
|
|
|
201
225
|
RecorderManager.prototype.getSessionReplayId = function() {
|
|
226
|
+
// Child iframe uses parent's replay ID
|
|
227
|
+
if (this._parentReplayId) {
|
|
228
|
+
return this._parentReplayId;
|
|
229
|
+
}
|
|
202
230
|
var replay_id = null;
|
|
203
231
|
if (this._recorder) {
|
|
204
232
|
replay_id = this._recorder['replayId'];
|
|
@@ -211,6 +239,86 @@ RecorderManager.prototype.getRecorder = function() {
|
|
|
211
239
|
return this._recorder;
|
|
212
240
|
};
|
|
213
241
|
|
|
242
|
+
RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
|
|
243
|
+
if (this._childFrameMessageHandler) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
var self = this;
|
|
247
|
+
this._childFrameMessageHandler = function(event) {
|
|
248
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
249
|
+
var data = event.data;
|
|
250
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
|
|
251
|
+
self._parentReplayId = data['replayId'];
|
|
252
|
+
if (data['distinctId']) {
|
|
253
|
+
self.mixpanelInstance['identify'](data['distinctId']);
|
|
254
|
+
}
|
|
255
|
+
self._parentFrameRetryActive = false;
|
|
256
|
+
window.removeEventListener('message', self._childFrameMessageHandler);
|
|
257
|
+
self._childFrameMessageHandler = null;
|
|
258
|
+
loadRecorder(true);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
window.addEventListener('message', this._childFrameMessageHandler);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
|
|
265
|
+
var message = {};
|
|
266
|
+
message['type'] = IFRAME_HANDSHAKE_REQUEST;
|
|
267
|
+
message['token'] = this.getMpConfig('token');
|
|
268
|
+
for (var i = 0; i < allowedOrigins.length; i++) {
|
|
269
|
+
try {
|
|
270
|
+
window.parent.postMessage(message, allowedOrigins[i]);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
// origin mismatch - ignore
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
|
|
278
|
+
var self = this;
|
|
279
|
+
var maxRetries = 10;
|
|
280
|
+
var retryCount = 0;
|
|
281
|
+
var delay = 50;
|
|
282
|
+
this._parentFrameRetryActive = true;
|
|
283
|
+
|
|
284
|
+
this._sendParentFrameRequest(allowedOrigins);
|
|
285
|
+
|
|
286
|
+
function scheduleRetry() {
|
|
287
|
+
setTimeout(function() {
|
|
288
|
+
if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
self._sendParentFrameRequest(allowedOrigins);
|
|
292
|
+
delay *= 2;
|
|
293
|
+
scheduleRetry();
|
|
294
|
+
}, delay);
|
|
295
|
+
}
|
|
296
|
+
scheduleRetry();
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
|
|
300
|
+
if (this._parentFrameMessageHandler) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
var self = this;
|
|
304
|
+
this._parentFrameMessageHandler = function(event) {
|
|
305
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
306
|
+
var data = event.data;
|
|
307
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
|
|
308
|
+
var replayId = self.getSessionReplayId();
|
|
309
|
+
if (replayId) {
|
|
310
|
+
var response = {};
|
|
311
|
+
response['type'] = IFRAME_HANDSHAKE_RESPONSE;
|
|
312
|
+
response['token'] = self.getMpConfig('token');
|
|
313
|
+
response['replayId'] = replayId;
|
|
314
|
+
response['distinctId'] = self.getDistinctId();
|
|
315
|
+
event.source.postMessage(response, event.origin);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
window.addEventListener('message', this._parentFrameMessageHandler);
|
|
320
|
+
};
|
|
321
|
+
|
|
214
322
|
safewrapClass(RecorderManager);
|
|
215
323
|
|
|
216
324
|
export { RecorderManager };
|
package/testServer.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const express = require('express');
|
|
4
4
|
const cookieParser = require('cookie-parser');
|
|
5
5
|
const logger = require('morgan');
|
|
6
|
+
const { PARENT_PORT, CHILD_PORT } = require('./tests/browser/test-ports');
|
|
6
7
|
|
|
7
8
|
const app = express();
|
|
8
9
|
|
|
@@ -119,8 +120,22 @@ for (const [suiteId, suite] of Object.entries(TEST_SUITES)) {
|
|
|
119
120
|
testUrl: suite.testUrl
|
|
120
121
|
});
|
|
121
122
|
});
|
|
123
|
+
|
|
124
|
+
// Cross-origin child iframe page for session recording tests
|
|
125
|
+
app.get('/tests/new/' + suiteId + '-cross-origin-page', function(req, res) {
|
|
126
|
+
res.render('cross-origin-page.pug', {
|
|
127
|
+
customLibUrl: suite.customLibUrl || './static/build/mixpanel.js',
|
|
128
|
+
snippetUrl: suite.snippetUrl || './static/src/loaders/mixpanel-jslib-snippet.js',
|
|
129
|
+
testUrl: './static/build/test/browser/cross-origin-page.js'
|
|
130
|
+
});
|
|
131
|
+
});
|
|
122
132
|
}
|
|
123
133
|
|
|
124
|
-
const server = app.listen(
|
|
134
|
+
const server = app.listen(PARENT_PORT, function () {
|
|
125
135
|
console.log(`Mixpanel test app listening on port ${server.address().port}`);
|
|
126
136
|
});
|
|
137
|
+
|
|
138
|
+
// Second port for cross-origin iframe tests
|
|
139
|
+
const server2 = app.listen(CHILD_PORT, function () {
|
|
140
|
+
console.log(`Mixpanel cross-origin test server listening on port ${server2.address().port}`);
|
|
141
|
+
});
|