mixpanel-browser 2.56.0 → 2.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4510,11 +4510,9 @@
4510
4510
 
4511
4511
  var Config = {
4512
4512
  DEBUG: false,
4513
- LIB_VERSION: '2.56.0'
4513
+ LIB_VERSION: '2.57.1'
4514
4514
  };
4515
4515
 
4516
- /* eslint camelcase: "off", eqeqeq: "off" */
4517
-
4518
4516
  // since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
4519
4517
  var win;
4520
4518
  if (typeof(window) === 'undefined') {
@@ -4534,6 +4532,370 @@
4534
4532
  win = window;
4535
4533
  }
4536
4534
 
4535
+ var setImmediate = win['setImmediate'];
4536
+ var builtInProp, cycle, schedulingQueue,
4537
+ ToString = Object.prototype.toString,
4538
+ timer = (typeof setImmediate !== 'undefined') ?
4539
+ function timer(fn) { return setImmediate(fn); } :
4540
+ setTimeout;
4541
+
4542
+ // dammit, IE8.
4543
+ try {
4544
+ Object.defineProperty({},'x',{});
4545
+ builtInProp = function builtInProp(obj,name,val,config) {
4546
+ return Object.defineProperty(obj,name,{
4547
+ value: val,
4548
+ writable: true,
4549
+ configurable: config !== false
4550
+ });
4551
+ };
4552
+ }
4553
+ catch (err) {
4554
+ builtInProp = function builtInProp(obj,name,val) {
4555
+ obj[name] = val;
4556
+ return obj;
4557
+ };
4558
+ }
4559
+
4560
+ // Note: using a queue instead of array for efficiency
4561
+ schedulingQueue = (function Queue() {
4562
+ var first, last, item;
4563
+
4564
+ function Item(fn,self) {
4565
+ this.fn = fn;
4566
+ this.self = self;
4567
+ this.next = void 0;
4568
+ }
4569
+
4570
+ return {
4571
+ add: function add(fn,self) {
4572
+ item = new Item(fn,self);
4573
+ if (last) {
4574
+ last.next = item;
4575
+ }
4576
+ else {
4577
+ first = item;
4578
+ }
4579
+ last = item;
4580
+ item = void 0;
4581
+ },
4582
+ drain: function drain() {
4583
+ var f = first;
4584
+ first = last = cycle = void 0;
4585
+
4586
+ while (f) {
4587
+ f.fn.call(f.self);
4588
+ f = f.next;
4589
+ }
4590
+ }
4591
+ };
4592
+ })();
4593
+
4594
+ function schedule(fn,self) {
4595
+ schedulingQueue.add(fn,self);
4596
+ if (!cycle) {
4597
+ cycle = timer(schedulingQueue.drain);
4598
+ }
4599
+ }
4600
+
4601
+ // promise duck typing
4602
+ function isThenable(o) {
4603
+ var _then, oType = typeof o;
4604
+
4605
+ if (o !== null && (oType === 'object' || oType === 'function')) {
4606
+ _then = o.then;
4607
+ }
4608
+ return typeof _then === 'function' ? _then : false;
4609
+ }
4610
+
4611
+ function notify() {
4612
+ for (var i=0; i<this.chain.length; i++) {
4613
+ notifyIsolated(
4614
+ this,
4615
+ (this.state === 1) ? this.chain[i].success : this.chain[i].failure,
4616
+ this.chain[i]
4617
+ );
4618
+ }
4619
+ this.chain.length = 0;
4620
+ }
4621
+
4622
+ // NOTE: This is a separate function to isolate
4623
+ // the `try..catch` so that other code can be
4624
+ // optimized better
4625
+ function notifyIsolated(self,cb,chain) {
4626
+ var ret, _then;
4627
+ try {
4628
+ if (cb === false) {
4629
+ chain.reject(self.msg);
4630
+ }
4631
+ else {
4632
+ if (cb === true) {
4633
+ ret = self.msg;
4634
+ }
4635
+ else {
4636
+ ret = cb.call(void 0,self.msg);
4637
+ }
4638
+
4639
+ if (ret === chain.promise) {
4640
+ chain.reject(TypeError('Promise-chain cycle'));
4641
+ }
4642
+ // eslint-disable-next-line no-cond-assign
4643
+ else if (_then = isThenable(ret)) {
4644
+ _then.call(ret,chain.resolve,chain.reject);
4645
+ }
4646
+ else {
4647
+ chain.resolve(ret);
4648
+ }
4649
+ }
4650
+ }
4651
+ catch (err) {
4652
+ chain.reject(err);
4653
+ }
4654
+ }
4655
+
4656
+ function resolve(msg) {
4657
+ var _then, self = this;
4658
+
4659
+ // already triggered?
4660
+ if (self.triggered) { return; }
4661
+
4662
+ self.triggered = true;
4663
+
4664
+ // unwrap
4665
+ if (self.def) {
4666
+ self = self.def;
4667
+ }
4668
+
4669
+ try {
4670
+ // eslint-disable-next-line no-cond-assign
4671
+ if (_then = isThenable(msg)) {
4672
+ schedule(function(){
4673
+ var defWrapper = new MakeDefWrapper(self);
4674
+ try {
4675
+ _then.call(msg,
4676
+ function $resolve$(){ resolve.apply(defWrapper,arguments); },
4677
+ function $reject$(){ reject.apply(defWrapper,arguments); }
4678
+ );
4679
+ }
4680
+ catch (err) {
4681
+ reject.call(defWrapper,err);
4682
+ }
4683
+ });
4684
+ }
4685
+ else {
4686
+ self.msg = msg;
4687
+ self.state = 1;
4688
+ if (self.chain.length > 0) {
4689
+ schedule(notify,self);
4690
+ }
4691
+ }
4692
+ }
4693
+ catch (err) {
4694
+ reject.call(new MakeDefWrapper(self),err);
4695
+ }
4696
+ }
4697
+
4698
+ function reject(msg) {
4699
+ var self = this;
4700
+
4701
+ // already triggered?
4702
+ if (self.triggered) { return; }
4703
+
4704
+ self.triggered = true;
4705
+
4706
+ // unwrap
4707
+ if (self.def) {
4708
+ self = self.def;
4709
+ }
4710
+
4711
+ self.msg = msg;
4712
+ self.state = 2;
4713
+ if (self.chain.length > 0) {
4714
+ schedule(notify,self);
4715
+ }
4716
+ }
4717
+
4718
+ function iteratePromises(Constructor,arr,resolver,rejecter) {
4719
+ for (var idx=0; idx<arr.length; idx++) {
4720
+ (function IIFE(idx){
4721
+ Constructor.resolve(arr[idx])
4722
+ .then(
4723
+ function $resolver$(msg){
4724
+ resolver(idx,msg);
4725
+ },
4726
+ rejecter
4727
+ );
4728
+ })(idx);
4729
+ }
4730
+ }
4731
+
4732
+ function MakeDefWrapper(self) {
4733
+ this.def = self;
4734
+ this.triggered = false;
4735
+ }
4736
+
4737
+ function MakeDef(self) {
4738
+ this.promise = self;
4739
+ this.state = 0;
4740
+ this.triggered = false;
4741
+ this.chain = [];
4742
+ this.msg = void 0;
4743
+ }
4744
+
4745
+ function NpoPromise(executor) {
4746
+ if (typeof executor !== 'function') {
4747
+ throw TypeError('Not a function');
4748
+ }
4749
+
4750
+ if (this['__NPO__'] !== 0) {
4751
+ throw TypeError('Not a promise');
4752
+ }
4753
+
4754
+ // instance shadowing the inherited "brand"
4755
+ // to signal an already "initialized" promise
4756
+ this['__NPO__'] = 1;
4757
+
4758
+ var def = new MakeDef(this);
4759
+
4760
+ this['then'] = function then(success,failure) {
4761
+ var o = {
4762
+ success: typeof success === 'function' ? success : true,
4763
+ failure: typeof failure === 'function' ? failure : false
4764
+ };
4765
+ // Note: `then(..)` itself can be borrowed to be used against
4766
+ // a different promise constructor for making the chained promise,
4767
+ // by substituting a different `this` binding.
4768
+ o.promise = new this.constructor(function extractChain(resolve,reject) {
4769
+ if (typeof resolve !== 'function' || typeof reject !== 'function') {
4770
+ throw TypeError('Not a function');
4771
+ }
4772
+
4773
+ o.resolve = resolve;
4774
+ o.reject = reject;
4775
+ });
4776
+ def.chain.push(o);
4777
+
4778
+ if (def.state !== 0) {
4779
+ schedule(notify,def);
4780
+ }
4781
+
4782
+ return o.promise;
4783
+ };
4784
+ this['catch'] = function $catch$(failure) {
4785
+ return this.then(void 0,failure);
4786
+ };
4787
+
4788
+ try {
4789
+ executor.call(
4790
+ void 0,
4791
+ function publicResolve(msg){
4792
+ resolve.call(def,msg);
4793
+ },
4794
+ function publicReject(msg) {
4795
+ reject.call(def,msg);
4796
+ }
4797
+ );
4798
+ }
4799
+ catch (err) {
4800
+ reject.call(def,err);
4801
+ }
4802
+ }
4803
+
4804
+ var PromisePrototype = builtInProp({},'constructor',NpoPromise,
4805
+ /*configurable=*/false
4806
+ );
4807
+
4808
+ // Note: Android 4 cannot use `Object.defineProperty(..)` here
4809
+ NpoPromise.prototype = PromisePrototype;
4810
+
4811
+ // built-in "brand" to signal an "uninitialized" promise
4812
+ builtInProp(PromisePrototype,'__NPO__',0,
4813
+ /*configurable=*/false
4814
+ );
4815
+
4816
+ builtInProp(NpoPromise,'resolve',function Promise$resolve(msg) {
4817
+ var Constructor = this;
4818
+
4819
+ // spec mandated checks
4820
+ // note: best "isPromise" check that's practical for now
4821
+ if (msg && typeof msg === 'object' && msg['__NPO__'] === 1) {
4822
+ return msg;
4823
+ }
4824
+
4825
+ return new Constructor(function executor(resolve,reject){
4826
+ if (typeof resolve !== 'function' || typeof reject !== 'function') {
4827
+ throw TypeError('Not a function');
4828
+ }
4829
+
4830
+ resolve(msg);
4831
+ });
4832
+ });
4833
+
4834
+ builtInProp(NpoPromise,'reject',function Promise$reject(msg) {
4835
+ return new this(function executor(resolve,reject){
4836
+ if (typeof resolve !== 'function' || typeof reject !== 'function') {
4837
+ throw TypeError('Not a function');
4838
+ }
4839
+
4840
+ reject(msg);
4841
+ });
4842
+ });
4843
+
4844
+ builtInProp(NpoPromise,'all',function Promise$all(arr) {
4845
+ var Constructor = this;
4846
+
4847
+ // spec mandated checks
4848
+ if (ToString.call(arr) !== '[object Array]') {
4849
+ return Constructor.reject(TypeError('Not an array'));
4850
+ }
4851
+ if (arr.length === 0) {
4852
+ return Constructor.resolve([]);
4853
+ }
4854
+
4855
+ return new Constructor(function executor(resolve,reject){
4856
+ if (typeof resolve !== 'function' || typeof reject !== 'function') {
4857
+ throw TypeError('Not a function');
4858
+ }
4859
+
4860
+ var len = arr.length, msgs = Array(len), count = 0;
4861
+
4862
+ iteratePromises(Constructor,arr,function resolver(idx,msg) {
4863
+ msgs[idx] = msg;
4864
+ if (++count === len) {
4865
+ resolve(msgs);
4866
+ }
4867
+ },reject);
4868
+ });
4869
+ });
4870
+
4871
+ builtInProp(NpoPromise,'race',function Promise$race(arr) {
4872
+ var Constructor = this;
4873
+
4874
+ // spec mandated checks
4875
+ if (ToString.call(arr) !== '[object Array]') {
4876
+ return Constructor.reject(TypeError('Not an array'));
4877
+ }
4878
+
4879
+ return new Constructor(function executor(resolve,reject){
4880
+ if (typeof resolve !== 'function' || typeof reject !== 'function') {
4881
+ throw TypeError('Not a function');
4882
+ }
4883
+
4884
+ iteratePromises(Constructor,arr,function resolver(idx,msg){
4885
+ resolve(msg);
4886
+ },reject);
4887
+ });
4888
+ });
4889
+
4890
+ var PromisePolyfill;
4891
+ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1) {
4892
+ PromisePolyfill = Promise;
4893
+ } else {
4894
+ PromisePolyfill = NpoPromise;
4895
+ }
4896
+
4897
+ /* eslint camelcase: "off", eqeqeq: "off" */
4898
+
4537
4899
  // Maximum allowed session recording length
