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