mixpanel-browser 2.56.0 → 2.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +1 -15
- package/build.sh +3 -3
- package/dist/mixpanel-core.cjs.js +899 -410
- package/dist/mixpanel-recorder.js +909 -410
- package/dist/mixpanel-recorder.min.js +10 -10
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +899 -410
- package/dist/mixpanel.amd.js +911 -411
- package/dist/mixpanel.cjs.js +911 -411
- package/dist/mixpanel.globals.js +899 -410
- package/dist/mixpanel.min.js +122 -112
- package/dist/mixpanel.module.js +911 -411
- package/dist/mixpanel.umd.js +911 -411
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/externs.js +14 -0
- package/src/gdpr-utils.js +2 -1
- package/src/mixpanel-core.js +4 -2
- package/src/promise-polyfill.js +379 -0
- package/src/recorder/index.js +2 -1
- package/src/recorder/session-recording.js +14 -2
- package/src/request-batcher.js +185 -164
- package/src/request-queue.js +200 -147
- package/src/shared-lock.js +104 -98
- package/src/storage/local-storage.js +53 -0
- package/src/storage/wrapper.js +14 -0
- package/src/utils.js +16 -33
- package/src/window.js +20 -0
- package/dist/mixpanel.min.js.map +0 -8
package/src/request-queue.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SharedLock } from './shared-lock';
|
|
2
2
|
import { cheap_guid, console_with_prefix, localStorageSupported, JSONParse, JSONStringify, _ } from './utils'; // eslint-disable-line camelcase
|
|
3
|
+
import { LocalStorageWrapper } from './storage/local-storage';
|
|
4
|
+
import { Promise } from './promise-polyfill';
|
|
3
5
|
|
|
4
6
|
var logger = console_with_prefix('batch');
|
|
5
7
|
|
|
@@ -19,19 +21,38 @@ var logger = console_with_prefix('batch');
|
|
|
19
21
|
* to data loss in some situations).
|
|
20
22
|
* @constructor
|
|
21
23
|
*/
|
|
22
|
-
var RequestQueue = function(storageKey, options) {
|
|
24
|
+
var RequestQueue = function (storageKey, options) {
|
|
23
25
|
options = options || {};
|
|
24
26
|
this.storageKey = storageKey;
|
|
25
27
|
this.usePersistence = options.usePersistence;
|
|
26
28
|
if (this.usePersistence) {
|
|
27
|
-
this.
|
|
28
|
-
this.lock = new SharedLock(storageKey, {storage:
|
|
29
|
+
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
30
|
+
this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
|
|
31
|
+
this.queueStorage.init();
|
|
29
32
|
}
|
|
30
33
|
this.reportError = options.errorReporter || _.bind(logger.error, logger);
|
|
31
34
|
|
|
32
35
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
33
36
|
|
|
34
37
|
this.memQueue = [];
|
|
38
|
+
this.initialized = false;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
RequestQueue.prototype.ensureInit = function () {
|
|
42
|
+
if (this.initialized) {
|
|
43
|
+
return Promise.resolve();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this.queueStorage
|
|
47
|
+
.init()
|
|
48
|
+
.then(_.bind(function () {
|
|
49
|
+
this.initialized = true;
|
|
50
|
+
}, this))
|
|
51
|
+
.catch(_.bind(function (err) {
|
|
52
|
+
this.reportError('Error initializing queue persistence. Disabling persistence', err);
|
|
53
|
+
this.initialized = true;
|
|
54
|
+
this.usePersistence = false;
|
|
55
|
+
}, this));
|
|
35
56
|
};
|
|
36
57
|
|
|
37
58
|
/**
|
|
@@ -46,7 +67,7 @@ var RequestQueue = function(storageKey, options) {
|
|
|
46
67
|
* failure of the enqueue operation; it is asynchronous because the localStorage
|
|
47
68
|
* lock is asynchronous.
|
|
48
69
|
*/
|
|
49
|
-
RequestQueue.prototype.enqueue = function(item, flushInterval
|
|
70
|
+
RequestQueue.prototype.enqueue = function (item, flushInterval) {
|
|
50
71
|
var queueEntry = {
|
|
51
72
|
'id': cheap_guid(),
|
|
52
73
|
'flushAfter': new Date().getTime() + flushInterval * 2,
|
|
@@ -55,33 +76,37 @@ RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
|
|
|
55
76
|
|
|
56
77
|
if (!this.usePersistence) {
|
|
57
78
|
this.memQueue.push(queueEntry);
|
|
58
|
-
|
|
59
|
-
cb(true);
|
|
60
|
-
}
|
|
79
|
+
return Promise.resolve(true);
|
|
61
80
|
} else {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
|
|
82
|
+
var enqueueItem = _.bind(function () {
|
|
83
|
+
return this.ensureInit()
|
|
84
|
+
.then(_.bind(function () {
|
|
85
|
+
return this.readFromStorage();
|
|
86
|
+
}, this))
|
|
87
|
+
.then(_.bind(function (storedQueue) {
|
|
88
|
+
storedQueue.push(queueEntry);
|
|
89
|
+
return this.saveToStorage(storedQueue);
|
|
90
|
+
}, this))
|
|
91
|
+
.then(_.bind(function (succeeded) {
|
|
69
92
|
// only add to in-memory queue when storage succeeds
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}, this)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
if (succeeded) {
|
|
94
|
+
this.memQueue.push(queueEntry);
|
|
95
|
+
}
|
|
96
|
+
return succeeded;
|
|
97
|
+
}, this))
|
|
98
|
+
.catch(_.bind(function (err) {
|
|
99
|
+
this.reportError('Error enqueueing item', err, item);
|
|
100
|
+
return false;
|
|
101
|
+
}, this));
|
|
102
|
+
}, this);
|
|
103
|
+
|
|
104
|
+
return this.lock
|
|
105
|
+
.withLock(enqueueItem, this.pid)
|
|
106
|
+
.catch(_.bind(function (err) {
|
|
107
|
+
this.reportError('Error acquiring storage lock', err);
|
|
108
|
+
return false;
|
|
109
|
+
}, this));
|
|
85
110
|
}
|
|
86
111
|
};
|
|
87
112
|
|
|
@@ -91,31 +116,41 @@ RequestQueue.prototype.enqueue = function(item, flushInterval, cb) {
|
|
|
91
116
|
* in the persisted queue (items where the 'flushAfter' time has
|
|
92
117
|
* already passed).
|
|
93
118
|
*/
|
|
94
|
-
RequestQueue.prototype.fillBatch = function(batchSize) {
|
|
119
|
+
RequestQueue.prototype.fillBatch = function (batchSize) {
|
|
95
120
|
var batch = this.memQueue.slice(0, batchSize);
|
|
96
121
|
if (this.usePersistence && batch.length < batchSize) {
|
|
97
122
|
// don't need lock just to read events; localStorage is thread-safe
|
|
98
123
|
// and the worst that could happen is a duplicate send of some
|
|
99
124
|
// orphaned events, which will be deduplicated on the server side
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
_.
|
|
125
|
+
return this.ensureInit()
|
|
126
|
+
.then(_.bind(function () {
|
|
127
|
+
return this.readFromStorage();
|
|
128
|
+
}, this))
|
|
129
|
+
.then(_.bind(function (storedQueue) {
|
|
130
|
+
if (storedQueue.length) {
|
|
131
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
132
|
+
var idsInBatch = {}; // poor man's Set
|
|
133
|
+
_.each(batch, function (item) {
|
|
134
|
+
idsInBatch[item['id']] = true;
|
|
135
|
+
});
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
137
|
+
for (var i = 0; i < storedQueue.length; i++) {
|
|
138
|
+
var item = storedQueue[i];
|
|
139
|
+
if (new Date().getTime() > item['flushAfter'] && !idsInBatch[item['id']]) {
|
|
140
|
+
item.orphaned = true;
|
|
141
|
+
batch.push(item);
|
|
142
|
+
if (batch.length >= batchSize) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
113
146
|
}
|
|
114
147
|
}
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
|
|
149
|
+
return batch;
|
|
150
|
+
}, this));
|
|
151
|
+
} else {
|
|
152
|
+
return Promise.resolve(batch);
|
|
117
153
|
}
|
|
118
|
-
return batch;
|
|
119
154
|
};
|
|
120
155
|
|
|
121
156
|
/**
|
|
@@ -123,9 +158,9 @@ RequestQueue.prototype.fillBatch = function(batchSize) {
|
|
|
123
158
|
* also remove any item without a valid id (e.g., malformed
|
|
124
159
|
* storage entries).
|
|
125
160
|
*/
|
|
126
|
-
var filterOutIDsAndInvalid = function(items, idSet) {
|
|
161
|
+
var filterOutIDsAndInvalid = function (items, idSet) {
|
|
127
162
|
var filteredItems = [];
|
|
128
|
-
_.each(items, function(item) {
|
|
163
|
+
_.each(items, function (item) {
|
|
129
164
|
if (item['id'] && !idSet[item['id']]) {
|
|
130
165
|
filteredItems.push(item);
|
|
131
166
|
}
|
|
@@ -137,78 +172,80 @@ var filterOutIDsAndInvalid = function(items, idSet) {
|
|
|
137
172
|
* Remove items with matching IDs from both in-memory queue
|
|
138
173
|
* and persisted queue
|
|
139
174
|
*/
|
|
140
|
-
RequestQueue.prototype.removeItemsByID = function(ids
|
|
175
|
+
RequestQueue.prototype.removeItemsByID = function (ids) {
|
|
141
176
|
var idSet = {}; // poor man's Set
|
|
142
|
-
_.each(ids, function(id) {
|
|
177
|
+
_.each(ids, function (id) {
|
|
178
|
+
idSet[id] = true;
|
|
179
|
+
});
|
|
143
180
|
|
|
144
181
|
this.memQueue = filterOutIDsAndInvalid(this.memQueue, idSet);
|
|
145
182
|
if (!this.usePersistence) {
|
|
146
|
-
|
|
147
|
-
cb(true);
|
|
148
|
-
}
|
|
183
|
+
return Promise.resolve(true);
|
|
149
184
|
} else {
|
|
150
|
-
var removeFromStorage = _.bind(function() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
185
|
+
var removeFromStorage = _.bind(function () {
|
|
186
|
+
return this.ensureInit()
|
|
187
|
+
.then(_.bind(function () {
|
|
188
|
+
return this.readFromStorage();
|
|
189
|
+
}, this))
|
|
190
|
+
.then(_.bind(function (storedQueue) {
|
|
191
|
+
storedQueue = filterOutIDsAndInvalid(storedQueue, idSet);
|
|
192
|
+
return this.saveToStorage(storedQueue);
|
|
193
|
+
}, this))
|
|
194
|
+
.then(_.bind(function () {
|
|
195
|
+
return this.readFromStorage();
|
|
196
|
+
}, this))
|
|
197
|
+
.then(_.bind(function (storedQueue) {
|
|
198
|
+
// an extra check: did storage report success but somehow
|
|
199
|
+
// the items are still there?
|
|
161
200
|
for (var i = 0; i < storedQueue.length; i++) {
|
|
162
201
|
var item = storedQueue[i];
|
|
163
202
|
if (item['id'] && !!idSet[item['id']]) {
|
|
164
|
-
|
|
165
|
-
return false;
|
|
203
|
+
throw new Error('Item not removed from storage');
|
|
166
204
|
}
|
|
167
205
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
206
|
+
return true;
|
|
207
|
+
}, this))
|
|
208
|
+
.catch(_.bind(function (err) {
|
|
209
|
+
this.reportError('Error removing items', err, ids);
|
|
210
|
+
return false;
|
|
211
|
+
}, this));
|
|
174
212
|
}, this);
|
|
175
213
|
|
|
176
|
-
this.lock
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
214
|
+
return this.lock
|
|
215
|
+
.withLock(removeFromStorage, this.pid)
|
|
216
|
+
.catch(_.bind(function (err) {
|
|
217
|
+
this.reportError('Error acquiring storage lock', err);
|
|
218
|
+
if (!localStorageSupported(this.queueStorage.storage, true)) {
|
|
219
|
+
// Looks like localStorage writes have stopped working sometime after
|
|
220
|
+
// initialization (probably full), and so nobody can acquire locks
|
|
221
|
+
// anymore. Consider it temporarily safe to remove items without the
|
|
222
|
+
// lock, since nobody's writing successfully anyway.
|
|
223
|
+
return removeFromStorage()
|
|
224
|
+
.then(_.bind(function (success) {
|
|
225
|
+
if (!success) {
|
|
226
|
+
// OK, we couldn't even write out the smaller queue. Try clearing it
|
|
227
|
+
// entirely.
|
|
228
|
+
return this.queueStorage.removeItem(this.storageKey).then(function () {
|
|
229
|
+
return success;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return success;
|
|
233
|
+
}, this))
|
|
234
|
+
.catch(_.bind(function (err) {
|
|
235
|
+
this.reportError('Error clearing queue', err);
|
|
236
|
+
return false;
|
|
237
|
+
}, this));
|
|
238
|
+
} else {
|
|
239
|
+
return false;
|
|
198
240
|
}
|
|
199
|
-
}
|
|
200
|
-
if (cb) {
|
|
201
|
-
cb(succeeded);
|
|
202
|
-
}
|
|
203
|
-
}, this), this.pid);
|
|
241
|
+
}, this));
|
|
204
242
|
}
|
|
205
|
-
|
|
206
243
|
};
|
|
207
244
|
|
|
208
245
|
// internal helper for RequestQueue.updatePayloads
|
|
209
|
-
var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
246
|
+
var updatePayloads = function (existingItems, itemsToUpdate) {
|
|
210
247
|
var newItems = [];
|
|
211
|
-
_.each(existingItems, function(item) {
|
|
248
|
+
_.each(existingItems, function (item) {
|
|
212
249
|
var id = item['id'];
|
|
213
250
|
if (id in itemsToUpdate) {
|
|
214
251
|
var newPayload = itemsToUpdate[id];
|
|
@@ -228,79 +265,95 @@ var updatePayloads = function(existingItems, itemsToUpdate) {
|
|
|
228
265
|
* Update payloads of given items in both in-memory queue and
|
|
229
266
|
* persisted queue. Items set to null are removed from queues.
|
|
230
267
|
*/
|
|
231
|
-
RequestQueue.prototype.updatePayloads = function(itemsToUpdate
|
|
268
|
+
RequestQueue.prototype.updatePayloads = function (itemsToUpdate) {
|
|
232
269
|
this.memQueue = updatePayloads(this.memQueue, itemsToUpdate);
|
|
233
270
|
if (!this.usePersistence) {
|
|
234
|
-
|
|
235
|
-
cb(true);
|
|
236
|
-
}
|
|
271
|
+
return Promise.resolve(true);
|
|
237
272
|
} else {
|
|
238
|
-
this.lock
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
this
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
273
|
+
return this.lock
|
|
274
|
+
.withLock(_.bind(function lockAcquired() {
|
|
275
|
+
return this.ensureInit()
|
|
276
|
+
.then(_.bind(function () {
|
|
277
|
+
return this.readFromStorage();
|
|
278
|
+
}, this))
|
|
279
|
+
.then(_.bind(function (storedQueue) {
|
|
280
|
+
storedQueue = updatePayloads(storedQueue, itemsToUpdate);
|
|
281
|
+
return this.saveToStorage(storedQueue);
|
|
282
|
+
}, this))
|
|
283
|
+
.catch(_.bind(function (err) {
|
|
284
|
+
this.reportError('Error updating items', itemsToUpdate, err);
|
|
285
|
+
return false;
|
|
286
|
+
}, this));
|
|
287
|
+
}, this), this.pid)
|
|
288
|
+
.catch(_.bind(function (err) {
|
|
289
|
+
this.reportError('Error acquiring storage lock', err);
|
|
290
|
+
return false;
|
|
291
|
+
}, this));
|
|
257
292
|
}
|
|
258
|
-
|
|
259
293
|
};
|
|
260
294
|
|
|
261
295
|
/**
|
|
262
296
|
* Read and parse items array from localStorage entry, handling
|
|
263
297
|
* malformed/missing data if necessary.
|
|
264
298
|
*/
|
|
265
|
-
RequestQueue.prototype.readFromStorage = function() {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
storageEntry
|
|
299
|
+
RequestQueue.prototype.readFromStorage = function () {
|
|
300
|
+
return this.ensureInit()
|
|
301
|
+
.then(_.bind(function () {
|
|
302
|
+
return this.queueStorage.getItem(this.storageKey);
|
|
303
|
+
}, this))
|
|
304
|
+
.then(_.bind(function (storageEntry) {
|
|
305
|
+
if (storageEntry) {
|
|
306
|
+
storageEntry = JSONParse(storageEntry);
|
|
307
|
+
if (!_.isArray(storageEntry)) {
|
|
308
|
+
this.reportError('Invalid storage entry:', storageEntry);
|
|
309
|
+
storageEntry = null;
|
|
310
|
+
}
|
|
274
311
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
312
|
+
return storageEntry || [];
|
|
313
|
+
}, this))
|
|
314
|
+
.catch(_.bind(function (err) {
|
|
315
|
+
this.reportError('Error retrieving queue', err);
|
|
316
|
+
return [];
|
|
317
|
+
}, this));
|
|
281
318
|
};
|
|
282
319
|
|
|
283
320
|
/**
|
|
284
321
|
* Serialize the given items array to localStorage.
|
|
285
322
|
*/
|
|
286
|
-
RequestQueue.prototype.saveToStorage = function(queue) {
|
|
323
|
+
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
287
324
|
try {
|
|
288
|
-
|
|
289
|
-
return true;
|
|
325
|
+
var serialized = JSONStringify(queue);
|
|
290
326
|
} catch (err) {
|
|
291
|
-
this.reportError('Error
|
|
292
|
-
return false;
|
|
327
|
+
this.reportError('Error serializing queue', err);
|
|
328
|
+
return Promise.resolve(false);
|
|
293
329
|
}
|
|
330
|
+
|
|
331
|
+
return this.ensureInit()
|
|
332
|
+
.then(_.bind(function () {
|
|
333
|
+
return this.queueStorage.setItem(this.storageKey, serialized);
|
|
334
|
+
}, this))
|
|
335
|
+
.then(function () {
|
|
336
|
+
return true;
|
|
337
|
+
})
|
|
338
|
+
.catch(_.bind(function (err) {
|
|
339
|
+
this.reportError('Error saving queue', err);
|
|
340
|
+
return false;
|
|
341
|
+
}, this));
|
|
294
342
|
};
|
|
295
343
|
|
|
296
344
|
/**
|
|
297
345
|
* Clear out queues (memory and localStorage).
|
|
298
346
|
*/
|
|
299
|
-
RequestQueue.prototype.clear = function() {
|
|
347
|
+
RequestQueue.prototype.clear = function () {
|
|
300
348
|
this.memQueue = [];
|
|
301
349
|
|
|
302
350
|
if (this.usePersistence) {
|
|
303
|
-
this.
|
|
351
|
+
return this.ensureInit()
|
|
352
|
+
.then(_.bind(function () {
|
|
353
|
+
return this.queueStorage.removeItem(this.storageKey);
|
|
354
|
+
}, this));
|
|
355
|
+
} else {
|
|
356
|
+
return Promise.resolve();
|
|
304
357
|
}
|
|
305
358
|
};
|
|
306
359
|
|