4538
4900
  var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
4539
4901
  // Maximum allowed value for minimum session recording length
@@ -5566,7 +5928,7 @@
5566
5928
 
5567
5929
  var supported = true;
5568
5930
  try {
5569
- storage = storage || window.localStorage;
5931
+ storage = storage || win.localStorage;
5570
5932
  var key = '__mplss_' + cheap_guid(8),
5571
5933
  val = 'xyz';
5572
5934
  storage.setItem(key, val);
@@ -5598,7 +5960,7 @@
5598
5960
 
5599
5961
  get: function(name) {
5600
5962
  try {
5601
- return window.localStorage.getItem(name);
5963
+ return win.localStorage.getItem(name);
5602
5964
  } catch (err) {
5603
5965
  _.localStorage.error(err);
5604
5966
  }
@@ -5616,7 +5978,7 @@
5616
5978
 
5617
5979
  set: function(name, value) {
5618
5980
  try {
5619
- window.localStorage.setItem(name, value);
5981
+ win.localStorage.setItem(name, value);
5620
5982
  } catch (err) {
5621
5983
  _.localStorage.error(err);
5622
5984
  }
@@ -5624,7 +5986,7 @@
5624
5986
 
5625
5987
  remove: function(name) {
5626
5988
  try {
5627
- window.localStorage.removeItem(name);
5989
+ win.localStorage.removeItem(name);
5628
5990
  } catch (err) {
5629
5991
  _.localStorage.error(err);
5630
5992
  }
@@ -5663,7 +6025,7 @@
5663
6025
 
5664
6026
  function makeHandler(element, new_handler, old_handlers) {
5665
6027
  var handler = function(event) {
5666
- event = event || fixEvent(window.event);
6028
+ event = event || fixEvent(win.event);
5667
6029
 
5668
6030
  // this basically happens in firefox whenever another script
5669
6031
  // overwrites the onload callback and doesn't pass the event
@@ -6218,6 +6580,7 @@
6218
6580
  _['info']['browser'] = _.info.browser;
6219
6581
  _['info']['browserVersion'] = _.info.browserVersion;
6220
6582
  _['info']['properties'] = _.info.properties;
6583
+ _['NPO'] = NpoPromise;
6221
6584
 
6222
6585
  /**
6223
6586
  * GDPR utils
@@ -6415,121 +6778,175 @@
6415
6778
  this.storage = options.storage || window.localStorage;
6416
6779
  this.pollIntervalMS = options.pollIntervalMS || 100;
6417
6780
  this.timeoutMS = options.timeoutMS || 2000;
6781
+
6782
+ // dependency-inject promise implementation for testing purposes
6783
+ this.promiseImpl = options.promiseImpl || PromisePolyfill;
6418
6784
  };
6419
6785
 
6420
6786
  // pass in a specific pid to test contention scenarios; otherwise
6421
6787
  // it is chosen randomly for each acquisition attempt
6422
- SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) {
6423
- if (!pid && typeof errorCB !== 'function') {
6424
- pid = errorCB;
6425
- errorCB = null;
6426
- }
6427
-
6428
- var i = pid || (new Date().getTime() + '|' + Math.random());
6429
- var startTime = new Date().getTime();
6788
+ SharedLock.prototype.withLock = function(lockedCB, pid) {
6789
+ var Promise = this.promiseImpl;
6790
+ return new Promise(_.bind(function (resolve, reject) {
6791
+ var i = pid || (new Date().getTime() + '|' + Math.random());
6792
+ var startTime = new Date().getTime();
6430
6793
 
6431
- var key = this.storageKey;
6432
- var pollIntervalMS = this.pollIntervalMS;
6433
- var timeoutMS = this.timeoutMS;
6434
- var storage = this.storage;
6794
+ var key = this.storageKey;
6795
+ var pollIntervalMS = this.pollIntervalMS;
6796
+ var timeoutMS = this.timeoutMS;
6797
+ var storage = this.storage;
6435
6798
 
6436
- var keyX = key + ':X';
6437
- var keyY = key + ':Y';
6438
- var keyZ = key + ':Z';
6799
+ var keyX = key + ':X';
6800
+ var keyY = key + ':Y';
6801
+ var keyZ = key + ':Z';
6439
6802
 
6440
- var reportError = function(err) {
6441
- errorCB && errorCB(err);
6442
- };
6803
+ var delay = function(cb) {
6804
+ if (new Date().getTime() - startTime > timeoutMS) {
6805
+ logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6806
+ storage.removeItem(keyZ);
6807
+ storage.removeItem(keyY);
6808
+ loop();
6809
+ return;
6810
+ }
6811
+ setTimeout(function() {
6812
+ try {
6813
+ cb();
6814
+ } catch(err) {
6815
+ reject(err);
6816
+ }
6817
+ }, pollIntervalMS * (Math.random() + 0.1));
6818
+ };
6443
6819
 
6444
- var delay = function(cb) {
6445
- if (new Date().getTime() - startTime > timeoutMS) {
6446
- logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6447
- storage.removeItem(keyZ);
6448
- storage.removeItem(keyY);
6449
- loop();
6450
- return;
6451
- }
6452
- setTimeout(function() {
6453
- try {
6820
+ var waitFor = function(predicate, cb) {
6821
+ if (predicate()) {
6454
6822
  cb();
6455
- } catch(err) {
6456
- reportError(err);
6823
+ } else {
6824
+ delay(function() {
6825
+ waitFor(predicate, cb);
6826
+ });
6457
6827
  }
6458
- }, pollIntervalMS * (Math.random() + 0.1));
6459
- };
6460
-
6461
- var waitFor = function(predicate, cb) {
6462
- if (predicate()) {
6463
- cb();
6464
- } else {
6465
- delay(function() {
6466
- waitFor(predicate, cb);
6467
- });
6468
- }
6469
- };
6828
+ };
6470
6829
 
6471
- var getSetY = function() {
6472
- var valY = storage.getItem(keyY);
6473
- if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases)
6474
- return false;
6475
- } else {
6476
- storage.setItem(keyY, i);
6477
- if (storage.getItem(keyY) === i) {
6478
- return true;
6830
+ var getSetY = function() {
6831
+ var valY = storage.getItem(keyY);
6832
+ if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases)
6833
+ return false;
6479
6834
  } else {
6480
- if (!localStorageSupported(storage, true)) {
6481
- throw new Error('localStorage support dropped while acquiring lock');
6835
+ storage.setItem(keyY, i);
6836
+ if (storage.getItem(keyY) === i) {
6837
+ return true;
6838
+ } else {
6839
+ if (!localStorageSupported(storage, true)) {
6840
+ reject(new Error('localStorage support dropped while acquiring lock'));
6841
+ }
6842
+ return false;
6482
6843
  }
6483
- return false;
6484
6844
  }
6485
- }
6486
- };
6487
-
6488
- var loop = function() {
6489
- storage.setItem(keyX, i);
6845
+ };
6490
6846
 
6491
- waitFor(getSetY, function() {
6492
- if (storage.getItem(keyX) === i) {
6493
- criticalSection();
6494
- return;
6495
- }
6847
+ var loop = function() {
6848
+ storage.setItem(keyX, i);
6496
6849
 
6497
- delay(function() {
6498
- if (storage.getItem(keyY) !== i) {
6499
- loop();
6850
+ waitFor(getSetY, function() {
6851
+ if (storage.getItem(keyX) === i) {
6852
+ criticalSection();
6500
6853
  return;
6501
6854
  }
6502
- waitFor(function() {
6503
- return !storage.getItem(keyZ);
6504
- }, criticalSection);
6855
+
6856
+ delay(function() {
6857
+ if (storage.getItem(keyY) !== i) {
6858
+ loop();
6859
+ return;
6860
+ }
6861
+ waitFor(function() {
6862
+ return !storage.getItem(keyZ);
6863
+ }, criticalSection);
6864
+ });
6505
6865
  });
6506
- });
6507
- };
6866
+ };
6867
+
6868
+ var criticalSection = function() {
6869
+ storage.setItem(keyZ, '1');
6870
+ var removeLock = function () {
6871
+ storage.removeItem(keyZ);
6872
+ if (storage.getItem(keyY) === i) {
6873
+ storage.removeItem(keyY);
6874
+ }
6875
+ if (storage.getItem(keyX) === i) {
6876
+ storage.removeItem(keyX);
6877
+ }
6878
+ };
6879
+
6880
+ lockedCB()
6881
+ .then(function (ret) {
6882
+ removeLock();
6883
+ resolve(ret);
6884
+ })
6885
+ .catch(function (err) {
6886
+ removeLock();
6887
+ reject(err);
6888
+ });
6889
+ };
6508
6890
 
6509
- var criticalSection = function() {
6510
- storage.setItem(keyZ, '1');
6511
6891
  try {
6512
- lockedCB();
6513
- } finally {
6514
- storage.removeItem(keyZ);
6515
- if (storage.getItem(keyY) === i) {
6516
- storage.removeItem(keyY);
6517
- }
6518
- if (storage.getItem(keyX) === i) {
6519
- storage.removeItem(keyX);
6892
+ if (localStorageSupported(storage, true)) {
6893
+ loop();
6894
+ } else {
6895
+ throw new Error('localStorage support check failed');
6520
6896
  }
6897
+ } catch(err) {
6898
+ reject(err);
6521
6899
  }
6522
- };
6900
+ }, this));
6901
+ };
6523
6902
 
6524
- try {
6525
- if (localStorageSupported(storage, true)) {
6526
- loop();
6527
- } else {
6528
- throw new Error('localStorage support check failed');
6903
+ /**
6904
+ * @typedef {import('./wrapper').StorageWrapper}
6905
+ */
6906
+
6907
+ /**
6908
+ * @type {StorageWrapper}
6909
+ */
6910
+ var LocalStorageWrapper = function (storageOverride) {
6911
+ this.storage = storageOverride || localStorage;
6912
+ };
6913
+
6914
+ LocalStorageWrapper.prototype.init = function () {
6915
+ return PromisePolyfill.resolve();
6916
+ };
6917
+
6918
+ LocalStorageWrapper.prototype.setItem = function (key, value) {
6919
+ return new PromisePolyfill(_.bind(function (resolve, reject) {
6920
+ try {
6921
+ this.storage.setItem(key, value);
6922
+ } catch (e) {
6923
+ reject(e);
6529
6924
  }
6530
- } catch(err) {
6531
- reportError(err);
6532
- }
6925
+ resolve();
6926
+ }, this));
6927
+ };
6928
+
6929
+ LocalStorageWrapper.prototype.getItem = function (key) {
6930
+ return new PromisePolyfill(_.bind(function (resolve, reject) {
6931
+ var item;
6932
+ try {
6933
+ item = this.storage.getItem(key);
6934
+ } catch (e) {
6935
+ reject(e);
6936
+ }
6937
+ resolve(item);
6938
+ }, this));
6939
+ };
6940
+
6941
+ LocalStorageWrapper.prototype.removeItem = function (key) {
6942
+ return new PromisePolyfill(_.bind(function (resolve, reject) {
6943
+ try {
6944
+ this.storage.removeItem(key);
6945
+ } catch (e) {
6946
+ reject(e);
6947
+ }
6948
+ resolve();
6949
+ }, this));
6533
6950
  };
6534
6951
 
6535
6952
  var logger$3 = console_with_prefix('batch');
@@ -6550,19 +6967,38 @@
6550
6967
  * to data loss in some situations).
6551
6968
  * @constructor
6552
6969
  */
6553
- var RequestQueue = function(storageKey, options) {
6970
+ var RequestQueue = function (storageKey, options) {
6554
6971
  options = options || {};
6555
6972
  this.storageKey = storageKey;
6556
6973
  this.usePersistence = options.usePersistence;
6557
6974
  if (this.usePersistence) {
6558
- this.storage = options.storage || window.localStorage;
6559
- this.lock = new SharedLock(storageKey, {storage: this.storage});
6975
+ this.queueStorage = options.queueStorage || new LocalStorageWrapper();
6976
+ this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
6977
+ this.queueStorage.init();
6560
6978
  }
6561
6979
  this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
6562
6980
 
6563
6981
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
6564
6982
 
6565
6983
  this.memQueue = [];
6984
+ this.initialized = false;
6985
+ };
6986
+
6987
+ RequestQueue.prototype.ensureInit = function () {
6988
+ if (this.initialized) {
6989
+ return PromisePolyfill.resolve();
6990
+ }
6991
+
6992
+ return this.queueStorage
6993
+ .init()
6994
+ .then(_.bind(function () {
6995
+ this.initialized = true;
6996
+ }, this))
6997
+ .catch(_.bind(function (err) {
6998
+ this.reportError('Error initializing queue persistence. Disabling persistence', err);
6999
+ this.initialized = true;
7000
+ this.usePersistence = false;
7001
+ }, this));
6566
7002
  };
6567
7003
 
6568
7004
  /**
@@ -6577,7 +7013,7 @@
6577
7013
  * failure of the enqueue operation; it is asynchronous because the localStorage
6578
7014
  * lock is asynchronous.
6579
7015
  */
6580
- RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
7016
+ RequestQueue.prototype.enqueue = function (item, flushInterval) {
6581
7017
  var queueEntry = {
6582
7018
  'id': cheap_guid(),
6583
7019
  'flushAfter': new Date().getTime() + flushInterval * 2,
@@ -6586,33 +7022,37 @@
6586
7022
 
6587
7023
  if (!this.usePersistence) {
6588
7024
  this.memQueue.push(queueEntry);
6589
- if (cb) {
6590
- cb(true);
6591
- }
7025
+ return PromisePolyfill.resolve(true);
6592
7026
  } else {
6593
- this.lock.withLock(_.bind(function lockAcquired() {
6594
- var succeeded;
6595
- try {
6596
- var storedQueue = this.readFromStorage();
6597
- storedQueue.push(queueEntry);
6598
- succeeded = this.saveToStorage(storedQueue);
6599
- if (succeeded) {
7027
+
7028
+ var enqueueItem = _.bind(function () {
7029
+ return this.ensureInit()
7030
+ .then(_.bind(function () {
7031
+ return this.readFromStorage();
7032
+ }, this))
7033
+ .then(_.bind(function (storedQueue) {
7034
+ storedQueue.push(queueEntry);
7035
+ return this.saveToStorage(storedQueue);
7036
+ }, this))
7037
+ .then(_.bind(function (succeeded) {
6600
7038
  // only add to in-memory queue when storage succeeds
6601
- this.memQueue.push(queueEntry);
6602
- }
6603
- } catch(err) {
6604
- this.reportError('Error enqueueing item', item);
6605
- succeeded = false;
6606
- }
6607
- if (cb) {
6608
- cb(succeeded);
6609
- }
6610
- }, this), _.bind(function lockFailure(err) {
6611
- this.reportError('Error acquiring storage lock', err);
6612
- if (cb) {
6613
- cb(false);
6614
- }
6615
- }, this), this.pid);
7039
+ if (succeeded) {
7040
+ this.memQueue.push(queueEntry);
7041
+ }
7042
+ return succeeded;
7043
+ }, this))
7044
+ .catch(_.bind(function (err) {
7045
+ this.reportError('Error enqueueing item', err, item);
7046
+ return false;
7047
+ }, this));
7048
+ }, this);
7049
+
7050
+ return this.lock
7051
+ .withLock(enqueueItem, this.pid)
7052
+ .catch(_.bind(function (err) {
7053
+ this.reportError('Error acquiring storage lock', err);
7054
+ return false;
7055
+ }, this));
6616
7056
  }
6617
7057
  };
6618
7058
 
@@ -6622,31 +7062,41 @@
6622
7062
  * in the persisted queue (items where the 'flushAfter' time has
6623
7063
  * already passed).
6624
7064
  */
6625
- RequestQueue.prototype.fillBatch = function(batchSize) {
7065
+ RequestQueue.prototype.fillBatch = function (batchSize) {
6626
7066
  var batch = this.memQueue.slice(0, batchSize);
6627
7067
  if (this.usePersistence && batch.length < batchSize) {
6628
7068
  // don't need lock just to read events; localStorage is thread-safe
6629
7069
  // and the worst that could happen is a duplicate send of some
6630
7070
  // orphaned events, which will be deduplicated on the server side
6631
- var storedQueue = this.readFromStorage();
6632
- if (storedQueue.length) {
6633
- // item IDs already in batch; don't duplicate out of storage
6634
- var idsInBatch = {}; // poor man's Set
6635
- _.each(batch, function(item) { idsInBatch[item['id']] = true; });
6636
-
6637
- for (var i = 0; i < storedQueue.length; i++) {
6638
- var item = storedQueue[i];
6639
- if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
6640
- item.orphaned = true;
6641
- batch.push(item);
6642
- if (batch.length >= batchSize) {
6643
- break;
7071
+ return this.ensureInit()
7072
+ .then(_.bind(function () {
7073
+ return this.readFromStorage();
7074
+ }, this))
7075
+ .then(_.bind(function (storedQueue) {
7076
+ if (storedQueue.length) {
7077
+ // item IDs already in batch; don't duplicate out of storage
7078
+ var idsInBatch = {}; // poor man's Set
7079
+ _.each(batch, function (item) {
7080
+ idsInBatch[item['id']] = true;
7081
+ });
7082
+
7083
+ for (var i = 0; i < storedQueue.length; i++) {
7084
+ var item = storedQueue[i];
7085
+ if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
7086
+ item.orphaned = true;
7087
+ batch.push(item);
7088
+ if (batch.length >= batchSize) {
7089
+ break;
7090
+ }
7091
+ }
6644
7092
  }
6645
7093
  }
6646
- }
6647
- }
7094
+
7095
+ return batch;
7096
+ }, this));
7097
+ } else {
7098
+ return PromisePolyfill.resolve(batch);
6648
7099
  }
6649
- return batch;
6650
7100
  };
6651
7101
 
6652
7102
  /**
@@ -6654,9 +7104,9 @@
6654
7104
  * also remove any item without a valid id (e.g., malformed
6655
7105
  * storage entries).
6656
7106
  */
6657
- var filterOutIDsAndInvalid = function(items, idSet) {
7107
+ var filterOutIDsAndInvalid = function (items, idSet) {
6658
7108
  var filteredItems = [];
6659
- _.each(items, function(item) {
7109
+ _.each(items, function (item) {
6660
7110
  if (item['id'] && !idSet[item['id']]) {
6661
7111
  filteredItems.push(item);
6662
7112
  }
@@ -6668,78 +7118,80 @@
6668
7118
  * Remove items with matching IDs from both in-memory queue
6669
7119
  * and persisted queue
6670
7120
  */
6671
- RequestQueue.prototype.removeItemsByID = function(ids, cb) {
7121
+ RequestQueue.prototype.removeItemsByID = function (ids) {
6672
7122
  var idSet = {}; // poor man's Set
6673
- _.each(ids, function(id) { idSet[id] = true; });
7123
+ _.each(ids, function (id) {
7124
+ idSet[id] = true;
7125
+ });
6674
7126
 
6675
7127
  this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
6676
7128
  if (!this.usePersistence) {
6677
- if (cb) {
6678
- cb(true);
6679
- }
7129
+ return PromisePolyfill.resolve(true);
6680
7130
  } else {
6681
- var removeFromStorage = _.bind(function() {
6682
- var succeeded;
6683
- try {
6684
- var storedQueue = this.readFromStorage();
6685
- storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
6686
- succeeded = this.saveToStorage(storedQueue);
6687
-
6688
- // an extra check: did storage report success but somehow
6689
- // the items are still there?
6690
- if (succeeded) {
6691
- storedQueue = this.readFromStorage();
7131
+ var removeFromStorage = _.bind(function () {
7132
+ return this.ensureInit()
7133
+ .then(_.bind(function () {
7134
+ return this.readFromStorage();
7135
+ }, this))
7136
+ .then(_.bind(function (storedQueue) {
7137
+ storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
7138
+ return this.saveToStorage(storedQueue);
7139
+ }, this))
7140
+ .then(_.bind(function () {
7141
+ return this.readFromStorage();
7142
+ }, this))
7143
+ .then(_.bind(function (storedQueue) {
7144
+ // an extra check: did storage report success but somehow
7145
+ // the items are still there?
6692
7146
  for (var i = 0; i < storedQueue.length; i++) {
6693
7147
  var item = storedQueue[i];
6694
7148
  if (item['id'] && !!idSet[item['id']]) {
6695
- this.reportError('Item not removed from storage');
6696
- return false;
7149
+ throw new Error('Item not removed from storage');
6697
7150
  }
6698
7151
  }
6699
- }
6700
- } catch(err) {
6701
- this.reportError('Error removing items', ids);
6702
- succeeded = false;
6703
- }
6704
- return succeeded;
7152
+ return true;
7153
+ }, this))
7154
+ .catch(_.bind(function (err) {
7155
+ this.reportError('Error removing items', err, ids);
7156
+ return false;
7157
+ }, this));
6705
7158
  }, this);
6706
7159
 
6707
- this.lock.withLock(function lockAcquired() {
6708
- var succeeded = removeFromStorage();
6709
- if (cb) {
6710
- cb(succeeded);
6711
- }
6712
- }, _.bind(function lockFailure(err) {
6713
- var succeeded = false;
6714
- this.reportError('Error acquiring storage lock', err);
6715
- if (!localStorageSupported(this.storage, true)) {
6716
- // Looks like localStorage writes have stopped working sometime after
6717
- // initialization (probably full), and so nobody can acquire locks
6718
- // anymore. Consider it temporarily safe to remove items without the
6719
- // lock, since nobody's writing successfully anyway.
6720
- succeeded = removeFromStorage();
6721
- if (!succeeded) {
6722
- // OK, we couldn't even write out the smaller queue. Try clearing it
6723
- // entirely.
6724
- try {
6725
- this.storage.removeItem(this.storageKey);
6726
- } catch(err) {
6727
- this.reportError('Error clearing queue', err);
6728
- }
7160
+ return this.lock
7161
+ .withLock(removeFromStorage, this.pid)
7162
+ .catch(_.bind(function (err) {
7163
+ this.reportError('Error acquiring storage lock', err);
7164
+ if (!localStorageSupported(this.queueStorage.storage, true)) {
7165
+ // Looks like localStorage writes have stopped working sometime after
7166
+ // initialization (probably full), and so nobody can acquire locks
7167
+ // anymore. Consider it temporarily safe to remove items without the
7168
+ // lock, since nobody's writing successfully anyway.
7169
+ return removeFromStorage()
7170
+ .then(_.bind(function (success) {
7171
+ if (!success) {
7172
+ // OK, we couldn't even write out the smaller queue. Try clearing it
7173
+ // entirely.
7174
+ return this.queueStorage.removeItem(this.storageKey).then(function () {
7175
+ return success;
7176
+ });
7177
+ }
7178
+ return success;
7179
+ }, this))
7180
+ .catch(_.bind(function (err) {
7181
+ this.reportError('Error clearing queue', err);
7182
+ return false;
7183
+ }, this));
7184
+ } else {
7185
+ return false;
6729
7186
  }
6730
- }
6731
- if (cb) {
6732
- cb(succeeded);
6733
- }
6734
- }, this), this.pid);
7187
+ }, this));
6735
7188
  }
6736
-
6737
7189
  };
6738
7190
 
6739
7191
  // internal helper for RequestQueue.updatePayloads
6740
- var updatePayloads = function(existingItems, itemsToUpdate) {
7192
+ var updatePayloads = function (existingItems, itemsToUpdate) {
6741
7193
  var newItems = [];
6742
- _.each(existingItems, function(item) {
7194
+ _.each(existingItems, function (item) {
6743
7195
  var id = item['id'];
6744
7196
  if (id in itemsToUpdate) {
6745
7197
  var newPayload = itemsToUpdate[id];
@@ -6759,79 +7211,95 @@
6759
7211
  * Update payloads of given items in both in-memory queue and
6760
7212
  * persisted queue. Items set to null are removed from queues.
6761
7213
  */
6762
- RequestQueue.prototype.updatePayloads = function(itemsToUpdate, cb) {
7214
+ RequestQueue.prototype.updatePayloads = function (itemsToUpdate) {
6763
7215
  this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
6764
7216
  if (!this.usePersistence) {
6765
- if (cb) {
6766
- cb(true);
6767
- }
7217
+ return PromisePolyfill.resolve(true);
6768
7218
  } else {
6769
- this.lock.withLock(_.bind(function lockAcquired() {
6770
- var succeeded;
6771
- try {
6772
- var storedQueue = this.readFromStorage();
6773
- storedQueue = updatePayloads(storedQueue, itemsToUpdate);
6774
- succeeded = this.saveToStorage(storedQueue);
6775
- } catch(err) {
6776
- this.reportError('Error updating items', itemsToUpdate);
6777
- succeeded = false;
6778
- }
6779
- if (cb) {
6780
- cb(succeeded);
6781
- }
6782
- }, this), _.bind(function lockFailure(err) {
6783
- this.reportError('Error acquiring storage lock', err);
6784
- if (cb) {
6785
- cb(false);
6786
- }
6787
- }, this), this.pid);
7219
+ return this.lock
7220
+ .withLock(_.bind(function lockAcquired() {
7221
+ return this.ensureInit()
7222
+ .then(_.bind(function () {
7223
+ return this.readFromStorage();
7224
+ }, this))
7225
+ .then(_.bind(function (storedQueue) {
7226
+ storedQueue = updatePayloads(storedQueue, itemsToUpdate);
7227
+ return this.saveToStorage(storedQueue);
7228
+ }, this))
7229
+ .catch(_.bind(function (err) {
7230
+ this.reportError('Error updating items', itemsToUpdate, err);
7231
+ return false;
7232
+ }, this));
7233
+ }, this), this.pid)
7234
+ .catch(_.bind(function (err) {
7235
+ this.reportError('Error acquiring storage lock', err);
7236
+ return false;
7237
+ }, this));
6788
7238
  }
6789
-
6790
7239
  };
6791
7240
 
6792
7241
  /**
6793
7242
  * Read and parse items array from localStorage entry, handling
6794
7243
  * malformed/missing data if necessary.
6795
7244
  */
6796
- RequestQueue.prototype.readFromStorage = function() {
6797
- var storageEntry;
6798
- try {
6799
- storageEntry = this.storage.getItem(this.storageKey);
6800
- if (storageEntry) {
6801
- storageEntry = JSONParse(storageEntry);
6802
- if (!_.isArray(storageEntry)) {
6803
- this.reportError('Invalid storage entry:', storageEntry);
6804
- storageEntry = null;
7245
+ RequestQueue.prototype.readFromStorage = function () {
7246
+ return this.ensureInit()
7247
+ .then(_.bind(function () {
7248
+ return this.queueStorage.getItem(this.storageKey);
7249
+ }, this))
7250
+ .then(_.bind(function (storageEntry) {
7251
+ if (storageEntry) {
7252
+ storageEntry = JSONParse(storageEntry);
7253
+ if (!_.isArray(storageEntry)) {
7254
+ this.reportError('Invalid storage entry:', storageEntry);
7255
+ storageEntry = null;
7256
+ }
6805
7257
  }
6806
- }
6807
- } catch (err) {
6808
- this.reportError('Error retrieving queue', err);
6809
- storageEntry = null;
6810
- }
6811
- return storageEntry || [];
7258
+ return storageEntry || [];
7259
+ }, this))
7260
+ .catch(_.bind(function (err) {
7261
+ this.reportError('Error retrieving queue', err);
7262
+ return [];
7263
+ }, this));
6812
7264
  };
6813
7265
 
6814
7266
  /**
6815
7267
  * Serialize the given items array to localStorage.
6816
7268
  */
6817
- RequestQueue.prototype.saveToStorage = function(queue) {
7269
+ RequestQueue.prototype.saveToStorage = function (queue) {
6818
7270
  try {
6819
- this.storage.setItem(this.storageKey, JSONStringify(queue));
6820
- return true;
7271
+ var serialized = JSONStringify(queue);
6821
7272
  } catch (err) {
6822
- this.reportError('Error saving queue', err);
6823
- return false;
7273
+ this.reportError('Error serializing queue', err);
7274
+ return PromisePolyfill.resolve(false);
6824
7275
  }
7276
+
7277
+ return this.ensureInit()
7278
+ .then(_.bind(function () {
7279
+ return this.queueStorage.setItem(this.storageKey, serialized);
7280
+ }, this))
7281
+ .then(function () {
7282
+ return true;
7283
+ })
7284
+ .catch(_.bind(function (err) {
7285
+ this.reportError('Error saving queue', err);
7286
+ return false;
7287
+ }, this));
6825
7288
  };
6826
7289
 
6827
7290
  /**
6828
7291
  * Clear out queues (memory and localStorage).
6829
7292
  */
6830
- RequestQueue.prototype.clear = function() {
7293
+ RequestQueue.prototype.clear = function () {
6831
7294
  this.memQueue = [];
6832
7295
 
6833
7296
  if (this.usePersistence) {
6834
- this.storage.removeItem(this.storageKey);
7297
+ return this.ensureInit()
7298
+ .then(_.bind(function () {
7299
+ return this.queueStorage.removeItem(this.storageKey);
7300
+ }, this));
7301
+ } else {
7302
+ return PromisePolyfill.resolve();
6835
7303
  }
6836
7304
  };
6837
7305
 
@@ -6850,7 +7318,8 @@
6850
7318
  this.errorReporter = options.errorReporter;
6851
7319
  this.queue = new RequestQueue(storageKey, {
6852
7320
  errorReporter: _.bind(this.reportError, this),
6853
- storage: options.storage,
7321
+ queueStorage: options.queueStorage,
7322
+ sharedLockStorage: options.sharedLockStorage,
6854
7323
  usePersistence: options.usePersistence
6855
7324
  });
6856
7325
 
@@ -6878,8 +7347,8 @@
6878
7347
  /**
6879
7348
  * Add one item to queue.
6880
7349
  */
6881
- RequestBatcher.prototype.enqueue = function(item, cb) {
6882
- this.queue.enqueue(item, this.flushInterval, cb);
7350
+ RequestBatcher.prototype.enqueue = function(item) {
7351
+ return this.queue.enqueue(item, this.flushInterval);
6883
7352
  };
6884
7353
 
6885
7354
  /**
@@ -6889,7 +7358,7 @@
6889
7358
  RequestBatcher.prototype.start = function() {
6890
7359
  this.stopped = false;
6891
7360
  this.consecutiveRemovalFailures = 0;
6892
- this.flush();
7361
+ return this.flush();
6893
7362
  };
6894
7363
 
6895
7364
  /**
@@ -6907,7 +7376,7 @@
6907
7376
  * Clear out queue.
6908
7377
  */
6909
7378
  RequestBatcher.prototype.clear = function() {
6910
- this.queue.clear();
7379
+ return this.queue.clear();
6911
7380
  };
6912
7381
 
6913
7382
  /**
@@ -6938,6 +7407,17 @@
6938
7407
  }
6939
7408
  };
6940
7409
 
7410
+ /**
7411
+ * Send a request using the sendRequest callback, but promisified.
7412
+ * TODO: sendRequest should be promisified in the first place.
7413
+ */
7414
+ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
7415
+ return new PromisePolyfill(_.bind(function(resolve) {
7416
+ this.sendRequest(data, options, resolve);
7417
+ }, this));
7418
+ };
7419
+
7420
+
6941
7421
  /**
6942
7422
  * Flush one batch to network. Depending on success/failure modes, it will either
6943
7423
  * remove the batch from the queue or leave it in for retry, and schedule the next
@@ -6949,183 +7429,191 @@
6949
7429
  * sendBeacon offers no callbacks or status indications)
6950
7430
  */
6951
7431
  RequestBatcher.prototype.flush = function(options) {
6952
- try {
7432
+ if (this.requestInProgress) {
7433
+ logger$2.log('Flush: Request already in progress');
7434
+ return PromisePolyfill.resolve();
7435
+ }
6953
7436
 
6954
- if (this.requestInProgress) {
6955
- logger$2.log('Flush: Request already in progress');
6956
- return;
6957
- }
7437
+ this.requestInProgress = true;
6958
7438
 
6959
- options = options || {};
6960
- var timeoutMS = this.libConfig['batch_request_timeout_ms'];
6961
- var startTime = new Date().getTime();
6962
- var currentBatchSize = this.batchSize;
6963
- var batch = this.queue.fillBatch(currentBatchSize);
6964
- // if there's more items in the queue than the batch size, attempt
6965
- // to flush again after the current batch is done.
6966
- var attemptSecondaryFlush = batch.length === currentBatchSize;
6967
- var dataForRequest = [];
6968
- var transformedItems = {};
6969
- _.each(batch, function(item) {
6970
- var payload = item['payload'];
6971
- if (this.beforeSendHook && !item.orphaned) {
6972
- payload = this.beforeSendHook(payload);
6973
- }
6974
- if (payload) {
6975
- // mp_sent_by_lib_version prop captures which lib version actually
6976
- // sends each event (regardless of which version originally queued
6977
- // it for sending)
6978
- if (payload['event'] && payload['properties']) {
6979
- payload['properties'] = _.extend(
6980
- {},
6981
- payload['properties'],
6982
- {'mp_sent_by_lib_version': Config.LIB_VERSION}
6983
- );
7439
+ options = options || {};
7440
+ var timeoutMS = this.libConfig['batch_request_timeout_ms'];
7441
+ var startTime = new Date().getTime();
7442
+ var currentBatchSize = this.batchSize;
7443
+
7444
+ return this.queue.fillBatch(currentBatchSize)
7445
+ .then(_.bind(function(batch) {
7446
+
7447
+ // if there's more items in the queue than the batch size, attempt
7448
+ // to flush again after the current batch is done.
7449
+ var attemptSecondaryFlush = batch.length === currentBatchSize;
7450
+ var dataForRequest = [];
7451
+ var transformedItems = {};
7452
+ _.each(batch, function(item) {
7453
+ var payload = item['payload'];
7454
+ if (this.beforeSendHook && !item.orphaned) {
7455
+ payload = this.beforeSendHook(payload);
6984
7456
  }
6985
- var addPayload = true;
6986
- var itemId = item['id'];
6987
- if (itemId) {
6988
- if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) {
6989
- this.reportError('[dupe] item ID sent too many times, not sending', {
6990
- item: item,
6991
- batchSize: batch.length,
6992
- timesSent: this.itemIdsSentSuccessfully[itemId]
6993
- });
6994
- addPayload = false;
7457
+ if (payload) {
7458
+ // mp_sent_by_lib_version prop captures which lib version actually
7459
+ // sends each event (regardless of which version originally queued
7460
+ // it for sending)
7461
+ if (payload['event'] && payload['properties']) {
7462
+ payload['properties'] = _.extend(
7463
+ {},
7464
+ payload['properties'],
7465
+ {'mp_sent_by_lib_version': Config.LIB_VERSION}
7466
+ );
7467
+ }
7468
+ var addPayload = true;
7469
+ var itemId = item['id'];
7470
+ if (itemId) {
7471
+ if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) {
7472
+ this.reportError('[dupe] item ID sent too many times, not sending', {
7473
+ item: item,
7474
+ batchSize: batch.length,
7475
+ timesSent: this.itemIdsSentSuccessfully[itemId]
7476
+ });
7477
+ addPayload = false;
7478
+ }
7479
+ } else {
7480
+ this.reportError('[dupe] found item with no ID', {item: item});
6995
7481
  }
6996
- } else {
6997
- this.reportError('[dupe] found item with no ID', {item: item});
6998
- }
6999
7482
 
7000
- if (addPayload) {
7001
- dataForRequest.push(payload);
7483
+ if (addPayload) {
7484
+ dataForRequest.push(payload);
7485
+ }
7002
7486
  }
7003
- }
7004
- transformedItems[item['id']] = payload;
7005
- }, this);
7006
- if (dataForRequest.length < 1) {
7007
- this.resetFlush();
7008
- return; // nothing to do
7009
- }
7010
-
7011
- this.requestInProgress = true;
7487
+ transformedItems[item['id']] = payload;
7488
+ }, this);
7012
7489
 
7013
- var batchSendCallback = _.bind(function(res) {
7014
- this.requestInProgress = false;
7015
-
7016
- try {
7490
+ if (dataForRequest.length < 1) {
7491
+ this.requestInProgress = false;
7492
+ this.resetFlush();
7493
+ return PromisePolyfill.resolve(); // nothing to do
7494
+ }
7017
7495
 
7018
- // handle API response in a try-catch to make sure we can reset the
7019
- // flush operation if something goes wrong
7020
-
7021
- var removeItemsFromQueue = false;
7022
- if (options.unloading) {
7023
- // update persisted data to include hook transformations
7024
- this.queue.updatePayloads(transformedItems);
7025
- } else if (
7026
- _.isObject(res) &&
7027
- res.error === 'timeout' &&
7028
- new Date().getTime() - startTime >= timeoutMS
7029
- ) {
7030
- this.reportError('Network timeout; retrying');
7031
- this.flush();
7032
- } else if (
7033
- _.isObject(res) &&
7034
- (
7035
- res.httpStatusCode >= 500
7036
- || res.httpStatusCode === 429
7037
- || (res.httpStatusCode <= 0 && !isOnline())
7038
- || res.error === 'timeout'
7496
+ var removeItemsFromQueue = _.bind(function () {
7497
+ return this.queue
7498
+ .removeItemsByID(
7499
+ _.map(batch, function (item) {
7500
+ return item['id'];
7501
+ })
7039
7502
  )
7040
- ) {
7041
- // network or API error, or 429 Too Many Requests, retry
7042
- var retryMS = this.flushInterval * 2;
7043
- if (res.retryAfter) {
7044
- retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
7045
- }
7046
- retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
7047
- this.reportError('Error; retry in ' + retryMS + ' ms');
7048
- this.scheduleFlush(retryMS);
7049
- } else if (_.isObject(res) && res.httpStatusCode === 413) {
7050
- // 413 Payload Too Large
7051
- if (batch.length > 1) {
7052
- var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
7053
- this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
7054
- this.reportError('413 response; reducing batch size to ' + this.batchSize);
7055
- this.resetFlush();
7056
- } else {
7057
- this.reportError('Single-event request too large; dropping', batch);
7058
- this.resetBatchSize();
7059
- removeItemsFromQueue = true;
7060
- }
7061
- } else {
7062
- // successful network request+response; remove each item in batch from queue
7063
- // (even if it was e.g. a 400, in which case retrying won't help)
7064
- removeItemsFromQueue = true;
7065
- }
7066
-
7067
- if (removeItemsFromQueue) {
7068
- this.queue.removeItemsByID(
7069
- _.map(batch, function(item) { return item['id']; }),
7070
- _.bind(function(succeeded) {
7071
- if (succeeded) {
7072
- this.consecutiveRemovalFailures = 0;
7073
- if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
7074
- this.resetFlush(); // schedule next batch with a delay
7075
- } else {
7076
- this.flush(); // handle next batch if the queue isn't empty
7503
+ .then(_.bind(function (succeeded) {
7504
+ // client-side dedupe
7505
+ _.each(batch, _.bind(function(item) {
7506
+ var itemId = item['id'];
7507
+ if (itemId) {
7508
+ this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0;
7509
+ this.itemIdsSentSuccessfully[itemId]++;
7510
+ if (this.itemIdsSentSuccessfully[itemId] > 5) {
7511
+ this.reportError('[dupe] item ID sent too many times', {
7512
+ item: item,
7513
+ batchSize: batch.length,
7514
+ timesSent: this.itemIdsSentSuccessfully[itemId]
7515
+ });
7077
7516
  }
7078
7517
  } else {
7079
- this.reportError('Failed to remove items from queue');
7080
- if (++this.consecutiveRemovalFailures > 5) {
7081
- this.reportError('Too many queue failures; disabling batching system.');
7082
- this.stopAllBatching();
7083
- } else {
7084
- this.resetFlush();
7085
- }
7518
+ this.reportError('[dupe] found item with no ID while removing', {item: item});
7086
7519
  }
7087
- }, this)
7088
- );
7520
+ }, this));
7089
7521
 
7090
- // client-side dedupe
7091
- _.each(batch, _.bind(function(item) {
7092
- var itemId = item['id'];
7093
- if (itemId) {
7094
- this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0;
7095
- this.itemIdsSentSuccessfully[itemId]++;
7096
- if (this.itemIdsSentSuccessfully[itemId] > 5) {
7097
- this.reportError('[dupe] item ID sent too many times', {
7098
- item: item,
7099
- batchSize: batch.length,
7100
- timesSent: this.itemIdsSentSuccessfully[itemId]
7101
- });
7522
+ if (succeeded) {
7523
+ this.consecutiveRemovalFailures = 0;
7524
+ if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
7525
+ this.resetFlush(); // schedule next batch with a delay
7526
+ return PromisePolyfill.resolve();
7527
+ } else {
7528
+ return this.flush(); // handle next batch if the queue isn't empty
7102
7529
  }
7103
7530
  } else {
7104
- this.reportError('[dupe] found item with no ID while removing', {item: item});
7531
+ if (++this.consecutiveRemovalFailures > 5) {
7532
+ this.reportError('Too many queue failures; disabling batching system.');
7533
+ this.stopAllBatching();
7534
+ } else {
7535
+ this.resetFlush();
7536
+ }
7537
+ return PromisePolyfill.resolve();
7105
7538
  }
7106
7539
  }, this));
7107
- }
7540
+ }, this);
7108
7541
 
7109
- } catch(err) {
7110
- this.reportError('Error handling API response', err);
7111
- this.resetFlush();
7542
+ var batchSendCallback = _.bind(function(res) {
7543
+ this.requestInProgress = false;
7544
+
7545
+ try {
7546
+
7547
+ // handle API response in a try-catch to make sure we can reset the
7548
+ // flush operation if something goes wrong
7549
+
7550
+ if (options.unloading) {
7551
+ // update persisted data to include hook transformations
7552
+ return this.queue.updatePayloads(transformedItems);
7553
+ } else if (
7554
+ _.isObject(res) &&
7555
+ res.error === 'timeout' &&
7556
+ new Date().getTime() - startTime >= timeoutMS
7557
+ ) {
7558
+ this.reportError('Network timeout; retrying');
7559
+ return this.flush();
7560
+ } else if (
7561
+ _.isObject(res) &&
7562
+ (
7563
+ res.httpStatusCode >= 500
7564
+ || res.httpStatusCode === 429
7565
+ || (res.httpStatusCode <= 0 && !isOnline())
7566
+ || res.error === 'timeout'
7567
+ )
7568
+ ) {
7569
+ // network or API error, or 429 Too Many Requests, retry
7570
+ var retryMS = this.flushInterval * 2;
7571
+ if (res.retryAfter) {
7572
+ retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
7573
+ }
7574
+ retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
7575
+ this.reportError('Error; retry in ' + retryMS + ' ms');
7576
+ this.scheduleFlush(retryMS);
7577
+ return PromisePolyfill.resolve();
7578
+ } else if (_.isObject(res) && res.httpStatusCode === 413) {
7579
+ // 413 Payload Too Large
7580
+ if (batch.length > 1) {
7581
+ var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
7582
+ this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
7583
+ this.reportError('413 response; reducing batch size to ' + this.batchSize);
7584
+ this.resetFlush();
7585
+ return PromisePolyfill.resolve();
7586
+ } else {
7587
+ this.reportError('Single-event request too large; dropping', batch);
7588
+ this.resetBatchSize();
7589
+ return removeItemsFromQueue();
7590
+ }
7591
+ } else {
7592
+ // successful network request+response; remove each item in batch from queue
7593
+ // (even if it was e.g. a 400, in which case retrying won't help)
7594
+ return removeItemsFromQueue();
7595
+ }
7596
+ } catch(err) {
7597
+ this.reportError('Error handling API response', err);
7598
+ this.resetFlush();
7599
+ }
7600
+ }, this);
7601
+ var requestOptions = {
7602
+ method: 'POST',
7603
+ verbose: true,
7604
+ ignore_json_errors: true, // eslint-disable-line camelcase
7605
+ timeout_ms: timeoutMS // eslint-disable-line camelcase
7606
+ };
7607
+ if (options.unloading) {
7608
+ requestOptions.transport = 'sendBeacon';
7112
7609
  }
7113
- }, this);
7114
- var requestOptions = {
7115
- method: 'POST',
7116
- verbose: true,
7117
- ignore_json_errors: true, // eslint-disable-line camelcase
7118
- timeout_ms: timeoutMS // eslint-disable-line camelcase
7119
- };
7120
- if (options.unloading) {
7121
- requestOptions.transport = 'sendBeacon';
7122
- }
7123
- logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7124
- this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
7125
- } catch(err) {
7126
- this.reportError('Error flushing request queue', err);
7127
- this.resetFlush();
7128
- }
7610
+ logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7611
+ return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
7612
+ }, this))
7613
+ .catch(_.bind(function(err) {
7614
+ this.reportError('Error flushing request queue', err);
7615
+ this.resetFlush();
7616
+ }, this));
7129
7617
  };
7130
7618
 
7131
7619
  /**