mixpanel-browser 2.59.0 → 2.61.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 +5 -1
- package/README.md +3 -3
- package/dist/mixpanel-core.cjs.js +612 -176
- package/dist/mixpanel-recorder.js +670 -224
- package/dist/mixpanel-recorder.min.js +11 -11
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +612 -176
- package/dist/mixpanel.amd.js +1000 -290
- package/dist/mixpanel.cjs.js +1000 -290
- package/dist/mixpanel.globals.js +612 -176
- package/dist/mixpanel.min.js +143 -134
- package/dist/mixpanel.module.js +1000 -290
- package/dist/mixpanel.umd.js +1000 -290
- package/package.json +2 -1
- package/src/autocapture/index.js +80 -9
- package/src/autocapture/utils.js +129 -38
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +119 -19
- package/src/mixpanel-persistence.js +6 -2
- package/src/recorder/index.js +1 -70
- package/src/recorder/recorder.js +137 -0
- package/src/recorder/recording-registry.js +98 -0
- package/src/recorder/session-recording.js +162 -43
- package/src/recorder/utils.js +12 -0
- package/src/request-batcher.js +6 -2
- package/src/request-queue.js +45 -39
- package/src/shared-lock.js +1 -1
- package/src/storage/indexed-db.js +127 -0
- package/src/storage/local-storage.js +4 -8
- package/src/storage/wrapper.js +3 -3
- package/src/utils.js +99 -61
package/src/request-batcher.js
CHANGED
|
@@ -20,7 +20,9 @@ var RequestBatcher = function(storageKey, options) {
|
|
|
20
20
|
errorReporter: _.bind(this.reportError, this),
|
|
21
21
|
queueStorage: options.queueStorage,
|
|
22
22
|
sharedLockStorage: options.sharedLockStorage,
|
|
23
|
-
|
|
23
|
+
sharedLockTimeoutMS: options.sharedLockTimeoutMS,
|
|
24
|
+
usePersistence: options.usePersistence,
|
|
25
|
+
enqueueThrottleMs: options.enqueueThrottleMs
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
this.libConfig = options.libConfig;
|
|
@@ -42,6 +44,8 @@ var RequestBatcher = function(storageKey, options) {
|
|
|
42
44
|
// as long as the queue is not empty. This is useful for high-frequency events like Session Replay where we might end up
|
|
43
45
|
// in a request loop and get ratelimited by the server.
|
|
44
46
|
this.flushOnlyOnInterval = options.flushOnlyOnInterval || false;
|
|
47
|
+
|
|
48
|
+
this._flushPromise = null;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
/**
|
|
@@ -101,7 +105,7 @@ RequestBatcher.prototype.scheduleFlush = function(flushMS) {
|
|
|
101
105
|
if (!this.stopped) { // don't schedule anymore if batching has been stopped
|
|
102
106
|
this.timeoutID = setTimeout(_.bind(function() {
|
|
103
107
|
if (!this.stopped) {
|
|
104
|
-
this.flush();
|
|
108
|
+
this._flushPromise = this.flush();
|
|
105
109
|
}
|
|
106
110
|
}, this), this.flushInterval);
|
|
107
111
|
}
|
package/src/request-queue.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SharedLock } from './shared-lock';
|
|
2
|
-
import { cheap_guid, console_with_prefix, localStorageSupported,
|
|
2
|
+
import { batchedThrottle, cheap_guid, console_with_prefix, localStorageSupported, _ } from './utils'; // eslint-disable-line camelcase
|
|
3
|
+
import { window } from './window';
|
|
3
4
|
import { LocalStorageWrapper } from './storage/local-storage';
|
|
4
5
|
import { Promise } from './promise-polyfill';
|
|
5
6
|
|
|
@@ -27,8 +28,10 @@ var RequestQueue = function (storageKey, options) {
|
|
|
27
28
|
this.usePersistence = options.usePersistence;
|
|
28
29
|
if (this.usePersistence) {
|
|
29
30
|
this.queueStorage = options.queueStorage || new LocalStorageWrapper();
|
|
30
|
-
this.lock = new SharedLock(storageKey, {
|
|
31
|
-
|
|
31
|
+
this.lock = new SharedLock(storageKey, {
|
|
32
|
+
storage: options.sharedLockStorage || window.localStorage,
|
|
33
|
+
timeoutMS: options.sharedLockTimeoutMS,
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
this.reportError = options.errorReporter || _.bind(logger.error, logger);
|
|
34
37
|
|
|
@@ -36,6 +39,14 @@ var RequestQueue = function (storageKey, options) {
|
|
|
36
39
|
|
|
37
40
|
this.memQueue = [];
|
|
38
41
|
this.initialized = false;
|
|
42
|
+
|
|
43
|
+
if (options.enqueueThrottleMs) {
|
|
44
|
+
this.enqueuePersisted = batchedThrottle(_.bind(this._enqueuePersisted, this), options.enqueueThrottleMs);
|
|
45
|
+
} else {
|
|
46
|
+
this.enqueuePersisted = _.bind(function (queueEntry) {
|
|
47
|
+
return this._enqueuePersisted([queueEntry]);
|
|
48
|
+
}, this);
|
|
49
|
+
}
|
|
39
50
|
};
|
|
40
51
|
|
|
41
52
|
RequestQueue.prototype.ensureInit = function () {
|
|
@@ -78,36 +89,39 @@ RequestQueue.prototype.enqueue = function (item, flushInterval) {
|
|
|
78
89
|
this.memQueue.push(queueEntry);
|
|
79
90
|
return Promise.resolve(true);
|
|
80
91
|
} else {
|
|
92
|
+
return this.enqueuePersisted(queueEntry);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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);
|
|
96
|
+
RequestQueue.prototype._enqueuePersisted = function (queueEntries) {
|
|
97
|
+
var enqueueItem = _.bind(function () {
|
|
98
|
+
return this.ensureInit()
|
|
99
|
+
.then(_.bind(function () {
|
|
100
|
+
return this.readFromStorage();
|
|
101
|
+
}, this))
|
|
102
|
+
.then(_.bind(function (storedQueue) {
|
|
103
|
+
return this.saveToStorage(storedQueue.concat(queueEntries));
|
|
104
|
+
}, this))
|
|
105
|
+
.then(_.bind(function (succeeded) {
|
|
106
|
+
// only add to in-memory queue when storage succeeds
|
|
107
|
+
if (succeeded) {
|
|
108
|
+
this.memQueue = this.memQueue.concat(queueEntries);
|
|
109
|
+
}
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
return succeeded;
|
|
112
|
+
}, this))
|
|
106
113
|
.catch(_.bind(function (err) {
|
|
107
|
-
this.reportError('Error
|
|
114
|
+
this.reportError('Error enqueueing items', err, queueEntries);
|
|
108
115
|
return false;
|
|
109
116
|
}, this));
|
|
110
|
-
}
|
|
117
|
+
}, this);
|
|
118
|
+
|
|
119
|
+
return this.lock
|
|
120
|
+
.withLock(enqueueItem, this.pid)
|
|
121
|
+
.catch(_.bind(function (err) {
|
|
122
|
+
this.reportError('Error acquiring storage lock', err);
|
|
123
|
+
return false;
|
|
124
|
+
}, this));
|
|
111
125
|
};
|
|
112
126
|
|
|
113
127
|
/**
|
|
@@ -128,7 +142,7 @@ RequestQueue.prototype.fillBatch = function (batchSize) {
|
|
|
128
142
|
}, this))
|
|
129
143
|
.then(_.bind(function (storedQueue) {
|
|
130
144
|
if (storedQueue.length) {
|
|
131
|
-
|
|
145
|
+
// item IDs already in batch; don't duplicate out of storage
|
|
132
146
|
var idsInBatch = {}; // poor man's Set
|
|
133
147
|
_.each(batch, function (item) {
|
|
134
148
|
idsInBatch[item['id']] = true;
|
|
@@ -215,7 +229,7 @@ RequestQueue.prototype.removeItemsByID = function (ids) {
|
|
|
215
229
|
.withLock(removeFromStorage, this.pid)
|
|
216
230
|
.catch(_.bind(function (err) {
|
|
217
231
|
this.reportError('Error acquiring storage lock', err);
|
|
218
|
-
if (!localStorageSupported(this.
|
|
232
|
+
if (!localStorageSupported(this.lock.storage, true)) {
|
|
219
233
|
// Looks like localStorage writes have stopped working sometime after
|
|
220
234
|
// initialization (probably full), and so nobody can acquire locks
|
|
221
235
|
// anymore. Consider it temporarily safe to remove items without the
|
|
@@ -303,7 +317,6 @@ RequestQueue.prototype.readFromStorage = function () {
|
|
|
303
317
|
}, this))
|
|
304
318
|
.then(_.bind(function (storageEntry) {
|
|
305
319
|
if (storageEntry) {
|
|
306
|
-
storageEntry = JSONParse(storageEntry);
|
|
307
320
|
if (!_.isArray(storageEntry)) {
|
|
308
321
|
this.reportError('Invalid storage entry:', storageEntry);
|
|
309
322
|
storageEntry = null;
|
|
@@ -321,16 +334,9 @@ RequestQueue.prototype.readFromStorage = function () {
|
|
|
321
334
|
* Serialize the given items array to localStorage.
|
|
322
335
|
*/
|
|
323
336
|
RequestQueue.prototype.saveToStorage = function (queue) {
|
|
324
|
-
try {
|
|
325
|
-
var serialized = JSONStringify(queue);
|
|
326
|
-
} catch (err) {
|
|
327
|
-
this.reportError('Error serializing queue', err);
|
|
328
|
-
return Promise.resolve(false);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
337
|
return this.ensureInit()
|
|
332
338
|
.then(_.bind(function () {
|
|
333
|
-
return this.queueStorage.setItem(this.storageKey,
|
|
339
|
+
return this.queueStorage.setItem(this.storageKey, queue);
|
|
334
340
|
}, this))
|
|
335
341
|
.then(function () {
|
|
336
342
|
return true;
|
package/src/shared-lock.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Promise } from './promise-polyfill';
|
|
2
2
|
import { console_with_prefix, localStorageSupported, _ } from './utils'; // eslint-disable-line camelcase
|
|
3
|
+
import { window } from './window';
|
|
3
4
|
|
|
4
5
|
var logger = console_with_prefix('lock');
|
|
5
6
|
|
|
@@ -42,7 +43,6 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
|
42
43
|
return new Promise(_.bind(function (resolve, reject) {
|
|
43
44
|
var i = pid || (new Date().getTime() + '|' + Math.random());
|
|
44
45
|
var startTime = new Date().getTime();
|
|
45
|
-
|
|
46
46
|
var key = this.storageKey;
|
|
47
47
|
var pollIntervalMS = this.pollIntervalMS;
|
|
48
48
|
var timeoutMS = this.timeoutMS;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Promise } from '../promise-polyfill';
|
|
2
|
+
import { window } from '../window';
|
|
3
|
+
|
|
4
|
+
var MIXPANEL_DB_NAME = 'mixpanelBrowserDb';
|
|
5
|
+
|
|
6
|
+
var RECORDING_EVENTS_STORE_NAME = 'mixpanelRecordingEvents';
|
|
7
|
+
var RECORDING_REGISTRY_STORE_NAME = 'mixpanelRecordingRegistry';
|
|
8
|
+
|
|
9
|
+
// note: increment the version number when adding new object stores
|
|
10
|
+
var DB_VERSION = 1;
|
|
11
|
+
var OBJECT_STORES = [RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
15
|
+
*/
|
|
16
|
+
var IDBStorageWrapper = function (storeName) {
|
|
17
|
+
/**
|
|
18
|
+
* @type {Promise<IDBDatabase>|null}
|
|
19
|
+
*/
|
|
20
|
+
this.dbPromise = null;
|
|
21
|
+
this.storeName = storeName;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
IDBStorageWrapper.prototype._openDb = function () {
|
|
25
|
+
return new Promise(function (resolve, reject) {
|
|
26
|
+
var openRequest = window.indexedDB.open(MIXPANEL_DB_NAME, DB_VERSION);
|
|
27
|
+
openRequest['onerror'] = function () {
|
|
28
|
+
reject(openRequest.error);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
openRequest['onsuccess'] = function () {
|
|
32
|
+
resolve(openRequest.result);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
openRequest['onupgradeneeded'] = function (ev) {
|
|
36
|
+
var db = ev.target.result;
|
|
37
|
+
|
|
38
|
+
OBJECT_STORES.forEach(function (storeName) {
|
|
39
|
+
db.createObjectStore(storeName);
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
IDBStorageWrapper.prototype.init = function () {
|
|
46
|
+
if (!window.indexedDB) {
|
|
47
|
+
return Promise.reject('indexedDB is not supported in this browser');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!this.dbPromise) {
|
|
51
|
+
this.dbPromise = this._openDb();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.dbPromise
|
|
55
|
+
.then(function (dbOrError) {
|
|
56
|
+
if (dbOrError instanceof window['IDBDatabase']) {
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
} else {
|
|
59
|
+
return Promise.reject(dbOrError);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {IDBTransactionMode} mode
|
|
66
|
+
* @param {function(IDBObjectStore): void} storeCb
|
|
67
|
+
*/
|
|
68
|
+
IDBStorageWrapper.prototype.makeTransaction = function (mode, storeCb) {
|
|
69
|
+
var storeName = this.storeName;
|
|
70
|
+
var doTransaction = function (db) {
|
|
71
|
+
return new Promise(function (resolve, reject) {
|
|
72
|
+
var transaction = db.transaction(storeName, mode);
|
|
73
|
+
transaction.oncomplete = function () {
|
|
74
|
+
resolve(transaction);
|
|
75
|
+
};
|
|
76
|
+
transaction.onabort = transaction.onerror = function () {
|
|
77
|
+
reject(transaction.error);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
storeCb(transaction.objectStore(storeName));
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return this.dbPromise
|
|
85
|
+
.then(doTransaction)
|
|
86
|
+
.catch(function (err) {
|
|
87
|
+
if (err['name'] === 'InvalidStateError') {
|
|
88
|
+
// try reopening the DB if the connection is closed
|
|
89
|
+
this.dbPromise = this._openDb();
|
|
90
|
+
return this.dbPromise.then(doTransaction);
|
|
91
|
+
} else {
|
|
92
|
+
return Promise.reject(err);
|
|
93
|
+
}
|
|
94
|
+
}.bind(this));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
IDBStorageWrapper.prototype.setItem = function (key, value) {
|
|
98
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
99
|
+
objectStore.put(value, key);
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
IDBStorageWrapper.prototype.getItem = function (key) {
|
|
104
|
+
var req;
|
|
105
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
106
|
+
req = objectStore.get(key);
|
|
107
|
+
}).then(function () {
|
|
108
|
+
return req.result;
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
IDBStorageWrapper.prototype.removeItem = function (key) {
|
|
113
|
+
return this.makeTransaction('readwrite', function (objectStore) {
|
|
114
|
+
objectStore.delete(key);
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
IDBStorageWrapper.prototype.getAll = function () {
|
|
119
|
+
var req;
|
|
120
|
+
return this.makeTransaction('readonly', function (objectStore) {
|
|
121
|
+
req = objectStore.getAll();
|
|
122
|
+
}).then(function () {
|
|
123
|
+
return req.result;
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export { IDBStorageWrapper, RECORDING_EVENTS_STORE_NAME, RECORDING_REGISTRY_STORE_NAME };
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { Promise } from '../promise-polyfill';
|
|
2
|
-
import { _ } from '../utils'; // eslint-disable-line camelcase
|
|
2
|
+
import { _, JSONParse, JSONStringify } from '../utils'; // eslint-disable-line camelcase
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @type {StorageWrapper}
|
|
5
|
+
* @type {import('./wrapper').StorageWrapper}
|
|
10
6
|
*/
|
|
11
7
|
var LocalStorageWrapper = function (storageOverride) {
|
|
12
8
|
this.storage = storageOverride || localStorage;
|
|
@@ -19,7 +15,7 @@ LocalStorageWrapper.prototype.init = function () {
|
|
|
19
15
|
LocalStorageWrapper.prototype.setItem = function (key, value) {
|
|
20
16
|
return new Promise(_.bind(function (resolve, reject) {
|
|
21
17
|
try {
|
|
22
|
-
this.storage.setItem(key, value);
|
|
18
|
+
this.storage.setItem(key, JSONStringify(value));
|
|
23
19
|
} catch (e) {
|
|
24
20
|
reject(e);
|
|
25
21
|
}
|
|
@@ -31,7 +27,7 @@ LocalStorageWrapper.prototype.getItem = function (key) {
|
|
|
31
27
|
return new Promise(_.bind(function (resolve, reject) {
|
|
32
28
|
var item;
|
|
33
29
|
try {
|
|
34
|
-
item = this.storage.getItem(key);
|
|
30
|
+
item = JSONParse(this.storage.getItem(key));
|
|
35
31
|
} catch (e) {
|
|
36
32
|
reject(e);
|
|
37
33
|
}
|
package/src/storage/wrapper.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {Object} StorageWrapper
|
|
8
8
|
* @property {function():Promise<void>} init - Initializes the wrapper, async storage like IDB needs to create a table and upgrade if needed.
|
|
9
|
-
* @property {function(string,
|
|
10
|
-
* @property {function(string):Promise<
|
|
11
|
-
* @property {function(string
|
|
9
|
+
* @property {function(string, any):Promise<void>} setItem - Sets an item in storage.
|
|
10
|
+
* @property {function(string):Promise<any>} getItem - Retrieves an item from storage.
|
|
11
|
+
* @property {function(string):Promise<void>} removeItem - Removes an item from storage.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
export { };
|
package/src/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
2
2
|
import Config from './config';
|
|
3
|
-
import { NpoPromise } from './promise-polyfill';
|
|
3
|
+
import { NpoPromise, Promise } from './promise-polyfill';
|
|
4
4
|
import { window } from './window';
|
|
5
5
|
|
|
6
6
|
// Maximum allowed session recording length
|
|
@@ -1080,15 +1080,9 @@ _.cookie = {
|
|
|
1080
1080
|
}
|
|
1081
1081
|
};
|
|
1082
1082
|
|
|
1083
|
-
var
|
|
1084
|
-
var localStorageSupported = function(storage, forceCheck) {
|
|
1085
|
-
if (_localStorageSupported !== null && !forceCheck) {
|
|
1086
|
-
return _localStorageSupported;
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1083
|
+
var _testStorageSupported = function (storage) {
|
|
1089
1084
|
var supported = true;
|
|
1090
1085
|
try {
|
|
1091
|
-
storage = storage || window.localStorage;
|
|
1092
1086
|
var key = '__mplss_' + cheap_guid(8),
|
|
1093
1087
|
val = 'xyz';
|
|
1094
1088
|
storage.setItem(key, val);
|
|
@@ -1099,59 +1093,74 @@ var localStorageSupported = function(storage, forceCheck) {
|
|
|
1099
1093
|
} catch (err) {
|
|
1100
1094
|
supported = false;
|
|
1101
1095
|
}
|
|
1102
|
-
|
|
1103
|
-
_localStorageSupported = supported;
|
|
1104
1096
|
return supported;
|
|
1105
1097
|
};
|
|
1106
1098
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
return supported;
|
|
1115
|
-
},
|
|
1116
|
-
|
|
1117
|
-
error: function(msg) {
|
|
1118
|
-
console.error('localStorage error: ' + msg);
|
|
1119
|
-
},
|
|
1099
|
+
var _localStorageSupported = null;
|
|
1100
|
+
var localStorageSupported = function(storage, forceCheck) {
|
|
1101
|
+
if (_localStorageSupported !== null && !forceCheck) {
|
|
1102
|
+
return _localStorageSupported;
|
|
1103
|
+
}
|
|
1104
|
+
return _localStorageSupported = _testStorageSupported(storage || window.localStorage);
|
|
1105
|
+
};
|
|
1120
1106
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
},
|
|
1107
|
+
var _sessionStorageSupported = null;
|
|
1108
|
+
var sessionStorageSupported = function(storage, forceCheck) {
|
|
1109
|
+
if (_sessionStorageSupported !== null && !forceCheck) {
|
|
1110
|
+
return _sessionStorageSupported;
|
|
1111
|
+
}
|
|
1112
|
+
return _sessionStorageSupported = _testStorageSupported(storage || window.sessionStorage);
|
|
1113
|
+
};
|
|
1129
1114
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
// noop
|
|
1135
|
-
}
|
|
1136
|
-
return null;
|
|
1137
|
-
},
|
|
1115
|
+
function _storageWrapper(storage, name, is_supported_fn) {
|
|
1116
|
+
var log_error = function(msg) {
|
|
1117
|
+
console.error(name + ' error: ' + msg);
|
|
1118
|
+
};
|
|
1138
1119
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1120
|
+
return {
|
|
1121
|
+
is_supported: function(forceCheck) {
|
|
1122
|
+
var supported = is_supported_fn(storage, forceCheck);
|
|
1123
|
+
if (!supported) {
|
|
1124
|
+
console.error(name + ' unsupported');
|
|
1125
|
+
}
|
|
1126
|
+
return supported;
|
|
1127
|
+
},
|
|
1128
|
+
error: log_error,
|
|
1129
|
+
get: function(key) {
|
|
1130
|
+
try {
|
|
1131
|
+
return storage.getItem(key);
|
|
1132
|
+
} catch (err) {
|
|
1133
|
+
log_error(err);
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
},
|
|
1137
|
+
parse: function(key) {
|
|
1138
|
+
try {
|
|
1139
|
+
return _.JSONDecode(storage.getItem(key)) || {};
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
// noop
|
|
1142
|
+
}
|
|
1143
|
+
return null;
|
|
1144
|
+
},
|
|
1145
|
+
set: function(key, value) {
|
|
1146
|
+
try {
|
|
1147
|
+
storage.setItem(key, value);
|
|
1148
|
+
} catch (err) {
|
|
1149
|
+
log_error(err);
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
remove: function(key) {
|
|
1153
|
+
try {
|
|
1154
|
+
storage.removeItem(key);
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
log_error(err);
|
|
1157
|
+
}
|
|
1144
1158
|
}
|
|
1145
|
-
}
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1146
1161
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
window.localStorage.removeItem(name);
|
|
1150
|
-
} catch (err) {
|
|
1151
|
-
_.localStorage.error(err);
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
};
|
|
1162
|
+
_.localStorage = _storageWrapper(window.localStorage, 'localStorage', localStorageSupported);
|
|
1163
|
+
_.sessionStorage = _storageWrapper(window.sessionStorage, 'sessionStorage', sessionStorageSupported);
|
|
1155
1164
|
|
|
1156
1165
|
_.register_event = (function() {
|
|
1157
1166
|
// written by Dean Edwards, 2005
|
|
@@ -1678,6 +1687,31 @@ _.info = {
|
|
|
1678
1687
|
}
|
|
1679
1688
|
};
|
|
1680
1689
|
|
|
1690
|
+
/**
|
|
1691
|
+
* Returns a throttled function that will only run at most every `waitMs` and returns a promise that resolves with the next invocation.
|
|
1692
|
+
* Throttled calls will build up a batch of args and invoke the callback with all args since the last invocation.
|
|
1693
|
+
*/
|
|
1694
|
+
var batchedThrottle = function (fn, waitMs) {
|
|
1695
|
+
var timeoutPromise = null;
|
|
1696
|
+
var throttledItems = [];
|
|
1697
|
+
return function (item) {
|
|
1698
|
+
var self = this;
|
|
1699
|
+
throttledItems.push(item);
|
|
1700
|
+
|
|
1701
|
+
if (!timeoutPromise) {
|
|
1702
|
+
timeoutPromise = new Promise(function (resolve) {
|
|
1703
|
+
setTimeout(function () {
|
|
1704
|
+
var returnValue = fn.apply(self, [throttledItems]);
|
|
1705
|
+
timeoutPromise = null;
|
|
1706
|
+
throttledItems = [];
|
|
1707
|
+
resolve(returnValue);
|
|
1708
|
+
}, waitMs);
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
return timeoutPromise;
|
|
1712
|
+
};
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1681
1715
|
var cheap_guid = function(maxlen) {
|
|
1682
1716
|
var guid = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
|
|
1683
1717
|
return maxlen ? guid.substring(0, maxlen) : guid;
|
|
@@ -1720,6 +1754,8 @@ var isOnline = function() {
|
|
|
1720
1754
|
return _.isUndefined(onLine) || onLine;
|
|
1721
1755
|
};
|
|
1722
1756
|
|
|
1757
|
+
var NOOP_FUNC = function () {};
|
|
1758
|
+
|
|
1723
1759
|
var JSONStringify = null, JSONParse = null;
|
|
1724
1760
|
if (typeof JSON !== 'undefined') {
|
|
1725
1761
|
JSONStringify = JSON.stringify;
|
|
@@ -1728,22 +1764,23 @@ if (typeof JSON !== 'undefined') {
|
|
|
1728
1764
|
JSONStringify = JSONStringify || _.JSONEncode;
|
|
1729
1765
|
JSONParse = JSONParse || _.JSONDecode;
|
|
1730
1766
|
|
|
1731
|
-
// EXPORTS (for closure compiler)
|
|
1732
|
-
_['toArray'] = _.toArray;
|
|
1733
|
-
_['isObject'] = _.isObject;
|
|
1734
|
-
_['JSONEncode'] = _.JSONEncode;
|
|
1735
|
-
_['JSONDecode'] = _.JSONDecode;
|
|
1736
|
-
_['isBlockedUA'] = _.isBlockedUA;
|
|
1737
|
-
_['isEmptyObject'] = _.isEmptyObject;
|
|
1767
|
+
// UNMINIFIED EXPORTS (for closure compiler)
|
|
1738
1768
|
_['info'] = _.info;
|
|
1739
|
-
_['info']['device'] = _.info.device;
|
|
1740
1769
|
_['info']['browser'] = _.info.browser;
|
|
1741
1770
|
_['info']['browserVersion'] = _.info.browserVersion;
|
|
1771
|
+
_['info']['device'] = _.info.device;
|
|
1742
1772
|
_['info']['properties'] = _.info.properties;
|
|
1773
|
+
_['isBlockedUA'] = _.isBlockedUA;
|
|
1774
|
+
_['isEmptyObject'] = _.isEmptyObject;
|
|
1775
|
+
_['isObject'] = _.isObject;
|
|
1776
|
+
_['JSONDecode'] = _.JSONDecode;
|
|
1777
|
+
_['JSONEncode'] = _.JSONEncode;
|
|
1778
|
+
_['toArray'] = _.toArray;
|
|
1743
1779
|
_['NPO'] = NpoPromise;
|
|
1744
1780
|
|
|
1745
1781
|
export {
|
|
1746
1782
|
_,
|
|
1783
|
+
batchedThrottle,
|
|
1747
1784
|
cheap_guid,
|
|
1748
1785
|
console_with_prefix,
|
|
1749
1786
|
console,
|
|
@@ -1756,6 +1793,7 @@ export {
|
|
|
1756
1793
|
MAX_RECORDING_MS,
|
|
1757
1794
|
MAX_VALUE_FOR_MIN_RECORDING_MS,
|
|
1758
1795
|
navigator,
|
|
1796
|
+
NOOP_FUNC,
|
|
1759
1797
|
safewrap,
|
|
1760
1798
|
safewrapClass,
|
|
1761
1799
|
slice,
|