mixpanel-browser 2.60.0 → 2.61.1
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 +8 -0
- package/README.md +2 -2
- package/dist/mixpanel-core.cjs.js +398 -128
- package/dist/mixpanel-recorder.js +721 -255
- package/dist/mixpanel-recorder.min.js +11 -11
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +398 -128
- package/dist/mixpanel.amd.js +837 -273
- package/dist/mixpanel.cjs.js +837 -273
- package/dist/mixpanel.globals.js +398 -128
- package/dist/mixpanel.min.js +143 -138
- package/dist/mixpanel.module.js +837 -273
- package/dist/mixpanel.umd.js +837 -273
- package/package.json +2 -1
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +119 -19
- package/src/recorder/index.js +1 -70
- package/src/recorder/recorder.js +137 -0
- package/src/recorder/recording-registry.js +98 -0
- package/src/recorder/session-recording.js +213 -74
- package/src/recorder/utils.js +12 -0
- package/src/request-batcher.js +6 -2
- package/src/request-queue.js +45 -39
- package/src/shared-lock.js +1 -1
- package/src/storage/indexed-db.js +127 -0
- package/src/storage/local-storage.js +4 -8
- package/src/storage/wrapper.js +3 -3
- package/src/utils.js +99 -61
package/dist/mixpanel.globals.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var Config = {
|
|
5
5
|
DEBUG: false,
|
|
6
|
-
LIB_VERSION: '2.
|
|
6
|
+
LIB_VERSION: '2.61.1'
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -1467,15 +1467,9 @@
|
|
|
1467
1467
|
}
|
|
1468
1468
|
};
|
|
1469
1469
|
|
|
1470
|
-
var
|
|
1471
|
-
var localStorageSupported = function(storage, forceCheck) {
|
|
1472
|
-
if (_localStorageSupported !== null && !forceCheck) {
|
|
1473
|
-
return _localStorageSupported;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1470
|
+
var _testStorageSupported = function (storage) {
|
|
1476
1471
|
var supported = true;
|
|
1477
1472
|
try {
|
|
1478
|
-
storage = storage || win.localStorage;
|
|
1479
1473
|
var key = '__mplss_' + cheap_guid(8),
|
|
1480
1474
|
val = 'xyz';
|
|
1481
1475
|
storage.setItem(key, val);
|
|
@@ -1486,59 +1480,74 @@
|
|
|
1486
1480
|
} catch (err) {
|
|
1487
1481
|
supported = false;
|
|
1488
1482
|
}
|
|
1489
|
-
|
|
1490
|
-
_localStorageSupported = supported;
|
|
1491
1483
|
return supported;
|
|
1492
1484
|
};
|
|
1493
1485
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
return supported;
|
|
1502
|
-
},
|
|
1486
|
+
var _localStorageSupported = null;
|
|
1487
|
+
var localStorageSupported = function(storage, forceCheck) {
|
|
1488
|
+
if (_localStorageSupported !== null && !forceCheck) {
|
|
1489
|
+
return _localStorageSupported;
|
|
1490
|
+
}
|
|
1491
|
+
return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
|
|
1492
|
+
};
|
|
1503
1493
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1494
|
+
var _sessionStorageSupported = null;
|
|
1495
|
+
var sessionStorageSupported = function(storage, forceCheck) {
|
|
1496
|
+
if (_sessionStorageSupported !== null && !forceCheck) {
|
|
1497
|
+
return _sessionStorageSupported;
|
|
1498
|
+
}
|
|
1499
|
+
return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
|
|
1500
|
+
};
|
|
1507
1501
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
_.localStorage.error(err);
|
|
1513
|
-
}
|
|
1514
|
-
return null;
|
|
1515
|
-
},
|
|
1502
|
+
function _storageWrapper(storage, name, is_supported_fn) {
|
|
1503
|
+
var log_error = function(msg) {
|
|
1504
|
+
console.error(name + ' error: ' + msg);
|
|
1505
|
+
};
|
|
1516
1506
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1507
|
+
return {
|
|
1508
|
+
is_supported: function(forceCheck) {
|
|
1509
|
+
var supported = is_supported_fn(storage, forceCheck);
|
|
1510
|
+
if (!supported) {
|
|
1511
|
+
console.error(name + ' unsupported');
|
|
1512
|
+
}
|
|
1513
|
+
return supported;
|
|
1514
|
+
},
|
|
1515
|
+
error: log_error,
|
|
1516
|
+
get: function(key) {
|
|
1517
|
+
try {
|
|
1518
|
+
return storage.getItem(key);
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
log_error(err);
|
|
1521
|
+
}
|
|
1522
|
+
return null;
|
|
1523
|
+
},
|
|
1524
|
+
parse: function(key) {
|
|
1525
|
+
try {
|
|
1526
|
+
return _.JSONDecode(storage.getItem(key)) || {};
|
|
1527
|
+
} catch (err) {
|
|
1528
|
+
// noop
|
|
1529
|
+
}
|
|
1530
|
+
return null;
|
|
1531
|
+
},
|
|
1532
|
+
set: function(key, value) {
|
|
1533
|
+
try {
|
|
1534
|
+
storage.setItem(key, value);
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
log_error(err);
|
|
1537
|
+
}
|
|
1538
|
+
},
|
|
1539
|
+
remove: function(key) {
|
|
1540
|
+
try {
|
|
1541
|
+
storage.removeItem(key);
|
|
1542
|
+
} catch (err) {
|
|
1543
|
+
log_error(err);
|
|
1544
|
+
}
|
|
1522
1545
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1525
1548
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
win.localStorage.setItem(name, value);
|
|
1529
|
-
} catch (err) {
|
|
1530
|
-
_.localStorage.error(err);
|
|
1531
|
-
}
|
|
1532
|
-
},
|
|
1533
|
-
|
|
1534
|
-
remove: function(name) {
|
|
1535
|
-
try {
|
|
1536
|
-
win.localStorage.removeItem(name);
|
|
1537
|
-
} catch (err) {
|
|
1538
|
-
_.localStorage.error(err);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1549
|
+
_.localStorage = _storageWrapper(win.localStorage, 'localStorage', localStorageSupported);
|
|
1550
|
+
_.sessionStorage = _storageWrapper(win.sessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
1542
1551
|
|
|
1543
1552
|
_.register_event = (function() {
|
|
1544
1553
|
// written by Dean Edwards, 2005
|
|
@@ -2065,6 +2074,31 @@
|
|
|
2065
2074
|
}
|
|
2066
2075
|
};
|
|
2067
2076
|
|
|
2077
|
+
/**
|
|
2078
|
+
* Returns a throttled function that will only run at most every `waitMs` and returns a promise that resolves with the next invocation.
|
|
2079
|
+
* Throttled calls will build up a batch of args and invoke the callback with all args since the last invocation.
|
|
2080
|
+
*/
|
|
2081
|
+
var batchedThrottle = function (fn, waitMs) {
|
|
2082
|
+
var timeoutPromise = null;
|
|
2083
|
+
var throttledItems = [];
|
|
2084
|
+
return function (item) {
|
|
2085
|
+
var self = this;
|
|
2086
|
+
throttledItems.push(item);
|
|
2087
|
+
|
|
2088
|
+
if (!timeoutPromise) {
|
|
2089
|
+
timeoutPromise = new PromisePolyfill(function (resolve) {
|
|
2090
|
+
setTimeout(function () {
|
|
2091
|
+
var returnValue = fn.apply(self, [throttledItems]);
|
|
2092
|
+
timeoutPromise = null;
|
|
2093
|
+
throttledItems = [];
|
|
2094
|
+
resolve(returnValue);
|
|
2095
|
+
}, waitMs);
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
return timeoutPromise;
|
|
2099
|
+
};
|
|
2100
|
+
};
|
|
2101
|
+
|
|
2068
2102
|
var cheap_guid = function(maxlen) {
|
|
2069
2103
|
var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
|
|
2070
2104
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
@@ -2107,6 +2141,8 @@
|
|
|
2107
2141
|
return _.isUndefined(onLine) || onLine;
|
|
2108
2142
|
};
|
|
2109
2143
|
|
|
2144
|
+
var NOOP_FUNC = function () {};
|
|
2145
|
+
|
|
2110
2146
|
var JSONStringify = null, JSONParse = null;
|
|
2111
2147
|
if (typeof JSON !== 'undefined') {
|
|
2112
2148
|
JSONStringify = JSON.stringify;
|
|
@@ -2115,20 +2151,29 @@
|
|
|
2115
2151
|
JSONStringify = JSONStringify || _.JSONEncode;
|
|
2116
2152
|
JSONParse = JSONParse || _.JSONDecode;
|
|
2117
2153
|
|
|
2118
|
-
// EXPORTS (for closure compiler)
|
|
2119
|
-
_['toArray'] = _.toArray;
|
|
2120
|
-
_['isObject'] = _.isObject;
|
|
2121
|
-
_['JSONEncode'] = _.JSONEncode;
|
|
2122
|
-
_['JSONDecode'] = _.JSONDecode;
|
|
2123
|
-
_['isBlockedUA'] = _.isBlockedUA;
|
|
2124
|
-
_['isEmptyObject'] = _.isEmptyObject;
|
|
2154
|
+
// UNMINIFIED EXPORTS (for closure compiler)
|
|
2125
2155
|
_['info'] = _.info;
|
|
2126
|
-
_['info']['device'] = _.info.device;
|
|
2127
2156
|
_['info']['browser'] = _.info.browser;
|
|
2128
2157
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
2158
|
+
_['info']['device'] = _.info.device;
|
|
2129
2159
|
_['info']['properties'] = _.info.properties;
|
|
2160
|
+
_['isBlockedUA'] = _.isBlockedUA;
|
|
2161
|
+
_['isEmptyObject'] = _.isEmptyObject;
|
|
2162
|
+
_['isObject'] = _.isObject;
|
|
2163
|
+
_['JSONDecode'] = _.JSONDecode;
|
|
2164
|
+
_['JSONEncode'] = _.JSONEncode;
|
|
2165
|
+
_['toArray'] = _.toArray;
|
|
2130
2166
|
_['NPO'] = NpoPromise;
|
|
2131
2167
|
|
|
2168
|
+
/**
|
|
2169
|
+
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
2170
|
+
* @returns {boolean}
|
|
2171
|
+
*/
|
|
2172
|
+
var isRecordingExpired = function(serializedRecording) {
|
|
2173
|
+
var now = Date.now();
|
|
2174
|
+
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
2175
|
+
};
|
|
2176
|
+
|
|
2132
2177
|
// stateless utils
|
|
2133
2178
|
|
|
2134
2179
|
var EV_CHANGE = 'change';
|
|
@@ -3151,7 +3196,7 @@
|
|
|
3151
3196
|
options = options || {};
|
|
3152
3197
|
|
|
3153
3198
|
this.storageKey = key;
|
|
3154
|
-
this.storage = options.storage ||
|
|
3199
|
+
this.storage = options.storage || win.localStorage;
|
|
3155
3200
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
3156
3201
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
3157
3202
|
|
|
@@ -3166,7 +3211,6 @@
|
|
|
3166
3211
|
return new Promise(_.bind(function (resolve, reject) {
|
|
3167
3212
|
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
3168
3213
|
var startTime = new Date().getTime();
|
|
3169
|
-
|
|
3170
3214
|
var key = this.storageKey;
|
|
3171
3215
|
var pollIntervalMS = this.pollIntervalMS;
|
|
3172
3216
|
var timeoutMS = this.timeoutMS;
|
|
@@ -3277,11 +3321,7 @@
|
|
|
3277
3321
|
};
|
|
3278
3322
|
|
|
3279
3323
|
/**
|
|
3280
|
-
* @
|
|
3281
|
-
*/
|
|
3282
|
-
|
|
3283
|
-
/**
|
|
3284
|
-
* @type {StorageWrapper}
|
|
3324
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
3285
3325
|
*/
|
|
3286
3326
|
var LocalStorageWrapper = function (storageOverride) {
|
|
3287
3327
|
this.storage = storageOverride || localStorage;
|
|
@@ -3294,7 +3334,7 @@
|
|
|
3294
3334
|
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
3295
3335
|
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
3296
3336
|
try {
|
|
3297
|
-
this.storage.setItem(key, value);
|
|
3337
|
+
this.storage.setItem(key, JSONStringify(value));
|
|
3298
3338
|
} catch (e) {
|
|
3299
3339
|
reject(e);
|
|
3300
3340
|
}
|
|
@@ -3306,7 +3346,7 @@
|
|
|
3306
3346
|
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
3307
3347
|
var item;
|
|
3308
3348
|
try {
|
|
3309
|
-
item = this.storage.getItem(key);
|
|
3349
|
+
item = JSONParse(this.storage.getItem(key));
|
|
3310
3350
|
} catch (e) {
|
|
3311
3351
|
reject(e);
|
|
3312
3352
|
}
|
|
@@ -3349,8 +3389,10 @@
|
|
|
3349
3389
|
this.usePersistence = options.usePersistence;
|
|
3350
3390
|
if (this.usePersistence) {
|
|
3351
3391
|
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
3352
|
-
this.lock = new SharedLock(storageKey, {
|
|
3353
|
-
|
|
3392
|
+
this.lock = new SharedLock(storageKey, {
|
|
3393
|
+
storage: options.sharedLockStorage || win.localStorage,
|
|
3394
|
+
timeoutMS: options.sharedLockTimeoutMS,
|
|
3395
|
+
});
|
|
3354
3396
|
}
|
|
3355
3397
|
this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
|
|
3356
3398
|
|
|
@@ -3358,6 +3400,14 @@
|
|
|
3358
3400
|
|
|
3359
3401
|
this.memQueue = [];
|
|
3360
3402
|
this.initialized = false;
|
|
3403
|
+
|
|
3404
|
+
if (options.enqueueThrottleMs) {
|
|
3405
|
+
this.enqueuePersisted = batchedThrottle(_.bind(this._enqueuePersisted, this), options.enqueueThrottleMs);
|
|
3406
|
+
} else {
|
|
3407
|
+
this.enqueuePersisted = _.bind(function (queueEntry) {
|
|
3408
|
+
return this._enqueuePersisted([queueEntry]);
|
|
3409
|
+
}, this);
|
|
3410
|
+
}
|
|
3361
3411
|
};
|
|
3362
3412
|
|
|
3363
3413
|
RequestQueue.prototype.ensureInit = function () {
|
|
@@ -3400,36 +3450,39 @@
|
|
|
3400
3450
|
this.memQueue.push(queueEntry);
|
|
3401
3451
|
return PromisePolyfill.resolve(true);
|
|
3402
3452
|
} else {
|
|
3453
|
+
return this.enqueuePersisted(queueEntry);
|
|
3454
|
+
}
|
|
3455
|
+
};
|
|
3403
3456
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
return succeeded;
|
|
3419
|
-
}, this))
|
|
3420
|
-
.catch(_.bind(function (err) {
|
|
3421
|
-
this.reportError('Error enqueueing item', err, item);
|
|
3422
|
-
return false;
|
|
3423
|
-
}, this));
|
|
3424
|
-
}, this);
|
|
3457
|
+
RequestQueue.prototype._enqueuePersisted = function (queueEntries) {
|
|
3458
|
+
var enqueueItem = _.bind(function () {
|
|
3459
|
+
return this.ensureInit()
|
|
3460
|
+
.then(_.bind(function () {
|
|
3461
|
+
return this.readFromStorage();
|
|
3462
|
+
}, this))
|
|
3463
|
+
.then(_.bind(function (storedQueue) {
|
|
3464
|
+
return this.saveToStorage(storedQueue.concat(queueEntries));
|
|
3465
|
+
}, this))
|
|
3466
|
+
.then(_.bind(function (succeeded) {
|
|
3467
|
+
// only add to in-memory queue when storage succeeds
|
|
3468
|
+
if (succeeded) {
|
|
3469
|
+
this.memQueue = this.memQueue.concat(queueEntries);
|
|
3470
|
+
}
|
|
3425
3471
|
|
|
3426
|
-
|
|
3427
|
-
|
|
3472
|
+
return succeeded;
|
|
3473
|
+
}, this))
|
|
3428
3474
|
.catch(_.bind(function (err) {
|
|
3429
|
-
this.reportError('Error
|
|
3475
|
+
this.reportError('Error enqueueing items', err, queueEntries);
|
|
3430
3476
|
return false;
|
|
3431
3477
|
}, this));
|
|
3432
|
-
}
|
|
3478
|
+
}, this);
|
|
3479
|
+
|
|
3480
|
+
return this.lock
|
|
3481
|
+
.withLock(enqueueItem, this.pid)
|
|
3482
|
+
.catch(_.bind(function (err) {
|
|
3483
|
+
this.reportError('Error acquiring storage lock', err);
|
|
3484
|
+
return false;
|
|
3485
|
+
}, this));
|
|
3433
3486
|
};
|
|
3434
3487
|
|
|
3435
3488
|
/**
|
|
@@ -3450,7 +3503,7 @@
|
|
|
3450
3503
|
}, this))
|
|
3451
3504
|
.then(_.bind(function (storedQueue) {
|
|
3452
3505
|
if (storedQueue.length) {
|
|
3453
|
-
|
|
3506
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
3454
3507
|
var idsInBatch = {}; // poor man's Set
|
|
3455
3508
|
_.each(batch, function (item) {
|
|
3456
3509
|
idsInBatch[item['id']] = true;
|
|
@@ -3537,7 +3590,7 @@
|
|
|
3537
3590
|
.withLock(removeFromStorage, this.pid)
|
|
3538
3591
|
.catch(_.bind(function (err) {
|
|
3539
3592
|
this.reportError('Error acquiring storage lock', err);
|
|
3540
|
-
if (!localStorageSupported(this.
|
|
3593
|
+
if (!localStorageSupported(this.lock.storage, true)) {
|
|
3541
3594
|
// Looks like localStorage writes have stopped working sometime after
|
|
3542
3595
|
// initialization (probably full), and so nobody can acquire locks
|
|
3543
3596
|
// anymore. Consider it temporarily safe to remove items without the
|
|
@@ -3625,7 +3678,6 @@
|
|
|
3625
3678
|
}, this))
|
|
3626
3679
|
.then(_.bind(function (storageEntry) {
|
|
3627
3680
|
if (storageEntry) {
|
|
3628
|
-
storageEntry = JSONParse(storageEntry);
|
|
3629
3681
|
if (!_.isArray(storageEntry)) {
|
|
3630
3682
|
this.reportError('Invalid storage entry:', storageEntry);
|
|
3631
3683
|
storageEntry = null;
|
|
@@ -3643,16 +3695,9 @@
|
|
|
3643
3695
|
* Serialize the given items array to localStorage.
|
|
3644
3696
|
*/
|
|
3645
3697
|
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
3646
|
-
try {
|
|
3647
|
-
var serialized = JSONStringify(queue);
|
|
3648
|
-
} catch (err) {
|
|
3649
|
-
this.reportError('Error serializing queue', err);
|
|
3650
|
-
return PromisePolyfill.resolve(false);
|
|
3651
|
-
}
|
|
3652
|
-
|
|
3653
3698
|
return this.ensureInit()
|
|
3654
3699
|
.then(_.bind(function () {
|
|
3655
|
-
return this.queueStorage.setItem(this.storageKey,
|
|
3700
|
+
return this.queueStorage.setItem(this.storageKey, queue);
|
|
3656
3701
|
}, this))
|
|
3657
3702
|
.then(function () {
|
|
3658
3703
|
return true;
|
|
@@ -3696,7 +3741,9 @@
|
|
|
3696
3741
|
errorReporter: _.bind(this.reportError, this),
|
|
3697
3742
|
queueStorage: options.queueStorage,
|
|
3698
3743
|
sharedLockStorage: options.sharedLockStorage,
|
|
3699
|
-
|
|
3744
|
+
sharedLockTimeoutMS: options.sharedLockTimeoutMS,
|
|
3745
|
+
usePersistence: options.usePersistence,
|
|
3746
|
+
enqueueThrottleMs: options.enqueueThrottleMs
|
|
3700
3747
|
});
|
|
3701
3748
|
|
|
3702
3749
|
this.libConfig = options.libConfig;
|
|
@@ -3718,6 +3765,8 @@
|
|
|
3718
3765
|
// as long as the queue is not empty. This is useful for high-frequency events like Session Replay where we might end up
|
|
3719
3766
|
// in a request loop and get ratelimited by the server.
|
|
3720
3767
|
this.flushOnlyOnInterval = options.flushOnlyOnInterval || false;
|
|
3768
|
+
|
|
3769
|
+
this._flushPromise = null;
|
|
3721
3770
|
};
|
|
3722
3771
|
|
|
3723
3772
|
/**
|
|
@@ -3777,7 +3826,7 @@
|
|
|
3777
3826
|
if (!this.stopped) { // don't schedule anymore if batching has been stopped
|
|
3778
3827
|
this.timeoutID = setTimeout(_.bind(function() {
|
|
3779
3828
|
if (!this.stopped) {
|
|
3780
|
-
this.flush();
|
|
3829
|
+
this._flushPromise = this.flush();
|
|
3781
3830
|
}
|
|
3782
3831
|
}, this), this.flushInterval);
|
|
3783
3832
|
}
|
|
@@ -5493,6 +5542,129 @@
|
|
|
5493
5542
|
return timestamp;
|
|
5494
5543
|
};
|
|
5495
5544
|
|
|
5545
|
+
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
5546
|
+
|
|
5547
|
+
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
5548
|
+
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
5549
|
+
|
|
5550
|
+
// note: increment the version number when adding new object stores
|
|
5551
|
+
var DB_VERSION = 1;
|
|
5552
|
+
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
5553
|
+
|
|
5554
|
+
/**
|
|
5555
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
5556
|
+
*/
|
|
5557
|
+
var IDBStorageWrapper = function (storeName) {
|
|
5558
|
+
/**
|
|
5559
|
+
* @type {Promise<IDBDatabase>|null}
|
|
5560
|
+
*/
|
|
5561
|
+
this.dbPromise = null;
|
|
5562
|
+
this.storeName = storeName;
|
|
5563
|
+
};
|
|
5564
|
+
|
|
5565
|
+
IDBStorageWrapper.prototype._openDb = function () {
|
|
5566
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
5567
|
+
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
5568
|
+
openRequest['onerror'] = function () {
|
|
5569
|
+
reject(openRequest.error);
|
|
5570
|
+
};
|
|
5571
|
+
|
|
5572
|
+
openRequest['onsuccess'] = function () {
|
|
5573
|
+
resolve(openRequest.result);
|
|
5574
|
+
};
|
|
5575
|
+
|
|
5576
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
5577
|
+
var db = ev.target.result;
|
|
5578
|
+
|
|
5579
|
+
OBJECT_STORES.forEach(function (storeName) {
|
|
5580
|
+
db.createObjectStore(storeName);
|
|
5581
|
+
});
|
|
5582
|
+
};
|
|
5583
|
+
});
|
|
5584
|
+
};
|
|
5585
|
+
|
|
5586
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
5587
|
+
if (!win.indexedDB) {
|
|
5588
|
+
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
5589
|
+
}
|
|
5590
|
+
|
|
5591
|
+
if (!this.dbPromise) {
|
|
5592
|
+
this.dbPromise = this._openDb();
|
|
5593
|
+
}
|
|
5594
|
+
|
|
5595
|
+
return this.dbPromise
|
|
5596
|
+
.then(function (dbOrError) {
|
|
5597
|
+
if (dbOrError instanceof win['IDBDatabase']) {
|
|
5598
|
+
return PromisePolyfill.resolve();
|
|
5599
|
+
} else {
|
|
5600
|
+
return PromisePolyfill.reject(dbOrError);
|
|
5601
|
+
}
|
|
5602
|
+
});
|
|
5603
|
+
};
|
|
5604
|
+
|
|
5605
|
+
/**
|
|
5606
|
+
* @param {IDBTransactionMode} mode
|
|
5607
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
5608
|
+
*/
|
|
5609
|
+
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
5610
|
+
var storeName = this.storeName;
|
|
5611
|
+
var doTransaction = function (db) {
|
|
5612
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
5613
|
+
var transaction = db.transaction(storeName, mode);
|
|
5614
|
+
transaction.oncomplete = function () {
|
|
5615
|
+
resolve(transaction);
|
|
5616
|
+
};
|
|
5617
|
+
transaction.onabort = transaction.onerror = function () {
|
|
5618
|
+
reject(transaction.error);
|
|
5619
|
+
};
|
|
5620
|
+
|
|
5621
|
+
storeCb(transaction.objectStore(storeName));
|
|
5622
|
+
});
|
|
5623
|
+
};
|
|
5624
|
+
|
|
5625
|
+
return this.dbPromise
|
|
5626
|
+
.then(doTransaction)
|
|
5627
|
+
.catch(function (err) {
|
|
5628
|
+
if (err && err['name'] === 'InvalidStateError') {
|
|
5629
|
+
// try reopening the DB if the connection is closed
|
|
5630
|
+
this.dbPromise = this._openDb();
|
|
5631
|
+
return this.dbPromise.then(doTransaction);
|
|
5632
|
+
} else {
|
|
5633
|
+
return PromisePolyfill.reject(err);
|
|
5634
|
+
}
|
|
5635
|
+
}.bind(this));
|
|
5636
|
+
};
|
|
5637
|
+
|
|
5638
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
5639
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
5640
|
+
objectStore.put(value, key);
|
|
5641
|
+
});
|
|
5642
|
+
};
|
|
5643
|
+
|
|
5644
|
+
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
5645
|
+
var req;
|
|
5646
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
5647
|
+
req = objectStore.get(key);
|
|
5648
|
+
}).then(function () {
|
|
5649
|
+
return req.result;
|
|
5650
|
+
});
|
|
5651
|
+
};
|
|
5652
|
+
|
|
5653
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
5654
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
5655
|
+
objectStore.delete(key);
|
|
5656
|
+
});
|
|
5657
|
+
};
|
|
5658
|
+
|
|
5659
|
+
IDBStorageWrapper.prototype.getAll = function () {
|
|
5660
|
+
var req;
|
|
5661
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
5662
|
+
req = objectStore.getAll();
|
|
5663
|
+
}).then(function () {
|
|
5664
|
+
return req.result;
|
|
5665
|
+
});
|
|
5666
|
+
};
|
|
5667
|
+
|
|
5496
5668
|
/* eslint camelcase: "off" */
|
|
5497
5669
|
|
|
5498
5670
|
/*
|
|
@@ -5507,11 +5679,6 @@
|
|
|
5507
5679
|
* Released under the MIT License.
|
|
5508
5680
|
*/
|
|
5509
5681
|
|
|
5510
|
-
// ==ClosureCompiler==
|
|
5511
|
-
// @compilation_level ADVANCED_OPTIMIZATIONS
|
|
5512
|
-
// @output_file_name mixpanel-2.8.min.js
|
|
5513
|
-
// ==/ClosureCompiler==
|
|
5514
|
-
|
|
5515
5682
|
/*
|
|
5516
5683
|
SIMPLE STYLE GUIDE:
|
|
5517
5684
|
|
|
@@ -5534,7 +5701,6 @@
|
|
|
5534
5701
|
var INIT_SNIPPET = 1;
|
|
5535
5702
|
|
|
5536
5703
|
var IDENTITY_FUNC = function(x) {return x;};
|
|
5537
|
-
var NOOP_FUNC = function() {};
|
|
5538
5704
|
|
|
5539
5705
|
/** @const */ var PRIMARY_INSTANCE_NAME = 'mixpanel';
|
|
5540
5706
|
/** @const */ var PAYLOAD_TYPE_BASE64 = 'base64';
|
|
@@ -5843,34 +6009,125 @@
|
|
|
5843
6009
|
this.autocapture = new Autocapture(this);
|
|
5844
6010
|
this.autocapture.init();
|
|
5845
6011
|
|
|
5846
|
-
|
|
5847
|
-
|
|
6012
|
+
this._init_tab_id();
|
|
6013
|
+
this._check_and_start_session_recording();
|
|
6014
|
+
};
|
|
6015
|
+
|
|
6016
|
+
/**
|
|
6017
|
+
* Assigns a unique UUID to this tab / window by leveraging sessionStorage.
|
|
6018
|
+
* This is primarily used for session recording, where data must be isolated to the current tab.
|
|
6019
|
+
*/
|
|
6020
|
+
MixpanelLib.prototype._init_tab_id = function() {
|
|
6021
|
+
if (_.sessionStorage.is_supported()) {
|
|
6022
|
+
try {
|
|
6023
|
+
var key_suffix = this.get_config('name') + '_' + this.get_config('token');
|
|
6024
|
+
var tab_id_key = 'mp_tab_id_' + key_suffix;
|
|
6025
|
+
|
|
6026
|
+
// A flag is used to determine if sessionStorage is copied over and we need to generate a new tab ID.
|
|
6027
|
+
// This enforces a unique ID in the cases like duplicated tab, window.open(...)
|
|
6028
|
+
var should_generate_new_tab_id_key = 'mp_gen_new_tab_id_' + key_suffix;
|
|
6029
|
+
if (_.sessionStorage.get(should_generate_new_tab_id_key) || !_.sessionStorage.get(tab_id_key)) {
|
|
6030
|
+
_.sessionStorage.set(tab_id_key, '$tab-' + _.UUID());
|
|
6031
|
+
}
|
|
6032
|
+
|
|
6033
|
+
_.sessionStorage.set(should_generate_new_tab_id_key, '1');
|
|
6034
|
+
this.tab_id = _.sessionStorage.get(tab_id_key);
|
|
6035
|
+
|
|
6036
|
+
// Remove the flag when the tab is unloaded to indicate the stored tab ID can be reused. This event is not reliable to detect all page unloads,
|
|
6037
|
+
// but reliable in cases where the user remains in the tab e.g. a refresh or href navigation.
|
|
6038
|
+
// If the flag is absent, this indicates to the next SDK instance that we can reuse the stored tab_id.
|
|
6039
|
+
win.addEventListener('beforeunload', function () {
|
|
6040
|
+
_.sessionStorage.remove(should_generate_new_tab_id_key);
|
|
6041
|
+
});
|
|
6042
|
+
} catch(err) {
|
|
6043
|
+
this.report_error('Error initializing tab id', err);
|
|
6044
|
+
}
|
|
6045
|
+
} else {
|
|
6046
|
+
this.report_error('Session storage is not supported, cannot keep track of unique tab ID.');
|
|
5848
6047
|
}
|
|
5849
6048
|
};
|
|
5850
6049
|
|
|
5851
|
-
MixpanelLib.prototype.
|
|
6050
|
+
MixpanelLib.prototype.get_tab_id = function () {
|
|
6051
|
+
return this.tab_id || null;
|
|
6052
|
+
};
|
|
6053
|
+
|
|
6054
|
+
MixpanelLib.prototype._should_load_recorder = function () {
|
|
6055
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
6056
|
+
var tab_id = this.get_tab_id();
|
|
6057
|
+
return recording_registry_idb.init()
|
|
6058
|
+
.then(function () {
|
|
6059
|
+
return recording_registry_idb.getAll();
|
|
6060
|
+
})
|
|
6061
|
+
.then(function (recordings) {
|
|
6062
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
6063
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
6064
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
6065
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
6066
|
+
return true;
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
return false;
|
|
6070
|
+
})
|
|
6071
|
+
.catch(_.bind(function (err) {
|
|
6072
|
+
this.report_error('Error checking recording registry', err);
|
|
6073
|
+
}, this));
|
|
6074
|
+
};
|
|
6075
|
+
|
|
6076
|
+
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
5852
6077
|
if (!win['MutationObserver']) {
|
|
5853
6078
|
console.critical('Browser does not support MutationObserver; skipping session recording');
|
|
5854
6079
|
return;
|
|
5855
6080
|
}
|
|
5856
6081
|
|
|
5857
|
-
var
|
|
5858
|
-
|
|
5859
|
-
|
|
6082
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
6083
|
+
var handleLoadedRecorder = _.bind(function() {
|
|
6084
|
+
this._recorder = this._recorder || new win['__mp_recorder'](this);
|
|
6085
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
6086
|
+
}, this);
|
|
6087
|
+
|
|
6088
|
+
if (_.isUndefined(win['__mp_recorder'])) {
|
|
6089
|
+
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
6090
|
+
} else {
|
|
6091
|
+
handleLoadedRecorder();
|
|
6092
|
+
}
|
|
5860
6093
|
}, this);
|
|
5861
6094
|
|
|
5862
|
-
|
|
5863
|
-
|
|
6095
|
+
/**
|
|
6096
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
6097
|
+
* 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.
|
|
6098
|
+
*/
|
|
6099
|
+
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
6100
|
+
if (force_start || is_sampled) {
|
|
6101
|
+
loadRecorder(true);
|
|
5864
6102
|
} else {
|
|
5865
|
-
|
|
6103
|
+
this._should_load_recorder()
|
|
6104
|
+
.then(function (shouldLoad) {
|
|
6105
|
+
if (shouldLoad) {
|
|
6106
|
+
loadRecorder(false);
|
|
6107
|
+
}
|
|
6108
|
+
});
|
|
5866
6109
|
}
|
|
5867
6110
|
});
|
|
5868
6111
|
|
|
6112
|
+
MixpanelLib.prototype.start_session_recording = function () {
|
|
6113
|
+
this._check_and_start_session_recording(true);
|
|
6114
|
+
};
|
|
6115
|
+
|
|
5869
6116
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
5870
6117
|
if (this._recorder) {
|
|
5871
6118
|
this._recorder['stopRecording']();
|
|
5872
|
-
}
|
|
5873
|
-
|
|
6119
|
+
}
|
|
6120
|
+
};
|
|
6121
|
+
|
|
6122
|
+
MixpanelLib.prototype.pause_session_recording = function () {
|
|
6123
|
+
if (this._recorder) {
|
|
6124
|
+
this._recorder['pauseRecording']();
|
|
6125
|
+
}
|
|
6126
|
+
};
|
|
6127
|
+
|
|
6128
|
+
MixpanelLib.prototype.resume_session_recording = function () {
|
|
6129
|
+
if (this._recorder) {
|
|
6130
|
+
this._recorder['resumeRecording']();
|
|
5874
6131
|
}
|
|
5875
6132
|
};
|
|
5876
6133
|
|
|
@@ -5905,6 +6162,11 @@
|
|
|
5905
6162
|
return replay_id || null;
|
|
5906
6163
|
};
|
|
5907
6164
|
|
|
6165
|
+
// "private" public method to reach into the recorder in test cases
|
|
6166
|
+
MixpanelLib.prototype.__get_recorder = function () {
|
|
6167
|
+
return this._recorder;
|
|
6168
|
+
};
|
|
6169
|
+
|
|
5908
6170
|
// Private methods
|
|
5909
6171
|
|
|
5910
6172
|
MixpanelLib.prototype._loaded = function() {
|
|
@@ -6244,7 +6506,8 @@
|
|
|
6244
6506
|
return this._run_hook('before_send_' + attrs.type, item);
|
|
6245
6507
|
}, this),
|
|
6246
6508
|
stopAllBatchingFunc: _.bind(this.stop_batch_senders, this),
|
|
6247
|
-
usePersistence: true
|
|
6509
|
+
usePersistence: true,
|
|
6510
|
+
enqueueThrottleMs: 10,
|
|
6248
6511
|
}
|
|
6249
6512
|
);
|
|
6250
6513
|
}, this);
|
|
@@ -7345,6 +7608,7 @@
|
|
|
7345
7608
|
|
|
7346
7609
|
if (disabled) {
|
|
7347
7610
|
this.stop_batch_senders();
|
|
7611
|
+
this.stop_session_recording();
|
|
7348
7612
|
} else {
|
|
7349
7613
|
// only start batchers after opt-in if they have previously been started
|
|
7350
7614
|
// in order to avoid unintentionally starting up batching for the first time
|
|
@@ -7585,10 +7849,16 @@
|
|
|
7585
7849
|
MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.prototype.stop_batch_senders;
|
|
7586
7850
|
MixpanelLib.prototype['start_session_recording'] = MixpanelLib.prototype.start_session_recording;
|
|
7587
7851
|
MixpanelLib.prototype['stop_session_recording'] = MixpanelLib.prototype.stop_session_recording;
|
|
7852
|
+
MixpanelLib.prototype['pause_session_recording'] = MixpanelLib.prototype.pause_session_recording;
|
|
7853
|
+
MixpanelLib.prototype['resume_session_recording'] = MixpanelLib.prototype.resume_session_recording;
|
|
7588
7854
|
MixpanelLib.prototype['get_session_recording_properties'] = MixpanelLib.prototype.get_session_recording_properties;
|
|
7589
7855
|
MixpanelLib.prototype['get_session_replay_url'] = MixpanelLib.prototype.get_session_replay_url;
|
|
7856
|
+
MixpanelLib.prototype['get_tab_id'] = MixpanelLib.prototype.get_tab_id;
|
|
7590
7857
|
MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES;
|
|
7591
7858
|
|
|
7859
|
+
// Exports intended only for testing
|
|
7860
|
+
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
7861
|
+
|
|
7592
7862
|
// MixpanelPersistence Exports
|
|
7593
7863
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|
|
7594
7864
|
MixpanelPersistence.prototype['update_search_keyword'] = MixpanelPersistence.prototype.update_search_keyword;
|