mixpanel-browser 2.56.0 → 2.58.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/CHANGELOG.md +10 -0
- package/LICENSE +1 -15
- package/build.sh +3 -3
- package/dist/mixpanel-core.cjs.js +899 -410
- package/dist/mixpanel-recorder.js +909 -410
- package/dist/mixpanel-recorder.min.js +10 -10
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +899 -410
- package/dist/mixpanel.amd.js +911 -411
- package/dist/mixpanel.cjs.js +911 -411
- package/dist/mixpanel.globals.js +899 -410
- package/dist/mixpanel.min.js +122 -112
- package/dist/mixpanel.module.js +911 -411
- package/dist/mixpanel.umd.js +911 -411
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/externs.js +14 -0
- package/src/gdpr-utils.js +2 -1
- package/src/mixpanel-core.js +4 -2
- package/src/promise-polyfill.js +379 -0
- package/src/recorder/index.js +2 -1
- package/src/recorder/session-recording.js +14 -2
- package/src/request-batcher.js +185 -164
- package/src/request-queue.js +200 -147
- package/src/shared-lock.js +104 -98
- package/src/storage/local-storage.js +53 -0
- package/src/storage/wrapper.js +14 -0
- package/src/utils.js +16 -33
- package/src/window.js +20 -0
- package/dist/mixpanel.min.js.map +0 -8
package/dist/mixpanel.umd.js
CHANGED
|
@@ -4513,11 +4513,9 @@
|
|
|
4513
4513
|
|
|
4514
4514
|
var Config = {
|
|
4515
4515
|
DEBUG: false,
|
|
4516
|
-
LIB_VERSION: '2.
|
|
4516
|
+
LIB_VERSION: '2.58.0'
|
|
4517
4517
|
};
|
|
4518
4518
|
|
|
4519
|
-
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
4520
|
-
|
|
4521
4519
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
4522
4520
|
var win;
|
|
4523
4521
|
if (typeof(window) === 'undefined') {
|
|
@@ -4537,6 +4535,370 @@
|
|
|
4537
4535
|
win = window;
|
|
4538
4536
|
}
|
|
4539
4537
|
|
|
4538
|
+
var setImmediate = win['setImmediate'];
|
|
4539
|
+
var builtInProp, cycle, schedulingQueue,
|
|
4540
|
+
ToString = Object.prototype.toString,
|
|
4541
|
+
timer = (typeof setImmediate !== 'undefined') ?
|
|
4542
|
+
function timer(fn) { return setImmediate(fn); } :
|
|
4543
|
+
setTimeout;
|
|
4544
|
+
|
|
4545
|
+
// dammit, IE8.
|
|
4546
|
+
try {
|
|
4547
|
+
Object.defineProperty({},'x',{});
|
|
4548
|
+
builtInProp = function builtInProp(obj,name,val,config) {
|
|
4549
|
+
return Object.defineProperty(obj,name,{
|
|
4550
|
+
value: val,
|
|
4551
|
+
writable: true,
|
|
4552
|
+
configurable: config !== false
|
|
4553
|
+
});
|
|
4554
|
+
};
|
|
4555
|
+
}
|
|
4556
|
+
catch (err) {
|
|
4557
|
+
builtInProp = function builtInProp(obj,name,val) {
|
|
4558
|
+
obj[name] = val;
|
|
4559
|
+
return obj;
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4563
|
+
// Note: using a queue instead of array for efficiency
|
|
4564
|
+
schedulingQueue = (function Queue() {
|
|
4565
|
+
var first, last, item;
|
|
4566
|
+
|
|
4567
|
+
function Item(fn,self) {
|
|
4568
|
+
this.fn = fn;
|
|
4569
|
+
this.self = self;
|
|
4570
|
+
this.next = void 0;
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
return {
|
|
4574
|
+
add: function add(fn,self) {
|
|
4575
|
+
item = new Item(fn,self);
|
|
4576
|
+
if (last) {
|
|
4577
|
+
last.next = item;
|
|
4578
|
+
}
|
|
4579
|
+
else {
|
|
4580
|
+
first = item;
|
|
4581
|
+
}
|
|
4582
|
+
last = item;
|
|
4583
|
+
item = void 0;
|
|
4584
|
+
},
|
|
4585
|
+
drain: function drain() {
|
|
4586
|
+
var f = first;
|
|
4587
|
+
first = last = cycle = void 0;
|
|
4588
|
+
|
|
4589
|
+
while (f) {
|
|
4590
|
+
f.fn.call(f.self);
|
|
4591
|
+
f = f.next;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
};
|
|
4595
|
+
})();
|
|
4596
|
+
|
|
4597
|
+
function schedule(fn,self) {
|
|
4598
|
+
schedulingQueue.add(fn,self);
|
|
4599
|
+
if (!cycle) {
|
|
4600
|
+
cycle = timer(schedulingQueue.drain);
|
|
4601
|
+
}
|
|
4602
|
+
}
|
|
4603
|
+
|
|
4604
|
+
// promise duck typing
|
|
4605
|
+
function isThenable(o) {
|
|
4606
|
+
var _then, oType = typeof o;
|
|
4607
|
+
|
|
4608
|
+
if (o !== null && (oType === 'object' || oType === 'function')) {
|
|
4609
|
+
_then = o.then;
|
|
4610
|
+
}
|
|
4611
|
+
return typeof _then === 'function' ? _then : false;
|
|
4612
|
+
}
|
|
4613
|
+
|
|
4614
|
+
function notify() {
|
|
4615
|
+
for (var i=0; i<this.chain.length; i++) {
|
|
4616
|
+
notifyIsolated(
|
|
4617
|
+
this,
|
|
4618
|
+
(this.state === 1) ? this.chain[i].success : this.chain[i].failure,
|
|
4619
|
+
this.chain[i]
|
|
4620
|
+
);
|
|
4621
|
+
}
|
|
4622
|
+
this.chain.length = 0;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
// NOTE: This is a separate function to isolate
|
|
4626
|
+
// the `try..catch` so that other code can be
|
|
4627
|
+
// optimized better
|
|
4628
|
+
function notifyIsolated(self,cb,chain) {
|
|
4629
|
+
var ret, _then;
|
|
4630
|
+
try {
|
|
4631
|
+
if (cb === false) {
|
|
4632
|
+
chain.reject(self.msg);
|
|
4633
|
+
}
|
|
4634
|
+
else {
|
|
4635
|
+
if (cb === true) {
|
|
4636
|
+
ret = self.msg;
|
|
4637
|
+
}
|
|
4638
|
+
else {
|
|
4639
|
+
ret = cb.call(void 0,self.msg);
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
if (ret === chain.promise) {
|
|
4643
|
+
chain.reject(TypeError('Promise-chain cycle'));
|
|
4644
|
+
}
|
|
4645
|
+
// eslint-disable-next-line no-cond-assign
|
|
4646
|
+
else if (_then = isThenable(ret)) {
|
|
4647
|
+
_then.call(ret,chain.resolve,chain.reject);
|
|
4648
|
+
}
|
|
4649
|
+
else {
|
|
4650
|
+
chain.resolve(ret);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
catch (err) {
|
|
4655
|
+
chain.reject(err);
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
function resolve(msg) {
|
|
4660
|
+
var _then, self = this;
|
|
4661
|
+
|
|
4662
|
+
// already triggered?
|
|
4663
|
+
if (self.triggered) { return; }
|
|
4664
|
+
|
|
4665
|
+
self.triggered = true;
|
|
4666
|
+
|
|
4667
|
+
// unwrap
|
|
4668
|
+
if (self.def) {
|
|
4669
|
+
self = self.def;
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4672
|
+
try {
|
|
4673
|
+
// eslint-disable-next-line no-cond-assign
|
|
4674
|
+
if (_then = isThenable(msg)) {
|
|
4675
|
+
schedule(function(){
|
|
4676
|
+
var defWrapper = new MakeDefWrapper(self);
|
|
4677
|
+
try {
|
|
4678
|
+
_then.call(msg,
|
|
4679
|
+
function $resolve$(){ resolve.apply(defWrapper,arguments); },
|
|
4680
|
+
function $reject$(){ reject.apply(defWrapper,arguments); }
|
|
4681
|
+
);
|
|
4682
|
+
}
|
|
4683
|
+
catch (err) {
|
|
4684
|
+
reject.call(defWrapper,err);
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
else {
|
|
4689
|
+
self.msg = msg;
|
|
4690
|
+
self.state = 1;
|
|
4691
|
+
if (self.chain.length > 0) {
|
|
4692
|
+
schedule(notify,self);
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
catch (err) {
|
|
4697
|
+
reject.call(new MakeDefWrapper(self),err);
|
|
4698
|
+
}
|
|
4699
|
+
}
|
|
4700
|
+
|
|
4701
|
+
function reject(msg) {
|
|
4702
|
+
var self = this;
|
|
4703
|
+
|
|
4704
|
+
// already triggered?
|
|
4705
|
+
if (self.triggered) { return; }
|
|
4706
|
+
|
|
4707
|
+
self.triggered = true;
|
|
4708
|
+
|
|
4709
|
+
// unwrap
|
|
4710
|
+
if (self.def) {
|
|
4711
|
+
self = self.def;
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
self.msg = msg;
|
|
4715
|
+
self.state = 2;
|
|
4716
|
+
if (self.chain.length > 0) {
|
|
4717
|
+
schedule(notify,self);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
|
|
4721
|
+
function iteratePromises(Constructor,arr,resolver,rejecter) {
|
|
4722
|
+
for (var idx=0; idx<arr.length; idx++) {
|
|
4723
|
+
(function IIFE(idx){
|
|
4724
|
+
Constructor.resolve(arr[idx])
|
|
4725
|
+
.then(
|
|
4726
|
+
function $resolver$(msg){
|
|
4727
|
+
resolver(idx,msg);
|
|
4728
|
+
},
|
|
4729
|
+
rejecter
|
|
4730
|
+
);
|
|
4731
|
+
})(idx);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
function MakeDefWrapper(self) {
|
|
4736
|
+
this.def = self;
|
|
4737
|
+
this.triggered = false;
|
|
4738
|
+
}
|
|
4739
|
+
|
|
4740
|
+
function MakeDef(self) {
|
|
4741
|
+
this.promise = self;
|
|
4742
|
+
this.state = 0;
|
|
4743
|
+
this.triggered = false;
|
|
4744
|
+
this.chain = [];
|
|
4745
|
+
this.msg = void 0;
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4748
|
+
function NpoPromise(executor) {
|
|
4749
|
+
if (typeof executor !== 'function') {
|
|
4750
|
+
throw TypeError('Not a function');
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
if (this['__NPO__'] !== 0) {
|
|
4754
|
+
throw TypeError('Not a promise');
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
// instance shadowing the inherited "brand"
|
|
4758
|
+
// to signal an already "initialized" promise
|
|
4759
|
+
this['__NPO__'] = 1;
|
|
4760
|
+
|
|
4761
|
+
var def = new MakeDef(this);
|
|
4762
|
+
|
|
4763
|
+
this['then'] = function then(success,failure) {
|
|
4764
|
+
var o = {
|
|
4765
|
+
success: typeof success === 'function' ? success : true,
|
|
4766
|
+
failure: typeof failure === 'function' ? failure : false
|
|
4767
|
+
};
|
|
4768
|
+
// Note: `then(..)` itself can be borrowed to be used against
|
|
4769
|
+
// a different promise constructor for making the chained promise,
|
|
4770
|
+
// by substituting a different `this` binding.
|
|
4771
|
+
o.promise = new this.constructor(function extractChain(resolve,reject) {
|
|
4772
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
4773
|
+
throw TypeError('Not a function');
|
|
4774
|
+
}
|
|
4775
|
+
|
|
4776
|
+
o.resolve = resolve;
|
|
4777
|
+
o.reject = reject;
|
|
4778
|
+
});
|
|
4779
|
+
def.chain.push(o);
|
|
4780
|
+
|
|
4781
|
+
if (def.state !== 0) {
|
|
4782
|
+
schedule(notify,def);
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
return o.promise;
|
|
4786
|
+
};
|
|
4787
|
+
this['catch'] = function $catch$(failure) {
|
|
4788
|
+
return this.then(void 0,failure);
|
|
4789
|
+
};
|
|
4790
|
+
|
|
4791
|
+
try {
|
|
4792
|
+
executor.call(
|
|
4793
|
+
void 0,
|
|
4794
|
+
function publicResolve(msg){
|
|
4795
|
+
resolve.call(def,msg);
|
|
4796
|
+
},
|
|
4797
|
+
function publicReject(msg) {
|
|
4798
|
+
reject.call(def,msg);
|
|
4799
|
+
}
|
|
4800
|
+
);
|
|
4801
|
+
}
|
|
4802
|
+
catch (err) {
|
|
4803
|
+
reject.call(def,err);
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
var PromisePrototype = builtInProp({},'constructor',NpoPromise,
|
|
4808
|
+
/*configurable=*/false
|
|
4809
|
+
);
|
|
4810
|
+
|
|
4811
|
+
// Note: Android 4 cannot use `Object.defineProperty(..)` here
|
|
4812
|
+
NpoPromise.prototype = PromisePrototype;
|
|
4813
|
+
|
|
4814
|
+
// built-in "brand" to signal an "uninitialized" promise
|
|
4815
|
+
builtInProp(PromisePrototype,'__NPO__',0,
|
|
4816
|
+
/*configurable=*/false
|
|
4817
|
+
);
|
|
4818
|
+
|
|
4819
|
+
builtInProp(NpoPromise,'resolve',function Promise$resolve(msg) {
|
|
4820
|
+
var Constructor = this;
|
|
4821
|
+
|
|
4822
|
+
// spec mandated checks
|
|
4823
|
+
// note: best "isPromise" check that's practical for now
|
|
4824
|
+
if (msg && typeof msg === 'object' && msg['__NPO__'] === 1) {
|
|
4825
|
+
return msg;
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4828
|
+
return new Constructor(function executor(resolve,reject){
|
|
4829
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
4830
|
+
throw TypeError('Not a function');
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
resolve(msg);
|
|
4834
|
+
});
|
|
4835
|
+
});
|
|
4836
|
+
|
|
4837
|
+
builtInProp(NpoPromise,'reject',function Promise$reject(msg) {
|
|
4838
|
+
return new this(function executor(resolve,reject){
|
|
4839
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
4840
|
+
throw TypeError('Not a function');
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
reject(msg);
|
|
4844
|
+
});
|
|
4845
|
+
});
|
|
4846
|
+
|
|
4847
|
+
builtInProp(NpoPromise,'all',function Promise$all(arr) {
|
|
4848
|
+
var Constructor = this;
|
|
4849
|
+
|
|
4850
|
+
// spec mandated checks
|
|
4851
|
+
if (ToString.call(arr) !== '[object Array]') {
|
|
4852
|
+
return Constructor.reject(TypeError('Not an array'));
|
|
4853
|
+
}
|
|
4854
|
+
if (arr.length === 0) {
|
|
4855
|
+
return Constructor.resolve([]);
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
return new Constructor(function executor(resolve,reject){
|
|
4859
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
4860
|
+
throw TypeError('Not a function');
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4863
|
+
var len = arr.length, msgs = Array(len), count = 0;
|
|
4864
|
+
|
|
4865
|
+
iteratePromises(Constructor,arr,function resolver(idx,msg) {
|
|
4866
|
+
msgs[idx] = msg;
|
|
4867
|
+
if (++count === len) {
|
|
4868
|
+
resolve(msgs);
|
|
4869
|
+
}
|
|
4870
|
+
},reject);
|
|
4871
|
+
});
|
|
4872
|
+
});
|
|
4873
|
+
|
|
4874
|
+
builtInProp(NpoPromise,'race',function Promise$race(arr) {
|
|
4875
|
+
var Constructor = this;
|
|
4876
|
+
|
|
4877
|
+
// spec mandated checks
|
|
4878
|
+
if (ToString.call(arr) !== '[object Array]') {
|
|
4879
|
+
return Constructor.reject(TypeError('Not an array'));
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
return new Constructor(function executor(resolve,reject){
|
|
4883
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
4884
|
+
throw TypeError('Not a function');
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
iteratePromises(Constructor,arr,function resolver(idx,msg){
|
|
4888
|
+
resolve(msg);
|
|
4889
|
+
},reject);
|
|
4890
|
+
});
|
|
4891
|
+
});
|
|
4892
|
+
|
|
4893
|
+
var PromisePolyfill;
|
|
4894
|
+
if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1) {
|
|
4895
|
+
PromisePolyfill = Promise;
|
|
4896
|
+
} else {
|
|
4897
|
+
PromisePolyfill = NpoPromise;
|
|
4898
|
+
}
|
|
4899
|
+
|
|
4900
|
+
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
4901
|
+
|
|
4540
4902
|
// Maximum allowed session recording length
|
|
4541
4903
|
var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
4542
4904
|
// Maximum allowed value for minimum session recording length
|
|
@@ -5598,7 +5960,7 @@
|
|
|
5598
5960
|
|
|
5599
5961
|
var supported = true;
|
|
5600
5962
|
try {
|
|
5601
|
-
storage = storage ||
|
|
5963
|
+
storage = storage || win.localStorage;
|
|
5602
5964
|
var key = '__mplss_' + cheap_guid(8),
|
|
5603
5965
|
val = 'xyz';
|
|
5604
5966
|
storage.setItem(key, val);
|
|
@@ -5630,7 +5992,7 @@
|
|
|
5630
5992
|
|
|
5631
5993
|
get: function(name) {
|
|
5632
5994
|
try {
|
|
5633
|
-
return
|
|
5995
|
+
return win.localStorage.getItem(name);
|
|
5634
5996
|
} catch (err) {
|
|
5635
5997
|
_.localStorage.error(err);
|
|
5636
5998
|
}
|
|
@@ -5648,7 +6010,7 @@
|
|
|
5648
6010
|
|
|
5649
6011
|
set: function(name, value) {
|
|
5650
6012
|
try {
|
|
5651
|
-
|
|
6013
|
+
win.localStorage.setItem(name, value);
|
|
5652
6014
|
} catch (err) {
|
|
5653
6015
|
_.localStorage.error(err);
|
|
5654
6016
|
}
|
|
@@ -5656,7 +6018,7 @@
|
|
|
5656
6018
|
|
|
5657
6019
|
remove: function(name) {
|
|
5658
6020
|
try {
|
|
5659
|
-
|
|
6021
|
+
win.localStorage.removeItem(name);
|
|
5660
6022
|
} catch (err) {
|
|
5661
6023
|
_.localStorage.error(err);
|
|
5662
6024
|
}
|
|
@@ -5695,7 +6057,7 @@
|
|
|
5695
6057
|
|
|
5696
6058
|
function makeHandler(element, new_handler, old_handlers) {
|
|
5697
6059
|
var handler = function(event) {
|
|
5698
|
-
event = event || fixEvent(
|
|
6060
|
+
event = event || fixEvent(win.event);
|
|
5699
6061
|
|
|
5700
6062
|
// this basically happens in firefox whenever another script
|
|
5701
6063
|
// overwrites the onload callback and doesn't pass the event
|
|
@@ -6250,6 +6612,7 @@
|
|
|
6250
6612
|
_['info']['browser'] = _.info.browser;
|
|
6251
6613
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
6252
6614
|
_['info']['properties'] = _.info.properties;
|
|
6615
|
+
_['NPO'] = NpoPromise;
|
|
6253
6616
|
|
|
6254
6617
|
/**
|
|
6255
6618
|
* GDPR utils
|
|
@@ -6579,121 +6942,175 @@
|
|
|
6579
6942
|
this.storage = options.storage || window.localStorage;
|
|
6580
6943
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
6581
6944
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
6945
|
+
|
|
6946
|
+
// dependency-inject promise implementation for testing purposes
|
|
6947
|
+
this.promiseImpl = options.promiseImpl || PromisePolyfill;
|
|
6582
6948
|
};
|
|
6583
6949
|
|
|
6584
6950
|
// pass in a specific pid to test contention scenarios; otherwise
|
|
6585
6951
|
// it is chosen randomly for each acquisition attempt
|
|
6586
|
-
SharedLock.prototype.withLock = function(lockedCB,
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
6593
|
-
var startTime = new Date().getTime();
|
|
6952
|
+
SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
6953
|
+
var Promise = this.promiseImpl;
|
|
6954
|
+
return new Promise(_.bind(function (resolve, reject) {
|
|
6955
|
+
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
6956
|
+
var startTime = new Date().getTime();
|
|
6594
6957
|
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6958
|
+
var key = this.storageKey;
|
|
6959
|
+
var pollIntervalMS = this.pollIntervalMS;
|
|
6960
|
+
var timeoutMS = this.timeoutMS;
|
|
6961
|
+
var storage = this.storage;
|
|
6599
6962
|
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6963
|
+
var keyX = key + ':X';
|
|
6964
|
+
var keyY = key + ':Y';
|
|
6965
|
+
var keyZ = key + ':Z';
|
|
6603
6966
|
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6967
|
+
var delay = function(cb) {
|
|
6968
|
+
if (new Date().getTime() - startTime > timeoutMS) {
|
|
6969
|
+
logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
6970
|
+
storage.removeItem(keyZ);
|
|
6971
|
+
storage.removeItem(keyY);
|
|
6972
|
+
loop();
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
setTimeout(function() {
|
|
6976
|
+
try {
|
|
6977
|
+
cb();
|
|
6978
|
+
} catch(err) {
|
|
6979
|
+
reject(err);
|
|
6980
|
+
}
|
|
6981
|
+
}, pollIntervalMS * (Math.random() + 0.1));
|
|
6982
|
+
};
|
|
6607
6983
|
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
6611
|
-
storage.removeItem(keyZ);
|
|
6612
|
-
storage.removeItem(keyY);
|
|
6613
|
-
loop();
|
|
6614
|
-
return;
|
|
6615
|
-
}
|
|
6616
|
-
setTimeout(function() {
|
|
6617
|
-
try {
|
|
6984
|
+
var waitFor = function(predicate, cb) {
|
|
6985
|
+
if (predicate()) {
|
|
6618
6986
|
cb();
|
|
6619
|
-
}
|
|
6620
|
-
|
|
6987
|
+
} else {
|
|
6988
|
+
delay(function() {
|
|
6989
|
+
waitFor(predicate, cb);
|
|
6990
|
+
});
|
|
6621
6991
|
}
|
|
6622
|
-
}
|
|
6623
|
-
};
|
|
6624
|
-
|
|
6625
|
-
var waitFor = function(predicate, cb) {
|
|
6626
|
-
if (predicate()) {
|
|
6627
|
-
cb();
|
|
6628
|
-
} else {
|
|
6629
|
-
delay(function() {
|
|
6630
|
-
waitFor(predicate, cb);
|
|
6631
|
-
});
|
|
6632
|
-
}
|
|
6633
|
-
};
|
|
6992
|
+
};
|
|
6634
6993
|
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
} else {
|
|
6640
|
-
storage.setItem(keyY, i);
|
|
6641
|
-
if (storage.getItem(keyY) === i) {
|
|
6642
|
-
return true;
|
|
6994
|
+
var getSetY = function() {
|
|
6995
|
+
var valY = storage.getItem(keyY);
|
|
6996
|
+
if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases)
|
|
6997
|
+
return false;
|
|
6643
6998
|
} else {
|
|
6644
|
-
|
|
6645
|
-
|
|
6999
|
+
storage.setItem(keyY, i);
|
|
7000
|
+
if (storage.getItem(keyY) === i) {
|
|
7001
|
+
return true;
|
|
7002
|
+
} else {
|
|
7003
|
+
if (!localStorageSupported(storage, true)) {
|
|
7004
|
+
reject(new Error('localStorage support dropped while acquiring lock'));
|
|
7005
|
+
}
|
|
7006
|
+
return false;
|
|
6646
7007
|
}
|
|
6647
|
-
return false;
|
|
6648
7008
|
}
|
|
6649
|
-
}
|
|
6650
|
-
};
|
|
7009
|
+
};
|
|
6651
7010
|
|
|
6652
|
-
|
|
6653
|
-
|
|
7011
|
+
var loop = function() {
|
|
7012
|
+
storage.setItem(keyX, i);
|
|
6654
7013
|
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
return;
|
|
6659
|
-
}
|
|
6660
|
-
|
|
6661
|
-
delay(function() {
|
|
6662
|
-
if (storage.getItem(keyY) !== i) {
|
|
6663
|
-
loop();
|
|
7014
|
+
waitFor(getSetY, function() {
|
|
7015
|
+
if (storage.getItem(keyX) === i) {
|
|
7016
|
+
criticalSection();
|
|
6664
7017
|
return;
|
|
6665
7018
|
}
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
7019
|
+
|
|
7020
|
+
delay(function() {
|
|
7021
|
+
if (storage.getItem(keyY) !== i) {
|
|
7022
|
+
loop();
|
|
7023
|
+
return;
|
|
7024
|
+
}
|
|
7025
|
+
waitFor(function() {
|
|
7026
|
+
return !storage.getItem(keyZ);
|
|
7027
|
+
}, criticalSection);
|
|
7028
|
+
});
|
|
6669
7029
|
});
|
|
6670
|
-
}
|
|
6671
|
-
|
|
7030
|
+
};
|
|
7031
|
+
|
|
7032
|
+
var criticalSection = function() {
|
|
7033
|
+
storage.setItem(keyZ, '1');
|
|
7034
|
+
var removeLock = function () {
|
|
7035
|
+
storage.removeItem(keyZ);
|
|
7036
|
+
if (storage.getItem(keyY) === i) {
|
|
7037
|
+
storage.removeItem(keyY);
|
|
7038
|
+
}
|
|
7039
|
+
if (storage.getItem(keyX) === i) {
|
|
7040
|
+
storage.removeItem(keyX);
|
|
7041
|
+
}
|
|
7042
|
+
};
|
|
7043
|
+
|
|
7044
|
+
lockedCB()
|
|
7045
|
+
.then(function (ret) {
|
|
7046
|
+
removeLock();
|
|
7047
|
+
resolve(ret);
|
|
7048
|
+
})
|
|
7049
|
+
.catch(function (err) {
|
|
7050
|
+
removeLock();
|
|
7051
|
+
reject(err);
|
|
7052
|
+
});
|
|
7053
|
+
};
|
|
6672
7054
|
|
|
6673
|
-
var criticalSection = function() {
|
|
6674
|
-
storage.setItem(keyZ, '1');
|
|
6675
7055
|
try {
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
storage.removeItem(keyY);
|
|
6681
|
-
}
|
|
6682
|
-
if (storage.getItem(keyX) === i) {
|
|
6683
|
-
storage.removeItem(keyX);
|
|
7056
|
+
if (localStorageSupported(storage, true)) {
|
|
7057
|
+
loop();
|
|
7058
|
+
} else {
|
|
7059
|
+
throw new Error('localStorage support check failed');
|
|
6684
7060
|
}
|
|
7061
|
+
} catch(err) {
|
|
7062
|
+
reject(err);
|
|
6685
7063
|
}
|
|
6686
|
-
};
|
|
7064
|
+
}, this));
|
|
7065
|
+
};
|
|
6687
7066
|
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
7067
|
+
/**
|
|
7068
|
+
* @typedef {import('./wrapper').StorageWrapper}
|
|
7069
|
+
*/
|
|
7070
|
+
|
|
7071
|
+
/**
|
|
7072
|
+
* @type {StorageWrapper}
|
|
7073
|
+
*/
|
|
7074
|
+
var LocalStorageWrapper = function (storageOverride) {
|
|
7075
|
+
this.storage = storageOverride || localStorage;
|
|
7076
|
+
};
|
|
7077
|
+
|
|
7078
|
+
LocalStorageWrapper.prototype.init = function () {
|
|
7079
|
+
return PromisePolyfill.resolve();
|
|
7080
|
+
};
|
|
7081
|
+
|
|
7082
|
+
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
7083
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
7084
|
+
try {
|
|
7085
|
+
this.storage.setItem(key, value);
|
|
7086
|
+
} catch (e) {
|
|
7087
|
+
reject(e);
|
|
6693
7088
|
}
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
7089
|
+
resolve();
|
|
7090
|
+
}, this));
|
|
7091
|
+
};
|
|
7092
|
+
|
|
7093
|
+
LocalStorageWrapper.prototype.getItem = function (key) {
|
|
7094
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
7095
|
+
var item;
|
|
7096
|
+
try {
|
|
7097
|
+
item = this.storage.getItem(key);
|
|
7098
|
+
} catch (e) {
|
|
7099
|
+
reject(e);
|
|
7100
|
+
}
|
|
7101
|
+
resolve(item);
|
|
7102
|
+
}, this));
|
|
7103
|
+
};
|
|
7104
|
+
|
|
7105
|
+
LocalStorageWrapper.prototype.removeItem = function (key) {
|
|
7106
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
7107
|
+
try {
|
|
7108
|
+
this.storage.removeItem(key);
|
|
7109
|
+
} catch (e) {
|
|
7110
|
+
reject(e);
|
|
7111
|
+
}
|
|
7112
|
+
resolve();
|
|
7113
|
+
}, this));
|
|
6697
7114
|
};
|
|
6698
7115
|
|
|
6699
7116
|
var logger$3 = console_with_prefix('batch');
|
|
@@ -6714,19 +7131,38 @@
|
|
|
6714
7131
|
* to data loss in some situations).
|
|
6715
7132
|
* @constructor
|
|
6716
7133
|
*/
|
|
6717
|
-
var RequestQueue = function(storageKey, options) {
|
|
7134
|
+
var RequestQueue = function (storageKey, options) {
|
|
6718
7135
|
options = options || {};
|
|
6719
7136
|
this.storageKey = storageKey;
|
|
6720
7137
|
this.usePersistence = options.usePersistence;
|
|
6721
7138
|
if (this.usePersistence) {
|
|
6722
|
-
this.
|
|
6723
|
-
this.lock = new SharedLock(storageKey, {storage:
|
|
7139
|
+
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
7140
|
+
this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
|
|
7141
|
+
this.queueStorage.init();
|
|
6724
7142
|
}
|
|
6725
7143
|
this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
|
|
6726
7144
|
|
|
6727
7145
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
6728
7146
|
|
|
6729
7147
|
this.memQueue = [];
|
|
7148
|
+
this.initialized = false;
|
|
7149
|
+
};
|
|
7150
|
+
|
|
7151
|
+
RequestQueue.prototype.ensureInit = function () {
|
|
7152
|
+
if (this.initialized) {
|
|
7153
|
+
return PromisePolyfill.resolve();
|
|
7154
|
+
}
|
|
7155
|
+
|
|
7156
|
+
return this.queueStorage
|
|
7157
|
+
.init()
|
|
7158
|
+
.then(_.bind(function () {
|
|
7159
|
+
this.initialized = true;
|
|
7160
|
+
}, this))
|
|
7161
|
+
.catch(_.bind(function (err) {
|
|
7162
|
+
this.reportError('Error initializing queue persistence. Disabling persistence', err);
|
|
7163
|
+
this.initialized = true;
|
|
7164
|
+
this.usePersistence = false;
|
|
7165
|
+
}, this));
|
|
6730
7166
|
};
|
|
6731
7167
|
|
|
6732
7168
|
/**
|
|
@@ -6741,7 +7177,7 @@
|
|
|
6741
7177
|
* failure of the enqueue operation; it is asynchronous because the localStorage
|
|
6742
7178
|
* lock is asynchronous.
|
|
6743
7179
|
*/
|
|
6744
|
-
RequestQueue.prototype.enqueue = function(item, flushInterval
|
|
7180
|
+
RequestQueue.prototype.enqueue = function (item, flushInterval) {
|
|
6745
7181
|
var queueEntry = {
|
|
6746
7182
|
'id': cheap_guid(),
|
|
6747
7183
|
'flushAfter': new Date().getTime() + flushInterval * 2,
|
|
@@ -6750,33 +7186,37 @@
|
|
|
6750
7186
|
|
|
6751
7187
|
if (!this.usePersistence) {
|
|
6752
7188
|
this.memQueue.push(queueEntry);
|
|
6753
|
-
|
|
6754
|
-
cb(true);
|
|
6755
|
-
}
|
|
7189
|
+
return PromisePolyfill.resolve(true);
|
|
6756
7190
|
} else {
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
7191
|
+
|
|
7192
|
+
var enqueueItem = _.bind(function () {
|
|
7193
|
+
return this.ensureInit()
|
|
7194
|
+
.then(_.bind(function () {
|
|
7195
|
+
return this.readFromStorage();
|
|
7196
|
+
}, this))
|
|
7197
|
+
.then(_.bind(function (storedQueue) {
|
|
7198
|
+
storedQueue.push(queueEntry);
|
|
7199
|
+
return this.saveToStorage(storedQueue);
|
|
7200
|
+
}, this))
|
|
7201
|
+
.then(_.bind(function (succeeded) {
|
|
6764
7202
|
// only add to in-memory queue when storage succeeds
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
}, this)
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
7203
|
+
if (succeeded) {
|
|
7204
|
+
this.memQueue.push(queueEntry);
|
|
7205
|
+
}
|
|
7206
|
+
return succeeded;
|
|
7207
|
+
}, this))
|
|
7208
|
+
.catch(_.bind(function (err) {
|
|
7209
|
+
this.reportError('Error enqueueing item', err, item);
|
|
7210
|
+
return false;
|
|
7211
|
+
}, this));
|
|
7212
|
+
}, this);
|
|
7213
|
+
|
|
7214
|
+
return this.lock
|
|
7215
|
+
.withLock(enqueueItem, this.pid)
|
|
7216
|
+
.catch(_.bind(function (err) {
|
|
7217
|
+
this.reportError('Error acquiring storage lock', err);
|
|
7218
|
+
return false;
|
|
7219
|
+
}, this));
|
|
6780
7220
|
}
|
|
6781
7221
|
};
|
|
6782
7222
|
|
|
@@ -6786,31 +7226,41 @@
|
|
|
6786
7226
|
* in the persisted queue (items where the 'flushAfter' time has
|
|
6787
7227
|
* already passed).
|
|
6788
7228
|
*/
|
|
6789
|
-
RequestQueue.prototype.fillBatch = function(batchSize) {
|
|
7229
|
+
RequestQueue.prototype.fillBatch = function (batchSize) {
|
|
6790
7230
|
var batch = this.memQueue.slice(0, batchSize);
|
|
6791
7231
|
if (this.usePersistence && batch.length < batchSize) {
|
|
6792
7232
|
// don't need lock just to read events; localStorage is thread-safe
|
|
6793
7233
|
// and the worst that could happen is a duplicate send of some
|
|
6794
7234
|
// orphaned events, which will be deduplicated on the server side
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
_.
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
7235
|
+
return this.ensureInit()
|
|
7236
|
+
.then(_.bind(function () {
|
|
7237
|
+
return this.readFromStorage();
|
|
7238
|
+
}, this))
|
|
7239
|
+
.then(_.bind(function (storedQueue) {
|
|
7240
|
+
if (storedQueue.length) {
|
|
7241
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
7242
|
+
var idsInBatch = {}; // poor man's Set
|
|
7243
|
+
_.each(batch, function (item) {
|
|
7244
|
+
idsInBatch[item['id']] = true;
|
|
7245
|
+
});
|
|
7246
|
+
|
|
7247
|
+
for (var i = 0; i < storedQueue.length; i++) {
|
|
7248
|
+
var item = storedQueue[i];
|
|
7249
|
+
if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
|
|
7250
|
+
item.orphaned = true;
|
|
7251
|
+
batch.push(item);
|
|
7252
|
+
if (batch.length >= batchSize) {
|
|
7253
|
+
break;
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
6808
7256
|
}
|
|
6809
7257
|
}
|
|
6810
|
-
|
|
6811
|
-
|
|
7258
|
+
|
|
7259
|
+
return batch;
|
|
7260
|
+
}, this));
|
|
7261
|
+
} else {
|
|
7262
|
+
return PromisePolyfill.resolve(batch);
|
|
6812
7263
|
}
|
|
6813
|
-
return batch;
|
|
6814
7264
|
};
|
|
6815
7265
|
|
|
6816
7266
|
/**
|
|
@@ -6818,9 +7268,9 @@
|
|
|
6818
7268
|
* also remove any item without a valid id (e.g., malformed
|
|
6819
7269
|
* storage entries).
|
|
6820
7270
|
*/
|
|
6821
|
-
var filterOutIDsAndInvalid = function(items, idSet) {
|
|
7271
|
+
var filterOutIDsAndInvalid = function (items, idSet) {
|
|
6822
7272
|
var filteredItems = [];
|
|
6823
|
-
_.each(items, function(item) {
|
|
7273
|
+
_.each(items, function (item) {
|
|
6824
7274
|
if (item['id'] && !idSet[item['id']]) {
|
|
6825
7275
|
filteredItems.push(item);
|
|
6826
7276
|
}
|
|
@@ -6832,78 +7282,80 @@
|
|
|
6832
7282
|
* Remove items with matching IDs from both in-memory queue
|
|
6833
7283
|
* and persisted queue
|
|
6834
7284
|
*/
|
|
6835
|
-
RequestQueue.prototype.removeItemsByID = function(ids
|
|
7285
|
+
RequestQueue.prototype.removeItemsByID = function (ids) {
|
|
6836
7286
|
var idSet = {}; // poor man's Set
|
|
6837
|
-
_.each(ids, function(id) {
|
|
7287
|
+
_.each(ids, function (id) {
|
|
7288
|
+
idSet[id] = true;
|
|
7289
|
+
});
|
|
6838
7290
|
|
|
6839
7291
|
this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
|
|
6840
7292
|
if (!this.usePersistence) {
|
|
6841
|
-
|
|
6842
|
-
cb(true);
|
|
6843
|
-
}
|
|
7293
|
+
return PromisePolyfill.resolve(true);
|
|
6844
7294
|
} else {
|
|
6845
|
-
var removeFromStorage = _.bind(function() {
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
7295
|
+
var removeFromStorage = _.bind(function () {
|
|
7296
|
+
return this.ensureInit()
|
|
7297
|
+
.then(_.bind(function () {
|
|
7298
|
+
return this.readFromStorage();
|
|
7299
|
+
}, this))
|
|
7300
|
+
.then(_.bind(function (storedQueue) {
|
|
7301
|
+
storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
|
|
7302
|
+
return this.saveToStorage(storedQueue);
|
|
7303
|
+
}, this))
|
|
7304
|
+
.then(_.bind(function () {
|
|
7305
|
+
return this.readFromStorage();
|
|
7306
|
+
}, this))
|
|
7307
|
+
.then(_.bind(function (storedQueue) {
|
|
7308
|
+
// an extra check: did storage report success but somehow
|
|
7309
|
+
// the items are still there?
|
|
6856
7310
|
for (var i = 0; i < storedQueue.length; i++) {
|
|
6857
7311
|
var item = storedQueue[i];
|
|
6858
7312
|
if (item['id'] && !!idSet[item['id']]) {
|
|
6859
|
-
|
|
6860
|
-
return false;
|
|
7313
|
+
throw new Error('Item not removed from storage');
|
|
6861
7314
|
}
|
|
6862
7315
|
}
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
7316
|
+
return true;
|
|
7317
|
+
}, this))
|
|
7318
|
+
.catch(_.bind(function (err) {
|
|
7319
|
+
this.reportError('Error removing items', err, ids);
|
|
7320
|
+
return false;
|
|
7321
|
+
}, this));
|
|
6869
7322
|
}, this);
|
|
6870
7323
|
|
|
6871
|
-
this.lock
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
7324
|
+
return this.lock
|
|
7325
|
+
.withLock(removeFromStorage, this.pid)
|
|
7326
|
+
.catch(_.bind(function (err) {
|
|
7327
|
+
this.reportError('Error acquiring storage lock', err);
|
|
7328
|
+
if (!localStorageSupported(this.queueStorage.storage, true)) {
|
|
7329
|
+
// Looks like localStorage writes have stopped working sometime after
|
|
7330
|
+
// initialization (probably full), and so nobody can acquire locks
|
|
7331
|
+
// anymore. Consider it temporarily safe to remove items without the
|
|
7332
|
+
// lock, since nobody's writing successfully anyway.
|
|
7333
|
+
return removeFromStorage()
|
|
7334
|
+
.then(_.bind(function (success) {
|
|
7335
|
+
if (!success) {
|
|
7336
|
+
// OK, we couldn't even write out the smaller queue. Try clearing it
|
|
7337
|
+
// entirely.
|
|
7338
|
+
return this.queueStorage.removeItem(this.storageKey).then(function () {
|
|
7339
|
+
return success;
|
|
7340
|
+
});
|
|
7341
|
+
}
|
|
7342
|
+
return success;
|
|
7343
|
+
}, this))
|
|
7344
|
+
.catch(_.bind(function (err) {
|
|
7345
|
+
this.reportError('Error clearing queue', err);
|
|
7346
|
+
return false;
|
|
7347
|
+
}, this));
|
|
7348
|
+
} else {
|
|
7349
|
+
return false;
|
|
6893
7350
|
}
|
|
6894
|
-
}
|
|
6895
|
-
if (cb) {
|
|
6896
|
-
cb(succeeded);
|
|
6897
|
-
}
|
|
6898
|
-
}, this), this.pid);
|
|
7351
|
+
}, this));
|
|
6899
7352
|
}
|
|
6900
|
-
|
|
6901
7353
|
};
|
|
6902
7354
|
|
|
6903
7355
|
// internal helper for RequestQueue.updatePayloads
|
|
6904
|
-
var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
7356
|
+
var updatePayloads = function (existingItems, itemsToUpdate) {
|
|
6905
7357
|
var newItems = [];
|
|
6906
|
-
_.each(existingItems, function(item) {
|
|
7358
|
+
_.each(existingItems, function (item) {
|
|
6907
7359
|
var id = item['id'];
|
|
6908
7360
|
if (id in itemsToUpdate) {
|
|
6909
7361
|
var newPayload = itemsToUpdate[id];
|
|
@@ -6923,79 +7375,95 @@
|
|
|
6923
7375
|
* Update payloads of given items in both in-memory queue and
|
|
6924
7376
|
* persisted queue. Items set to null are removed from queues.
|
|
6925
7377
|
*/
|
|
6926
|
-
RequestQueue.prototype.updatePayloads = function(itemsToUpdate
|
|
7378
|
+
RequestQueue.prototype.updatePayloads = function (itemsToUpdate) {
|
|
6927
7379
|
this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
|
|
6928
7380
|
if (!this.usePersistence) {
|
|
6929
|
-
|
|
6930
|
-
cb(true);
|
|
6931
|
-
}
|
|
7381
|
+
return PromisePolyfill.resolve(true);
|
|
6932
7382
|
} else {
|
|
6933
|
-
this.lock
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
this
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
7383
|
+
return this.lock
|
|
7384
|
+
.withLock(_.bind(function lockAcquired() {
|
|
7385
|
+
return this.ensureInit()
|
|
7386
|
+
.then(_.bind(function () {
|
|
7387
|
+
return this.readFromStorage();
|
|
7388
|
+
}, this))
|
|
7389
|
+
.then(_.bind(function (storedQueue) {
|
|
7390
|
+
storedQueue = updatePayloads(storedQueue, itemsToUpdate);
|
|
7391
|
+
return this.saveToStorage(storedQueue);
|
|
7392
|
+
}, this))
|
|
7393
|
+
.catch(_.bind(function (err) {
|
|
7394
|
+
this.reportError('Error updating items', itemsToUpdate, err);
|
|
7395
|
+
return false;
|
|
7396
|
+
}, this));
|
|
7397
|
+
}, this), this.pid)
|
|
7398
|
+
.catch(_.bind(function (err) {
|
|
7399
|
+
this.reportError('Error acquiring storage lock', err);
|
|
7400
|
+
return false;
|
|
7401
|
+
}, this));
|
|
6952
7402
|
}
|
|
6953
|
-
|
|
6954
7403
|
};
|
|
6955
7404
|
|
|
6956
7405
|
/**
|
|
6957
7406
|
* Read and parse items array from localStorage entry, handling
|
|
6958
7407
|
* malformed/missing data if necessary.
|
|
6959
7408
|
*/
|
|
6960
|
-
RequestQueue.prototype.readFromStorage = function() {
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
if (
|
|
6967
|
-
|
|
6968
|
-
storageEntry
|
|
7409
|
+
RequestQueue.prototype.readFromStorage = function () {
|
|
7410
|
+
return this.ensureInit()
|
|
7411
|
+
.then(_.bind(function () {
|
|
7412
|
+
return this.queueStorage.getItem(this.storageKey);
|
|
7413
|
+
}, this))
|
|
7414
|
+
.then(_.bind(function (storageEntry) {
|
|
7415
|
+
if (storageEntry) {
|
|
7416
|
+
storageEntry = JSONParse(storageEntry);
|
|
7417
|
+
if (!_.isArray(storageEntry)) {
|
|
7418
|
+
this.reportError('Invalid storage entry:', storageEntry);
|
|
7419
|
+
storageEntry = null;
|
|
7420
|
+
}
|
|
6969
7421
|
}
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
7422
|
+
return storageEntry || [];
|
|
7423
|
+
}, this))
|
|
7424
|
+
.catch(_.bind(function (err) {
|
|
7425
|
+
this.reportError('Error retrieving queue', err);
|
|
7426
|
+
return [];
|
|
7427
|
+
}, this));
|
|
6976
7428
|
};
|
|
6977
7429
|
|
|
6978
7430
|
/**
|
|
6979
7431
|
* Serialize the given items array to localStorage.
|
|
6980
7432
|
*/
|
|
6981
|
-
RequestQueue.prototype.saveToStorage = function(queue) {
|
|
7433
|
+
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
6982
7434
|
try {
|
|
6983
|
-
|
|
6984
|
-
return true;
|
|
7435
|
+
var serialized = JSONStringify(queue);
|
|
6985
7436
|
} catch (err) {
|
|
6986
|
-
this.reportError('Error
|
|
6987
|
-
return false;
|
|
7437
|
+
this.reportError('Error serializing queue', err);
|
|
7438
|
+
return PromisePolyfill.resolve(false);
|
|
6988
7439
|
}
|
|
7440
|
+
|
|
7441
|
+
return this.ensureInit()
|
|
7442
|
+
.then(_.bind(function () {
|
|
7443
|
+
return this.queueStorage.setItem(this.storageKey, serialized);
|
|
7444
|
+
}, this))
|
|
7445
|
+
.then(function () {
|
|
7446
|
+
return true;
|
|
7447
|
+
})
|
|
7448
|
+
.catch(_.bind(function (err) {
|
|
7449
|
+
this.reportError('Error saving queue', err);
|
|
7450
|
+
return false;
|
|
7451
|
+
}, this));
|
|
6989
7452
|
};
|
|
6990
7453
|
|
|
6991
7454
|
/**
|
|
6992
7455
|
* Clear out queues (memory and localStorage).
|
|
6993
7456
|
*/
|
|
6994
|
-
RequestQueue.prototype.clear = function() {
|
|
7457
|
+
RequestQueue.prototype.clear = function () {
|
|
6995
7458
|
this.memQueue = [];
|
|
6996
7459
|
|
|
6997
7460
|
if (this.usePersistence) {
|
|
6998
|
-
this.
|
|
7461
|
+
return this.ensureInit()
|
|
7462
|
+
.then(_.bind(function () {
|
|
7463
|
+
return this.queueStorage.removeItem(this.storageKey);
|
|
7464
|
+
}, this));
|
|
7465
|
+
} else {
|
|
7466
|
+
return PromisePolyfill.resolve();
|
|
6999
7467
|
}
|
|
7000
7468
|
};
|
|
7001
7469
|
|
|
@@ -7014,7 +7482,8 @@
|
|
|
7014
7482
|
this.errorReporter = options.errorReporter;
|
|
7015
7483
|
this.queue = new RequestQueue(storageKey, {
|
|
7016
7484
|
errorReporter: _.bind(this.reportError, this),
|
|
7017
|
-
|
|
7485
|
+
queueStorage: options.queueStorage,
|
|
7486
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
7018
7487
|
usePersistence: options.usePersistence
|
|
7019
7488
|
});
|
|
7020
7489
|
|
|
@@ -7042,8 +7511,8 @@
|
|
|
7042
7511
|
/**
|
|
7043
7512
|
* Add one item to queue.
|
|
7044
7513
|
*/
|
|
7045
|
-
RequestBatcher.prototype.enqueue = function(item
|
|
7046
|
-
this.queue.enqueue(item, this.flushInterval
|
|
7514
|
+
RequestBatcher.prototype.enqueue = function(item) {
|
|
7515
|
+
return this.queue.enqueue(item, this.flushInterval);
|
|
7047
7516
|
};
|
|
7048
7517
|
|
|
7049
7518
|
/**
|
|
@@ -7053,7 +7522,7 @@
|
|
|
7053
7522
|
RequestBatcher.prototype.start = function() {
|
|
7054
7523
|
this.stopped = false;
|
|
7055
7524
|
this.consecutiveRemovalFailures = 0;
|
|
7056
|
-
this.flush();
|
|
7525
|
+
return this.flush();
|
|
7057
7526
|
};
|
|
7058
7527
|
|
|
7059
7528
|
/**
|
|
@@ -7071,7 +7540,7 @@
|
|
|
7071
7540
|
* Clear out queue.
|
|
7072
7541
|
*/
|
|
7073
7542
|
RequestBatcher.prototype.clear = function() {
|
|
7074
|
-
this.queue.clear();
|
|
7543
|
+
return this.queue.clear();
|
|
7075
7544
|
};
|
|
7076
7545
|
|
|
7077
7546
|
/**
|
|
@@ -7102,6 +7571,17 @@
|
|
|
7102
7571
|
}
|
|
7103
7572
|
};
|
|
7104
7573
|
|
|
7574
|
+
/**
|
|
7575
|
+
* Send a request using the sendRequest callback, but promisified.
|
|
7576
|
+
* TODO: sendRequest should be promisified in the first place.
|
|
7577
|
+
*/
|
|
7578
|
+
RequestBatcher.prototype.sendRequestPromise = function(data, options) {
|
|
7579
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
7580
|
+
this.sendRequest(data, options, resolve);
|
|
7581
|
+
}, this));
|
|
7582
|
+
};
|
|
7583
|
+
|
|
7584
|
+
|
|
7105
7585
|
/**
|
|
7106
7586
|
* Flush one batch to network. Depending on success/failure modes, it will either
|
|
7107
7587
|
* remove the batch from the queue or leave it in for retry, and schedule the next
|
|
@@ -7113,183 +7593,191 @@
|
|
|
7113
7593
|
* sendBeacon offers no callbacks or status indications)
|
|
7114
7594
|
*/
|
|
7115
7595
|
RequestBatcher.prototype.flush = function(options) {
|
|
7116
|
-
|
|
7596
|
+
if (this.requestInProgress) {
|
|
7597
|
+
logger$2.log('Flush: Request already in progress');
|
|
7598
|
+
return PromisePolyfill.resolve();
|
|
7599
|
+
}
|
|
7117
7600
|
|
|
7118
|
-
|
|
7119
|
-
logger$2.log('Flush: Request already in progress');
|
|
7120
|
-
return;
|
|
7121
|
-
}
|
|
7601
|
+
this.requestInProgress = true;
|
|
7122
7602
|
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
var
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
// sends each event (regardless of which version originally queued
|
|
7141
|
-
// it for sending)
|
|
7142
|
-
if (payload['event'] && payload['properties']) {
|
|
7143
|
-
payload['properties'] = _.extend(
|
|
7144
|
-
{},
|
|
7145
|
-
payload['properties'],
|
|
7146
|
-
{'mp_sent_by_lib_version': Config.LIB_VERSION}
|
|
7147
|
-
);
|
|
7603
|
+
options = options || {};
|
|
7604
|
+
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
7605
|
+
var startTime = new Date().getTime();
|
|
7606
|
+
var currentBatchSize = this.batchSize;
|
|
7607
|
+
|
|
7608
|
+
return this.queue.fillBatch(currentBatchSize)
|
|
7609
|
+
.then(_.bind(function(batch) {
|
|
7610
|
+
|
|
7611
|
+
// if there's more items in the queue than the batch size, attempt
|
|
7612
|
+
// to flush again after the current batch is done.
|
|
7613
|
+
var attemptSecondaryFlush = batch.length === currentBatchSize;
|
|
7614
|
+
var dataForRequest = [];
|
|
7615
|
+
var transformedItems = {};
|
|
7616
|
+
_.each(batch, function(item) {
|
|
7617
|
+
var payload = item['payload'];
|
|
7618
|
+
if (this.beforeSendHook && !item.orphaned) {
|
|
7619
|
+
payload = this.beforeSendHook(payload);
|
|
7148
7620
|
}
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7621
|
+
if (payload) {
|
|
7622
|
+
// mp_sent_by_lib_version prop captures which lib version actually
|
|
7623
|
+
// sends each event (regardless of which version originally queued
|
|
7624
|
+
// it for sending)
|
|
7625
|
+
if (payload['event'] && payload['properties']) {
|
|
7626
|
+
payload['properties'] = _.extend(
|
|
7627
|
+
{},
|
|
7628
|
+
payload['properties'],
|
|
7629
|
+
{'mp_sent_by_lib_version': Config.LIB_VERSION}
|
|
7630
|
+
);
|
|
7631
|
+
}
|
|
7632
|
+
var addPayload = true;
|
|
7633
|
+
var itemId = item['id'];
|
|
7634
|
+
if (itemId) {
|
|
7635
|
+
if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) {
|
|
7636
|
+
this.reportError('[dupe] item ID sent too many times, not sending', {
|
|
7637
|
+
item: item,
|
|
7638
|
+
batchSize: batch.length,
|
|
7639
|
+
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
7640
|
+
});
|
|
7641
|
+
addPayload = false;
|
|
7642
|
+
}
|
|
7643
|
+
} else {
|
|
7644
|
+
this.reportError('[dupe] found item with no ID', {item: item});
|
|
7159
7645
|
}
|
|
7160
|
-
} else {
|
|
7161
|
-
this.reportError('[dupe] found item with no ID', {item: item});
|
|
7162
|
-
}
|
|
7163
7646
|
|
|
7164
|
-
|
|
7165
|
-
|
|
7647
|
+
if (addPayload) {
|
|
7648
|
+
dataForRequest.push(payload);
|
|
7649
|
+
}
|
|
7166
7650
|
}
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
}, this);
|
|
7170
|
-
if (dataForRequest.length < 1) {
|
|
7171
|
-
this.resetFlush();
|
|
7172
|
-
return; // nothing to do
|
|
7173
|
-
}
|
|
7174
|
-
|
|
7175
|
-
this.requestInProgress = true;
|
|
7176
|
-
|
|
7177
|
-
var batchSendCallback = _.bind(function(res) {
|
|
7178
|
-
this.requestInProgress = false;
|
|
7651
|
+
transformedItems[item['id']] = payload;
|
|
7652
|
+
}, this);
|
|
7179
7653
|
|
|
7180
|
-
|
|
7654
|
+
if (dataForRequest.length < 1) {
|
|
7655
|
+
this.requestInProgress = false;
|
|
7656
|
+
this.resetFlush();
|
|
7657
|
+
return PromisePolyfill.resolve(); // nothing to do
|
|
7658
|
+
}
|
|
7181
7659
|
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
this.queue.updatePayloads(transformedItems);
|
|
7189
|
-
} else if (
|
|
7190
|
-
_.isObject(res) &&
|
|
7191
|
-
res.error === 'timeout' &&
|
|
7192
|
-
new Date().getTime() - startTime >= timeoutMS
|
|
7193
|
-
) {
|
|
7194
|
-
this.reportError('Network timeout; retrying');
|
|
7195
|
-
this.flush();
|
|
7196
|
-
} else if (
|
|
7197
|
-
_.isObject(res) &&
|
|
7198
|
-
(
|
|
7199
|
-
res.httpStatusCode >= 500
|
|
7200
|
-
|| res.httpStatusCode === 429
|
|
7201
|
-
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
7202
|
-
|| res.error === 'timeout'
|
|
7660
|
+
var removeItemsFromQueue = _.bind(function () {
|
|
7661
|
+
return this.queue
|
|
7662
|
+
.removeItemsByID(
|
|
7663
|
+
_.map(batch, function (item) {
|
|
7664
|
+
return item['id'];
|
|
7665
|
+
})
|
|
7203
7666
|
)
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
|
|
7218
|
-
this.reportError('413 response; reducing batch size to ' + this.batchSize);
|
|
7219
|
-
this.resetFlush();
|
|
7220
|
-
} else {
|
|
7221
|
-
this.reportError('Single-event request too large; dropping', batch);
|
|
7222
|
-
this.resetBatchSize();
|
|
7223
|
-
removeItemsFromQueue = true;
|
|
7224
|
-
}
|
|
7225
|
-
} else {
|
|
7226
|
-
// successful network request+response; remove each item in batch from queue
|
|
7227
|
-
// (even if it was e.g. a 400, in which case retrying won't help)
|
|
7228
|
-
removeItemsFromQueue = true;
|
|
7229
|
-
}
|
|
7230
|
-
|
|
7231
|
-
if (removeItemsFromQueue) {
|
|
7232
|
-
this.queue.removeItemsByID(
|
|
7233
|
-
_.map(batch, function(item) { return item['id']; }),
|
|
7234
|
-
_.bind(function(succeeded) {
|
|
7235
|
-
if (succeeded) {
|
|
7236
|
-
this.consecutiveRemovalFailures = 0;
|
|
7237
|
-
if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
|
|
7238
|
-
this.resetFlush(); // schedule next batch with a delay
|
|
7239
|
-
} else {
|
|
7240
|
-
this.flush(); // handle next batch if the queue isn't empty
|
|
7667
|
+
.then(_.bind(function (succeeded) {
|
|
7668
|
+
// client-side dedupe
|
|
7669
|
+
_.each(batch, _.bind(function(item) {
|
|
7670
|
+
var itemId = item['id'];
|
|
7671
|
+
if (itemId) {
|
|
7672
|
+
this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0;
|
|
7673
|
+
this.itemIdsSentSuccessfully[itemId]++;
|
|
7674
|
+
if (this.itemIdsSentSuccessfully[itemId] > 5) {
|
|
7675
|
+
this.reportError('[dupe] item ID sent too many times', {
|
|
7676
|
+
item: item,
|
|
7677
|
+
batchSize: batch.length,
|
|
7678
|
+
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
7679
|
+
});
|
|
7241
7680
|
}
|
|
7242
7681
|
} else {
|
|
7243
|
-
this.reportError('
|
|
7244
|
-
if (++this.consecutiveRemovalFailures > 5) {
|
|
7245
|
-
this.reportError('Too many queue failures; disabling batching system.');
|
|
7246
|
-
this.stopAllBatching();
|
|
7247
|
-
} else {
|
|
7248
|
-
this.resetFlush();
|
|
7249
|
-
}
|
|
7682
|
+
this.reportError('[dupe] found item with no ID while removing', {item: item});
|
|
7250
7683
|
}
|
|
7251
|
-
}, this)
|
|
7252
|
-
);
|
|
7684
|
+
}, this));
|
|
7253
7685
|
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
this.reportError('[dupe] item ID sent too many times', {
|
|
7262
|
-
item: item,
|
|
7263
|
-
batchSize: batch.length,
|
|
7264
|
-
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
7265
|
-
});
|
|
7686
|
+
if (succeeded) {
|
|
7687
|
+
this.consecutiveRemovalFailures = 0;
|
|
7688
|
+
if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
|
|
7689
|
+
this.resetFlush(); // schedule next batch with a delay
|
|
7690
|
+
return PromisePolyfill.resolve();
|
|
7691
|
+
} else {
|
|
7692
|
+
return this.flush(); // handle next batch if the queue isn't empty
|
|
7266
7693
|
}
|
|
7267
7694
|
} else {
|
|
7268
|
-
this.
|
|
7695
|
+
if (++this.consecutiveRemovalFailures > 5) {
|
|
7696
|
+
this.reportError('Too many queue failures; disabling batching system.');
|
|
7697
|
+
this.stopAllBatching();
|
|
7698
|
+
} else {
|
|
7699
|
+
this.resetFlush();
|
|
7700
|
+
}
|
|
7701
|
+
return PromisePolyfill.resolve();
|
|
7269
7702
|
}
|
|
7270
7703
|
}, this));
|
|
7271
|
-
|
|
7704
|
+
}, this);
|
|
7272
7705
|
|
|
7273
|
-
|
|
7274
|
-
this.
|
|
7275
|
-
|
|
7706
|
+
var batchSendCallback = _.bind(function(res) {
|
|
7707
|
+
this.requestInProgress = false;
|
|
7708
|
+
|
|
7709
|
+
try {
|
|
7710
|
+
|
|
7711
|
+
// handle API response in a try-catch to make sure we can reset the
|
|
7712
|
+
// flush operation if something goes wrong
|
|
7713
|
+
|
|
7714
|
+
if (options.unloading) {
|
|
7715
|
+
// update persisted data to include hook transformations
|
|
7716
|
+
return this.queue.updatePayloads(transformedItems);
|
|
7717
|
+
} else if (
|
|
7718
|
+
_.isObject(res) &&
|
|
7719
|
+
res.error === 'timeout' &&
|
|
7720
|
+
new Date().getTime() - startTime >= timeoutMS
|
|
7721
|
+
) {
|
|
7722
|
+
this.reportError('Network timeout; retrying');
|
|
7723
|
+
return this.flush();
|
|
7724
|
+
} else if (
|
|
7725
|
+
_.isObject(res) &&
|
|
7726
|
+
(
|
|
7727
|
+
res.httpStatusCode >= 500
|
|
7728
|
+
|| res.httpStatusCode === 429
|
|
7729
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
7730
|
+
|| res.error === 'timeout'
|
|
7731
|
+
)
|
|
7732
|
+
) {
|
|
7733
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
7734
|
+
var retryMS = this.flushInterval * 2;
|
|
7735
|
+
if (res.retryAfter) {
|
|
7736
|
+
retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
|
|
7737
|
+
}
|
|
7738
|
+
retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
|
|
7739
|
+
this.reportError('Error; retry in ' + retryMS + ' ms');
|
|
7740
|
+
this.scheduleFlush(retryMS);
|
|
7741
|
+
return PromisePolyfill.resolve();
|
|
7742
|
+
} else if (_.isObject(res) && res.httpStatusCode === 413) {
|
|
7743
|
+
// 413 Payload Too Large
|
|
7744
|
+
if (batch.length > 1) {
|
|
7745
|
+
var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
|
|
7746
|
+
this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
|
|
7747
|
+
this.reportError('413 response; reducing batch size to ' + this.batchSize);
|
|
7748
|
+
this.resetFlush();
|
|
7749
|
+
return PromisePolyfill.resolve();
|
|
7750
|
+
} else {
|
|
7751
|
+
this.reportError('Single-event request too large; dropping', batch);
|
|
7752
|
+
this.resetBatchSize();
|
|
7753
|
+
return removeItemsFromQueue();
|
|
7754
|
+
}
|
|
7755
|
+
} else {
|
|
7756
|
+
// successful network request+response; remove each item in batch from queue
|
|
7757
|
+
// (even if it was e.g. a 400, in which case retrying won't help)
|
|
7758
|
+
return removeItemsFromQueue();
|
|
7759
|
+
}
|
|
7760
|
+
} catch(err) {
|
|
7761
|
+
this.reportError('Error handling API response', err);
|
|
7762
|
+
this.resetFlush();
|
|
7763
|
+
}
|
|
7764
|
+
}, this);
|
|
7765
|
+
var requestOptions = {
|
|
7766
|
+
method: 'POST',
|
|
7767
|
+
verbose: true,
|
|
7768
|
+
ignore_json_errors: true, // eslint-disable-line camelcase
|
|
7769
|
+
timeout_ms: timeoutMS // eslint-disable-line camelcase
|
|
7770
|
+
};
|
|
7771
|
+
if (options.unloading) {
|
|
7772
|
+
requestOptions.transport = 'sendBeacon';
|
|
7276
7773
|
}
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
};
|
|
7284
|
-
if (options.unloading) {
|
|
7285
|
-
requestOptions.transport = 'sendBeacon';
|
|
7286
|
-
}
|
|
7287
|
-
logger$2.log('MIXPANEL REQUEST:', dataForRequest);
|
|
7288
|
-
this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
|
|
7289
|
-
} catch(err) {
|
|
7290
|
-
this.reportError('Error flushing request queue', err);
|
|
7291
|
-
this.resetFlush();
|
|
7292
|
-
}
|
|
7774
|
+
logger$2.log('MIXPANEL REQUEST:', dataForRequest);
|
|
7775
|
+
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
7776
|
+
}, this))
|
|
7777
|
+
.catch(_.bind(function(err) {
|
|
7778
|
+
this.reportError('Error flushing request queue', err);
|
|
7779
|
+
this.resetFlush();
|
|
7780
|
+
}, this));
|
|
7293
7781
|
};
|
|
7294
7782
|
|
|
7295
7783
|
/**
|
|
@@ -7356,6 +7844,7 @@
|
|
|
7356
7844
|
|
|
7357
7845
|
this.seqNo = 0;
|
|
7358
7846
|
this.replayStartTime = null;
|
|
7847
|
+
this.replayStartUrl = null;
|
|
7359
7848
|
this.batchStartUrl = null;
|
|
7360
7849
|
|
|
7361
7850
|
this.idleTimeoutId = null;
|
|
@@ -7407,6 +7896,7 @@
|
|
|
7407
7896
|
|
|
7408
7897
|
this.replayStartTime = new Date().getTime();
|
|
7409
7898
|
this.batchStartUrl = _.info.currentUrl();
|
|
7899
|
+
this.replayStartUrl = _.info.currentUrl();
|
|
7410
7900
|
|
|
7411
7901
|
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
7412
7902
|
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
@@ -7443,9 +7933,17 @@
|
|
|
7443
7933
|
'blockClass': this.getConfig('record_block_class'),
|
|
7444
7934
|
'blockSelector': blockSelector,
|
|
7445
7935
|
'collectFonts': this.getConfig('record_collect_fonts'),
|
|
7936
|
+
'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
|
|
7937
|
+
'type': 'image/webp',
|
|
7938
|
+
'quality': 0.6
|
|
7939
|
+
},
|
|
7446
7940
|
'maskAllInputs': true,
|
|
7447
7941
|
'maskTextClass': this.getConfig('record_mask_text_class'),
|
|
7448
|
-
'maskTextSelector': this.getConfig('record_mask_text_selector')
|
|
7942
|
+
'maskTextSelector': this.getConfig('record_mask_text_selector'),
|
|
7943
|
+
'recordCanvas': this.getConfig('record_canvas'),
|
|
7944
|
+
'sampling': {
|
|
7945
|
+
'canvas': 15
|
|
7946
|
+
}
|
|
7449
7947
|
});
|
|
7450
7948
|
|
|
7451
7949
|
if (typeof this._stopRecording !== 'function') {
|
|
@@ -7563,6 +8061,7 @@
|
|
|
7563
8061
|
'replay_id': replayId,
|
|
7564
8062
|
'replay_length_ms': replayLengthMs,
|
|
7565
8063
|
'replay_start_time': this.replayStartTime / 1000,
|
|
8064
|
+
'replay_start_url': this.replayStartUrl,
|
|
7566
8065
|
'seq': this.seqNo
|
|
7567
8066
|
};
|
|
7568
8067
|
var eventsJson = _.JSONEncode(data);
|
|
@@ -9135,6 +9634,7 @@
|
|
|
9135
9634
|
'hooks': {},
|
|
9136
9635
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
9137
9636
|
'record_block_selector': 'img, video',
|
|
9637
|
+
'record_canvas': false,
|
|
9138
9638
|
'record_collect_fonts': false,
|
|
9139
9639
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
9140
9640
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
@@ -9912,7 +10412,7 @@
|
|
|
9912
10412
|
}, this);
|
|
9913
10413
|
|
|
9914
10414
|
if (this._batch_requests && !should_send_immediately) {
|
|
9915
|
-
batcher.enqueue(truncated_data
|
|
10415
|
+
batcher.enqueue(truncated_data).then(function(succeeded) {
|
|
9916
10416
|
if (succeeded) {
|
|
9917
10417
|
callback(1, truncated_data);
|
|
9918
10418
|
} else {
|