@splitsoftware/splitio-commons 1.7.1 → 1.7.3-rc.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/CHANGES.txt +8 -0
- package/cjs/listeners/browser.js +9 -3
- package/cjs/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -1
- package/cjs/storages/inMemory/InMemoryStorage.js +14 -3
- package/cjs/storages/inMemory/InMemoryStorageCS.js +14 -3
- package/cjs/storages/inMemory/UniqueKeysCacheInMemory.js +3 -3
- package/cjs/storages/inMemory/UniqueKeysCacheInMemoryCS.js +3 -3
- package/cjs/storages/inRedis/UniqueKeysCacheInRedis.js +6 -8
- package/cjs/storages/pluggable/UniqueKeysCachePluggable.js +5 -4
- package/cjs/utils/settingsValidation/index.js +1 -1
- package/esm/listeners/browser.js +9 -3
- package/esm/storages/inMemory/ImpressionCountsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/InMemoryStorage.js +15 -4
- package/esm/storages/inMemory/InMemoryStorageCS.js +15 -4
- package/esm/storages/inMemory/UniqueKeysCacheInMemory.js +3 -3
- package/esm/storages/inMemory/UniqueKeysCacheInMemoryCS.js +3 -3
- package/esm/storages/inRedis/UniqueKeysCacheInRedis.js +6 -8
- package/esm/storages/pluggable/UniqueKeysCachePluggable.js +5 -4
- package/esm/utils/settingsValidation/index.js +1 -1
- package/package.json +1 -1
- package/src/listeners/browser.ts +8 -3
- package/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +1 -1
- package/src/storages/inMemory/InMemoryStorage.ts +15 -3
- package/src/storages/inMemory/InMemoryStorageCS.ts +15 -3
- package/src/storages/inMemory/UniqueKeysCacheInMemory.ts +3 -4
- package/src/storages/inMemory/UniqueKeysCacheInMemoryCS.ts +3 -4
- package/src/storages/inRedis/UniqueKeysCacheInRedis.ts +5 -7
- package/src/storages/pluggable/UniqueKeysCachePluggable.ts +8 -8
- package/src/utils/settingsValidation/index.ts +1 -1
- package/types/listeners/browser.d.ts +1 -0
- package/types/storages/pluggable/UniqueKeysCachePluggable.d.ts +2 -2
- package/src/logger/.DS_Store +0 -0
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
1.7.3 (December 16, 2022)
|
|
2
|
+
- Updated events and impressions cache in localhost mode in order to avoid memory leaks (Related to issue https://github.com/splitio/javascript-commons/issues/181).
|
|
3
|
+
- Updated unique keys cache for Redis and Pluggable storages to optimize the usage of the underlying storage.
|
|
4
|
+
- Updated some transitive dependencies for vulnerability fixes.
|
|
5
|
+
|
|
6
|
+
1.7.2 (October 14, 2022)
|
|
7
|
+
- Bugfixing - Handle `Navigator.sendBeacon` API exceptions in the browser, and fallback to regular Fetch/XHR transport in case of error.
|
|
8
|
+
|
|
1
9
|
1.7.1 (October 5, 2022)
|
|
2
10
|
- Updated default value of `scheduler.featuresRefreshRate` config parameter to 60 seconds.
|
|
3
11
|
|
package/cjs/listeners/browser.js
CHANGED
|
@@ -100,13 +100,14 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
100
100
|
if (!cache.isEmpty()) {
|
|
101
101
|
var dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
|
|
102
102
|
if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
|
|
103
|
-
postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op
|
|
103
|
+
postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op to handle possible promise rejection
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
107
|
/**
|
|
108
108
|
* _sendBeacon method.
|
|
109
109
|
* Util method that check if beacon API is available, build the payload and send it.
|
|
110
|
+
* Returns true if beacon API was used successfully, false otherwise.
|
|
110
111
|
*/
|
|
111
112
|
BrowserSignalListener.prototype._sendBeacon = function (url, data, extraMetadata) {
|
|
112
113
|
// eslint-disable-next-line compat/compat
|
|
@@ -121,8 +122,13 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
121
122
|
(0, objectAssign_1.objectAssign)(json, extraMetadata);
|
|
122
123
|
// Stringify the payload
|
|
123
124
|
var payload = JSON.stringify(json);
|
|
124
|
-
//
|
|
125
|
-
|
|
125
|
+
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
126
|
+
try { // eslint-disable-next-line compat/compat
|
|
127
|
+
return navigator.sendBeacon(url, payload);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
126
132
|
}
|
|
127
133
|
return false;
|
|
128
134
|
};
|
|
@@ -27,7 +27,6 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
27
27
|
this.cacheSize = this.cacheSize + amount;
|
|
28
28
|
if (this.cacheSize >= this.maxStorage) {
|
|
29
29
|
this.onFullQueue();
|
|
30
|
-
this.cacheSize = 0;
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
32
|
};
|
|
@@ -53,6 +52,7 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
53
52
|
*/
|
|
54
53
|
ImpressionCountsCacheInMemory.prototype.clear = function () {
|
|
55
54
|
this.cache = {};
|
|
55
|
+
this.cacheSize = 0;
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
58
|
* Check if the cache is empty.
|
|
@@ -18,7 +18,7 @@ function InMemoryStorageFactory(params) {
|
|
|
18
18
|
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, impressionsMode = _a.sync.impressionsMode;
|
|
19
19
|
var splits = new SplitsCacheInMemory_1.SplitsCacheInMemory();
|
|
20
20
|
var segments = new SegmentsCacheInMemory_1.SegmentsCacheInMemory();
|
|
21
|
-
|
|
21
|
+
var storage = {
|
|
22
22
|
splits: splits,
|
|
23
23
|
segments: segments,
|
|
24
24
|
impressions: new ImpressionsCacheInMemory_1.ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -28,15 +28,26 @@ function InMemoryStorageFactory(params) {
|
|
|
28
28
|
uniqueKeys: impressionsMode === constants_1.NONE ? new UniqueKeysCacheInMemory_1.UniqueKeysCacheInMemory() : undefined,
|
|
29
29
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
30
30
|
destroy: function () {
|
|
31
|
-
var _a;
|
|
32
31
|
this.splits.clear();
|
|
33
32
|
this.segments.clear();
|
|
34
33
|
this.impressions.clear();
|
|
35
34
|
this.impressionCounts && this.impressionCounts.clear();
|
|
36
35
|
this.events.clear();
|
|
37
|
-
|
|
36
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
38
37
|
}
|
|
39
38
|
};
|
|
39
|
+
// @TODO revisit storage logic in localhost mode
|
|
40
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
41
|
+
if (params.settings.mode === constants_1.LOCALHOST_MODE) {
|
|
42
|
+
var noopTrack = function () { return true; };
|
|
43
|
+
storage.impressions.track = noopTrack;
|
|
44
|
+
storage.events.track = noopTrack;
|
|
45
|
+
if (storage.impressionCounts)
|
|
46
|
+
storage.impressionCounts.track = noopTrack;
|
|
47
|
+
if (storage.uniqueKeys)
|
|
48
|
+
storage.uniqueKeys.track = noopTrack;
|
|
49
|
+
}
|
|
50
|
+
return storage;
|
|
40
51
|
}
|
|
41
52
|
exports.InMemoryStorageFactory = InMemoryStorageFactory;
|
|
42
53
|
InMemoryStorageFactory.type = constants_1.STORAGE_MEMORY;
|
|
@@ -18,7 +18,7 @@ function InMemoryStorageCSFactory(params) {
|
|
|
18
18
|
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, impressionsMode = _a.sync.impressionsMode;
|
|
19
19
|
var splits = new SplitsCacheInMemory_1.SplitsCacheInMemory();
|
|
20
20
|
var segments = new MySegmentsCacheInMemory_1.MySegmentsCacheInMemory();
|
|
21
|
-
|
|
21
|
+
var storage = {
|
|
22
22
|
splits: splits,
|
|
23
23
|
segments: segments,
|
|
24
24
|
impressions: new ImpressionsCacheInMemory_1.ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -28,13 +28,12 @@ function InMemoryStorageCSFactory(params) {
|
|
|
28
28
|
uniqueKeys: impressionsMode === constants_1.NONE ? new UniqueKeysCacheInMemoryCS_1.UniqueKeysCacheInMemoryCS() : undefined,
|
|
29
29
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
30
30
|
destroy: function () {
|
|
31
|
-
var _a;
|
|
32
31
|
this.splits.clear();
|
|
33
32
|
this.segments.clear();
|
|
34
33
|
this.impressions.clear();
|
|
35
34
|
this.impressionCounts && this.impressionCounts.clear();
|
|
36
35
|
this.events.clear();
|
|
37
|
-
|
|
36
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
38
37
|
},
|
|
39
38
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
40
39
|
shared: function () {
|
|
@@ -53,6 +52,18 @@ function InMemoryStorageCSFactory(params) {
|
|
|
53
52
|
};
|
|
54
53
|
},
|
|
55
54
|
};
|
|
55
|
+
// @TODO revisit storage logic in localhost mode
|
|
56
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
57
|
+
if (params.settings.mode === constants_1.LOCALHOST_MODE) {
|
|
58
|
+
var noopTrack = function () { return true; };
|
|
59
|
+
storage.impressions.track = noopTrack;
|
|
60
|
+
storage.events.track = noopTrack;
|
|
61
|
+
if (storage.impressionCounts)
|
|
62
|
+
storage.impressionCounts.track = noopTrack;
|
|
63
|
+
if (storage.uniqueKeys)
|
|
64
|
+
storage.uniqueKeys.track = noopTrack;
|
|
65
|
+
}
|
|
66
|
+
return storage;
|
|
56
67
|
}
|
|
57
68
|
exports.InMemoryStorageCSFactory = InMemoryStorageCSFactory;
|
|
58
69
|
InMemoryStorageCSFactory.type = constants_1.STORAGE_MEMORY;
|
|
@@ -25,8 +25,8 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
25
25
|
function UniqueKeysCacheInMemory(uniqueKeysQueueSize) {
|
|
26
26
|
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = constants_1.DEFAULT_CACHE_SIZE; }
|
|
27
27
|
this.uniqueTrackerSize = 0;
|
|
28
|
-
this.maxStorage = uniqueKeysQueueSize;
|
|
29
28
|
this.uniqueKeysTracker = {};
|
|
29
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
30
30
|
}
|
|
31
31
|
UniqueKeysCacheInMemory.prototype.setOnFullQueueCb = function (cb) {
|
|
32
32
|
this.onFullQueue = cb;
|
|
@@ -43,7 +43,6 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
43
43
|
this.uniqueTrackerSize++;
|
|
44
44
|
}
|
|
45
45
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
46
|
-
this.uniqueTrackerSize = 0;
|
|
47
46
|
this.onFullQueue();
|
|
48
47
|
}
|
|
49
48
|
};
|
|
@@ -51,6 +50,7 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
51
50
|
* Clear the data stored on the cache.
|
|
52
51
|
*/
|
|
53
52
|
UniqueKeysCacheInMemory.prototype.clear = function () {
|
|
53
|
+
this.uniqueTrackerSize = 0;
|
|
54
54
|
this.uniqueKeysTracker = {};
|
|
55
55
|
};
|
|
56
56
|
/**
|
|
@@ -58,7 +58,7 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
58
58
|
*/
|
|
59
59
|
UniqueKeysCacheInMemory.prototype.pop = function () {
|
|
60
60
|
var data = this.uniqueKeysTracker;
|
|
61
|
-
this.
|
|
61
|
+
this.clear();
|
|
62
62
|
return fromUniqueKeysCollector(data);
|
|
63
63
|
};
|
|
64
64
|
/**
|
|
@@ -12,8 +12,8 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
12
12
|
function UniqueKeysCacheInMemoryCS(uniqueKeysQueueSize) {
|
|
13
13
|
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = constants_1.DEFAULT_CACHE_SIZE; }
|
|
14
14
|
this.uniqueTrackerSize = 0;
|
|
15
|
-
this.maxStorage = uniqueKeysQueueSize;
|
|
16
15
|
this.uniqueKeysTracker = {};
|
|
16
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
17
17
|
}
|
|
18
18
|
UniqueKeysCacheInMemoryCS.prototype.setOnFullQueueCb = function (cb) {
|
|
19
19
|
this.onFullQueue = cb;
|
|
@@ -30,7 +30,6 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
30
30
|
this.uniqueTrackerSize++;
|
|
31
31
|
}
|
|
32
32
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
33
|
-
this.uniqueTrackerSize = 0;
|
|
34
33
|
this.onFullQueue();
|
|
35
34
|
}
|
|
36
35
|
};
|
|
@@ -38,6 +37,7 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
38
37
|
* Clear the data stored on the cache.
|
|
39
38
|
*/
|
|
40
39
|
UniqueKeysCacheInMemoryCS.prototype.clear = function () {
|
|
40
|
+
this.uniqueTrackerSize = 0;
|
|
41
41
|
this.uniqueKeysTracker = {};
|
|
42
42
|
};
|
|
43
43
|
/**
|
|
@@ -45,7 +45,7 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
45
45
|
*/
|
|
46
46
|
UniqueKeysCacheInMemoryCS.prototype.pop = function () {
|
|
47
47
|
var data = this.uniqueKeysTracker;
|
|
48
|
-
this.
|
|
48
|
+
this.clear();
|
|
49
49
|
return this.fromUniqueKeysCollector(data);
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
@@ -24,21 +24,19 @@ var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
|
|
|
24
24
|
var featureNames = Object.keys(this.uniqueKeysTracker);
|
|
25
25
|
if (!featureNames.length)
|
|
26
26
|
return Promise.resolve(false);
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
var featureName = featureNames[i];
|
|
30
|
-
var featureKeys = (0, sets_1.setToArray)(this.uniqueKeysTracker[featureName]);
|
|
27
|
+
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
28
|
+
var featureKeys = (0, sets_1.setToArray)(_this.uniqueKeysTracker[featureName]);
|
|
31
29
|
var uniqueKeysPayload = {
|
|
32
30
|
f: featureName,
|
|
33
31
|
ks: featureKeys
|
|
34
32
|
};
|
|
35
|
-
|
|
36
|
-
}
|
|
33
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
34
|
+
});
|
|
37
35
|
this.clear();
|
|
38
|
-
return
|
|
36
|
+
return this.redis.rpush(this.key, uniqueKeysArray)
|
|
39
37
|
.then(function (data) {
|
|
40
38
|
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
41
|
-
if (data
|
|
39
|
+
if (data === featureNames.length) {
|
|
42
40
|
return _this.redis.expire(_this.key, constants_1.TTL_REFRESH);
|
|
43
41
|
}
|
|
44
42
|
})
|
|
@@ -24,16 +24,17 @@ var UniqueKeysCachePluggable = /** @class */ (function (_super) {
|
|
|
24
24
|
var featureNames = Object.keys(this.uniqueKeysTracker);
|
|
25
25
|
if (!featureNames.length)
|
|
26
26
|
return Promise.resolve(false);
|
|
27
|
-
var
|
|
27
|
+
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
28
28
|
var featureKeys = (0, sets_1.setToArray)(_this.uniqueKeysTracker[featureName]);
|
|
29
29
|
var uniqueKeysPayload = {
|
|
30
30
|
f: featureName,
|
|
31
31
|
ks: featureKeys
|
|
32
32
|
};
|
|
33
|
-
return
|
|
34
|
-
}
|
|
33
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
34
|
+
});
|
|
35
35
|
this.clear();
|
|
36
|
-
return
|
|
36
|
+
return this.wrapper.pushItems(this.key, uniqueKeysArray)
|
|
37
|
+
.catch(function (err) {
|
|
37
38
|
_this.log.error(constants_2.LOG_PREFIX + "Error in uniqueKeys pipeline: " + err + ".");
|
|
38
39
|
return false;
|
|
39
40
|
});
|
|
@@ -48,7 +48,7 @@ exports.base = {
|
|
|
48
48
|
urls: {
|
|
49
49
|
// CDN having all the information for your environment
|
|
50
50
|
sdk: 'https://sdk.split.io/api',
|
|
51
|
-
//
|
|
51
|
+
// SDK event and impression endpoints
|
|
52
52
|
events: 'https://events.split.io/api',
|
|
53
53
|
// SDK Auth Server
|
|
54
54
|
auth: 'https://auth.split.io/api',
|
package/esm/listeners/browser.js
CHANGED
|
@@ -97,13 +97,14 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
97
97
|
if (!cache.isEmpty()) {
|
|
98
98
|
var dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
|
|
99
99
|
if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
|
|
100
|
-
postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op
|
|
100
|
+
postService(JSON.stringify(dataPayload)).catch(function () { }); // no-op to handle possible promise rejection
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
104
|
/**
|
|
105
105
|
* _sendBeacon method.
|
|
106
106
|
* Util method that check if beacon API is available, build the payload and send it.
|
|
107
|
+
* Returns true if beacon API was used successfully, false otherwise.
|
|
107
108
|
*/
|
|
108
109
|
BrowserSignalListener.prototype._sendBeacon = function (url, data, extraMetadata) {
|
|
109
110
|
// eslint-disable-next-line compat/compat
|
|
@@ -118,8 +119,13 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
118
119
|
objectAssign(json, extraMetadata);
|
|
119
120
|
// Stringify the payload
|
|
120
121
|
var payload = JSON.stringify(json);
|
|
121
|
-
//
|
|
122
|
-
|
|
122
|
+
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
123
|
+
try { // eslint-disable-next-line compat/compat
|
|
124
|
+
return navigator.sendBeacon(url, payload);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
123
129
|
}
|
|
124
130
|
return false;
|
|
125
131
|
};
|
|
@@ -24,7 +24,6 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
24
24
|
this.cacheSize = this.cacheSize + amount;
|
|
25
25
|
if (this.cacheSize >= this.maxStorage) {
|
|
26
26
|
this.onFullQueue();
|
|
27
|
-
this.cacheSize = 0;
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
};
|
|
@@ -50,6 +49,7 @@ var ImpressionCountsCacheInMemory = /** @class */ (function () {
|
|
|
50
49
|
*/
|
|
51
50
|
ImpressionCountsCacheInMemory.prototype.clear = function () {
|
|
52
51
|
this.cache = {};
|
|
52
|
+
this.cacheSize = 0;
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
55
55
|
* Check if the cache is empty.
|
|
@@ -3,7 +3,7 @@ import { SegmentsCacheInMemory } from './SegmentsCacheInMemory';
|
|
|
3
3
|
import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
6
|
-
import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
6
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
7
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
8
8
|
import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
|
|
9
9
|
/**
|
|
@@ -15,7 +15,7 @@ export function InMemoryStorageFactory(params) {
|
|
|
15
15
|
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, impressionsMode = _a.sync.impressionsMode;
|
|
16
16
|
var splits = new SplitsCacheInMemory();
|
|
17
17
|
var segments = new SegmentsCacheInMemory();
|
|
18
|
-
|
|
18
|
+
var storage = {
|
|
19
19
|
splits: splits,
|
|
20
20
|
segments: segments,
|
|
21
21
|
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -25,14 +25,25 @@ export function InMemoryStorageFactory(params) {
|
|
|
25
25
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
|
|
26
26
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
27
27
|
destroy: function () {
|
|
28
|
-
var _a;
|
|
29
28
|
this.splits.clear();
|
|
30
29
|
this.segments.clear();
|
|
31
30
|
this.impressions.clear();
|
|
32
31
|
this.impressionCounts && this.impressionCounts.clear();
|
|
33
32
|
this.events.clear();
|
|
34
|
-
|
|
33
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
35
34
|
}
|
|
36
35
|
};
|
|
36
|
+
// @TODO revisit storage logic in localhost mode
|
|
37
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
38
|
+
if (params.settings.mode === LOCALHOST_MODE) {
|
|
39
|
+
var noopTrack = function () { return true; };
|
|
40
|
+
storage.impressions.track = noopTrack;
|
|
41
|
+
storage.events.track = noopTrack;
|
|
42
|
+
if (storage.impressionCounts)
|
|
43
|
+
storage.impressionCounts.track = noopTrack;
|
|
44
|
+
if (storage.uniqueKeys)
|
|
45
|
+
storage.uniqueKeys.track = noopTrack;
|
|
46
|
+
}
|
|
47
|
+
return storage;
|
|
37
48
|
}
|
|
38
49
|
InMemoryStorageFactory.type = STORAGE_MEMORY;
|
|
@@ -3,7 +3,7 @@ import { MySegmentsCacheInMemory } from './MySegmentsCacheInMemory';
|
|
|
3
3
|
import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
6
|
-
import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
6
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
7
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
8
8
|
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
|
|
9
9
|
/**
|
|
@@ -15,7 +15,7 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
15
15
|
var _a = params.settings, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize, impressionsMode = _a.sync.impressionsMode;
|
|
16
16
|
var splits = new SplitsCacheInMemory();
|
|
17
17
|
var segments = new MySegmentsCacheInMemory();
|
|
18
|
-
|
|
18
|
+
var storage = {
|
|
19
19
|
splits: splits,
|
|
20
20
|
segments: segments,
|
|
21
21
|
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -25,13 +25,12 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
25
25
|
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
|
|
26
26
|
// When using MEMORY we should clean all the caches to leave them empty
|
|
27
27
|
destroy: function () {
|
|
28
|
-
var _a;
|
|
29
28
|
this.splits.clear();
|
|
30
29
|
this.segments.clear();
|
|
31
30
|
this.impressions.clear();
|
|
32
31
|
this.impressionCounts && this.impressionCounts.clear();
|
|
33
32
|
this.events.clear();
|
|
34
|
-
|
|
33
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
35
34
|
},
|
|
36
35
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
37
36
|
shared: function () {
|
|
@@ -50,5 +49,17 @@ export function InMemoryStorageCSFactory(params) {
|
|
|
50
49
|
};
|
|
51
50
|
},
|
|
52
51
|
};
|
|
52
|
+
// @TODO revisit storage logic in localhost mode
|
|
53
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
54
|
+
if (params.settings.mode === LOCALHOST_MODE) {
|
|
55
|
+
var noopTrack = function () { return true; };
|
|
56
|
+
storage.impressions.track = noopTrack;
|
|
57
|
+
storage.events.track = noopTrack;
|
|
58
|
+
if (storage.impressionCounts)
|
|
59
|
+
storage.impressionCounts.track = noopTrack;
|
|
60
|
+
if (storage.uniqueKeys)
|
|
61
|
+
storage.uniqueKeys.track = noopTrack;
|
|
62
|
+
}
|
|
63
|
+
return storage;
|
|
53
64
|
}
|
|
54
65
|
InMemoryStorageCSFactory.type = STORAGE_MEMORY;
|
|
@@ -21,8 +21,8 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
21
21
|
function UniqueKeysCacheInMemory(uniqueKeysQueueSize) {
|
|
22
22
|
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = DEFAULT_CACHE_SIZE; }
|
|
23
23
|
this.uniqueTrackerSize = 0;
|
|
24
|
-
this.maxStorage = uniqueKeysQueueSize;
|
|
25
24
|
this.uniqueKeysTracker = {};
|
|
25
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
26
26
|
}
|
|
27
27
|
UniqueKeysCacheInMemory.prototype.setOnFullQueueCb = function (cb) {
|
|
28
28
|
this.onFullQueue = cb;
|
|
@@ -39,7 +39,6 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
39
39
|
this.uniqueTrackerSize++;
|
|
40
40
|
}
|
|
41
41
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
42
|
-
this.uniqueTrackerSize = 0;
|
|
43
42
|
this.onFullQueue();
|
|
44
43
|
}
|
|
45
44
|
};
|
|
@@ -47,6 +46,7 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
47
46
|
* Clear the data stored on the cache.
|
|
48
47
|
*/
|
|
49
48
|
UniqueKeysCacheInMemory.prototype.clear = function () {
|
|
49
|
+
this.uniqueTrackerSize = 0;
|
|
50
50
|
this.uniqueKeysTracker = {};
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
@@ -54,7 +54,7 @@ var UniqueKeysCacheInMemory = /** @class */ (function () {
|
|
|
54
54
|
*/
|
|
55
55
|
UniqueKeysCacheInMemory.prototype.pop = function () {
|
|
56
56
|
var data = this.uniqueKeysTracker;
|
|
57
|
-
this.
|
|
57
|
+
this.clear();
|
|
58
58
|
return fromUniqueKeysCollector(data);
|
|
59
59
|
};
|
|
60
60
|
/**
|
|
@@ -9,8 +9,8 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
9
9
|
function UniqueKeysCacheInMemoryCS(uniqueKeysQueueSize) {
|
|
10
10
|
if (uniqueKeysQueueSize === void 0) { uniqueKeysQueueSize = DEFAULT_CACHE_SIZE; }
|
|
11
11
|
this.uniqueTrackerSize = 0;
|
|
12
|
-
this.maxStorage = uniqueKeysQueueSize;
|
|
13
12
|
this.uniqueKeysTracker = {};
|
|
13
|
+
this.maxStorage = uniqueKeysQueueSize;
|
|
14
14
|
}
|
|
15
15
|
UniqueKeysCacheInMemoryCS.prototype.setOnFullQueueCb = function (cb) {
|
|
16
16
|
this.onFullQueue = cb;
|
|
@@ -27,7 +27,6 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
27
27
|
this.uniqueTrackerSize++;
|
|
28
28
|
}
|
|
29
29
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
30
|
-
this.uniqueTrackerSize = 0;
|
|
31
30
|
this.onFullQueue();
|
|
32
31
|
}
|
|
33
32
|
};
|
|
@@ -35,6 +34,7 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
35
34
|
* Clear the data stored on the cache.
|
|
36
35
|
*/
|
|
37
36
|
UniqueKeysCacheInMemoryCS.prototype.clear = function () {
|
|
37
|
+
this.uniqueTrackerSize = 0;
|
|
38
38
|
this.uniqueKeysTracker = {};
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
@@ -42,7 +42,7 @@ var UniqueKeysCacheInMemoryCS = /** @class */ (function () {
|
|
|
42
42
|
*/
|
|
43
43
|
UniqueKeysCacheInMemoryCS.prototype.pop = function () {
|
|
44
44
|
var data = this.uniqueKeysTracker;
|
|
45
|
-
this.
|
|
45
|
+
this.clear();
|
|
46
46
|
return this.fromUniqueKeysCollector(data);
|
|
47
47
|
};
|
|
48
48
|
/**
|
|
@@ -21,21 +21,19 @@ var UniqueKeysCacheInRedis = /** @class */ (function (_super) {
|
|
|
21
21
|
var featureNames = Object.keys(this.uniqueKeysTracker);
|
|
22
22
|
if (!featureNames.length)
|
|
23
23
|
return Promise.resolve(false);
|
|
24
|
-
var
|
|
25
|
-
|
|
26
|
-
var featureName = featureNames[i];
|
|
27
|
-
var featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
24
|
+
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
25
|
+
var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
|
|
28
26
|
var uniqueKeysPayload = {
|
|
29
27
|
f: featureName,
|
|
30
28
|
ks: featureKeys
|
|
31
29
|
};
|
|
32
|
-
|
|
33
|
-
}
|
|
30
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
31
|
+
});
|
|
34
32
|
this.clear();
|
|
35
|
-
return
|
|
33
|
+
return this.redis.rpush(this.key, uniqueKeysArray)
|
|
36
34
|
.then(function (data) {
|
|
37
35
|
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
38
|
-
if (data
|
|
36
|
+
if (data === featureNames.length) {
|
|
39
37
|
return _this.redis.expire(_this.key, TTL_REFRESH);
|
|
40
38
|
}
|
|
41
39
|
})
|
|
@@ -21,16 +21,17 @@ var UniqueKeysCachePluggable = /** @class */ (function (_super) {
|
|
|
21
21
|
var featureNames = Object.keys(this.uniqueKeysTracker);
|
|
22
22
|
if (!featureNames.length)
|
|
23
23
|
return Promise.resolve(false);
|
|
24
|
-
var
|
|
24
|
+
var uniqueKeysArray = featureNames.map(function (featureName) {
|
|
25
25
|
var featureKeys = setToArray(_this.uniqueKeysTracker[featureName]);
|
|
26
26
|
var uniqueKeysPayload = {
|
|
27
27
|
f: featureName,
|
|
28
28
|
ks: featureKeys
|
|
29
29
|
};
|
|
30
|
-
return
|
|
31
|
-
}
|
|
30
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
31
|
+
});
|
|
32
32
|
this.clear();
|
|
33
|
-
return
|
|
33
|
+
return this.wrapper.pushItems(this.key, uniqueKeysArray)
|
|
34
|
+
.catch(function (err) {
|
|
34
35
|
_this.log.error(LOG_PREFIX + "Error in uniqueKeys pipeline: " + err + ".");
|
|
35
36
|
return false;
|
|
36
37
|
});
|
|
@@ -45,7 +45,7 @@ export var base = {
|
|
|
45
45
|
urls: {
|
|
46
46
|
// CDN having all the information for your environment
|
|
47
47
|
sdk: 'https://sdk.split.io/api',
|
|
48
|
-
//
|
|
48
|
+
// SDK event and impression endpoints
|
|
49
49
|
events: 'https://events.split.io/api',
|
|
50
50
|
// SDK Auth Server
|
|
51
51
|
auth: 'https://auth.split.io/api',
|
package/package.json
CHANGED
package/src/listeners/browser.ts
CHANGED
|
@@ -114,7 +114,7 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
114
114
|
if (!cache.isEmpty()) {
|
|
115
115
|
const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop();
|
|
116
116
|
if (!this._sendBeacon(url, dataPayload, extraMetadata)) {
|
|
117
|
-
postService(JSON.stringify(dataPayload)).catch(() => { }); // no-op
|
|
117
|
+
postService(JSON.stringify(dataPayload)).catch(() => { }); // no-op to handle possible promise rejection
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -122,6 +122,7 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
122
122
|
/**
|
|
123
123
|
* _sendBeacon method.
|
|
124
124
|
* Util method that check if beacon API is available, build the payload and send it.
|
|
125
|
+
* Returns true if beacon API was used successfully, false otherwise.
|
|
125
126
|
*/
|
|
126
127
|
private _sendBeacon(url: string, data: any, extraMetadata?: {}) {
|
|
127
128
|
// eslint-disable-next-line compat/compat
|
|
@@ -138,8 +139,12 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
138
139
|
// Stringify the payload
|
|
139
140
|
const payload = JSON.stringify(json);
|
|
140
141
|
|
|
141
|
-
//
|
|
142
|
-
|
|
142
|
+
// https://xgwang.me/posts/you-may-not-know-beacon/#it-may-throw-error%2C-be-sure-to-catch
|
|
143
|
+
try { // eslint-disable-next-line compat/compat
|
|
144
|
+
return navigator.sendBeacon(url, payload);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
143
148
|
}
|
|
144
149
|
return false;
|
|
145
150
|
}
|
|
@@ -30,7 +30,6 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync
|
|
|
30
30
|
this.cacheSize = this.cacheSize + amount;
|
|
31
31
|
if (this.cacheSize >= this.maxStorage) {
|
|
32
32
|
this.onFullQueue();
|
|
33
|
-
this.cacheSize = 0;
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
35
|
}
|
|
@@ -58,6 +57,7 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync
|
|
|
58
57
|
*/
|
|
59
58
|
clear() {
|
|
60
59
|
this.cache = {};
|
|
60
|
+
this.cacheSize = 0;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -4,7 +4,7 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { IStorageFactoryParams, IStorageSync } from '../types';
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
|
-
import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
9
|
import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
19
19
|
const splits = new SplitsCacheInMemory();
|
|
20
20
|
const segments = new SegmentsCacheInMemory();
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const storage = {
|
|
23
23
|
splits,
|
|
24
24
|
segments,
|
|
25
25
|
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -35,9 +35,21 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
|
|
|
35
35
|
this.impressions.clear();
|
|
36
36
|
this.impressionCounts && this.impressionCounts.clear();
|
|
37
37
|
this.events.clear();
|
|
38
|
-
this.uniqueKeys
|
|
38
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
|
+
|
|
42
|
+
// @TODO revisit storage logic in localhost mode
|
|
43
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
44
|
+
if (params.settings.mode === LOCALHOST_MODE) {
|
|
45
|
+
const noopTrack = () => true;
|
|
46
|
+
storage.impressions.track = noopTrack;
|
|
47
|
+
storage.events.track = noopTrack;
|
|
48
|
+
if (storage.impressionCounts) storage.impressionCounts.track = noopTrack;
|
|
49
|
+
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return storage;
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
InMemoryStorageFactory.type = STORAGE_MEMORY;
|
|
@@ -4,7 +4,7 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
|
|
|
4
4
|
import { EventsCacheInMemory } from './EventsCacheInMemory';
|
|
5
5
|
import { IStorageSync, IStorageFactoryParams } from '../types';
|
|
6
6
|
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
|
|
7
|
-
import { DEBUG, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
7
|
+
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
|
|
8
8
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
|
|
9
9
|
import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
|
|
10
10
|
|
|
@@ -19,7 +19,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
19
19
|
const splits = new SplitsCacheInMemory();
|
|
20
20
|
const segments = new MySegmentsCacheInMemory();
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const storage = {
|
|
23
23
|
splits,
|
|
24
24
|
segments,
|
|
25
25
|
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
|
|
@@ -35,7 +35,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
35
35
|
this.impressions.clear();
|
|
36
36
|
this.impressionCounts && this.impressionCounts.clear();
|
|
37
37
|
this.events.clear();
|
|
38
|
-
this.uniqueKeys
|
|
38
|
+
this.uniqueKeys && this.uniqueKeys.clear();
|
|
39
39
|
},
|
|
40
40
|
|
|
41
41
|
// When using shared instanciation with MEMORY we reuse everything but segments (they are unique per key)
|
|
@@ -56,6 +56,18 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
|
|
|
56
56
|
};
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
|
+
|
|
60
|
+
// @TODO revisit storage logic in localhost mode
|
|
61
|
+
// No tracking data in localhost mode to avoid memory leaks
|
|
62
|
+
if (params.settings.mode === LOCALHOST_MODE) {
|
|
63
|
+
const noopTrack = () => true;
|
|
64
|
+
storage.impressions.track = noopTrack;
|
|
65
|
+
storage.events.track = noopTrack;
|
|
66
|
+
if (storage.impressionCounts) storage.impressionCounts.track = noopTrack;
|
|
67
|
+
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return storage;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
InMemoryStorageCSFactory.type = STORAGE_MEMORY;
|
|
@@ -27,11 +27,10 @@ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
|
27
27
|
protected onFullQueue?: () => void;
|
|
28
28
|
private readonly maxStorage: number;
|
|
29
29
|
private uniqueTrackerSize = 0;
|
|
30
|
-
protected uniqueKeysTracker: { [featureName: string]: ISet<string> };
|
|
30
|
+
protected uniqueKeysTracker: { [featureName: string]: ISet<string> } = {};
|
|
31
31
|
|
|
32
32
|
constructor(uniqueKeysQueueSize = DEFAULT_CACHE_SIZE) {
|
|
33
33
|
this.maxStorage = uniqueKeysQueueSize;
|
|
34
|
-
this.uniqueKeysTracker = {};
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
setOnFullQueueCb(cb: () => void) {
|
|
@@ -49,7 +48,6 @@ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
|
49
48
|
this.uniqueTrackerSize++;
|
|
50
49
|
}
|
|
51
50
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
52
|
-
this.uniqueTrackerSize = 0;
|
|
53
51
|
this.onFullQueue();
|
|
54
52
|
}
|
|
55
53
|
}
|
|
@@ -58,6 +56,7 @@ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
|
58
56
|
* Clear the data stored on the cache.
|
|
59
57
|
*/
|
|
60
58
|
clear() {
|
|
59
|
+
this.uniqueTrackerSize = 0;
|
|
61
60
|
this.uniqueKeysTracker = {};
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -66,7 +65,7 @@ export class UniqueKeysCacheInMemory implements IUniqueKeysCacheBase {
|
|
|
66
65
|
*/
|
|
67
66
|
pop() {
|
|
68
67
|
const data = this.uniqueKeysTracker;
|
|
69
|
-
this.
|
|
68
|
+
this.clear();
|
|
70
69
|
return fromUniqueKeysCollector(data);
|
|
71
70
|
}
|
|
72
71
|
|
|
@@ -8,7 +8,7 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
8
8
|
private onFullQueue?: () => void;
|
|
9
9
|
private readonly maxStorage: number;
|
|
10
10
|
private uniqueTrackerSize = 0;
|
|
11
|
-
private uniqueKeysTracker: { [userKey: string]: ISet<string> };
|
|
11
|
+
private uniqueKeysTracker: { [userKey: string]: ISet<string> } = {};
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
*
|
|
@@ -17,7 +17,6 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
17
17
|
*/
|
|
18
18
|
constructor(uniqueKeysQueueSize = DEFAULT_CACHE_SIZE) {
|
|
19
19
|
this.maxStorage = uniqueKeysQueueSize;
|
|
20
|
-
this.uniqueKeysTracker = {};
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
setOnFullQueueCb(cb: () => void) {
|
|
@@ -36,7 +35,6 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
36
35
|
this.uniqueTrackerSize++;
|
|
37
36
|
}
|
|
38
37
|
if (this.uniqueTrackerSize >= this.maxStorage && this.onFullQueue) {
|
|
39
|
-
this.uniqueTrackerSize = 0;
|
|
40
38
|
this.onFullQueue();
|
|
41
39
|
}
|
|
42
40
|
}
|
|
@@ -45,6 +43,7 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
45
43
|
* Clear the data stored on the cache.
|
|
46
44
|
*/
|
|
47
45
|
clear() {
|
|
46
|
+
this.uniqueTrackerSize = 0;
|
|
48
47
|
this.uniqueKeysTracker = {};
|
|
49
48
|
}
|
|
50
49
|
|
|
@@ -53,7 +52,7 @@ export class UniqueKeysCacheInMemoryCS implements IUniqueKeysCacheBase {
|
|
|
53
52
|
*/
|
|
54
53
|
pop() {
|
|
55
54
|
const data = this.uniqueKeysTracker;
|
|
56
|
-
this.
|
|
55
|
+
this.clear();
|
|
57
56
|
return this.fromUniqueKeysCollector(data);
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -28,22 +28,20 @@ export class UniqueKeysCacheInRedis extends UniqueKeysCacheInMemory implements I
|
|
|
28
28
|
const featureNames = Object.keys(this.uniqueKeysTracker);
|
|
29
29
|
if (!featureNames.length) return Promise.resolve(false);
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
for (let i = 0; i < featureNames.length; i++) {
|
|
33
|
-
const featureName = featureNames[i];
|
|
31
|
+
const uniqueKeysArray = featureNames.map((featureName) => {
|
|
34
32
|
const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
35
33
|
const uniqueKeysPayload = {
|
|
36
34
|
f: featureName,
|
|
37
35
|
ks: featureKeys
|
|
38
36
|
};
|
|
37
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
38
|
+
});
|
|
39
39
|
|
|
40
|
-
pipeline.rpush(this.key, JSON.stringify(uniqueKeysPayload));
|
|
41
|
-
}
|
|
42
40
|
this.clear();
|
|
43
|
-
return
|
|
41
|
+
return this.redis.rpush(this.key, uniqueKeysArray)
|
|
44
42
|
.then(data => {
|
|
45
43
|
// If this is the creation of the key on Redis, set the expiration for it in 3600 seconds.
|
|
46
|
-
if (data
|
|
44
|
+
if (data === featureNames.length) {
|
|
47
45
|
return this.redis.expire(this.key, TTL_REFRESH);
|
|
48
46
|
}
|
|
49
47
|
})
|
|
@@ -27,21 +27,21 @@ export class UniqueKeysCachePluggable extends UniqueKeysCacheInMemory implements
|
|
|
27
27
|
const featureNames = Object.keys(this.uniqueKeysTracker);
|
|
28
28
|
if (!featureNames.length) return Promise.resolve(false);
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const uniqueKeysArray = featureNames.map((featureName) => {
|
|
31
31
|
const featureKeys = setToArray(this.uniqueKeysTracker[featureName]);
|
|
32
32
|
const uniqueKeysPayload = {
|
|
33
33
|
f: featureName,
|
|
34
34
|
ks: featureKeys
|
|
35
35
|
};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}, Promise.resolve());
|
|
36
|
+
return JSON.stringify(uniqueKeysPayload);
|
|
37
|
+
});
|
|
39
38
|
|
|
40
39
|
this.clear();
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
return this.wrapper.pushItems(this.key, uniqueKeysArray)
|
|
41
|
+
.catch(err => {
|
|
42
|
+
this.log.error(`${LOG_PREFIX}Error in uniqueKeys pipeline: ${err}.`);
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
|
|
@@ -51,7 +51,7 @@ export const base = {
|
|
|
51
51
|
urls: {
|
|
52
52
|
// CDN having all the information for your environment
|
|
53
53
|
sdk: 'https://sdk.split.io/api',
|
|
54
|
-
//
|
|
54
|
+
// SDK event and impression endpoints
|
|
55
55
|
events: 'https://events.split.io/api',
|
|
56
56
|
// SDK Auth Server
|
|
57
57
|
auth: 'https://auth.split.io/api',
|
|
@@ -35,6 +35,7 @@ export declare class BrowserSignalListener implements ISignalListener {
|
|
|
35
35
|
/**
|
|
36
36
|
* _sendBeacon method.
|
|
37
37
|
* Util method that check if beacon API is available, build the payload and send it.
|
|
38
|
+
* Returns true if beacon API was used successfully, false otherwise.
|
|
38
39
|
*/
|
|
39
40
|
private _sendBeacon;
|
|
40
41
|
}
|
|
@@ -9,9 +9,9 @@ export declare class UniqueKeysCachePluggable extends UniqueKeysCacheInMemory im
|
|
|
9
9
|
private readonly refreshRate;
|
|
10
10
|
private intervalId;
|
|
11
11
|
constructor(log: ILogger, key: string, wrapper: IPluggableStorageWrapper, uniqueKeysQueueSize?: number, refreshRate?: number);
|
|
12
|
-
storeUniqueKeys(): Promise<
|
|
12
|
+
storeUniqueKeys(): Promise<boolean | void>;
|
|
13
13
|
start(): void;
|
|
14
|
-
stop(): Promise<
|
|
14
|
+
stop(): Promise<boolean | void>;
|
|
15
15
|
/**
|
|
16
16
|
* Async consumer API, used by synchronizer.
|
|
17
17
|
* @param count number of items to pop from the queue. If not provided or equal 0, all items will be popped.
|
package/src/logger/.DS_Store
DELETED
|
Binary file
|