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.
- package/CHANGELOG.md +6 -0
- package/LICENSE +1 -15
- package/build.sh +3 -3
- package/dist/mixpanel-core.cjs.js +898 -410
- package/dist/mixpanel-recorder.js +897 -409
- package/dist/mixpanel-recorder.min.js +10 -10
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +898 -410
- package/dist/mixpanel.amd.js +898 -410
- package/dist/mixpanel.cjs.js +898 -410
- package/dist/mixpanel.globals.js +898 -410
- package/dist/mixpanel.min.js +122 -112
- package/dist/mixpanel.module.js +898 -410
- package/dist/mixpanel.umd.js +898 -410
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/externs.js +14 -0
- package/src/gdpr-utils.js +2 -1
- package/src/mixpanel-core.js +3 -2
- package/src/promise-polyfill.js +379 -0
- package/src/recorder/index.js +2 -1
- package/src/recorder/session-recording.js +2 -1
- package/src/request-batcher.js +185 -164
- package/src/request-queue.js +200 -147
- package/src/shared-lock.js +104 -98
- package/src/storage/local-storage.js +53 -0
- package/src/storage/wrapper.js +14 -0
- package/src/utils.js +16 -33
- package/src/window.js +20 -0
- package/dist/mixpanel.min.js.map +0 -8
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
var Config = {
|
|
4
4
|
DEBUG: false,
|
|
5
|
-
LIB_VERSION: '2.
|
|
5
|
+
LIB_VERSION: '2.57.1'
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
9
|
-
|
|
10
8
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
11
9
|
var win;
|
|
12
10
|
if (typeof(window) === 'undefined') {
|
|
@@ -26,6 +24,370 @@ if (typeof(window) === 'undefined') {
|
|
|
26
24
|
win = window;
|
|
27
25
|
}
|
|
28
26
|
|
|
27
|
+
var setImmediate = win['setImmediate'];
|
|
28
|
+
var builtInProp, cycle, schedulingQueue,
|
|
29
|
+
ToString = Object.prototype.toString,
|
|
30
|
+
timer = (typeof setImmediate !== 'undefined') ?
|
|
31
|
+
function timer(fn) { return setImmediate(fn); } :
|
|
32
|
+
setTimeout;
|
|
33
|
+
|
|
34
|
+
// dammit, IE8.
|
|
35
|
+
try {
|
|
36
|
+
Object.defineProperty({},'x',{});
|
|
37
|
+
builtInProp = function builtInProp(obj,name,val,config) {
|
|
38
|
+
return Object.defineProperty(obj,name,{
|
|
39
|
+
value: val,
|
|
40
|
+
writable: true,
|
|
41
|
+
configurable: config !== false
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
builtInProp = function builtInProp(obj,name,val) {
|
|
47
|
+
obj[name] = val;
|
|
48
|
+
return obj;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Note: using a queue instead of array for efficiency
|
|
53
|
+
schedulingQueue = (function Queue() {
|
|
54
|
+
var first, last, item;
|
|
55
|
+
|
|
56
|
+
function Item(fn,self) {
|
|
57
|
+
this.fn = fn;
|
|
58
|
+
this.self = self;
|
|
59
|
+
this.next = void 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
add: function add(fn,self) {
|
|
64
|
+
item = new Item(fn,self);
|
|
65
|
+
if (last) {
|
|
66
|
+
last.next = item;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
first = item;
|
|
70
|
+
}
|
|
71
|
+
last = item;
|
|
72
|
+
item = void 0;
|
|
73
|
+
},
|
|
74
|
+
drain: function drain() {
|
|
75
|
+
var f = first;
|
|
76
|
+
first = last = cycle = void 0;
|
|
77
|
+
|
|
78
|
+
while (f) {
|
|
79
|
+
f.fn.call(f.self);
|
|
80
|
+
f = f.next;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
})();
|
|
85
|
+
|
|
86
|
+
function schedule(fn,self) {
|
|
87
|
+
schedulingQueue.add(fn,self);
|
|
88
|
+
if (!cycle) {
|
|
89
|
+
cycle = timer(schedulingQueue.drain);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// promise duck typing
|
|
94
|
+
function isThenable(o) {
|
|
95
|
+
var _then, oType = typeof o;
|
|
96
|
+
|
|
97
|
+
if (o !== null && (oType === 'object' || oType === 'function')) {
|
|
98
|
+
_then = o.then;
|
|
99
|
+
}
|
|
100
|
+
return typeof _then === 'function' ? _then : false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function notify() {
|
|
104
|
+
for (var i=0; i<this.chain.length; i++) {
|
|
105
|
+
notifyIsolated(
|
|
106
|
+
this,
|
|
107
|
+
(this.state === 1) ? this.chain[i].success : this.chain[i].failure,
|
|
108
|
+
this.chain[i]
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
this.chain.length = 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// NOTE: This is a separate function to isolate
|
|
115
|
+
// the `try..catch` so that other code can be
|
|
116
|
+
// optimized better
|
|
117
|
+
function notifyIsolated(self,cb,chain) {
|
|
118
|
+
var ret, _then;
|
|
119
|
+
try {
|
|
120
|
+
if (cb === false) {
|
|
121
|
+
chain.reject(self.msg);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
if (cb === true) {
|
|
125
|
+
ret = self.msg;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
ret = cb.call(void 0,self.msg);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (ret === chain.promise) {
|
|
132
|
+
chain.reject(TypeError('Promise-chain cycle'));
|
|
133
|
+
}
|
|
134
|
+
// eslint-disable-next-line no-cond-assign
|
|
135
|
+
else if (_then = isThenable(ret)) {
|
|
136
|
+
_then.call(ret,chain.resolve,chain.reject);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
chain.resolve(ret);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
chain.reject(err);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolve(msg) {
|
|
149
|
+
var _then, self = this;
|
|
150
|
+
|
|
151
|
+
// already triggered?
|
|
152
|
+
if (self.triggered) { return; }
|
|
153
|
+
|
|
154
|
+
self.triggered = true;
|
|
155
|
+
|
|
156
|
+
// unwrap
|
|
157
|
+
if (self.def) {
|
|
158
|
+
self = self.def;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// eslint-disable-next-line no-cond-assign
|
|
163
|
+
if (_then = isThenable(msg)) {
|
|
164
|
+
schedule(function(){
|
|
165
|
+
var defWrapper = new MakeDefWrapper(self);
|
|
166
|
+
try {
|
|
167
|
+
_then.call(msg,
|
|
168
|
+
function $resolve$(){ resolve.apply(defWrapper,arguments); },
|
|
169
|
+
function $reject$(){ reject.apply(defWrapper,arguments); }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
reject.call(defWrapper,err);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
self.msg = msg;
|
|
179
|
+
self.state = 1;
|
|
180
|
+
if (self.chain.length > 0) {
|
|
181
|
+
schedule(notify,self);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
reject.call(new MakeDefWrapper(self),err);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function reject(msg) {
|
|
191
|
+
var self = this;
|
|
192
|
+
|
|
193
|
+
// already triggered?
|
|
194
|
+
if (self.triggered) { return; }
|
|
195
|
+
|
|
196
|
+
self.triggered = true;
|
|
197
|
+
|
|
198
|
+
// unwrap
|
|
199
|
+
if (self.def) {
|
|
200
|
+
self = self.def;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
self.msg = msg;
|
|
204
|
+
self.state = 2;
|
|
205
|
+
if (self.chain.length > 0) {
|
|
206
|
+
schedule(notify,self);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function iteratePromises(Constructor,arr,resolver,rejecter) {
|
|
211
|
+
for (var idx=0; idx<arr.length; idx++) {
|
|
212
|
+
(function IIFE(idx){
|
|
213
|
+
Constructor.resolve(arr[idx])
|
|
214
|
+
.then(
|
|
215
|
+
function $resolver$(msg){
|
|
216
|
+
resolver(idx,msg);
|
|
217
|
+
},
|
|
218
|
+
rejecter
|
|
219
|
+
);
|
|
220
|
+
})(idx);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function MakeDefWrapper(self) {
|
|
225
|
+
this.def = self;
|
|
226
|
+
this.triggered = false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function MakeDef(self) {
|
|
230
|
+
this.promise = self;
|
|
231
|
+
this.state = 0;
|
|
232
|
+
this.triggered = false;
|
|
233
|
+
this.chain = [];
|
|
234
|
+
this.msg = void 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function NpoPromise(executor) {
|
|
238
|
+
if (typeof executor !== 'function') {
|
|
239
|
+
throw TypeError('Not a function');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (this['__NPO__'] !== 0) {
|
|
243
|
+
throw TypeError('Not a promise');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// instance shadowing the inherited "brand"
|
|
247
|
+
// to signal an already "initialized" promise
|
|
248
|
+
this['__NPO__'] = 1;
|
|
249
|
+
|
|
250
|
+
var def = new MakeDef(this);
|
|
251
|
+
|
|
252
|
+
this['then'] = function then(success,failure) {
|
|
253
|
+
var o = {
|
|
254
|
+
success: typeof success === 'function' ? success : true,
|
|
255
|
+
failure: typeof failure === 'function' ? failure : false
|
|
256
|
+
};
|
|
257
|
+
// Note: `then(..)` itself can be borrowed to be used against
|
|
258
|
+
// a different promise constructor for making the chained promise,
|
|
259
|
+
// by substituting a different `this` binding.
|
|
260
|
+
o.promise = new this.constructor(function extractChain(resolve,reject) {
|
|
261
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
262
|
+
throw TypeError('Not a function');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
o.resolve = resolve;
|
|
266
|
+
o.reject = reject;
|
|
267
|
+
});
|
|
268
|
+
def.chain.push(o);
|
|
269
|
+
|
|
270
|
+
if (def.state !== 0) {
|
|
271
|
+
schedule(notify,def);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return o.promise;
|
|
275
|
+
};
|
|
276
|
+
this['catch'] = function $catch$(failure) {
|
|
277
|
+
return this.then(void 0,failure);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
executor.call(
|
|
282
|
+
void 0,
|
|
283
|
+
function publicResolve(msg){
|
|
284
|
+
resolve.call(def,msg);
|
|
285
|
+
},
|
|
286
|
+
function publicReject(msg) {
|
|
287
|
+
reject.call(def,msg);
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
reject.call(def,err);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
var PromisePrototype = builtInProp({},'constructor',NpoPromise,
|
|
297
|
+
/*configurable=*/false
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Note: Android 4 cannot use `Object.defineProperty(..)` here
|
|
301
|
+
NpoPromise.prototype = PromisePrototype;
|
|
302
|
+
|
|
303
|
+
// built-in "brand" to signal an "uninitialized" promise
|
|
304
|
+
builtInProp(PromisePrototype,'__NPO__',0,
|
|
305
|
+
/*configurable=*/false
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
builtInProp(NpoPromise,'resolve',function Promise$resolve(msg) {
|
|
309
|
+
var Constructor = this;
|
|
310
|
+
|
|
311
|
+
// spec mandated checks
|
|
312
|
+
// note: best "isPromise" check that's practical for now
|
|
313
|
+
if (msg && typeof msg === 'object' && msg['__NPO__'] === 1) {
|
|
314
|
+
return msg;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return new Constructor(function executor(resolve,reject){
|
|
318
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
319
|
+
throw TypeError('Not a function');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
resolve(msg);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
builtInProp(NpoPromise,'reject',function Promise$reject(msg) {
|
|
327
|
+
return new this(function executor(resolve,reject){
|
|
328
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
329
|
+
throw TypeError('Not a function');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
reject(msg);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
builtInProp(NpoPromise,'all',function Promise$all(arr) {
|
|
337
|
+
var Constructor = this;
|
|
338
|
+
|
|
339
|
+
// spec mandated checks
|
|
340
|
+
if (ToString.call(arr) !== '[object Array]') {
|
|
341
|
+
return Constructor.reject(TypeError('Not an array'));
|
|
342
|
+
}
|
|
343
|
+
if (arr.length === 0) {
|
|
344
|
+
return Constructor.resolve([]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return new Constructor(function executor(resolve,reject){
|
|
348
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
349
|
+
throw TypeError('Not a function');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
var len = arr.length, msgs = Array(len), count = 0;
|
|
353
|
+
|
|
354
|
+
iteratePromises(Constructor,arr,function resolver(idx,msg) {
|
|
355
|
+
msgs[idx] = msg;
|
|
356
|
+
if (++count === len) {
|
|
357
|
+
resolve(msgs);
|
|
358
|
+
}
|
|
359
|
+
},reject);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
builtInProp(NpoPromise,'race',function Promise$race(arr) {
|
|
364
|
+
var Constructor = this;
|
|
365
|
+
|
|
366
|
+
// spec mandated checks
|
|
367
|
+
if (ToString.call(arr) !== '[object Array]') {
|
|
368
|
+
return Constructor.reject(TypeError('Not an array'));
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return new Constructor(function executor(resolve,reject){
|
|
372
|
+
if (typeof resolve !== 'function' || typeof reject !== 'function') {
|
|
373
|
+
throw TypeError('Not a function');
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
iteratePromises(Constructor,arr,function resolver(idx,msg){
|
|
377
|
+
resolve(msg);
|
|
378
|
+
},reject);
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
var PromisePolyfill;
|
|
383
|
+
if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]') !== -1) {
|
|
384
|
+
PromisePolyfill = Promise;
|
|
385
|
+
} else {
|
|
386
|
+
PromisePolyfill = NpoPromise;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
390
|
+
|
|
29
391
|
// Maximum allowed session recording length
|
|
30
392
|
var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
31
393
|
|
|
@@ -1085,7 +1447,7 @@ var localStorageSupported = function(storage, forceCheck) {
|
|
|
1085
1447
|
|
|
1086
1448
|
var supported = true;
|
|
1087
1449
|
try {
|
|
1088
|
-
storage = storage ||
|
|
1450
|
+
storage = storage || win.localStorage;
|
|
1089
1451
|
var key = '__mplss_' + cheap_guid(8),
|
|
1090
1452
|
val = 'xyz';
|
|
1091
1453
|
storage.setItem(key, val);
|
|
@@ -1117,7 +1479,7 @@ _.localStorage = {
|
|
|
1117
1479
|
|
|
1118
1480
|
get: function(name) {
|
|
1119
1481
|
try {
|
|
1120
|
-
return
|
|
1482
|
+
return win.localStorage.getItem(name);
|
|
1121
1483
|
} catch (err) {
|
|
1122
1484
|
_.localStorage.error(err);
|
|
1123
1485
|
}
|
|
@@ -1135,7 +1497,7 @@ _.localStorage = {
|
|
|
1135
1497
|
|
|
1136
1498
|
set: function(name, value) {
|
|
1137
1499
|
try {
|
|
1138
|
-
|
|
1500
|
+
win.localStorage.setItem(name, value);
|
|
1139
1501
|
} catch (err) {
|
|
1140
1502
|
_.localStorage.error(err);
|
|
1141
1503
|
}
|
|
@@ -1143,7 +1505,7 @@ _.localStorage = {
|
|
|
1143
1505
|
|
|
1144
1506
|
remove: function(name) {
|
|
1145
1507
|
try {
|
|
1146
|
-
|
|
1508
|
+
win.localStorage.removeItem(name);
|
|
1147
1509
|
} catch (err) {
|
|
1148
1510
|
_.localStorage.error(err);
|
|
1149
1511
|
}
|
|
@@ -1182,7 +1544,7 @@ _.register_event = (function() {
|
|
|
1182
1544
|
|
|
1183
1545
|
function makeHandler(element, new_handler, old_handlers) {
|
|
1184
1546
|
var handler = function(event) {
|
|
1185
|
-
event = event || fixEvent(
|
|
1547
|
+
event = event || fixEvent(win.event);
|
|
1186
1548
|
|
|
1187
1549
|
// this basically happens in firefox whenever another script
|
|
1188
1550
|
// overwrites the onload callback and doesn't pass the event
|
|
@@ -1737,6 +2099,7 @@ _['info']['device'] = _.info.device;
|
|
|
1737
2099
|
_['info']['browser'] = _.info.browser;
|
|
1738
2100
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1739
2101
|
_['info']['properties'] = _.info.properties;
|
|
2102
|
+
_['NPO'] = NpoPromise;
|
|
1740
2103
|
|
|
1741
2104
|
/* eslint camelcase: "off" */
|
|
1742
2105
|
|
|
@@ -1918,121 +2281,175 @@ var SharedLock = function(key, options) {
|
|
|
1918
2281
|
this.storage = options.storage || window.localStorage;
|
|
1919
2282
|
this.pollIntervalMS = options.pollIntervalMS || 100;
|
|
1920
2283
|
this.timeoutMS = options.timeoutMS || 2000;
|
|
2284
|
+
|
|
2285
|
+
// dependency-inject promise implementation for testing purposes
|
|
2286
|
+
this.promiseImpl = options.promiseImpl || PromisePolyfill;
|
|
1921
2287
|
};
|
|
1922
2288
|
|
|
1923
2289
|
// pass in a specific pid to test contention scenarios; otherwise
|
|
1924
2290
|
// it is chosen randomly for each acquisition attempt
|
|
1925
|
-
SharedLock.prototype.withLock = function(lockedCB,
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
1932
|
-
var startTime = new Date().getTime();
|
|
2291
|
+
SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
2292
|
+
var Promise = this.promiseImpl;
|
|
2293
|
+
return new Promise(_.bind(function (resolve, reject) {
|
|
2294
|
+
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
2295
|
+
var startTime = new Date().getTime();
|
|
1933
2296
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2297
|
+
var key = this.storageKey;
|
|
2298
|
+
var pollIntervalMS = this.pollIntervalMS;
|
|
2299
|
+
var timeoutMS = this.timeoutMS;
|
|
2300
|
+
var storage = this.storage;
|
|
1938
2301
|
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2302
|
+
var keyX = key + ':X';
|
|
2303
|
+
var keyY = key + ':Y';
|
|
2304
|
+
var keyZ = key + ':Z';
|
|
1942
2305
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2306
|
+
var delay = function(cb) {
|
|
2307
|
+
if (new Date().getTime() - startTime > timeoutMS) {
|
|
2308
|
+
logger$2.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
2309
|
+
storage.removeItem(keyZ);
|
|
2310
|
+
storage.removeItem(keyY);
|
|
2311
|
+
loop();
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
setTimeout(function() {
|
|
2315
|
+
try {
|
|
2316
|
+
cb();
|
|
2317
|
+
} catch(err) {
|
|
2318
|
+
reject(err);
|
|
2319
|
+
}
|
|
2320
|
+
}, pollIntervalMS * (Math.random() + 0.1));
|
|
2321
|
+
};
|
|
1946
2322
|
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
logger$2.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
1950
|
-
storage.removeItem(keyZ);
|
|
1951
|
-
storage.removeItem(keyY);
|
|
1952
|
-
loop();
|
|
1953
|
-
return;
|
|
1954
|
-
}
|
|
1955
|
-
setTimeout(function() {
|
|
1956
|
-
try {
|
|
2323
|
+
var waitFor = function(predicate, cb) {
|
|
2324
|
+
if (predicate()) {
|
|
1957
2325
|
cb();
|
|
1958
|
-
}
|
|
1959
|
-
|
|
2326
|
+
} else {
|
|
2327
|
+
delay(function() {
|
|
2328
|
+
waitFor(predicate, cb);
|
|
2329
|
+
});
|
|
1960
2330
|
}
|
|
1961
|
-
}
|
|
1962
|
-
};
|
|
1963
|
-
|
|
1964
|
-
var waitFor = function(predicate, cb) {
|
|
1965
|
-
if (predicate()) {
|
|
1966
|
-
cb();
|
|
1967
|
-
} else {
|
|
1968
|
-
delay(function() {
|
|
1969
|
-
waitFor(predicate, cb);
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
};
|
|
2331
|
+
};
|
|
1973
2332
|
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
} else {
|
|
1979
|
-
storage.setItem(keyY, i);
|
|
1980
|
-
if (storage.getItem(keyY) === i) {
|
|
1981
|
-
return true;
|
|
2333
|
+
var getSetY = function() {
|
|
2334
|
+
var valY = storage.getItem(keyY);
|
|
2335
|
+
if (valY && valY !== i) { // if Y == i then this process already has the lock (useful for test cases)
|
|
2336
|
+
return false;
|
|
1982
2337
|
} else {
|
|
1983
|
-
|
|
1984
|
-
|
|
2338
|
+
storage.setItem(keyY, i);
|
|
2339
|
+
if (storage.getItem(keyY) === i) {
|
|
2340
|
+
return true;
|
|
2341
|
+
} else {
|
|
2342
|
+
if (!localStorageSupported(storage, true)) {
|
|
2343
|
+
reject(new Error('localStorage support dropped while acquiring lock'));
|
|
2344
|
+
}
|
|
2345
|
+
return false;
|
|
1985
2346
|
}
|
|
1986
|
-
return false;
|
|
1987
2347
|
}
|
|
1988
|
-
}
|
|
1989
|
-
};
|
|
1990
|
-
|
|
1991
|
-
var loop = function() {
|
|
1992
|
-
storage.setItem(keyX, i);
|
|
2348
|
+
};
|
|
1993
2349
|
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
criticalSection();
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
2350
|
+
var loop = function() {
|
|
2351
|
+
storage.setItem(keyX, i);
|
|
1999
2352
|
|
|
2000
|
-
|
|
2001
|
-
if (storage.getItem(
|
|
2002
|
-
|
|
2353
|
+
waitFor(getSetY, function() {
|
|
2354
|
+
if (storage.getItem(keyX) === i) {
|
|
2355
|
+
criticalSection();
|
|
2003
2356
|
return;
|
|
2004
2357
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2358
|
+
|
|
2359
|
+
delay(function() {
|
|
2360
|
+
if (storage.getItem(keyY) !== i) {
|
|
2361
|
+
loop();
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
waitFor(function() {
|
|
2365
|
+
return !storage.getItem(keyZ);
|
|
2366
|
+
}, criticalSection);
|
|
2367
|
+
});
|
|
2008
2368
|
});
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2371
|
+
var criticalSection = function() {
|
|
2372
|
+
storage.setItem(keyZ, '1');
|
|
2373
|
+
var removeLock = function () {
|
|
2374
|
+
storage.removeItem(keyZ);
|
|
2375
|
+
if (storage.getItem(keyY) === i) {
|
|
2376
|
+
storage.removeItem(keyY);
|
|
2377
|
+
}
|
|
2378
|
+
if (storage.getItem(keyX) === i) {
|
|
2379
|
+
storage.removeItem(keyX);
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
|
|
2383
|
+
lockedCB()
|
|
2384
|
+
.then(function (ret) {
|
|
2385
|
+
removeLock();
|
|
2386
|
+
resolve(ret);
|
|
2387
|
+
})
|
|
2388
|
+
.catch(function (err) {
|
|
2389
|
+
removeLock();
|
|
2390
|
+
reject(err);
|
|
2391
|
+
});
|
|
2392
|
+
};
|
|
2011
2393
|
|
|
2012
|
-
var criticalSection = function() {
|
|
2013
|
-
storage.setItem(keyZ, '1');
|
|
2014
2394
|
try {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
storage.removeItem(keyY);
|
|
2020
|
-
}
|
|
2021
|
-
if (storage.getItem(keyX) === i) {
|
|
2022
|
-
storage.removeItem(keyX);
|
|
2395
|
+
if (localStorageSupported(storage, true)) {
|
|
2396
|
+
loop();
|
|
2397
|
+
} else {
|
|
2398
|
+
throw new Error('localStorage support check failed');
|
|
2023
2399
|
}
|
|
2400
|
+
} catch(err) {
|
|
2401
|
+
reject(err);
|
|
2024
2402
|
}
|
|
2025
|
-
};
|
|
2403
|
+
}, this));
|
|
2404
|
+
};
|
|
2026
2405
|
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2406
|
+
/**
|
|
2407
|
+
* @typedef {import('./wrapper').StorageWrapper}
|
|
2408
|
+
*/
|
|
2409
|
+
|
|
2410
|
+
/**
|
|
2411
|
+
* @type {StorageWrapper}
|
|
2412
|
+
*/
|
|
2413
|
+
var LocalStorageWrapper = function (storageOverride) {
|
|
2414
|
+
this.storage = storageOverride || localStorage;
|
|
2415
|
+
};
|
|
2416
|
+
|
|
2417
|
+
LocalStorageWrapper.prototype.init = function () {
|
|
2418
|
+
return PromisePolyfill.resolve();
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
2422
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
2423
|
+
try {
|
|
2424
|
+
this.storage.setItem(key, value);
|
|
2425
|
+
} catch (e) {
|
|
2426
|
+
reject(e);
|
|
2032
2427
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2428
|
+
resolve();
|
|
2429
|
+
}, this));
|
|
2430
|
+
};
|
|
2431
|
+
|
|
2432
|
+
LocalStorageWrapper.prototype.getItem = function (key) {
|
|
2433
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
2434
|
+
var item;
|
|
2435
|
+
try {
|
|
2436
|
+
item = this.storage.getItem(key);
|
|
2437
|
+
} catch (e) {
|
|
2438
|
+
reject(e);
|
|
2439
|
+
}
|
|
2440
|
+
resolve(item);
|
|
2441
|
+
}, this));
|
|
2442
|
+
};
|
|
2443
|
+
|
|
2444
|
+
LocalStorageWrapper.prototype.removeItem = function (key) {
|
|
2445
|
+
return new PromisePolyfill(_.bind(function (resolve, reject) {
|
|
2446
|
+
try {
|
|
2447
|
+
this.storage.removeItem(key);
|
|
2448
|
+
} catch (e) {
|
|
2449
|
+
reject(e);
|
|
2450
|
+
}
|
|
2451
|
+
resolve();
|
|
2452
|
+
}, this));
|
|
2036
2453
|
};
|
|
2037
2454
|
|
|
2038
2455
|
var logger$1 = console_with_prefix('batch');
|
|
@@ -2053,19 +2470,38 @@ var logger$1 = console_with_prefix('batch');
|
|
|
2053
2470
|
* to data loss in some situations).
|
|
2054
2471
|
* @constructor
|
|
2055
2472
|
*/
|
|
2056
|
-
var RequestQueue = function(storageKey, options) {
|
|
2473
|
+
var RequestQueue = function (storageKey, options) {
|
|
2057
2474
|
options = options || {};
|
|
2058
2475
|
this.storageKey = storageKey;
|
|
2059
2476
|
this.usePersistence = options.usePersistence;
|
|
2060
2477
|
if (this.usePersistence) {
|
|
2061
|
-
this.
|
|
2062
|
-
this.lock = new SharedLock(storageKey, {storage:
|
|
2478
|
+
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
2479
|
+
this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
|
|
2480
|
+
this.queueStorage.init();
|
|
2063
2481
|
}
|
|
2064
2482
|
this.reportError = options.errorReporter || _.bind(logger$1.error, logger$1);
|
|
2065
2483
|
|
|
2066
2484
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
2067
2485
|
|
|
2068
2486
|
this.memQueue = [];
|
|
2487
|
+
this.initialized = false;
|
|
2488
|
+
};
|
|
2489
|
+
|
|
2490
|
+
RequestQueue.prototype.ensureInit = function () {
|
|
2491
|
+
if (this.initialized) {
|
|
2492
|
+
return PromisePolyfill.resolve();
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
return this.queueStorage
|
|
2496
|
+
.init()
|
|
2497
|
+
.then(_.bind(function () {
|
|
2498
|
+
this.initialized = true;
|
|
2499
|
+
}, this))
|
|
2500
|
+
.catch(_.bind(function (err) {
|
|
2501
|
+
this.reportError('Error initializing queue persistence. Disabling persistence', err);
|
|
2502
|
+
this.initialized = true;
|
|
2503
|
+
this.usePersistence = false;
|
|
2504
|
+
}, this));
|
|
2069
2505
|
};
|
|
2070
2506
|
|
|
2071
2507
|
/**
|
|
@@ -2080,7 +2516,7 @@ var RequestQueue = function(storageKey, options) {
|
|
|
2080
2516
|
* failure of the enqueue operation; it is asynchronous because the localStorage
|
|
2081
2517
|
* lock is asynchronous.
|
|
2082
2518
|
*/
|
|
2083
|
-
RequestQueue.prototype.enqueue = function(item, flushInterval
|
|
2519
|
+
RequestQueue.prototype.enqueue = function (item, flushInterval) {
|
|
2084
2520
|
var queueEntry = {
|
|
2085
2521
|
'id': cheap_guid(),
|
|
2086
2522
|
'flushAfter': new Date().getTime() + flushInterval * 2,
|
|
@@ -2089,33 +2525,37 @@ RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
|
|
|
2089
2525
|
|
|
2090
2526
|
if (!this.usePersistence) {
|
|
2091
2527
|
this.memQueue.push(queueEntry);
|
|
2092
|
-
|
|
2093
|
-
cb(true);
|
|
2094
|
-
}
|
|
2528
|
+
return PromisePolyfill.resolve(true);
|
|
2095
2529
|
} else {
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2530
|
+
|
|
2531
|
+
var enqueueItem = _.bind(function () {
|
|
2532
|
+
return this.ensureInit()
|
|
2533
|
+
.then(_.bind(function () {
|
|
2534
|
+
return this.readFromStorage();
|
|
2535
|
+
}, this))
|
|
2536
|
+
.then(_.bind(function (storedQueue) {
|
|
2537
|
+
storedQueue.push(queueEntry);
|
|
2538
|
+
return this.saveToStorage(storedQueue);
|
|
2539
|
+
}, this))
|
|
2540
|
+
.then(_.bind(function (succeeded) {
|
|
2103
2541
|
// only add to in-memory queue when storage succeeds
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
}, this)
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2542
|
+
if (succeeded) {
|
|
2543
|
+
this.memQueue.push(queueEntry);
|
|
2544
|
+
}
|
|
2545
|
+
return succeeded;
|
|
2546
|
+
}, this))
|
|
2547
|
+
.catch(_.bind(function (err) {
|
|
2548
|
+
this.reportError('Error enqueueing item', err, item);
|
|
2549
|
+
return false;
|
|
2550
|
+
}, this));
|
|
2551
|
+
}, this);
|
|
2552
|
+
|
|
2553
|
+
return this.lock
|
|
2554
|
+
.withLock(enqueueItem, this.pid)
|
|
2555
|
+
.catch(_.bind(function (err) {
|
|
2556
|
+
this.reportError('Error acquiring storage lock', err);
|
|
2557
|
+
return false;
|
|
2558
|
+
}, this));
|
|
2119
2559
|
}
|
|
2120
2560
|
};
|
|
2121
2561
|
|
|
@@ -2125,31 +2565,41 @@ RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
|
|
|
2125
2565
|
* in the persisted queue (items where the 'flushAfter' time has
|
|
2126
2566
|
* already passed).
|
|
2127
2567
|
*/
|
|
2128
|
-
RequestQueue.prototype.fillBatch = function(batchSize) {
|
|
2568
|
+
RequestQueue.prototype.fillBatch = function (batchSize) {
|
|
2129
2569
|
var batch = this.memQueue.slice(0, batchSize);
|
|
2130
2570
|
if (this.usePersistence && batch.length < batchSize) {
|
|
2131
2571
|
// don't need lock just to read events; localStorage is thread-safe
|
|
2132
2572
|
// and the worst that could happen is a duplicate send of some
|
|
2133
2573
|
// orphaned events, which will be deduplicated on the server side
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
_.
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2574
|
+
return this.ensureInit()
|
|
2575
|
+
.then(_.bind(function () {
|
|
2576
|
+
return this.readFromStorage();
|
|
2577
|
+
}, this))
|
|
2578
|
+
.then(_.bind(function (storedQueue) {
|
|
2579
|
+
if (storedQueue.length) {
|
|
2580
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
2581
|
+
var idsInBatch = {}; // poor man's Set
|
|
2582
|
+
_.each(batch, function (item) {
|
|
2583
|
+
idsInBatch[item['id']] = true;
|
|
2584
|
+
});
|
|
2585
|
+
|
|
2586
|
+
for (var i = 0; i < storedQueue.length; i++) {
|
|
2587
|
+
var item = storedQueue[i];
|
|
2588
|
+
if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
|
|
2589
|
+
item.orphaned = true;
|
|
2590
|
+
batch.push(item);
|
|
2591
|
+
if (batch.length >= batchSize) {
|
|
2592
|
+
break;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2147
2595
|
}
|
|
2148
2596
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2597
|
+
|
|
2598
|
+
return batch;
|
|
2599
|
+
}, this));
|
|
2600
|
+
} else {
|
|
2601
|
+
return PromisePolyfill.resolve(batch);
|
|
2151
2602
|
}
|
|
2152
|
-
return batch;
|
|
2153
2603
|
};
|
|
2154
2604
|
|
|
2155
2605
|
/**
|
|
@@ -2157,9 +2607,9 @@ RequestQueue.prototype.fillBatch = function(batchSize) {
|
|
|
2157
2607
|
* also remove any item without a valid id (e.g., malformed
|
|
2158
2608
|
* storage entries).
|
|
2159
2609
|
*/
|
|
2160
|
-
var filterOutIDsAndInvalid = function(items, idSet) {
|
|
2610
|
+
var filterOutIDsAndInvalid = function (items, idSet) {
|
|
2161
2611
|
var filteredItems = [];
|
|
2162
|
-
_.each(items, function(item) {
|
|
2612
|
+
_.each(items, function (item) {
|
|
2163
2613
|
if (item['id'] && !idSet[item['id']]) {
|
|
2164
2614
|
filteredItems.push(item);
|
|
2165
2615
|
}
|
|
@@ -2171,78 +2621,80 @@ var filterOutIDsAndInvalid = function(items, idSet) {
|
|
|
2171
2621
|
* Remove items with matching IDs from both in-memory queue
|
|
2172
2622
|
* and persisted queue
|
|
2173
2623
|
*/
|
|
2174
|
-
RequestQueue.prototype.removeItemsByID = function(ids
|
|
2624
|
+
RequestQueue.prototype.removeItemsByID = function (ids) {
|
|
2175
2625
|
var idSet = {}; // poor man's Set
|
|
2176
|
-
_.each(ids, function(id) {
|
|
2626
|
+
_.each(ids, function (id) {
|
|
2627
|
+
idSet[id] = true;
|
|
2628
|
+
});
|
|
2177
2629
|
|
|
2178
2630
|
this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
|
|
2179
2631
|
if (!this.usePersistence) {
|
|
2180
|
-
|
|
2181
|
-
cb(true);
|
|
2182
|
-
}
|
|
2632
|
+
return PromisePolyfill.resolve(true);
|
|
2183
2633
|
} else {
|
|
2184
|
-
var removeFromStorage = _.bind(function() {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2634
|
+
var removeFromStorage = _.bind(function () {
|
|
2635
|
+
return this.ensureInit()
|
|
2636
|
+
.then(_.bind(function () {
|
|
2637
|
+
return this.readFromStorage();
|
|
2638
|
+
}, this))
|
|
2639
|
+
.then(_.bind(function (storedQueue) {
|
|
2640
|
+
storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
|
|
2641
|
+
return this.saveToStorage(storedQueue);
|
|
2642
|
+
}, this))
|
|
2643
|
+
.then(_.bind(function () {
|
|
2644
|
+
return this.readFromStorage();
|
|
2645
|
+
}, this))
|
|
2646
|
+
.then(_.bind(function (storedQueue) {
|
|
2647
|
+
// an extra check: did storage report success but somehow
|
|
2648
|
+
// the items are still there?
|
|
2195
2649
|
for (var i = 0; i < storedQueue.length; i++) {
|
|
2196
2650
|
var item = storedQueue[i];
|
|
2197
2651
|
if (item['id'] && !!idSet[item['id']]) {
|
|
2198
|
-
|
|
2199
|
-
return false;
|
|
2652
|
+
throw new Error('Item not removed from storage');
|
|
2200
2653
|
}
|
|
2201
2654
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2655
|
+
return true;
|
|
2656
|
+
}, this))
|
|
2657
|
+
.catch(_.bind(function (err) {
|
|
2658
|
+
this.reportError('Error removing items', err, ids);
|
|
2659
|
+
return false;
|
|
2660
|
+
}, this));
|
|
2208
2661
|
}, this);
|
|
2209
2662
|
|
|
2210
|
-
this.lock
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2663
|
+
return this.lock
|
|
2664
|
+
.withLock(removeFromStorage, this.pid)
|
|
2665
|
+
.catch(_.bind(function (err) {
|
|
2666
|
+
this.reportError('Error acquiring storage lock', err);
|
|
2667
|
+
if (!localStorageSupported(this.queueStorage.storage, true)) {
|
|
2668
|
+
// Looks like localStorage writes have stopped working sometime after
|
|
2669
|
+
// initialization (probably full), and so nobody can acquire locks
|
|
2670
|
+
// anymore. Consider it temporarily safe to remove items without the
|
|
2671
|
+
// lock, since nobody's writing successfully anyway.
|
|
2672
|
+
return removeFromStorage()
|
|
2673
|
+
.then(_.bind(function (success) {
|
|
2674
|
+
if (!success) {
|
|
2675
|
+
// OK, we couldn't even write out the smaller queue. Try clearing it
|
|
2676
|
+
// entirely.
|
|
2677
|
+
return this.queueStorage.removeItem(this.storageKey).then(function () {
|
|
2678
|
+
return success;
|
|
2679
|
+
});
|
|
2680
|
+
}
|
|
2681
|
+
return success;
|
|
2682
|
+
}, this))
|
|
2683
|
+
.catch(_.bind(function (err) {
|
|
2684
|
+
this.reportError('Error clearing queue', err);
|
|
2685
|
+
return false;
|
|
2686
|
+
}, this));
|
|
2687
|
+
} else {
|
|
2688
|
+
return false;
|
|
2232
2689
|
}
|
|
2233
|
-
}
|
|
2234
|
-
if (cb) {
|
|
2235
|
-
cb(succeeded);
|
|
2236
|
-
}
|
|
2237
|
-
}, this), this.pid);
|
|
2690
|
+
}, this));
|
|
2238
2691
|
}
|
|
2239
|
-
|
|
2240
2692
|
};
|
|
2241
2693
|
|
|
2242
2694
|
// internal helper for RequestQueue.updatePayloads
|
|
2243
|
-
var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
2695
|
+
var updatePayloads = function (existingItems, itemsToUpdate) {
|
|
2244
2696
|
var newItems = [];
|
|
2245
|
-
_.each(existingItems, function(item) {
|
|
2697
|
+
_.each(existingItems, function (item) {
|
|
2246
2698
|
var id = item['id'];
|
|
2247
2699
|
if (id in itemsToUpdate) {
|
|
2248
2700
|
var newPayload = itemsToUpdate[id];
|
|
@@ -2262,79 +2714,95 @@ var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
|
2262
2714
|
* Update payloads of given items in both in-memory queue and
|
|
2263
2715
|
* persisted queue. Items set to null are removed from queues.
|
|
2264
2716
|
*/
|
|
2265
|
-
RequestQueue.prototype.updatePayloads = function(itemsToUpdate
|
|
2717
|
+
RequestQueue.prototype.updatePayloads = function (itemsToUpdate) {
|
|
2266
2718
|
this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
|
|
2267
2719
|
if (!this.usePersistence) {
|
|
2268
|
-
|
|
2269
|
-
cb(true);
|
|
2270
|
-
}
|
|
2720
|
+
return PromisePolyfill.resolve(true);
|
|
2271
2721
|
} else {
|
|
2272
|
-
this.lock
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
this
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2722
|
+
return this.lock
|
|
2723
|
+
.withLock(_.bind(function lockAcquired() {
|
|
2724
|
+
return this.ensureInit()
|
|
2725
|
+
.then(_.bind(function () {
|
|
2726
|
+
return this.readFromStorage();
|
|
2727
|
+
}, this))
|
|
2728
|
+
.then(_.bind(function (storedQueue) {
|
|
2729
|
+
storedQueue = updatePayloads(storedQueue, itemsToUpdate);
|
|
2730
|
+
return this.saveToStorage(storedQueue);
|
|
2731
|
+
}, this))
|
|
2732
|
+
.catch(_.bind(function (err) {
|
|
2733
|
+
this.reportError('Error updating items', itemsToUpdate, err);
|
|
2734
|
+
return false;
|
|
2735
|
+
}, this));
|
|
2736
|
+
}, this), this.pid)
|
|
2737
|
+
.catch(_.bind(function (err) {
|
|
2738
|
+
this.reportError('Error acquiring storage lock', err);
|
|
2739
|
+
return false;
|
|
2740
|
+
}, this));
|
|
2291
2741
|
}
|
|
2292
|
-
|
|
2293
2742
|
};
|
|
2294
2743
|
|
|
2295
2744
|
/**
|
|
2296
2745
|
* Read and parse items array from localStorage entry, handling
|
|
2297
2746
|
* malformed/missing data if necessary.
|
|
2298
2747
|
*/
|
|
2299
|
-
RequestQueue.prototype.readFromStorage = function() {
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
if (
|
|
2306
|
-
|
|
2307
|
-
storageEntry
|
|
2748
|
+
RequestQueue.prototype.readFromStorage = function () {
|
|
2749
|
+
return this.ensureInit()
|
|
2750
|
+
.then(_.bind(function () {
|
|
2751
|
+
return this.queueStorage.getItem(this.storageKey);
|
|
2752
|
+
}, this))
|
|
2753
|
+
.then(_.bind(function (storageEntry) {
|
|
2754
|
+
if (storageEntry) {
|
|
2755
|
+
storageEntry = JSONParse(storageEntry);
|
|
2756
|
+
if (!_.isArray(storageEntry)) {
|
|
2757
|
+
this.reportError('Invalid storage entry:', storageEntry);
|
|
2758
|
+
storageEntry = null;
|
|
2759
|
+
}
|
|
2308
2760
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2761
|
+
return storageEntry || [];
|
|
2762
|
+
}, this))
|
|
2763
|
+
.catch(_.bind(function (err) {
|
|
2764
|
+
this.reportError('Error retrieving queue', err);
|
|
2765
|
+
return [];
|
|
2766
|
+
}, this));
|
|
2315
2767
|
};
|
|
2316
2768
|
|
|
2317
2769
|
/**
|
|
2318
2770
|
* Serialize the given items array to localStorage.
|
|
2319
2771
|
*/
|
|
2320
|
-
RequestQueue.prototype.saveToStorage = function(queue) {
|
|
2772
|
+
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
2321
2773
|
try {
|
|
2322
|
-
|
|
2323
|
-
return true;
|
|
2774
|
+
var serialized = JSONStringify(queue);
|
|
2324
2775
|
} catch (err) {
|
|
2325
|
-
this.reportError('Error
|
|
2326
|
-
return false;
|
|
2776
|
+
this.reportError('Error serializing queue', err);
|
|
2777
|
+
return PromisePolyfill.resolve(false);
|
|
2327
2778
|
}
|
|
2779
|
+
|
|
2780
|
+
return this.ensureInit()
|
|
2781
|
+
.then(_.bind(function () {
|
|
2782
|
+
return this.queueStorage.setItem(this.storageKey, serialized);
|
|
2783
|
+
}, this))
|
|
2784
|
+
.then(function () {
|
|
2785
|
+
return true;
|
|
2786
|
+
})
|
|
2787
|
+
.catch(_.bind(function (err) {
|
|
2788
|
+
this.reportError('Error saving queue', err);
|
|
2789
|
+
return false;
|
|
2790
|
+
}, this));
|
|
2328
2791
|
};
|
|
2329
2792
|
|
|
2330
2793
|
/**
|
|
2331
2794
|
* Clear out queues (memory and localStorage).
|
|
2332
2795
|
*/
|
|
2333
|
-
RequestQueue.prototype.clear = function() {
|
|
2796
|
+
RequestQueue.prototype.clear = function () {
|
|
2334
2797
|
this.memQueue = [];
|
|
2335
2798
|
|
|
2336
2799
|
if (this.usePersistence) {
|
|
2337
|
-
this.
|
|
2800
|
+
return this.ensureInit()
|
|
2801
|
+
.then(_.bind(function () {
|
|
2802
|
+
return this.queueStorage.removeItem(this.storageKey);
|
|
2803
|
+
}, this));
|
|
2804
|
+
} else {
|
|
2805
|
+
return PromisePolyfill.resolve();
|
|
2338
2806
|
}
|
|
2339
2807
|
};
|
|
2340
2808
|
|
|
@@ -2353,7 +2821,8 @@ var RequestBatcher = function(storageKey, options) {
|
|
|
2353
2821
|
this.errorReporter = options.errorReporter;
|
|
2354
2822
|
this.queue = new RequestQueue(storageKey, {
|
|
2355
2823
|
errorReporter: _.bind(this.reportError, this),
|
|
2356
|
-
|
|
2824
|
+
queueStorage: options.queueStorage,
|
|
2825
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
2357
2826
|
usePersistence: options.usePersistence
|
|
2358
2827
|
});
|
|
2359
2828
|
|
|
@@ -2381,8 +2850,8 @@ var RequestBatcher = function(storageKey, options) {
|
|
|
2381
2850
|
/**
|
|
2382
2851
|
* Add one item to queue.
|
|
2383
2852
|
*/
|
|
2384
|
-
RequestBatcher.prototype.enqueue = function(item
|
|
2385
|
-
this.queue.enqueue(item, this.flushInterval
|
|
2853
|
+
RequestBatcher.prototype.enqueue = function(item) {
|
|
2854
|
+
return this.queue.enqueue(item, this.flushInterval);
|
|
2386
2855
|
};
|
|
2387
2856
|
|
|
2388
2857
|
/**
|
|
@@ -2392,7 +2861,7 @@ RequestBatcher.prototype.enqueue = function(item, cb) {
|
|
|
2392
2861
|
RequestBatcher.prototype.start = function() {
|
|
2393
2862
|
this.stopped = false;
|
|
2394
2863
|
this.consecutiveRemovalFailures = 0;
|
|
2395
|
-
this.flush();
|
|
2864
|
+
return this.flush();
|
|
2396
2865
|
};
|
|
2397
2866
|
|
|
2398
2867
|
/**
|
|
@@ -2410,7 +2879,7 @@ RequestBatcher.prototype.stop = function() {
|
|
|
2410
2879
|
* Clear out queue.
|
|
2411
2880
|
*/
|
|
2412
2881
|
RequestBatcher.prototype.clear = function() {
|
|
2413
|
-
this.queue.clear();
|
|
2882
|
+
return this.queue.clear();
|
|
2414
2883
|
};
|
|
2415
2884
|
|
|
2416
2885
|
/**
|
|
@@ -2441,6 +2910,17 @@ RequestBatcher.prototype.scheduleFlush = function(flushMS) {
|
|
|
2441
2910
|
}
|
|
2442
2911
|
};
|
|
2443
2912
|
|
|
2913
|
+
/**
|
|
2914
|
+
* Send a request using the sendRequest callback, but promisified.
|
|
2915
|
+
* TODO: sendRequest should be promisified in the first place.
|
|
2916
|
+
*/
|
|
2917
|
+
RequestBatcher.prototype.sendRequestPromise = function(data, options) {
|
|
2918
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
2919
|
+
this.sendRequest(data, options, resolve);
|
|
2920
|
+
}, this));
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
|
|
2444
2924
|
/**
|
|
2445
2925
|
* Flush one batch to network. Depending on success/failure modes, it will either
|
|
2446
2926
|
* remove the batch from the queue or leave it in for retry, and schedule the next
|
|
@@ -2452,183 +2932,191 @@ RequestBatcher.prototype.scheduleFlush = function(flushMS) {
|
|
|
2452
2932
|
* sendBeacon offers no callbacks or status indications)
|
|
2453
2933
|
*/
|
|
2454
2934
|
RequestBatcher.prototype.flush = function(options) {
|
|
2455
|
-
|
|
2935
|
+
if (this.requestInProgress) {
|
|
2936
|
+
logger.log('Flush: Request already in progress');
|
|
2937
|
+
return PromisePolyfill.resolve();
|
|
2938
|
+
}
|
|
2456
2939
|
|
|
2457
|
-
|
|
2458
|
-
logger.log('Flush: Request already in progress');
|
|
2459
|
-
return;
|
|
2460
|
-
}
|
|
2940
|
+
this.requestInProgress = true;
|
|
2461
2941
|
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
var
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
// sends each event (regardless of which version originally queued
|
|
2480
|
-
// it for sending)
|
|
2481
|
-
if (payload['event'] && payload['properties']) {
|
|
2482
|
-
payload['properties'] = _.extend(
|
|
2483
|
-
{},
|
|
2484
|
-
payload['properties'],
|
|
2485
|
-
{'mp_sent_by_lib_version': Config.LIB_VERSION}
|
|
2486
|
-
);
|
|
2942
|
+
options = options || {};
|
|
2943
|
+
var timeoutMS = this.libConfig['batch_request_timeout_ms'];
|
|
2944
|
+
var startTime = new Date().getTime();
|
|
2945
|
+
var currentBatchSize = this.batchSize;
|
|
2946
|
+
|
|
2947
|
+
return this.queue.fillBatch(currentBatchSize)
|
|
2948
|
+
.then(_.bind(function(batch) {
|
|
2949
|
+
|
|
2950
|
+
// if there's more items in the queue than the batch size, attempt
|
|
2951
|
+
// to flush again after the current batch is done.
|
|
2952
|
+
var attemptSecondaryFlush = batch.length === currentBatchSize;
|
|
2953
|
+
var dataForRequest = [];
|
|
2954
|
+
var transformedItems = {};
|
|
2955
|
+
_.each(batch, function(item) {
|
|
2956
|
+
var payload = item['payload'];
|
|
2957
|
+
if (this.beforeSendHook && !item.orphaned) {
|
|
2958
|
+
payload = this.beforeSendHook(payload);
|
|
2487
2959
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2960
|
+
if (payload) {
|
|
2961
|
+
// mp_sent_by_lib_version prop captures which lib version actually
|
|
2962
|
+
// sends each event (regardless of which version originally queued
|
|
2963
|
+
// it for sending)
|
|
2964
|
+
if (payload['event'] && payload['properties']) {
|
|
2965
|
+
payload['properties'] = _.extend(
|
|
2966
|
+
{},
|
|
2967
|
+
payload['properties'],
|
|
2968
|
+
{'mp_sent_by_lib_version': Config.LIB_VERSION}
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
var addPayload = true;
|
|
2972
|
+
var itemId = item['id'];
|
|
2973
|
+
if (itemId) {
|
|
2974
|
+
if ((this.itemIdsSentSuccessfully[itemId] || 0) > 5) {
|
|
2975
|
+
this.reportError('[dupe] item ID sent too many times, not sending', {
|
|
2976
|
+
item: item,
|
|
2977
|
+
batchSize: batch.length,
|
|
2978
|
+
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
2979
|
+
});
|
|
2980
|
+
addPayload = false;
|
|
2981
|
+
}
|
|
2982
|
+
} else {
|
|
2983
|
+
this.reportError('[dupe] found item with no ID', {item: item});
|
|
2498
2984
|
}
|
|
2499
|
-
} else {
|
|
2500
|
-
this.reportError('[dupe] found item with no ID', {item: item});
|
|
2501
|
-
}
|
|
2502
2985
|
|
|
2503
|
-
|
|
2504
|
-
|
|
2986
|
+
if (addPayload) {
|
|
2987
|
+
dataForRequest.push(payload);
|
|
2988
|
+
}
|
|
2505
2989
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
}, this);
|
|
2509
|
-
if (dataForRequest.length < 1) {
|
|
2510
|
-
this.resetFlush();
|
|
2511
|
-
return; // nothing to do
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
|
-
this.requestInProgress = true;
|
|
2515
|
-
|
|
2516
|
-
var batchSendCallback = _.bind(function(res) {
|
|
2517
|
-
this.requestInProgress = false;
|
|
2990
|
+
transformedItems[item['id']] = payload;
|
|
2991
|
+
}, this);
|
|
2518
2992
|
|
|
2519
|
-
|
|
2993
|
+
if (dataForRequest.length < 1) {
|
|
2994
|
+
this.requestInProgress = false;
|
|
2995
|
+
this.resetFlush();
|
|
2996
|
+
return PromisePolyfill.resolve(); // nothing to do
|
|
2997
|
+
}
|
|
2520
2998
|
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
this.queue.updatePayloads(transformedItems);
|
|
2528
|
-
} else if (
|
|
2529
|
-
_.isObject(res) &&
|
|
2530
|
-
res.error === 'timeout' &&
|
|
2531
|
-
new Date().getTime() - startTime >= timeoutMS
|
|
2532
|
-
) {
|
|
2533
|
-
this.reportError('Network timeout; retrying');
|
|
2534
|
-
this.flush();
|
|
2535
|
-
} else if (
|
|
2536
|
-
_.isObject(res) &&
|
|
2537
|
-
(
|
|
2538
|
-
res.httpStatusCode >= 500
|
|
2539
|
-
|| res.httpStatusCode === 429
|
|
2540
|
-
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
2541
|
-
|| res.error === 'timeout'
|
|
2999
|
+
var removeItemsFromQueue = _.bind(function () {
|
|
3000
|
+
return this.queue
|
|
3001
|
+
.removeItemsByID(
|
|
3002
|
+
_.map(batch, function (item) {
|
|
3003
|
+
return item['id'];
|
|
3004
|
+
})
|
|
2542
3005
|
)
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
|
|
2557
|
-
this.reportError('413 response; reducing batch size to ' + this.batchSize);
|
|
2558
|
-
this.resetFlush();
|
|
2559
|
-
} else {
|
|
2560
|
-
this.reportError('Single-event request too large; dropping', batch);
|
|
2561
|
-
this.resetBatchSize();
|
|
2562
|
-
removeItemsFromQueue = true;
|
|
2563
|
-
}
|
|
2564
|
-
} else {
|
|
2565
|
-
// successful network request+response; remove each item in batch from queue
|
|
2566
|
-
// (even if it was e.g. a 400, in which case retrying won't help)
|
|
2567
|
-
removeItemsFromQueue = true;
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
if (removeItemsFromQueue) {
|
|
2571
|
-
this.queue.removeItemsByID(
|
|
2572
|
-
_.map(batch, function(item) { return item['id']; }),
|
|
2573
|
-
_.bind(function(succeeded) {
|
|
2574
|
-
if (succeeded) {
|
|
2575
|
-
this.consecutiveRemovalFailures = 0;
|
|
2576
|
-
if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
|
|
2577
|
-
this.resetFlush(); // schedule next batch with a delay
|
|
2578
|
-
} else {
|
|
2579
|
-
this.flush(); // handle next batch if the queue isn't empty
|
|
3006
|
+
.then(_.bind(function (succeeded) {
|
|
3007
|
+
// client-side dedupe
|
|
3008
|
+
_.each(batch, _.bind(function(item) {
|
|
3009
|
+
var itemId = item['id'];
|
|
3010
|
+
if (itemId) {
|
|
3011
|
+
this.itemIdsSentSuccessfully[itemId] = this.itemIdsSentSuccessfully[itemId] || 0;
|
|
3012
|
+
this.itemIdsSentSuccessfully[itemId]++;
|
|
3013
|
+
if (this.itemIdsSentSuccessfully[itemId] > 5) {
|
|
3014
|
+
this.reportError('[dupe] item ID sent too many times', {
|
|
3015
|
+
item: item,
|
|
3016
|
+
batchSize: batch.length,
|
|
3017
|
+
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
3018
|
+
});
|
|
2580
3019
|
}
|
|
2581
3020
|
} else {
|
|
2582
|
-
this.reportError('
|
|
2583
|
-
if (++this.consecutiveRemovalFailures > 5) {
|
|
2584
|
-
this.reportError('Too many queue failures; disabling batching system.');
|
|
2585
|
-
this.stopAllBatching();
|
|
2586
|
-
} else {
|
|
2587
|
-
this.resetFlush();
|
|
2588
|
-
}
|
|
3021
|
+
this.reportError('[dupe] found item with no ID while removing', {item: item});
|
|
2589
3022
|
}
|
|
2590
|
-
}, this)
|
|
2591
|
-
);
|
|
3023
|
+
}, this));
|
|
2592
3024
|
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
this.reportError('[dupe] item ID sent too many times', {
|
|
2601
|
-
item: item,
|
|
2602
|
-
batchSize: batch.length,
|
|
2603
|
-
timesSent: this.itemIdsSentSuccessfully[itemId]
|
|
2604
|
-
});
|
|
3025
|
+
if (succeeded) {
|
|
3026
|
+
this.consecutiveRemovalFailures = 0;
|
|
3027
|
+
if (this.flushOnlyOnInterval && !attemptSecondaryFlush) {
|
|
3028
|
+
this.resetFlush(); // schedule next batch with a delay
|
|
3029
|
+
return PromisePolyfill.resolve();
|
|
3030
|
+
} else {
|
|
3031
|
+
return this.flush(); // handle next batch if the queue isn't empty
|
|
2605
3032
|
}
|
|
2606
3033
|
} else {
|
|
2607
|
-
this.
|
|
3034
|
+
if (++this.consecutiveRemovalFailures > 5) {
|
|
3035
|
+
this.reportError('Too many queue failures; disabling batching system.');
|
|
3036
|
+
this.stopAllBatching();
|
|
3037
|
+
} else {
|
|
3038
|
+
this.resetFlush();
|
|
3039
|
+
}
|
|
3040
|
+
return PromisePolyfill.resolve();
|
|
2608
3041
|
}
|
|
2609
3042
|
}, this));
|
|
2610
|
-
|
|
3043
|
+
}, this);
|
|
2611
3044
|
|
|
2612
|
-
|
|
2613
|
-
this.
|
|
2614
|
-
|
|
3045
|
+
var batchSendCallback = _.bind(function(res) {
|
|
3046
|
+
this.requestInProgress = false;
|
|
3047
|
+
|
|
3048
|
+
try {
|
|
3049
|
+
|
|
3050
|
+
// handle API response in a try-catch to make sure we can reset the
|
|
3051
|
+
// flush operation if something goes wrong
|
|
3052
|
+
|
|
3053
|
+
if (options.unloading) {
|
|
3054
|
+
// update persisted data to include hook transformations
|
|
3055
|
+
return this.queue.updatePayloads(transformedItems);
|
|
3056
|
+
} else if (
|
|
3057
|
+
_.isObject(res) &&
|
|
3058
|
+
res.error === 'timeout' &&
|
|
3059
|
+
new Date().getTime() - startTime >= timeoutMS
|
|
3060
|
+
) {
|
|
3061
|
+
this.reportError('Network timeout; retrying');
|
|
3062
|
+
return this.flush();
|
|
3063
|
+
} else if (
|
|
3064
|
+
_.isObject(res) &&
|
|
3065
|
+
(
|
|
3066
|
+
res.httpStatusCode >= 500
|
|
3067
|
+
|| res.httpStatusCode === 429
|
|
3068
|
+
|| (res.httpStatusCode <= 0 && !isOnline())
|
|
3069
|
+
|| res.error === 'timeout'
|
|
3070
|
+
)
|
|
3071
|
+
) {
|
|
3072
|
+
// network or API error, or 429 Too Many Requests, retry
|
|
3073
|
+
var retryMS = this.flushInterval * 2;
|
|
3074
|
+
if (res.retryAfter) {
|
|
3075
|
+
retryMS = (parseInt(res.retryAfter, 10) * 1000) || retryMS;
|
|
3076
|
+
}
|
|
3077
|
+
retryMS = Math.min(MAX_RETRY_INTERVAL_MS, retryMS);
|
|
3078
|
+
this.reportError('Error; retry in ' + retryMS + ' ms');
|
|
3079
|
+
this.scheduleFlush(retryMS);
|
|
3080
|
+
return PromisePolyfill.resolve();
|
|
3081
|
+
} else if (_.isObject(res) && res.httpStatusCode === 413) {
|
|
3082
|
+
// 413 Payload Too Large
|
|
3083
|
+
if (batch.length > 1) {
|
|
3084
|
+
var halvedBatchSize = Math.max(1, Math.floor(currentBatchSize / 2));
|
|
3085
|
+
this.batchSize = Math.min(this.batchSize, halvedBatchSize, batch.length - 1);
|
|
3086
|
+
this.reportError('413 response; reducing batch size to ' + this.batchSize);
|
|
3087
|
+
this.resetFlush();
|
|
3088
|
+
return PromisePolyfill.resolve();
|
|
3089
|
+
} else {
|
|
3090
|
+
this.reportError('Single-event request too large; dropping', batch);
|
|
3091
|
+
this.resetBatchSize();
|
|
3092
|
+
return removeItemsFromQueue();
|
|
3093
|
+
}
|
|
3094
|
+
} else {
|
|
3095
|
+
// successful network request+response; remove each item in batch from queue
|
|
3096
|
+
// (even if it was e.g. a 400, in which case retrying won't help)
|
|
3097
|
+
return removeItemsFromQueue();
|
|
3098
|
+
}
|
|
3099
|
+
} catch(err) {
|
|
3100
|
+
this.reportError('Error handling API response', err);
|
|
3101
|
+
this.resetFlush();
|
|
3102
|
+
}
|
|
3103
|
+
}, this);
|
|
3104
|
+
var requestOptions = {
|
|
3105
|
+
method: 'POST',
|
|
3106
|
+
verbose: true,
|
|
3107
|
+
ignore_json_errors: true, // eslint-disable-line camelcase
|
|
3108
|
+
timeout_ms: timeoutMS // eslint-disable-line camelcase
|
|
3109
|
+
};
|
|
3110
|
+
if (options.unloading) {
|
|
3111
|
+
requestOptions.transport = 'sendBeacon';
|
|
2615
3112
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
};
|
|
2623
|
-
if (options.unloading) {
|
|
2624
|
-
requestOptions.transport = 'sendBeacon';
|
|
2625
|
-
}
|
|
2626
|
-
logger.log('MIXPANEL REQUEST:', dataForRequest);
|
|
2627
|
-
this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
|
|
2628
|
-
} catch(err) {
|
|
2629
|
-
this.reportError('Error flushing request queue', err);
|
|
2630
|
-
this.resetFlush();
|
|
2631
|
-
}
|
|
3113
|
+
logger.log('MIXPANEL REQUEST:', dataForRequest);
|
|
3114
|
+
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
3115
|
+
}, this))
|
|
3116
|
+
.catch(_.bind(function(err) {
|
|
3117
|
+
this.reportError('Error flushing request queue', err);
|
|
3118
|
+
this.resetFlush();
|
|
3119
|
+
}, this));
|
|
2632
3120
|
};
|
|
2633
3121
|
|
|
2634
3122
|
/**
|
|
@@ -5034,7 +5522,7 @@ MixpanelLib.prototype._track_or_batch = function(options, callback) {
|
|
|
5034
5522
|
}, this);
|
|
5035
5523
|
|
|
5036
5524
|
if (this._batch_requests && !should_send_immediately) {
|
|
5037
|
-
batcher.enqueue(truncated_data
|
|
5525
|
+
batcher.enqueue(truncated_data).then(function(succeeded) {
|
|
5038
5526
|
if (succeeded) {
|
|
5039
5527
|
callback(1, truncated_data);
|
|
5040
5528
|
} else {
|