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
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
5
|
+
var win;
|
|
6
|
+
if (typeof(window) === 'undefined') {
|
|
7
|
+
var loc = {
|
|
8
|
+
hostname: ''
|
|
9
|
+
};
|
|
10
|
+
win = {
|
|
11
|
+
navigator: { userAgent: '', onLine: true },
|
|
12
|
+
document: {
|
|
13
|
+
createElement: function() { return {}; },
|
|
14
|
+
location: loc,
|
|
15
|
+
referrer: ''
|
|
16
|
+
},
|
|
17
|
+
screen: { width: 0, height: 0 },
|
|
18
|
+
location: loc,
|
|
19
|
+
addEventListener: function() {},
|
|
20
|
+
removeEventListener: function() {}
|
|
21
|
+
};
|
|
22
|
+
} else {
|
|
23
|
+
win = window;
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
var NodeType;
|
|
5
27
|
(function (NodeType) {
|
|
6
28
|
NodeType[NodeType["Document"] = 0] = "Document";
|
|
@@ -4477,64 +4499,6 @@
|
|
|
4477
4499
|
};
|
|
4478
4500
|
record.mirror = mirror;
|
|
4479
4501
|
|
|
4480
|
-
var EventType = /* @__PURE__ */ ((EventType2) => {
|
|
4481
|
-
EventType2[EventType2["DomContentLoaded"] = 0] = "DomContentLoaded";
|
|
4482
|
-
EventType2[EventType2["Load"] = 1] = "Load";
|
|
4483
|
-
EventType2[EventType2["FullSnapshot"] = 2] = "FullSnapshot";
|
|
4484
|
-
EventType2[EventType2["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
|
|
4485
|
-
EventType2[EventType2["Meta"] = 4] = "Meta";
|
|
4486
|
-
EventType2[EventType2["Custom"] = 5] = "Custom";
|
|
4487
|
-
EventType2[EventType2["Plugin"] = 6] = "Plugin";
|
|
4488
|
-
return EventType2;
|
|
4489
|
-
})(EventType || {});
|
|
4490
|
-
var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
|
|
4491
|
-
IncrementalSource2[IncrementalSource2["Mutation"] = 0] = "Mutation";
|
|
4492
|
-
IncrementalSource2[IncrementalSource2["MouseMove"] = 1] = "MouseMove";
|
|
4493
|
-
IncrementalSource2[IncrementalSource2["MouseInteraction"] = 2] = "MouseInteraction";
|
|
4494
|
-
IncrementalSource2[IncrementalSource2["Scroll"] = 3] = "Scroll";
|
|
4495
|
-
IncrementalSource2[IncrementalSource2["ViewportResize"] = 4] = "ViewportResize";
|
|
4496
|
-
IncrementalSource2[IncrementalSource2["Input"] = 5] = "Input";
|
|
4497
|
-
IncrementalSource2[IncrementalSource2["TouchMove"] = 6] = "TouchMove";
|
|
4498
|
-
IncrementalSource2[IncrementalSource2["MediaInteraction"] = 7] = "MediaInteraction";
|
|
4499
|
-
IncrementalSource2[IncrementalSource2["StyleSheetRule"] = 8] = "StyleSheetRule";
|
|
4500
|
-
IncrementalSource2[IncrementalSource2["CanvasMutation"] = 9] = "CanvasMutation";
|
|
4501
|
-
IncrementalSource2[IncrementalSource2["Font"] = 10] = "Font";
|
|
4502
|
-
IncrementalSource2[IncrementalSource2["Log"] = 11] = "Log";
|
|
4503
|
-
IncrementalSource2[IncrementalSource2["Drag"] = 12] = "Drag";
|
|
4504
|
-
IncrementalSource2[IncrementalSource2["StyleDeclaration"] = 13] = "StyleDeclaration";
|
|
4505
|
-
IncrementalSource2[IncrementalSource2["Selection"] = 14] = "Selection";
|
|
4506
|
-
IncrementalSource2[IncrementalSource2["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
|
|
4507
|
-
IncrementalSource2[IncrementalSource2["CustomElement"] = 16] = "CustomElement";
|
|
4508
|
-
return IncrementalSource2;
|
|
4509
|
-
})(IncrementalSource || {});
|
|
4510
|
-
|
|
4511
|
-
var Config = {
|
|
4512
|
-
DEBUG: false,
|
|
4513
|
-
LIB_VERSION: '2.60.0'
|
|
4514
|
-
};
|
|
4515
|
-
|
|
4516
|
-
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
4517
|
-
var win;
|
|
4518
|
-
if (typeof(window) === 'undefined') {
|
|
4519
|
-
var loc = {
|
|
4520
|
-
hostname: ''
|
|
4521
|
-
};
|
|
4522
|
-
win = {
|
|
4523
|
-
navigator: { userAgent: '', onLine: true },
|
|
4524
|
-
document: {
|
|
4525
|
-
createElement: function() { return {}; },
|
|
4526
|
-
location: loc,
|
|
4527
|
-
referrer: ''
|
|
4528
|
-
},
|
|
4529
|
-
screen: { width: 0, height: 0 },
|
|
4530
|
-
location: loc,
|
|
4531
|
-
addEventListener: function() {},
|
|
4532
|
-
removeEventListener: function() {}
|
|
4533
|
-
};
|
|
4534
|
-
} else {
|
|
4535
|
-
win = window;
|
|
4536
|
-
}
|
|
4537
|
-
|
|
4538
4502
|
var setImmediate = win['setImmediate'];
|
|
4539
4503
|
var builtInProp, cycle, schedulingQueue,
|
|
4540
4504
|
ToString = Object.prototype.toString,
|
|
@@ -4897,6 +4861,42 @@
|
|
|
4897
4861
|
PromisePolyfill = NpoPromise;
|
|
4898
4862
|
}
|
|
4899
4863
|
|
|
4864
|
+
var EventType = /* @__PURE__ */ ((EventType2) => {
|
|
4865
|
+
EventType2[EventType2["DomContentLoaded"] = 0] = "DomContentLoaded";
|
|
4866
|
+
EventType2[EventType2["Load"] = 1] = "Load";
|
|
4867
|
+
EventType2[EventType2["FullSnapshot"] = 2] = "FullSnapshot";
|
|
4868
|
+
EventType2[EventType2["IncrementalSnapshot"] = 3] = "IncrementalSnapshot";
|
|
4869
|
+
EventType2[EventType2["Meta"] = 4] = "Meta";
|
|
4870
|
+
EventType2[EventType2["Custom"] = 5] = "Custom";
|
|
4871
|
+
EventType2[EventType2["Plugin"] = 6] = "Plugin";
|
|
4872
|
+
return EventType2;
|
|
4873
|
+
})(EventType || {});
|
|
4874
|
+
var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
|
|
4875
|
+
IncrementalSource2[IncrementalSource2["Mutation"] = 0] = "Mutation";
|
|
4876
|
+
IncrementalSource2[IncrementalSource2["MouseMove"] = 1] = "MouseMove";
|
|
4877
|
+
IncrementalSource2[IncrementalSource2["MouseInteraction"] = 2] = "MouseInteraction";
|
|
4878
|
+
IncrementalSource2[IncrementalSource2["Scroll"] = 3] = "Scroll";
|
|
4879
|
+
IncrementalSource2[IncrementalSource2["ViewportResize"] = 4] = "ViewportResize";
|
|
4880
|
+
IncrementalSource2[IncrementalSource2["Input"] = 5] = "Input";
|
|
4881
|
+
IncrementalSource2[IncrementalSource2["TouchMove"] = 6] = "TouchMove";
|
|
4882
|
+
IncrementalSource2[IncrementalSource2["MediaInteraction"] = 7] = "MediaInteraction";
|
|
4883
|
+
IncrementalSource2[IncrementalSource2["StyleSheetRule"] = 8] = "StyleSheetRule";
|
|
4884
|
+
IncrementalSource2[IncrementalSource2["CanvasMutation"] = 9] = "CanvasMutation";
|
|
4885
|
+
IncrementalSource2[IncrementalSource2["Font"] = 10] = "Font";
|
|
4886
|
+
IncrementalSource2[IncrementalSource2["Log"] = 11] = "Log";
|
|
4887
|
+
IncrementalSource2[IncrementalSource2["Drag"] = 12] = "Drag";
|
|
4888
|
+
IncrementalSource2[IncrementalSource2["StyleDeclaration"] = 13] = "StyleDeclaration";
|
|
4889
|
+
IncrementalSource2[IncrementalSource2["Selection"] = 14] = "Selection";
|
|
4890
|
+
IncrementalSource2[IncrementalSource2["AdoptedStyleSheet"] = 15] = "AdoptedStyleSheet";
|
|
4891
|
+
IncrementalSource2[IncrementalSource2["CustomElement"] = 16] = "CustomElement";
|
|
4892
|
+
return IncrementalSource2;
|
|
4893
|
+
})(IncrementalSource || {});
|
|
4894
|
+
|
|
4895
|
+
var Config = {
|
|
4896
|
+
DEBUG: false,
|
|
4897
|
+
LIB_VERSION: '2.61.1'
|
|
4898
|
+
};
|
|
4899
|
+
|
|
4900
4900
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
4901
4901
|
|
|
4902
4902
|
// Maximum allowed session recording length
|
|
@@ -5924,15 +5924,9 @@
|
|
|
5924
5924
|
}
|
|
5925
5925
|
};
|
|
5926
5926
|
|
|
5927
|
-
var
|
|
5928
|
-
var localStorageSupported = function(storage, forceCheck) {
|
|
5929
|
-
if (_localStorageSupported !== null && !forceCheck) {
|
|
5930
|
-
return _localStorageSupported;
|
|
5931
|
-
}
|
|
5932
|
-
|
|
5927
|
+
var _testStorageSupported = function (storage) {
|
|
5933
5928
|
var supported = true;
|
|
5934
5929
|
try {
|
|
5935
|
-
storage = storage || win.localStorage;
|
|
5936
5930
|
var key = '__mplss_' + cheap_guid(8),
|
|
5937
5931
|
val = 'xyz';
|
|
5938
5932
|
storage.setItem(key, val);
|
|
@@ -5943,59 +5937,74 @@
|
|
|
5943
5937
|
} catch (err) {
|
|
5944
5938
|
supported = false;
|
|
5945
5939
|
}
|
|
5946
|
-
|
|
5947
|
-
_localStorageSupported = supported;
|
|
5948
5940
|
return supported;
|
|
5949
5941
|
};
|
|
5950
5942
|
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
return supported;
|
|
5959
|
-
},
|
|
5960
|
-
|
|
5961
|
-
error: function(msg) {
|
|
5962
|
-
console$1.error('localStorage error: ' + msg);
|
|
5963
|
-
},
|
|
5943
|
+
var _localStorageSupported = null;
|
|
5944
|
+
var localStorageSupported = function(storage, forceCheck) {
|
|
5945
|
+
if (_localStorageSupported !== null && !forceCheck) {
|
|
5946
|
+
return _localStorageSupported;
|
|
5947
|
+
}
|
|
5948
|
+
return _localStorageSupported = _testStorageSupported(storage || win.localStorage);
|
|
5949
|
+
};
|
|
5964
5950
|
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
},
|
|
5951
|
+
var _sessionStorageSupported = null;
|
|
5952
|
+
var sessionStorageSupported = function(storage, forceCheck) {
|
|
5953
|
+
if (_sessionStorageSupported !== null && !forceCheck) {
|
|
5954
|
+
return _sessionStorageSupported;
|
|
5955
|
+
}
|
|
5956
|
+
return _sessionStorageSupported = _testStorageSupported(storage || win.sessionStorage);
|
|
5957
|
+
};
|
|
5973
5958
|
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
// noop
|
|
5979
|
-
}
|
|
5980
|
-
return null;
|
|
5981
|
-
},
|
|
5959
|
+
function _storageWrapper(storage, name, is_supported_fn) {
|
|
5960
|
+
var log_error = function(msg) {
|
|
5961
|
+
console$1.error(name + ' error: ' + msg);
|
|
5962
|
+
};
|
|
5982
5963
|
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5964
|
+
return {
|
|
5965
|
+
is_supported: function(forceCheck) {
|
|
5966
|
+
var supported = is_supported_fn(storage, forceCheck);
|
|
5967
|
+
if (!supported) {
|
|
5968
|
+
console$1.error(name + ' unsupported');
|
|
5969
|
+
}
|
|
5970
|
+
return supported;
|
|
5971
|
+
},
|
|
5972
|
+
error: log_error,
|
|
5973
|
+
get: function(key) {
|
|
5974
|
+
try {
|
|
5975
|
+
return storage.getItem(key);
|
|
5976
|
+
} catch (err) {
|
|
5977
|
+
log_error(err);
|
|
5978
|
+
}
|
|
5979
|
+
return null;
|
|
5980
|
+
},
|
|
5981
|
+
parse: function(key) {
|
|
5982
|
+
try {
|
|
5983
|
+
return _.JSONDecode(storage.getItem(key)) || {};
|
|
5984
|
+
} catch (err) {
|
|
5985
|
+
// noop
|
|
5986
|
+
}
|
|
5987
|
+
return null;
|
|
5988
|
+
},
|
|
5989
|
+
set: function(key, value) {
|
|
5990
|
+
try {
|
|
5991
|
+
storage.setItem(key, value);
|
|
5992
|
+
} catch (err) {
|
|
5993
|
+
log_error(err);
|
|
5994
|
+
}
|
|
5995
|
+
},
|
|
5996
|
+
remove: function(key) {
|
|
5997
|
+
try {
|
|
5998
|
+
storage.removeItem(key);
|
|
5999
|
+
} catch (err) {
|
|
6000
|
+
log_error(err);
|
|
6001
|
+
}
|
|
5988
6002
|
}
|
|
5989
|
-
}
|
|
6003
|
+
};
|
|
6004
|
+
}
|
|
5990
6005
|
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
win.localStorage.removeItem(name);
|
|
5994
|
-
} catch (err) {
|
|
5995
|
-
_.localStorage.error(err);
|
|
5996
|
-
}
|
|
5997
|
-
}
|
|
5998
|
-
};
|
|
6006
|
+
_.localStorage = _storageWrapper(win.localStorage, 'localStorage', localStorageSupported);
|
|
6007
|
+
_.sessionStorage = _storageWrapper(win.sessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
5999
6008
|
|
|
6000
6009
|
_.register_event = (function() {
|
|
6001
6010
|
// written by Dean Edwards, 2005
|
|
@@ -6522,6 +6531,31 @@
|
|
|
6522
6531
|
}
|
|
6523
6532
|
};
|
|
6524
6533
|
|
|
6534
|
+
/**
|
|
6535
|
+
* Returns a throttled function that will only run at most every `waitMs` and returns a promise that resolves with the next invocation.
|
|
6536
|
+
* Throttled calls will build up a batch of args and invoke the callback with all args since the last invocation.
|
|
6537
|
+
*/
|
|
6538
|
+
var batchedThrottle = function (fn, waitMs) {
|
|
6539
|
+
var timeoutPromise = null;
|
|
6540
|
+
var throttledItems = [];
|
|
6541
|
+
return function (item) {
|
|
6542
|
+
var self = this;
|
|
6543
|
+
throttledItems.push(item);
|
|
6544
|
+
|
|
6545
|
+
if (!timeoutPromise) {
|
|
6546
|
+
timeoutPromise = new PromisePolyfill(function (resolve) {
|
|
6547
|
+
setTimeout(function () {
|
|
6548
|
+
var returnValue = fn.apply(self, [throttledItems]);
|
|
6549
|
+
timeoutPromise = null;
|
|
6550
|
+
throttledItems = [];
|
|
6551
|
+
resolve(returnValue);
|
|
6552
|
+
}, waitMs);
|
|
6553
|
+
});
|
|
6554
|
+
}
|
|
6555
|
+
return timeoutPromise;
|
|
6556
|
+
};
|
|
6557
|
+
};
|
|
6558
|
+
|
|
6525
6559
|
var cheap_guid = function(maxlen) {
|
|
6526
6560
|
var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
|
|
6527
6561
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
@@ -6564,6 +6598,8 @@
|
|
|
6564
6598
|
return _.isUndefined(onLine) || onLine;
|
|
6565
6599
|
};
|
|
6566
6600
|
|
|
6601
|
+
var NOOP_FUNC = function () {};
|
|
6602
|
+
|
|
6567
6603
|
var JSONStringify = null, JSONParse = null;
|
|
6568
6604
|
if (typeof JSON !== 'undefined') {
|
|
6569
6605
|
JSONStringify = JSON.stringify;
|
|
@@ -6572,20 +6608,143 @@
|
|
|
6572
6608
|
JSONStringify = JSONStringify || _.JSONEncode;
|
|
6573
6609
|
JSONParse = JSONParse || _.JSONDecode;
|
|
6574
6610
|
|
|
6575
|
-
// EXPORTS (for closure compiler)
|
|
6576
|
-
_['toArray'] = _.toArray;
|
|
6577
|
-
_['isObject'] = _.isObject;
|
|
6578
|
-
_['JSONEncode'] = _.JSONEncode;
|
|
6579
|
-
_['JSONDecode'] = _.JSONDecode;
|
|
6580
|
-
_['isBlockedUA'] = _.isBlockedUA;
|
|
6581
|
-
_['isEmptyObject'] = _.isEmptyObject;
|
|
6611
|
+
// UNMINIFIED EXPORTS (for closure compiler)
|
|
6582
6612
|
_['info'] = _.info;
|
|
6583
|
-
_['info']['device'] = _.info.device;
|
|
6584
6613
|
_['info']['browser'] = _.info.browser;
|
|
6585
6614
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
6615
|
+
_['info']['device'] = _.info.device;
|
|
6586
6616
|
_['info']['properties'] = _.info.properties;
|
|
6617
|
+
_['isBlockedUA'] = _.isBlockedUA;
|
|
6618
|
+
_['isEmptyObject'] = _.isEmptyObject;
|
|
6619
|
+
_['isObject'] = _.isObject;
|
|
6620
|
+
_['JSONDecode'] = _.JSONDecode;
|
|
6621
|
+
_['JSONEncode'] = _.JSONEncode;
|
|
6622
|
+
_['toArray'] = _.toArray;
|
|
6587
6623
|
_['NPO'] = NpoPromise;
|
|
6588
6624
|
|
|
6625
|
+
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
6626
|
+
|
|
6627
|
+
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
6628
|
+
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
6629
|
+
|
|
6630
|
+
// note: increment the version number when adding new object stores
|
|
6631
|
+
var DB_VERSION = 1;
|
|
6632
|
+
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
6633
|
+
|
|
6634
|
+
/**
|
|
6635
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
6636
|
+
*/
|
|
6637
|
+
var IDBStorageWrapper = function (storeName) {
|
|
6638
|
+
/**
|
|
6639
|
+
* @type {Promise<IDBDatabase>|null}
|
|
6640
|
+
*/
|
|
6641
|
+
this.dbPromise = null;
|
|
6642
|
+
this.storeName = storeName;
|
|
6643
|
+
};
|
|
6644
|
+
|
|
6645
|
+
IDBStorageWrapper.prototype._openDb = function () {
|
|
6646
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
6647
|
+
var openRequest = win.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
6648
|
+
openRequest['onerror'] = function () {
|
|
6649
|
+
reject(openRequest.error);
|
|
6650
|
+
};
|
|
6651
|
+
|
|
6652
|
+
openRequest['onsuccess'] = function () {
|
|
6653
|
+
resolve(openRequest.result);
|
|
6654
|
+
};
|
|
6655
|
+
|
|
6656
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
6657
|
+
var db = ev.target.result;
|
|
6658
|
+
|
|
6659
|
+
OBJECT_STORES.forEach(function (storeName) {
|
|
6660
|
+
db.createObjectStore(storeName);
|
|
6661
|
+
});
|
|
6662
|
+
};
|
|
6663
|
+
});
|
|
6664
|
+
};
|
|
6665
|
+
|
|
6666
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
6667
|
+
if (!win.indexedDB) {
|
|
6668
|
+
return PromisePolyfill.reject('indexedDB is not supported in this browser');
|
|
6669
|
+
}
|
|
6670
|
+
|
|
6671
|
+
if (!this.dbPromise) {
|
|
6672
|
+
this.dbPromise = this._openDb();
|
|
6673
|
+
}
|
|
6674
|
+
|
|
6675
|
+
return this.dbPromise
|
|
6676
|
+
.then(function (dbOrError) {
|
|
6677
|
+
if (dbOrError instanceof win['IDBDatabase']) {
|
|
6678
|
+
return PromisePolyfill.resolve();
|
|
6679
|
+
} else {
|
|
6680
|
+
return PromisePolyfill.reject(dbOrError);
|
|
6681
|
+
}
|
|
6682
|
+
});
|
|
6683
|
+
};
|
|
6684
|
+
|
|
6685
|
+
/**
|
|
6686
|
+
* @param {IDBTransactionMode} mode
|
|
6687
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
6688
|
+
*/
|
|
6689
|
+
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
6690
|
+
var storeName = this.storeName;
|
|
6691
|
+
var doTransaction = function (db) {
|
|
6692
|
+
return new PromisePolyfill(function (resolve, reject) {
|
|
6693
|
+
var transaction = db.transaction(storeName, mode);
|
|
6694
|
+
transaction.oncomplete = function () {
|
|
6695
|
+
resolve(transaction);
|
|
6696
|
+
};
|
|
6697
|
+
transaction.onabort = transaction.onerror = function () {
|
|
6698
|
+
reject(transaction.error);
|
|
6699
|
+
};
|
|
6700
|
+
|
|
6701
|
+
storeCb(transaction.objectStore(storeName));
|
|
6702
|
+
});
|
|
6703
|
+
};
|
|
6704
|
+
|
|
6705
|
+
return this.dbPromise
|
|
6706
|
+
.then(doTransaction)
|
|
6707
|
+
.catch(function (err) {
|
|
6708
|
+
if (err && err['name'] === 'InvalidStateError') {
|
|
6709
|
+
// try reopening the DB if the connection is closed
|
|
6710
|
+
this.dbPromise = this._openDb();
|
|
6711
|
+
return this.dbPromise.then(doTransaction);
|
|
6712
|
+
} else {
|
|
6713
|
+
return PromisePolyfill.reject(err);
|
|
6714
|
+
}
|
|
6715
|
+
}.bind(this));
|
|
6716
|
+
};
|
|
6717
|
+
|
|
6718
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
6719
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
6720
|
+
objectStore.put(value, key);
|
|
6721
|
+
});
|
|
6722
|
+
};
|
|
6723
|
+
|
|
6724
|
+
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
6725
|
+
var req;
|
|
6726
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
6727
|
+
req = objectStore.get(key);
|
|
6728
|
+
}).then(function () {
|
|
6729
|
+
return req.result;
|
|
6730
|
+
});
|
|
6731
|
+
};
|
|
6732
|
+
|
|
6733
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
6734
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
6735
|
+
objectStore.delete(key);
|
|
6736
|
+
});
|
|
6737
|
+
};
|
|
6738
|
+
|
|
6739
|
+
IDBStorageWrapper.prototype.getAll = function () {
|
|
6740
|
+
var req;
|
|
6741
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
6742
|
+
req = objectStore.getAll();
|
|
6743
|
+
}).then(function () {
|
|
6744
|
+
return req.result;
|
|
6745
|
+
});
|
|
6746
|
+
};
|
|
6747
|
+
|
|
6589
6748
|
/**
|
|
6590
6749
|
* GDPR utils
|
|
6591
6750
|
*
|
|
@@ -6779,7 +6938,7 @@
|
|
|
6779
6938
|
options = options || {};
|
|
6780
6939
|
|
|
6781
6940
|
this.storageKey = key;
|
|
6782
|
-
this.storage = options.storage ||
|
|
6941
|
+
this.storage = options.storage || win.localStorage;
|
|
6783
6942
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
6784
6943
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
6785
6944
|
|
|
@@ -6794,7 +6953,6 @@
|
|
|
6794
6953
|
return new Promise(_.bind(function (resolve, reject) {
|
|
6795
6954
|
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
6796
6955
|
var startTime = new Date().getTime();
|
|
6797
|
-
|
|
6798
6956
|
var key = this.storageKey;
|
|
6799
6957
|
var pollIntervalMS = this.pollIntervalMS;
|
|
6800
6958
|
var timeoutMS = this.timeoutMS;
|
|
@@ -6905,11 +7063,7 @@
|
|
|
6905
7063
|
};
|
|
6906
7064
|
|
|
6907
7065
|
/**
|
|
6908
|
-
* @
|
|
6909
|
-
*/
|
|
6910
|
-
|
|
6911
|
-
/**
|
|
6912
|
-
* @type {StorageWrapper}
|
|
7066
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
6913
7067
|
*/
|
|
6914
7068
|
var LocalStorageWrapper = function (storageOverride) {
|
|
6915
7069
|
this.storage = storageOverride || localStorage;
|
|
@@ -6922,7 +7076,7 @@
|
|
|
6922
7076
|
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
6923
7077
|
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
6924
7078
|
try {
|
|
6925
|
-
this.storage.setItem(key, value);
|
|
7079
|
+
this.storage.setItem(key, JSONStringify(value));
|
|
6926
7080
|
} catch (e) {
|
|
6927
7081
|
reject(e);
|
|
6928
7082
|
}
|
|
@@ -6934,7 +7088,7 @@
|
|
|
6934
7088
|
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
6935
7089
|
var item;
|
|
6936
7090
|
try {
|
|
6937
|
-
item = this.storage.getItem(key);
|
|
7091
|
+
item = JSONParse(this.storage.getItem(key));
|
|
6938
7092
|
} catch (e) {
|
|
6939
7093
|
reject(e);
|
|
6940
7094
|
}
|
|
@@ -6977,8 +7131,10 @@
|
|
|
6977
7131
|
this.usePersistence = options.usePersistence;
|
|
6978
7132
|
if (this.usePersistence) {
|
|
6979
7133
|
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
6980
|
-
this.lock = new SharedLock(storageKey, {
|
|
6981
|
-
|
|
7134
|
+
this.lock = new SharedLock(storageKey, {
|
|
7135
|
+
storage: options.sharedLockStorage || win.localStorage,
|
|
7136
|
+
timeoutMS: options.sharedLockTimeoutMS,
|
|
7137
|
+
});
|
|
6982
7138
|
}
|
|
6983
7139
|
this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
|
|
6984
7140
|
|
|
@@ -6986,6 +7142,14 @@
|
|
|
6986
7142
|
|
|
6987
7143
|
this.memQueue = [];
|
|
6988
7144
|
this.initialized = false;
|
|
7145
|
+
|
|
7146
|
+
if (options.enqueueThrottleMs) {
|
|
7147
|
+
this.enqueuePersisted = batchedThrottle(_.bind(this._enqueuePersisted, this), options.enqueueThrottleMs);
|
|
7148
|
+
} else {
|
|
7149
|
+
this.enqueuePersisted = _.bind(function (queueEntry) {
|
|
7150
|
+
return this._enqueuePersisted([queueEntry]);
|
|
7151
|
+
}, this);
|
|
7152
|
+
}
|
|
6989
7153
|
};
|
|
6990
7154
|
|
|
6991
7155
|
RequestQueue.prototype.ensureInit = function () {
|
|
@@ -7028,36 +7192,39 @@
|
|
|
7028
7192
|
this.memQueue.push(queueEntry);
|
|
7029
7193
|
return PromisePolyfill.resolve(true);
|
|
7030
7194
|
} else {
|
|
7195
|
+
return this.enqueuePersisted(queueEntry);
|
|
7196
|
+
}
|
|
7197
|
+
};
|
|
7031
7198
|
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
return succeeded;
|
|
7047
|
-
}, this))
|
|
7048
|
-
.catch(_.bind(function (err) {
|
|
7049
|
-
this.reportError('Error enqueueing item', err, item);
|
|
7050
|
-
return false;
|
|
7051
|
-
}, this));
|
|
7052
|
-
}, this);
|
|
7199
|
+
RequestQueue.prototype._enqueuePersisted = function (queueEntries) {
|
|
7200
|
+
var enqueueItem = _.bind(function () {
|
|
7201
|
+
return this.ensureInit()
|
|
7202
|
+
.then(_.bind(function () {
|
|
7203
|
+
return this.readFromStorage();
|
|
7204
|
+
}, this))
|
|
7205
|
+
.then(_.bind(function (storedQueue) {
|
|
7206
|
+
return this.saveToStorage(storedQueue.concat(queueEntries));
|
|
7207
|
+
}, this))
|
|
7208
|
+
.then(_.bind(function (succeeded) {
|
|
7209
|
+
// only add to in-memory queue when storage succeeds
|
|
7210
|
+
if (succeeded) {
|
|
7211
|
+
this.memQueue = this.memQueue.concat(queueEntries);
|
|
7212
|
+
}
|
|
7053
7213
|
|
|
7054
|
-
|
|
7055
|
-
|
|
7214
|
+
return succeeded;
|
|
7215
|
+
}, this))
|
|
7056
7216
|
.catch(_.bind(function (err) {
|
|
7057
|
-
this.reportError('Error
|
|
7217
|
+
this.reportError('Error enqueueing items', err, queueEntries);
|
|
7058
7218
|
return false;
|
|
7059
7219
|
}, this));
|
|
7060
|
-
}
|
|
7220
|
+
}, this);
|
|
7221
|
+
|
|
7222
|
+
return this.lock
|
|
7223
|
+
.withLock(enqueueItem, this.pid)
|
|
7224
|
+
.catch(_.bind(function (err) {
|
|
7225
|
+
this.reportError('Error acquiring storage lock', err);
|
|
7226
|
+
return false;
|
|
7227
|
+
}, this));
|
|
7061
7228
|
};
|
|
7062
7229
|
|
|
7063
7230
|
/**
|
|
@@ -7078,7 +7245,7 @@
|
|
|
7078
7245
|
}, this))
|
|
7079
7246
|
.then(_.bind(function (storedQueue) {
|
|
7080
7247
|
if (storedQueue.length) {
|
|
7081
|
-
|
|
7248
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
7082
7249
|
var idsInBatch = {}; // poor man's Set
|
|
7083
7250
|
_.each(batch, function (item) {
|
|
7084
7251
|
idsInBatch[item['id']] = true;
|
|
@@ -7165,7 +7332,7 @@
|
|
|
7165
7332
|
.withLock(removeFromStorage, this.pid)
|
|
7166
7333
|
.catch(_.bind(function (err) {
|
|
7167
7334
|
this.reportError('Error acquiring storage lock', err);
|
|
7168
|
-
if (!localStorageSupported(this.
|
|
7335
|
+
if (!localStorageSupported(this.lock.storage, true)) {
|
|
7169
7336
|
// Looks like localStorage writes have stopped working sometime after
|
|
7170
7337
|
// initialization (probably full), and so nobody can acquire locks
|
|
7171
7338
|
// anymore. Consider it temporarily safe to remove items without the
|
|
@@ -7253,7 +7420,6 @@
|
|
|
7253
7420
|
}, this))
|
|
7254
7421
|
.then(_.bind(function (storageEntry) {
|
|
7255
7422
|
if (storageEntry) {
|
|
7256
|
-
storageEntry = JSONParse(storageEntry);
|
|
7257
7423
|
if (!_.isArray(storageEntry)) {
|
|
7258
7424
|
this.reportError('Invalid storage entry:', storageEntry);
|
|
7259
7425
|
storageEntry = null;
|
|
@@ -7271,16 +7437,9 @@
|
|
|
7271
7437
|
* Serialize the given items array to localStorage.
|
|
7272
7438
|
*/
|
|
7273
7439
|
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
7274
|
-
try {
|
|
7275
|
-
var serialized = JSONStringify(queue);
|
|
7276
|
-
} catch (err) {
|
|
7277
|
-
this.reportError('Error serializing queue', err);
|
|
7278
|
-
return PromisePolyfill.resolve(false);
|
|
7279
|
-
}
|
|
7280
|
-
|
|
7281
7440
|
return this.ensureInit()
|
|
7282
7441
|
.then(_.bind(function () {
|
|
7283
|
-
return this.queueStorage.setItem(this.storageKey,
|
|
7442
|
+
return this.queueStorage.setItem(this.storageKey, queue);
|
|
7284
7443
|
}, this))
|
|
7285
7444
|
.then(function () {
|
|
7286
7445
|
return true;
|
|
@@ -7324,7 +7483,9 @@
|
|
|
7324
7483
|
errorReporter: _.bind(this.reportError, this),
|
|
7325
7484
|
queueStorage: options.queueStorage,
|
|
7326
7485
|
sharedLockStorage: options.sharedLockStorage,
|
|
7327
|
-
|
|
7486
|
+
sharedLockTimeoutMS: options.sharedLockTimeoutMS,
|
|
7487
|
+
usePersistence: options.usePersistence,
|
|
7488
|
+
enqueueThrottleMs: options.enqueueThrottleMs
|
|
7328
7489
|
});
|
|
7329
7490
|
|
|
7330
7491
|
this.libConfig = options.libConfig;
|
|
@@ -7346,6 +7507,8 @@
|
|
|
7346
7507
|
// as long as the queue is not empty. This is useful for high-frequency events like Session Replay where we might end up
|
|
7347
7508
|
// in a request loop and get ratelimited by the server.
|
|
7348
7509
|
this.flushOnlyOnInterval = options.flushOnlyOnInterval || false;
|
|
7510
|
+
|
|
7511
|
+
this._flushPromise = null;
|
|
7349
7512
|
};
|
|
7350
7513
|
|
|
7351
7514
|
/**
|
|
@@ -7405,7 +7568,7 @@
|
|
|
7405
7568
|
if (!this.stopped) { // don't schedule anymore if batching has been stopped
|
|
7406
7569
|
this.timeoutID = setTimeout(_.bind(function() {
|
|
7407
7570
|
if (!this.stopped) {
|
|
7408
|
-
this.flush();
|
|
7571
|
+
this._flushPromise = this.flush();
|
|
7409
7572
|
}
|
|
7410
7573
|
}, this), this.flushInterval);
|
|
7411
7574
|
}
|
|
@@ -7637,6 +7800,17 @@
|
|
|
7637
7800
|
}
|
|
7638
7801
|
};
|
|
7639
7802
|
|
|
7803
|
+
/**
|
|
7804
|
+
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
7805
|
+
* @returns {boolean}
|
|
7806
|
+
*/
|
|
7807
|
+
var isRecordingExpired = function(serializedRecording) {
|
|
7808
|
+
var now = Date.now();
|
|
7809
|
+
return !serializedRecording || now > serializedRecording['maxExpires'] || now > serializedRecording['idleExpires'];
|
|
7810
|
+
};
|
|
7811
|
+
|
|
7812
|
+
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
7813
|
+
|
|
7640
7814
|
var logger$1 = console_with_prefix('recorder');
|
|
7641
7815
|
var CompressionStream = win['CompressionStream'];
|
|
7642
7816
|
|
|
@@ -7663,29 +7837,58 @@
|
|
|
7663
7837
|
return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
|
|
7664
7838
|
}
|
|
7665
7839
|
|
|
7840
|
+
/**
|
|
7841
|
+
* @typedef {Object} SerializedRecording
|
|
7842
|
+
* @property {number} idleExpires
|
|
7843
|
+
* @property {number} maxExpires
|
|
7844
|
+
* @property {number} replayStartTime
|
|
7845
|
+
* @property {number} seqNo
|
|
7846
|
+
* @property {string} batchStartUrl
|
|
7847
|
+
* @property {string} replayId
|
|
7848
|
+
* @property {string} tabId
|
|
7849
|
+
* @property {string} replayStartUrl
|
|
7850
|
+
*/
|
|
7851
|
+
|
|
7852
|
+
/**
|
|
7853
|
+
* @typedef {Object} SessionRecordingOptions
|
|
7854
|
+
* @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
7855
|
+
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
7856
|
+
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
7857
|
+
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
7858
|
+
* @property {Function} [options.rrwebRecord] - rrweb's `record` function
|
|
7859
|
+
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
7860
|
+
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
7861
|
+
* optional properties for deserialization:
|
|
7862
|
+
* @property {number} idleExpires
|
|
7863
|
+
* @property {number} maxExpires
|
|
7864
|
+
* @property {number} replayStartTime
|
|
7865
|
+
* @property {number} seqNo
|
|
7866
|
+
* @property {string} batchStartUrl
|
|
7867
|
+
* @property {string} replayStartUrl
|
|
7868
|
+
*/
|
|
7869
|
+
|
|
7870
|
+
|
|
7666
7871
|
/**
|
|
7667
7872
|
* This class encapsulates a single session recording and its lifecycle.
|
|
7668
|
-
* @param {
|
|
7669
|
-
* @param {String} [options.replayId] - unique uuid for a single replay
|
|
7670
|
-
* @param {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
7671
|
-
* @param {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
7672
|
-
* @param {Function} [options.rrwebRecord] - rrweb's `record` function
|
|
7873
|
+
* @param {SessionRecordingOptions} options
|
|
7673
7874
|
*/
|
|
7674
7875
|
var SessionRecording = function(options) {
|
|
7675
7876
|
this._mixpanel = options.mixpanelInstance;
|
|
7676
|
-
this._onIdleTimeout = options.onIdleTimeout;
|
|
7677
|
-
this._onMaxLengthReached = options.onMaxLengthReached;
|
|
7678
|
-
this.
|
|
7679
|
-
|
|
7680
|
-
this.replayId = options.replayId;
|
|
7877
|
+
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
7878
|
+
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
7879
|
+
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
7880
|
+
this._rrwebRecord = options.rrwebRecord || null;
|
|
7681
7881
|
|
|
7682
7882
|
// internal rrweb stopRecording function
|
|
7683
7883
|
this._stopRecording = null;
|
|
7884
|
+
this.replayId = options.replayId;
|
|
7684
7885
|
|
|
7685
|
-
this.
|
|
7686
|
-
this.
|
|
7687
|
-
this.
|
|
7688
|
-
this.
|
|
7886
|
+
this.batchStartUrl = options.batchStartUrl || null;
|
|
7887
|
+
this.replayStartUrl = options.replayStartUrl || null;
|
|
7888
|
+
this.idleExpires = options.idleExpires || null;
|
|
7889
|
+
this.maxExpires = options.maxExpires || null;
|
|
7890
|
+
this.replayStartTime = options.replayStartTime || null;
|
|
7891
|
+
this.seqNo = options.seqNo || 0;
|
|
7689
7892
|
|
|
7690
7893
|
this.idleTimeoutId = null;
|
|
7691
7894
|
this.maxTimeoutId = null;
|
|
@@ -7693,18 +7896,40 @@
|
|
|
7693
7896
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
7694
7897
|
this.recordMinMs = 0;
|
|
7695
7898
|
|
|
7899
|
+
// disable persistence if localStorage is not supported
|
|
7900
|
+
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
7901
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true);
|
|
7902
|
+
|
|
7696
7903
|
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
7697
7904
|
// this will be important when persistence is introduced
|
|
7698
|
-
|
|
7699
|
-
this.
|
|
7700
|
-
|
|
7905
|
+
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
7906
|
+
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
7907
|
+
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
7908
|
+
errorReporter: this.reportError.bind(this),
|
|
7701
7909
|
flushOnlyOnInterval: true,
|
|
7702
7910
|
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
7703
|
-
sendRequestFunc:
|
|
7704
|
-
|
|
7911
|
+
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
7912
|
+
queueStorage: this.queueStorage,
|
|
7913
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
7914
|
+
usePersistence: usePersistence,
|
|
7915
|
+
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
7916
|
+
|
|
7917
|
+
// increased throttle and shared lock timeout because recording events are very high frequency.
|
|
7918
|
+
// this will minimize the amount of lock contention between enqueued events.
|
|
7919
|
+
// for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
|
|
7920
|
+
enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
|
|
7921
|
+
sharedLockTimeoutMS: 10 * 1000,
|
|
7705
7922
|
});
|
|
7706
7923
|
};
|
|
7707
7924
|
|
|
7925
|
+
SessionRecording.prototype.unloadPersistedData = function () {
|
|
7926
|
+
this.batcher.stop();
|
|
7927
|
+
return this.batcher.flush()
|
|
7928
|
+
.then(function () {
|
|
7929
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
7930
|
+
}.bind(this));
|
|
7931
|
+
};
|
|
7932
|
+
|
|
7708
7933
|
SessionRecording.prototype.getConfig = function(configVar) {
|
|
7709
7934
|
return this._mixpanel.get_config(configVar);
|
|
7710
7935
|
};
|
|
@@ -7717,6 +7942,11 @@
|
|
|
7717
7942
|
};
|
|
7718
7943
|
|
|
7719
7944
|
SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
7945
|
+
if (this._rrwebRecord === null) {
|
|
7946
|
+
this.reportError('rrweb record function not provided. ');
|
|
7947
|
+
return;
|
|
7948
|
+
}
|
|
7949
|
+
|
|
7720
7950
|
if (this._stopRecording !== null) {
|
|
7721
7951
|
logger$1.log('Recording already in progress, skipping startRecording.');
|
|
7722
7952
|
return;
|
|
@@ -7728,15 +7958,21 @@
|
|
|
7728
7958
|
logger$1.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
7729
7959
|
}
|
|
7730
7960
|
|
|
7961
|
+
if (!this.maxExpires) {
|
|
7962
|
+
this.maxExpires = new Date().getTime() + this.recordMaxMs;
|
|
7963
|
+
}
|
|
7964
|
+
|
|
7731
7965
|
this.recordMinMs = this.getConfig('record_min_ms');
|
|
7732
7966
|
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
7733
7967
|
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
7734
7968
|
logger$1.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
7735
7969
|
}
|
|
7736
7970
|
|
|
7737
|
-
this.replayStartTime
|
|
7738
|
-
|
|
7739
|
-
|
|
7971
|
+
if (!this.replayStartTime) {
|
|
7972
|
+
this.replayStartTime = new Date().getTime();
|
|
7973
|
+
this.batchStartUrl = _.info.currentUrl();
|
|
7974
|
+
this.replayStartUrl = _.info.currentUrl();
|
|
7975
|
+
}
|
|
7740
7976
|
|
|
7741
7977
|
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
7742
7978
|
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
@@ -7749,42 +7985,49 @@
|
|
|
7749
7985
|
this.batcher.start();
|
|
7750
7986
|
}
|
|
7751
7987
|
|
|
7752
|
-
var resetIdleTimeout =
|
|
7988
|
+
var resetIdleTimeout = function () {
|
|
7753
7989
|
clearTimeout(this.idleTimeoutId);
|
|
7754
|
-
|
|
7755
|
-
|
|
7990
|
+
var idleTimeoutMs = this.getConfig('record_idle_timeout_ms');
|
|
7991
|
+
this.idleTimeoutId = setTimeout(this._onIdleTimeout, idleTimeoutMs);
|
|
7992
|
+
this.idleExpires = new Date().getTime() + idleTimeoutMs;
|
|
7993
|
+
}.bind(this);
|
|
7756
7994
|
|
|
7757
7995
|
var blockSelector = this.getConfig('record_block_selector');
|
|
7758
7996
|
if (blockSelector === '' || blockSelector === null) {
|
|
7759
7997
|
blockSelector = undefined;
|
|
7760
7998
|
}
|
|
7761
7999
|
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
8000
|
+
try {
|
|
8001
|
+
this._stopRecording = this._rrwebRecord({
|
|
8002
|
+
'emit': function (ev) {
|
|
8003
|
+
if (isUserEvent(ev)) {
|
|
8004
|
+
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
8005
|
+
// start flushing again after user activity
|
|
8006
|
+
this.batcher.start();
|
|
8007
|
+
}
|
|
8008
|
+
resetIdleTimeout();
|
|
7769
8009
|
}
|
|
7770
|
-
|
|
8010
|
+
// promise only used to await during tests
|
|
8011
|
+
this.__enqueuePromise = this.batcher.enqueue(ev);
|
|
8012
|
+
}.bind(this),
|
|
8013
|
+
'blockClass': this.getConfig('record_block_class'),
|
|
8014
|
+
'blockSelector': blockSelector,
|
|
8015
|
+
'collectFonts': this.getConfig('record_collect_fonts'),
|
|
8016
|
+
'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
|
|
8017
|
+
'type': 'image/webp',
|
|
8018
|
+
'quality': 0.6
|
|
8019
|
+
},
|
|
8020
|
+
'maskAllInputs': true,
|
|
8021
|
+
'maskTextClass': this.getConfig('record_mask_text_class'),
|
|
8022
|
+
'maskTextSelector': this.getConfig('record_mask_text_selector'),
|
|
8023
|
+
'recordCanvas': this.getConfig('record_canvas'),
|
|
8024
|
+
'sampling': {
|
|
8025
|
+
'canvas': 15
|
|
7771
8026
|
}
|
|
7772
|
-
}
|
|
7773
|
-
|
|
7774
|
-
'
|
|
7775
|
-
|
|
7776
|
-
'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
|
|
7777
|
-
'type': 'image/webp',
|
|
7778
|
-
'quality': 0.6
|
|
7779
|
-
},
|
|
7780
|
-
'maskAllInputs': true,
|
|
7781
|
-
'maskTextClass': this.getConfig('record_mask_text_class'),
|
|
7782
|
-
'maskTextSelector': this.getConfig('record_mask_text_selector'),
|
|
7783
|
-
'recordCanvas': this.getConfig('record_canvas'),
|
|
7784
|
-
'sampling': {
|
|
7785
|
-
'canvas': 15
|
|
7786
|
-
}
|
|
7787
|
-
});
|
|
8027
|
+
});
|
|
8028
|
+
} catch (err) {
|
|
8029
|
+
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
8030
|
+
}
|
|
7788
8031
|
|
|
7789
8032
|
if (typeof this._stopRecording !== 'function') {
|
|
7790
8033
|
this.reportError('rrweb failed to start, skipping this recording.');
|
|
@@ -7795,10 +8038,11 @@
|
|
|
7795
8038
|
|
|
7796
8039
|
resetIdleTimeout();
|
|
7797
8040
|
|
|
7798
|
-
|
|
8041
|
+
var maxTimeoutMs = this.maxExpires - new Date().getTime();
|
|
8042
|
+
this.maxTimeoutId = setTimeout(this._onMaxLengthReached.bind(this), maxTimeoutMs);
|
|
7799
8043
|
};
|
|
7800
8044
|
|
|
7801
|
-
SessionRecording.prototype.stopRecording = function () {
|
|
8045
|
+
SessionRecording.prototype.stopRecording = function (skipFlush) {
|
|
7802
8046
|
if (!this.isRrwebStopped()) {
|
|
7803
8047
|
try {
|
|
7804
8048
|
this._stopRecording();
|
|
@@ -7808,40 +8052,91 @@
|
|
|
7808
8052
|
this._stopRecording = null;
|
|
7809
8053
|
}
|
|
7810
8054
|
|
|
8055
|
+
var flushPromise;
|
|
7811
8056
|
if (this.batcher.stopped) {
|
|
7812
8057
|
// never got user activity to flush after reset, so just clear the batcher
|
|
7813
|
-
this.batcher.clear();
|
|
7814
|
-
} else {
|
|
8058
|
+
flushPromise = this.batcher.clear();
|
|
8059
|
+
} else if (!skipFlush) {
|
|
7815
8060
|
// flush any remaining events from running batcher
|
|
7816
|
-
this.batcher.flush();
|
|
7817
|
-
this.batcher.stop();
|
|
8061
|
+
flushPromise = this.batcher.flush();
|
|
7818
8062
|
}
|
|
8063
|
+
this.batcher.stop();
|
|
7819
8064
|
|
|
7820
8065
|
clearTimeout(this.idleTimeoutId);
|
|
7821
8066
|
clearTimeout(this.maxTimeoutId);
|
|
8067
|
+
return flushPromise;
|
|
7822
8068
|
};
|
|
7823
8069
|
|
|
7824
8070
|
SessionRecording.prototype.isRrwebStopped = function () {
|
|
7825
8071
|
return this._stopRecording === null;
|
|
7826
8072
|
};
|
|
7827
8073
|
|
|
8074
|
+
|
|
7828
8075
|
/**
|
|
7829
8076
|
* Flushes the current batch of events to the server, but passes an opt-out callback to make sure
|
|
7830
8077
|
* we stop recording and dump any queued events if the user has opted out.
|
|
7831
8078
|
*/
|
|
7832
8079
|
SessionRecording.prototype.flushEventsWithOptOut = function (data, options, cb) {
|
|
7833
|
-
|
|
8080
|
+
var onOptOut = function (code) {
|
|
8081
|
+
// addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
|
|
8082
|
+
if (code === 0) {
|
|
8083
|
+
this.stopRecording();
|
|
8084
|
+
cb({error: 'Tracking has been opted out, stopping recording.'});
|
|
8085
|
+
}
|
|
8086
|
+
}.bind(this);
|
|
8087
|
+
|
|
8088
|
+
this._flushEvents(data, options, cb, onOptOut);
|
|
7834
8089
|
};
|
|
7835
8090
|
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
8091
|
+
/**
|
|
8092
|
+
* @returns {SerializedRecording}
|
|
8093
|
+
*/
|
|
8094
|
+
SessionRecording.prototype.serialize = function () {
|
|
8095
|
+
// don't break if mixpanel instance was destroyed at some point
|
|
8096
|
+
var tabId;
|
|
8097
|
+
try {
|
|
8098
|
+
tabId = this._mixpanel.get_tab_id();
|
|
8099
|
+
} catch (e) {
|
|
8100
|
+
this.reportError('Error getting tab ID for serialization ', e);
|
|
8101
|
+
tabId = null;
|
|
7840
8102
|
}
|
|
8103
|
+
|
|
8104
|
+
return {
|
|
8105
|
+
'replayId': this.replayId,
|
|
8106
|
+
'seqNo': this.seqNo,
|
|
8107
|
+
'replayStartTime': this.replayStartTime,
|
|
8108
|
+
'batchStartUrl': this.batchStartUrl,
|
|
8109
|
+
'replayStartUrl': this.replayStartUrl,
|
|
8110
|
+
'idleExpires': this.idleExpires,
|
|
8111
|
+
'maxExpires': this.maxExpires,
|
|
8112
|
+
'tabId': tabId,
|
|
8113
|
+
};
|
|
8114
|
+
};
|
|
8115
|
+
|
|
8116
|
+
|
|
8117
|
+
/**
|
|
8118
|
+
* @static
|
|
8119
|
+
* @param {SerializedRecording} serializedRecording
|
|
8120
|
+
* @param {SessionRecordingOptions} options
|
|
8121
|
+
* @returns {SessionRecording}
|
|
8122
|
+
*/
|
|
8123
|
+
SessionRecording.deserialize = function (serializedRecording, options) {
|
|
8124
|
+
var recording = new SessionRecording(_.extend({}, options, {
|
|
8125
|
+
replayId: serializedRecording['replayId'],
|
|
8126
|
+
batchStartUrl: serializedRecording['batchStartUrl'],
|
|
8127
|
+
replayStartUrl: serializedRecording['replayStartUrl'],
|
|
8128
|
+
idleExpires: serializedRecording['idleExpires'],
|
|
8129
|
+
maxExpires: serializedRecording['maxExpires'],
|
|
8130
|
+
replayStartTime: serializedRecording['replayStartTime'],
|
|
8131
|
+
seqNo: serializedRecording['seqNo'],
|
|
8132
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
8133
|
+
}));
|
|
8134
|
+
|
|
8135
|
+
return recording;
|
|
7841
8136
|
};
|
|
7842
8137
|
|
|
7843
8138
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
7844
|
-
var onSuccess =
|
|
8139
|
+
var onSuccess = function (response, responseBody) {
|
|
7845
8140
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
7846
8141
|
// RequestBatcher will always flush the next batch after the previous one succeeds.
|
|
7847
8142
|
// extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
|
|
@@ -7849,13 +8144,15 @@
|
|
|
7849
8144
|
this.seqNo++;
|
|
7850
8145
|
this.batchStartUrl = _.info.currentUrl();
|
|
7851
8146
|
}
|
|
8147
|
+
|
|
8148
|
+
this._onBatchSent();
|
|
7852
8149
|
callback({
|
|
7853
8150
|
status: 0,
|
|
7854
8151
|
httpStatusCode: response.status,
|
|
7855
8152
|
responseBody: responseBody,
|
|
7856
8153
|
retryAfter: response.headers.get('Retry-After')
|
|
7857
8154
|
});
|
|
7858
|
-
}
|
|
8155
|
+
}.bind(this);
|
|
7859
8156
|
|
|
7860
8157
|
win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
|
|
7861
8158
|
'method': 'POST',
|
|
@@ -7876,21 +8173,36 @@
|
|
|
7876
8173
|
};
|
|
7877
8174
|
|
|
7878
8175
|
SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
|
|
7879
|
-
|
|
8176
|
+
var numEvents = data.length;
|
|
7880
8177
|
|
|
7881
8178
|
if (numEvents > 0) {
|
|
7882
8179
|
var replayId = this.replayId;
|
|
8180
|
+
|
|
7883
8181
|
// each rrweb event has a timestamp - leverage those to get time properties
|
|
7884
|
-
var batchStartTime =
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
8182
|
+
var batchStartTime = Infinity;
|
|
8183
|
+
var batchEndTime = -Infinity;
|
|
8184
|
+
var hasFullSnapshot = false;
|
|
8185
|
+
for (var i = 0; i < numEvents; i++) {
|
|
8186
|
+
batchStartTime = Math.min(batchStartTime, data[i].timestamp);
|
|
8187
|
+
batchEndTime = Math.max(batchEndTime, data[i].timestamp);
|
|
8188
|
+
if (data[i].type === EventType.FullSnapshot) {
|
|
8189
|
+
hasFullSnapshot = true;
|
|
7889
8190
|
}
|
|
8191
|
+
}
|
|
7890
8192
|
|
|
8193
|
+
if (this.seqNo === 0) {
|
|
8194
|
+
if (!hasFullSnapshot) {
|
|
8195
|
+
callback({error: 'First batch does not contain a full snapshot. Aborting recording.'});
|
|
8196
|
+
this.stopRecording(true);
|
|
8197
|
+
return;
|
|
8198
|
+
}
|
|
8199
|
+
this.replayStartTime = batchStartTime;
|
|
8200
|
+
} else if (!this.replayStartTime) {
|
|
8201
|
+
this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
|
|
7891
8202
|
this.replayStartTime = batchStartTime;
|
|
7892
8203
|
}
|
|
7893
|
-
|
|
8204
|
+
|
|
8205
|
+
var replayLengthMs = batchEndTime - this.replayStartTime;
|
|
7894
8206
|
|
|
7895
8207
|
var reqParams = {
|
|
7896
8208
|
'$current_url': this.batchStartUrl,
|
|
@@ -7921,10 +8233,10 @@
|
|
|
7921
8233
|
var gzipStream = jsonStream.pipeThrough(new CompressionStream('gzip'));
|
|
7922
8234
|
new Response(gzipStream)
|
|
7923
8235
|
.blob()
|
|
7924
|
-
.then(
|
|
8236
|
+
.then(function(compressedBlob) {
|
|
7925
8237
|
reqParams['format'] = 'gzip';
|
|
7926
8238
|
this._sendRequest(replayId, reqParams, compressedBlob, callback);
|
|
7927
|
-
}
|
|
8239
|
+
}.bind(this));
|
|
7928
8240
|
} else {
|
|
7929
8241
|
reqParams['format'] = 'body';
|
|
7930
8242
|
this._sendRequest(replayId, reqParams, eventsJson, callback);
|
|
@@ -7945,54 +8257,208 @@
|
|
|
7945
8257
|
}
|
|
7946
8258
|
};
|
|
7947
8259
|
|
|
8260
|
+
/**
|
|
8261
|
+
* Module for handling the storage and retrieval of recording metadata as well as any active recordings.
|
|
8262
|
+
* Makes sure that only one tab can be recording at a time.
|
|
8263
|
+
*/
|
|
8264
|
+
var RecordingRegistry = function (options) {
|
|
8265
|
+
this.idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
8266
|
+
this.errorReporter = options.errorReporter;
|
|
8267
|
+
this.mixpanelInstance = options.mixpanelInstance;
|
|
8268
|
+
this.sharedLockStorage = options.sharedLockStorage;
|
|
8269
|
+
};
|
|
8270
|
+
|
|
8271
|
+
RecordingRegistry.prototype.handleError = function (err) {
|
|
8272
|
+
this.errorReporter('IndexedDB error: ', err);
|
|
8273
|
+
};
|
|
8274
|
+
|
|
8275
|
+
/**
|
|
8276
|
+
* @param {import('./session-recording').SerializedRecording} serializedRecording
|
|
8277
|
+
*/
|
|
8278
|
+
RecordingRegistry.prototype.setActiveRecording = function (serializedRecording) {
|
|
8279
|
+
var tabId = serializedRecording['tabId'];
|
|
8280
|
+
if (!tabId) {
|
|
8281
|
+
console.warn('No tab ID is set, cannot persist recording metadata.');
|
|
8282
|
+
return PromisePolyfill.resolve();
|
|
8283
|
+
}
|
|
8284
|
+
|
|
8285
|
+
return this.idb.init()
|
|
8286
|
+
.then(function () {
|
|
8287
|
+
return this.idb.setItem(tabId, serializedRecording);
|
|
8288
|
+
}.bind(this))
|
|
8289
|
+
.catch(this.handleError.bind(this));
|
|
8290
|
+
};
|
|
8291
|
+
|
|
8292
|
+
/**
|
|
8293
|
+
* @returns {Promise<import('./session-recording').SerializedRecording>}
|
|
8294
|
+
*/
|
|
8295
|
+
RecordingRegistry.prototype.getActiveRecording = function () {
|
|
8296
|
+
return this.idb.init()
|
|
8297
|
+
.then(function () {
|
|
8298
|
+
return this.idb.getItem(this.mixpanelInstance.get_tab_id());
|
|
8299
|
+
}.bind(this))
|
|
8300
|
+
.then(function (serializedRecording) {
|
|
8301
|
+
return isRecordingExpired(serializedRecording) ? null : serializedRecording;
|
|
8302
|
+
}.bind(this))
|
|
8303
|
+
.catch(this.handleError.bind(this));
|
|
8304
|
+
};
|
|
8305
|
+
|
|
8306
|
+
RecordingRegistry.prototype.clearActiveRecording = function () {
|
|
8307
|
+
// mark recording as expired instead of deleting it in case the page unloads mid-flush and doesn't make it to ingestion.
|
|
8308
|
+
// this will ensure the next pageload will flush the remaining events, but not try to continue the recording.
|
|
8309
|
+
return this.getActiveRecording()
|
|
8310
|
+
.then(function (serializedRecording) {
|
|
8311
|
+
if (serializedRecording) {
|
|
8312
|
+
serializedRecording['maxExpires'] = 0;
|
|
8313
|
+
return this.setActiveRecording(serializedRecording);
|
|
8314
|
+
}
|
|
8315
|
+
}.bind(this))
|
|
8316
|
+
.catch(this.handleError.bind(this));
|
|
8317
|
+
};
|
|
8318
|
+
|
|
8319
|
+
/**
|
|
8320
|
+
* Flush any inactive recordings from the registry to minimize data loss.
|
|
8321
|
+
* The main idea here is that we can flush remaining rrweb events on the next page load if a tab is closed mid-batch.
|
|
8322
|
+
*/
|
|
8323
|
+
RecordingRegistry.prototype.flushInactiveRecordings = function () {
|
|
8324
|
+
return this.idb.init()
|
|
8325
|
+
.then(function() {
|
|
8326
|
+
return this.idb.getAll();
|
|
8327
|
+
}.bind(this))
|
|
8328
|
+
.then(function (serializedRecordings) {
|
|
8329
|
+
// clean up any expired recordings from the registry, non-expired ones may be active in other tabs
|
|
8330
|
+
var unloadPromises = serializedRecordings
|
|
8331
|
+
.filter(function (serializedRecording) {
|
|
8332
|
+
return isRecordingExpired(serializedRecording);
|
|
8333
|
+
})
|
|
8334
|
+
.map(function (serializedRecording) {
|
|
8335
|
+
var sessionRecording = SessionRecording.deserialize(serializedRecording, {
|
|
8336
|
+
mixpanelInstance: this.mixpanelInstance,
|
|
8337
|
+
sharedLockStorage: this.sharedLockStorage
|
|
8338
|
+
});
|
|
8339
|
+
return sessionRecording.unloadPersistedData()
|
|
8340
|
+
.then(function () {
|
|
8341
|
+
// expired recording was successfully flushed, we can clean it up from the registry
|
|
8342
|
+
return this.idb.removeItem(serializedRecording['tabId']);
|
|
8343
|
+
}.bind(this))
|
|
8344
|
+
.catch(this.handleError.bind(this));
|
|
8345
|
+
}.bind(this));
|
|
8346
|
+
|
|
8347
|
+
return PromisePolyfill.all(unloadPromises);
|
|
8348
|
+
}.bind(this))
|
|
8349
|
+
.catch(this.handleError.bind(this));
|
|
8350
|
+
};
|
|
8351
|
+
|
|
7948
8352
|
var logger = console_with_prefix('recorder');
|
|
7949
8353
|
|
|
7950
8354
|
/**
|
|
7951
|
-
* Recorder API:
|
|
8355
|
+
* Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
|
|
7952
8356
|
* @param {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
7953
|
-
|
|
7954
|
-
var MixpanelRecorder = function(mixpanelInstance) {
|
|
7955
|
-
this.
|
|
8357
|
+
*/
|
|
8358
|
+
var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage) {
|
|
8359
|
+
this.mixpanelInstance = mixpanelInstance;
|
|
8360
|
+
this.rrwebRecord = rrwebRecord || record;
|
|
8361
|
+
this.sharedLockStorage = sharedLockStorage;
|
|
8362
|
+
|
|
8363
|
+
/**
|
|
8364
|
+
* @member {import('./registry').RecordingRegistry}
|
|
8365
|
+
*/
|
|
8366
|
+
this.recordingRegistry = new RecordingRegistry({
|
|
8367
|
+
mixpanelInstance: this.mixpanelInstance,
|
|
8368
|
+
errorReporter: logger.error,
|
|
8369
|
+
sharedLockStorage: sharedLockStorage
|
|
8370
|
+
});
|
|
8371
|
+
this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
|
|
8372
|
+
|
|
7956
8373
|
this.activeRecording = null;
|
|
7957
8374
|
};
|
|
7958
8375
|
|
|
7959
|
-
MixpanelRecorder.prototype.startRecording = function(
|
|
8376
|
+
MixpanelRecorder.prototype.startRecording = function(options) {
|
|
8377
|
+
options = options || {};
|
|
7960
8378
|
if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
|
|
7961
8379
|
logger.log('Recording already in progress, skipping startRecording.');
|
|
7962
8380
|
return;
|
|
7963
8381
|
}
|
|
7964
8382
|
|
|
7965
|
-
var onIdleTimeout =
|
|
8383
|
+
var onIdleTimeout = function () {
|
|
7966
8384
|
logger.log('Idle timeout reached, restarting recording.');
|
|
7967
8385
|
this.resetRecording();
|
|
7968
|
-
}
|
|
8386
|
+
}.bind(this);
|
|
7969
8387
|
|
|
7970
|
-
var onMaxLengthReached =
|
|
8388
|
+
var onMaxLengthReached = function () {
|
|
7971
8389
|
logger.log('Max recording length reached, stopping recording.');
|
|
7972
8390
|
this.resetRecording();
|
|
7973
|
-
}
|
|
8391
|
+
}.bind(this);
|
|
8392
|
+
|
|
8393
|
+
var onBatchSent = function () {
|
|
8394
|
+
this.recordingRegistry.setActiveRecording(this.activeRecording.serialize());
|
|
8395
|
+
this['__flushPromise'] = this.activeRecording.batcher._flushPromise;
|
|
8396
|
+
}.bind(this);
|
|
7974
8397
|
|
|
7975
|
-
|
|
7976
|
-
|
|
8398
|
+
/**
|
|
8399
|
+
* @type {import('./session-recording').SessionRecordingOptions}
|
|
8400
|
+
*/
|
|
8401
|
+
var sessionRecordingOptions = {
|
|
8402
|
+
mixpanelInstance: this.mixpanelInstance,
|
|
8403
|
+
onBatchSent: onBatchSent,
|
|
7977
8404
|
onIdleTimeout: onIdleTimeout,
|
|
7978
8405
|
onMaxLengthReached: onMaxLengthReached,
|
|
7979
8406
|
replayId: _.UUID(),
|
|
7980
|
-
rrwebRecord:
|
|
7981
|
-
|
|
8407
|
+
rrwebRecord: this.rrwebRecord,
|
|
8408
|
+
sharedLockStorage: this.sharedLockStorage
|
|
8409
|
+
};
|
|
8410
|
+
|
|
8411
|
+
if (options.activeSerializedRecording) {
|
|
8412
|
+
this.activeRecording = SessionRecording.deserialize(options.activeSerializedRecording, sessionRecordingOptions);
|
|
8413
|
+
} else {
|
|
8414
|
+
this.activeRecording = new SessionRecording(sessionRecordingOptions);
|
|
8415
|
+
}
|
|
7982
8416
|
|
|
7983
|
-
this.activeRecording.startRecording(shouldStopBatcher);
|
|
8417
|
+
this.activeRecording.startRecording(options.shouldStopBatcher);
|
|
8418
|
+
return this.recordingRegistry.setActiveRecording(this.activeRecording.serialize());
|
|
7984
8419
|
};
|
|
7985
8420
|
|
|
7986
8421
|
MixpanelRecorder.prototype.stopRecording = function() {
|
|
8422
|
+
var stopPromise = this._stopCurrentRecording(false);
|
|
8423
|
+
this.recordingRegistry.clearActiveRecording();
|
|
8424
|
+
this.activeRecording = null;
|
|
8425
|
+
return stopPromise;
|
|
8426
|
+
};
|
|
8427
|
+
|
|
8428
|
+
MixpanelRecorder.prototype.pauseRecording = function() {
|
|
8429
|
+
return this._stopCurrentRecording(false);
|
|
8430
|
+
};
|
|
8431
|
+
|
|
8432
|
+
MixpanelRecorder.prototype._stopCurrentRecording = function(skipFlush) {
|
|
7987
8433
|
if (this.activeRecording) {
|
|
7988
|
-
this.activeRecording.stopRecording();
|
|
7989
|
-
this.activeRecording = null;
|
|
8434
|
+
return this.activeRecording.stopRecording(skipFlush);
|
|
7990
8435
|
}
|
|
8436
|
+
return PromisePolyfill.resolve();
|
|
7991
8437
|
};
|
|
7992
8438
|
|
|
8439
|
+
MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
|
|
8440
|
+
if (this.activeRecording && this.activeRecording.isRrwebStopped()) {
|
|
8441
|
+
this.activeRecording.startRecording(false);
|
|
8442
|
+
return PromisePolyfill.resolve(null);
|
|
8443
|
+
}
|
|
8444
|
+
|
|
8445
|
+
return this.recordingRegistry.getActiveRecording()
|
|
8446
|
+
.then(function (activeSerializedRecording) {
|
|
8447
|
+
if (activeSerializedRecording) {
|
|
8448
|
+
return this.startRecording({activeSerializedRecording: activeSerializedRecording});
|
|
8449
|
+
} else if (startNewIfInactive) {
|
|
8450
|
+
return this.startRecording({shouldStopBatcher: false});
|
|
8451
|
+
} else {
|
|
8452
|
+
logger.log('No resumable recording found.');
|
|
8453
|
+
return null;
|
|
8454
|
+
}
|
|
8455
|
+
}.bind(this));
|
|
8456
|
+
};
|
|
8457
|
+
|
|
8458
|
+
|
|
7993
8459
|
MixpanelRecorder.prototype.resetRecording = function () {
|
|
7994
8460
|
this.stopRecording();
|
|
7995
|
-
this.startRecording(true);
|
|
8461
|
+
this.startRecording({shouldStopBatcher: true});
|
|
7996
8462
|
};
|
|
7997
8463
|
|
|
7998
8464
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|