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