@splitsoftware/splitio-commons 2.4.2-rc.1 → 2.4.2-rc.3
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 +13 -1
- package/cjs/logger/messages/error.js +1 -1
- package/cjs/sdkClient/sdkClient.js +0 -1
- package/cjs/sdkFactory/index.js +3 -10
- package/cjs/services/splitHttpClient.js +1 -1
- package/cjs/storages/AbstractSplitsCacheSync.js +5 -1
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
- package/cjs/storages/inLocalStorage/index.js +31 -13
- package/cjs/storages/inLocalStorage/storageAdapter.js +54 -0
- package/cjs/storages/inLocalStorage/validateCache.js +28 -23
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/cjs/sync/polling/pollingManagerCS.js +5 -4
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +5 -2
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +3 -1
- package/cjs/sync/syncManagerOnline.js +31 -26
- package/cjs/trackers/impressionsTracker.js +4 -4
- package/cjs/utils/env/isLocalStorageAvailable.js +28 -5
- package/cjs/utils/settingsValidation/splitFilters.js +0 -6
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/logger/messages/error.js +1 -1
- package/esm/sdkClient/sdkClient.js +0 -1
- package/esm/sdkFactory/index.js +3 -10
- package/esm/services/splitHttpClient.js +1 -1
- package/esm/storages/AbstractSplitsCacheSync.js +3 -0
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +17 -17
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +33 -37
- package/esm/storages/inLocalStorage/index.js +32 -14
- package/esm/storages/inLocalStorage/storageAdapter.js +50 -0
- package/esm/storages/inLocalStorage/validateCache.js +28 -23
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/esm/sync/polling/pollingManagerCS.js +5 -4
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +5 -2
- package/esm/sync/polling/updaters/splitChangesUpdater.js +3 -1
- package/esm/sync/syncManagerOnline.js +31 -26
- package/esm/trackers/impressionsTracker.js +4 -4
- package/esm/utils/env/isLocalStorageAvailable.js +24 -3
- package/esm/utils/settingsValidation/splitFilters.js +0 -6
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/logger/messages/error.ts +1 -1
- package/src/sdkClient/sdkClient.ts +0 -1
- package/src/sdkFactory/index.ts +3 -13
- package/src/services/splitHttpClient.ts +1 -1
- package/src/storages/AbstractSplitsCacheSync.ts +5 -1
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +18 -17
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +19 -18
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +34 -37
- package/src/storages/inLocalStorage/index.ts +37 -16
- package/src/storages/inLocalStorage/storageAdapter.ts +62 -0
- package/src/storages/inLocalStorage/validateCache.ts +29 -23
- package/src/storages/types.ts +19 -1
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +1 -2
- package/src/sync/polling/pollingManagerCS.ts +5 -4
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +5 -2
- package/src/sync/polling/updaters/splitChangesUpdater.ts +4 -2
- package/src/sync/syncManagerOnline.ts +30 -24
- package/src/trackers/impressionsTracker.ts +3 -3
- package/src/utils/env/isLocalStorageAvailable.ts +24 -3
- package/src/utils/settingsValidation/splitFilters.ts +0 -6
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +72 -16
|
@@ -3,25 +3,22 @@ import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSyn
|
|
|
3
3
|
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
|
|
4
4
|
import { LOG_PREFIX } from './constants';
|
|
5
5
|
import { setToArray } from '../../utils/lang/sets';
|
|
6
|
-
/**
|
|
7
|
-
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
8
|
-
*/
|
|
9
6
|
var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
10
7
|
__extends(SplitsCacheInLocal, _super);
|
|
11
|
-
function SplitsCacheInLocal(settings, keys) {
|
|
8
|
+
function SplitsCacheInLocal(settings, keys, storage) {
|
|
12
9
|
var _this = _super.call(this) || this;
|
|
13
10
|
_this.keys = keys;
|
|
14
11
|
_this.log = settings.log;
|
|
15
12
|
_this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
13
|
+
_this.storage = storage;
|
|
16
14
|
return _this;
|
|
17
15
|
}
|
|
18
16
|
SplitsCacheInLocal.prototype._decrementCount = function (key) {
|
|
19
|
-
var count = toNumber(
|
|
20
|
-
// @ts-expect-error
|
|
17
|
+
var count = toNumber(this.storage.getItem(key)) - 1;
|
|
21
18
|
if (count > 0)
|
|
22
|
-
|
|
19
|
+
this.storage.setItem(key, count + '');
|
|
23
20
|
else
|
|
24
|
-
|
|
21
|
+
this.storage.removeItem(key);
|
|
25
22
|
};
|
|
26
23
|
SplitsCacheInLocal.prototype._decrementCounts = function (split) {
|
|
27
24
|
try {
|
|
@@ -39,12 +36,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
39
36
|
SplitsCacheInLocal.prototype._incrementCounts = function (split) {
|
|
40
37
|
try {
|
|
41
38
|
var ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
42
|
-
|
|
43
|
-
localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
|
|
39
|
+
this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
|
|
44
40
|
if (usesSegments(split)) {
|
|
45
41
|
var segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
46
|
-
|
|
47
|
-
localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
|
|
42
|
+
this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
45
|
catch (e) {
|
|
@@ -56,17 +51,18 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
56
51
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
57
52
|
*/
|
|
58
53
|
SplitsCacheInLocal.prototype.clear = function () {
|
|
54
|
+
var _this = this;
|
|
59
55
|
// collect item keys
|
|
60
|
-
var len =
|
|
56
|
+
var len = this.storage.length;
|
|
61
57
|
var accum = [];
|
|
62
58
|
for (var cur = 0; cur < len; cur++) {
|
|
63
|
-
var key =
|
|
59
|
+
var key = this.storage.key(cur);
|
|
64
60
|
if (key != null && this.keys.isSplitsCacheKey(key))
|
|
65
61
|
accum.push(key);
|
|
66
62
|
}
|
|
67
63
|
// remove items
|
|
68
64
|
accum.forEach(function (key) {
|
|
69
|
-
|
|
65
|
+
_this.storage.removeItem(key);
|
|
70
66
|
});
|
|
71
67
|
this.hasSync = false;
|
|
72
68
|
};
|
|
@@ -74,13 +70,13 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
74
70
|
try {
|
|
75
71
|
var name_1 = split.name;
|
|
76
72
|
var splitKey = this.keys.buildSplitKey(name_1);
|
|
77
|
-
var
|
|
78
|
-
var previousSplit =
|
|
73
|
+
var splitFromStorage = this.storage.getItem(splitKey);
|
|
74
|
+
var previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
|
|
79
75
|
if (previousSplit) {
|
|
80
76
|
this._decrementCounts(previousSplit);
|
|
81
77
|
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
82
78
|
}
|
|
83
|
-
|
|
79
|
+
this.storage.setItem(splitKey, JSON.stringify(split));
|
|
84
80
|
this._incrementCounts(split);
|
|
85
81
|
this.addToFlagSets(split);
|
|
86
82
|
return true;
|
|
@@ -95,7 +91,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
95
91
|
var split = this.getSplit(name);
|
|
96
92
|
if (!split)
|
|
97
93
|
return false;
|
|
98
|
-
|
|
94
|
+
this.storage.removeItem(this.keys.buildSplitKey(name));
|
|
99
95
|
this._decrementCounts(split);
|
|
100
96
|
this.removeFromFlagSets(split.name, split.sets);
|
|
101
97
|
return true;
|
|
@@ -106,14 +102,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
106
102
|
}
|
|
107
103
|
};
|
|
108
104
|
SplitsCacheInLocal.prototype.getSplit = function (name) {
|
|
109
|
-
var item =
|
|
105
|
+
var item = this.storage.getItem(this.keys.buildSplitKey(name));
|
|
110
106
|
return item && JSON.parse(item);
|
|
111
107
|
};
|
|
112
108
|
SplitsCacheInLocal.prototype.setChangeNumber = function (changeNumber) {
|
|
113
109
|
try {
|
|
114
|
-
|
|
110
|
+
this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
115
111
|
// update "last updated" timestamp with current time
|
|
116
|
-
|
|
112
|
+
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
117
113
|
this.hasSync = true;
|
|
118
114
|
return true;
|
|
119
115
|
}
|
|
@@ -124,7 +120,7 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
124
120
|
};
|
|
125
121
|
SplitsCacheInLocal.prototype.getChangeNumber = function () {
|
|
126
122
|
var n = -1;
|
|
127
|
-
var value =
|
|
123
|
+
var value = this.storage.getItem(this.keys.buildSplitsTillKey());
|
|
128
124
|
if (value !== null) {
|
|
129
125
|
value = parseInt(value, 10);
|
|
130
126
|
return isNaNNumber(value) ? n : value;
|
|
@@ -132,11 +128,11 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
132
128
|
return n;
|
|
133
129
|
};
|
|
134
130
|
SplitsCacheInLocal.prototype.getSplitNames = function () {
|
|
135
|
-
var len =
|
|
131
|
+
var len = this.storage.length;
|
|
136
132
|
var accum = [];
|
|
137
133
|
var cur = 0;
|
|
138
134
|
while (cur < len) {
|
|
139
|
-
var key =
|
|
135
|
+
var key = this.storage.key(cur);
|
|
140
136
|
if (key != null && this.keys.isSplitKey(key))
|
|
141
137
|
accum.push(this.keys.extractKey(key));
|
|
142
138
|
cur++;
|
|
@@ -144,14 +140,14 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
144
140
|
return accum;
|
|
145
141
|
};
|
|
146
142
|
SplitsCacheInLocal.prototype.trafficTypeExists = function (trafficType) {
|
|
147
|
-
var ttCount = toNumber(
|
|
143
|
+
var ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
|
|
148
144
|
return isFiniteNumber(ttCount) && ttCount > 0;
|
|
149
145
|
};
|
|
150
146
|
SplitsCacheInLocal.prototype.usesSegments = function () {
|
|
151
147
|
// If cache hasn't been synchronized with the cloud, assume we need them.
|
|
152
148
|
if (!this.hasSync)
|
|
153
149
|
return true;
|
|
154
|
-
var storedCount =
|
|
150
|
+
var storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
155
151
|
var splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
156
152
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
157
153
|
splitsWithSegmentsCount > 0 :
|
|
@@ -161,8 +157,8 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
161
157
|
var _this = this;
|
|
162
158
|
return flagSets.map(function (flagSet) {
|
|
163
159
|
var flagSetKey = _this.keys.buildFlagSetKey(flagSet);
|
|
164
|
-
var
|
|
165
|
-
return new Set(
|
|
160
|
+
var flagSetFromStorage = _this.storage.getItem(flagSetKey);
|
|
161
|
+
return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
166
162
|
});
|
|
167
163
|
};
|
|
168
164
|
SplitsCacheInLocal.prototype.addToFlagSets = function (featureFlag) {
|
|
@@ -173,10 +169,10 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
173
169
|
if (_this.flagSetsFilter.length > 0 && !_this.flagSetsFilter.some(function (filterFlagSet) { return filterFlagSet === featureFlagSet; }))
|
|
174
170
|
return;
|
|
175
171
|
var flagSetKey = _this.keys.buildFlagSetKey(featureFlagSet);
|
|
176
|
-
var
|
|
177
|
-
var flagSetCache = new Set(
|
|
172
|
+
var flagSetFromStorage = _this.storage.getItem(flagSetKey);
|
|
173
|
+
var flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
178
174
|
flagSetCache.add(featureFlag.name);
|
|
179
|
-
|
|
175
|
+
_this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
180
176
|
});
|
|
181
177
|
};
|
|
182
178
|
SplitsCacheInLocal.prototype.removeFromFlagSets = function (featureFlagName, flagSets) {
|
|
@@ -189,16 +185,16 @@ var SplitsCacheInLocal = /** @class */ (function (_super) {
|
|
|
189
185
|
};
|
|
190
186
|
SplitsCacheInLocal.prototype.removeNames = function (flagSetName, featureFlagName) {
|
|
191
187
|
var flagSetKey = this.keys.buildFlagSetKey(flagSetName);
|
|
192
|
-
var
|
|
193
|
-
if (!
|
|
188
|
+
var flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
189
|
+
if (!flagSetFromStorage)
|
|
194
190
|
return;
|
|
195
|
-
var flagSetCache = new Set(JSON.parse(
|
|
191
|
+
var flagSetCache = new Set(JSON.parse(flagSetFromStorage));
|
|
196
192
|
flagSetCache.delete(featureFlagName);
|
|
197
193
|
if (flagSetCache.size === 0) {
|
|
198
|
-
|
|
194
|
+
this.storage.removeItem(flagSetKey);
|
|
199
195
|
return;
|
|
200
196
|
}
|
|
201
|
-
|
|
197
|
+
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
202
198
|
};
|
|
203
199
|
return SplitsCacheInLocal;
|
|
204
200
|
}(AbstractSplitsCacheSync));
|
|
@@ -3,7 +3,7 @@ import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCache
|
|
|
3
3
|
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
|
|
4
4
|
import { validatePrefix } from '../KeyBuilder';
|
|
5
5
|
import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
6
|
-
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
6
|
+
import { isLocalStorageAvailable, isValidStorageWrapper, isWebStorage } from '../../utils/env/isLocalStorageAvailable';
|
|
7
7
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
8
8
|
import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
|
|
9
9
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
@@ -14,6 +14,20 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/Telem
|
|
|
14
14
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
15
15
|
import { getMatching } from '../../utils/key';
|
|
16
16
|
import { validateCache } from './validateCache';
|
|
17
|
+
import { storageAdapter } from './storageAdapter';
|
|
18
|
+
function validateStorage(log, prefix, wrapper) {
|
|
19
|
+
if (wrapper) {
|
|
20
|
+
if (isValidStorageWrapper(wrapper)) {
|
|
21
|
+
return isWebStorage(wrapper) ?
|
|
22
|
+
wrapper : // localStorage and sessionStorage don't need adapter
|
|
23
|
+
storageAdapter(log, prefix, wrapper);
|
|
24
|
+
}
|
|
25
|
+
log.warn(LOG_PREFIX + 'Invalid storage provided. Falling back to LocalStorage API');
|
|
26
|
+
}
|
|
27
|
+
if (isLocalStorageAvailable())
|
|
28
|
+
return localStorage;
|
|
29
|
+
log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
30
|
+
}
|
|
17
31
|
/**
|
|
18
32
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
19
33
|
*/
|
|
@@ -21,18 +35,17 @@ export function InLocalStorage(options) {
|
|
|
21
35
|
if (options === void 0) { options = {}; }
|
|
22
36
|
var prefix = validatePrefix(options.prefix);
|
|
23
37
|
function InLocalStorageCSFactory(params) {
|
|
24
|
-
// Fallback to InMemoryStorage if LocalStorage API is not available
|
|
25
|
-
if (!isLocalStorageAvailable()) {
|
|
26
|
-
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
27
|
-
return InMemoryStorageCSFactory(params);
|
|
28
|
-
}
|
|
29
38
|
var settings = params.settings, _a = params.settings, log = _a.log, _b = _a.scheduler, impressionsQueueSize = _b.impressionsQueueSize, eventsQueueSize = _b.eventsQueueSize;
|
|
39
|
+
var storage = validateStorage(log, prefix, options.wrapper);
|
|
40
|
+
if (!storage)
|
|
41
|
+
return InMemoryStorageCSFactory(params);
|
|
30
42
|
var matchingKey = getMatching(settings.core.key);
|
|
31
43
|
var keys = new KeyBuilderCS(prefix, matchingKey);
|
|
32
|
-
var splits = new SplitsCacheInLocal(settings, keys);
|
|
33
|
-
var rbSegments = new RBSegmentsCacheInLocal(settings, keys);
|
|
34
|
-
var segments = new MySegmentsCacheInLocal(log, keys);
|
|
35
|
-
var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
44
|
+
var splits = new SplitsCacheInLocal(settings, keys, storage);
|
|
45
|
+
var rbSegments = new RBSegmentsCacheInLocal(settings, keys, storage);
|
|
46
|
+
var segments = new MySegmentsCacheInLocal(log, keys, storage);
|
|
47
|
+
var largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage);
|
|
48
|
+
var validateCachePromise;
|
|
36
49
|
return {
|
|
37
50
|
splits: splits,
|
|
38
51
|
rbSegments: rbSegments,
|
|
@@ -44,16 +57,21 @@ export function InLocalStorage(options) {
|
|
|
44
57
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
45
58
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
46
59
|
validateCache: function () {
|
|
47
|
-
return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
|
|
60
|
+
return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
|
|
61
|
+
},
|
|
62
|
+
save: function () {
|
|
63
|
+
return storage.save && storage.save();
|
|
64
|
+
},
|
|
65
|
+
destroy: function () {
|
|
66
|
+
return storage.whenSaved && storage.whenSaved();
|
|
48
67
|
},
|
|
49
|
-
destroy: function () { },
|
|
50
68
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
51
69
|
shared: function (matchingKey) {
|
|
52
70
|
return {
|
|
53
71
|
splits: this.splits,
|
|
54
72
|
rbSegments: this.rbSegments,
|
|
55
|
-
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
|
|
56
|
-
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
|
|
73
|
+
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey), storage),
|
|
74
|
+
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage),
|
|
57
75
|
impressions: this.impressions,
|
|
58
76
|
impressionCounts: this.impressionCounts,
|
|
59
77
|
events: this.events,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { LOG_PREFIX } from './constants';
|
|
2
|
+
export function storageAdapter(log, prefix, wrapper) {
|
|
3
|
+
var keys = [];
|
|
4
|
+
var cache = {};
|
|
5
|
+
var loadPromise;
|
|
6
|
+
var savePromise = Promise.resolve();
|
|
7
|
+
return {
|
|
8
|
+
load: function () {
|
|
9
|
+
return loadPromise || (loadPromise = Promise.resolve().then(function () {
|
|
10
|
+
return wrapper.getItem(prefix);
|
|
11
|
+
}).then(function (storedCache) {
|
|
12
|
+
cache = JSON.parse(storedCache || '{}');
|
|
13
|
+
keys = Object.keys(cache);
|
|
14
|
+
}).catch(function (e) {
|
|
15
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
16
|
+
}));
|
|
17
|
+
},
|
|
18
|
+
save: function () {
|
|
19
|
+
return savePromise = savePromise.then(function () {
|
|
20
|
+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
21
|
+
}).catch(function (e) {
|
|
22
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
whenSaved: function () {
|
|
26
|
+
return savePromise;
|
|
27
|
+
},
|
|
28
|
+
get length() {
|
|
29
|
+
return keys.length;
|
|
30
|
+
},
|
|
31
|
+
getItem: function (key) {
|
|
32
|
+
return cache[key] || null;
|
|
33
|
+
},
|
|
34
|
+
key: function (index) {
|
|
35
|
+
return keys[index] || null;
|
|
36
|
+
},
|
|
37
|
+
removeItem: function (key) {
|
|
38
|
+
var index = keys.indexOf(key);
|
|
39
|
+
if (index === -1)
|
|
40
|
+
return;
|
|
41
|
+
keys.splice(index, 1);
|
|
42
|
+
delete cache[key];
|
|
43
|
+
},
|
|
44
|
+
setItem: function (key, value) {
|
|
45
|
+
if (keys.indexOf(key) === -1)
|
|
46
|
+
keys.push(key);
|
|
47
|
+
cache[key] = value;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -8,10 +8,10 @@ var MILLIS_IN_A_DAY = 86400000;
|
|
|
8
8
|
*
|
|
9
9
|
* @returns `true` if cache should be cleared, `false` otherwise
|
|
10
10
|
*/
|
|
11
|
-
function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
|
|
11
|
+
function validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache) {
|
|
12
12
|
var log = settings.log;
|
|
13
13
|
// Check expiration
|
|
14
|
-
var lastUpdatedTimestamp = parseInt(
|
|
14
|
+
var lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()), 10);
|
|
15
15
|
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
16
16
|
var cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
17
17
|
var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
@@ -22,11 +22,11 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
|
|
|
22
22
|
}
|
|
23
23
|
// Check hash
|
|
24
24
|
var storageHashKey = keys.buildHashKey();
|
|
25
|
-
var storageHash =
|
|
25
|
+
var storageHash = storage.getItem(storageHashKey);
|
|
26
26
|
var currentStorageHash = getStorageHash(settings);
|
|
27
27
|
if (storageHash !== currentStorageHash) {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
storage.setItem(storageHashKey, currentStorageHash);
|
|
30
30
|
}
|
|
31
31
|
catch (e) {
|
|
32
32
|
log.error(LOG_PREFIX + e);
|
|
@@ -39,7 +39,7 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
|
|
|
39
39
|
}
|
|
40
40
|
// Clear on init
|
|
41
41
|
if (options.clearOnInit) {
|
|
42
|
-
var lastClearTimestamp = parseInt(
|
|
42
|
+
var lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()), 10);
|
|
43
43
|
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
44
44
|
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
45
45
|
return true;
|
|
@@ -54,23 +54,28 @@ function validateExpiration(options, settings, keys, currentTimestamp, isThereCa
|
|
|
54
54
|
*
|
|
55
55
|
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
56
56
|
*/
|
|
57
|
-
export function validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
57
|
+
export function validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments) {
|
|
58
|
+
return Promise.resolve(storage.load && storage.load()).then(function () {
|
|
59
|
+
var currentTimestamp = Date.now();
|
|
60
|
+
var isThereCache = splits.getChangeNumber() > -1;
|
|
61
|
+
if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
|
|
62
|
+
splits.clear();
|
|
63
|
+
rbSegments.clear();
|
|
64
|
+
segments.clear();
|
|
65
|
+
largeSegments.clear();
|
|
66
|
+
// Update last clear timestamp
|
|
67
|
+
try {
|
|
68
|
+
storage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
settings.log.error(LOG_PREFIX + e);
|
|
72
|
+
}
|
|
73
|
+
// Persist clear
|
|
74
|
+
if (storage.save)
|
|
75
|
+
storage.save();
|
|
76
|
+
return false;
|
|
71
77
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return isThereCache;
|
|
78
|
+
// Check if ready from cache
|
|
79
|
+
return isThereCache;
|
|
80
|
+
});
|
|
76
81
|
}
|
|
@@ -42,10 +42,9 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
42
42
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
43
43
|
if (startingUp) {
|
|
44
44
|
startingUp = false;
|
|
45
|
-
|
|
46
|
-
Promise.resolve().then(function () {
|
|
45
|
+
Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
|
|
47
46
|
// Emits SDK_READY_FROM_CACHE
|
|
48
|
-
if (
|
|
47
|
+
if (isCacheLoaded)
|
|
49
48
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
50
49
|
// Emits SDK_READY
|
|
51
50
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
@@ -4,6 +4,7 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
|
|
|
4
4
|
import { getMatching } from '../../utils/key';
|
|
5
5
|
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
|
|
6
6
|
import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants';
|
|
7
|
+
import { usesSegmentsSync } from '../../storages/AbstractSplitsCacheSync';
|
|
7
8
|
/**
|
|
8
9
|
* Expose start / stop mechanism for polling data from services.
|
|
9
10
|
* For client-side API with multiple clients.
|
|
@@ -31,7 +32,7 @@ export function pollingManagerCSFactory(params) {
|
|
|
31
32
|
readiness.splits.on(SDK_SPLITS_ARRIVED, function () {
|
|
32
33
|
if (!splitsSyncTask.isRunning())
|
|
33
34
|
return; // noop if not doing polling
|
|
34
|
-
var usingSegments =
|
|
35
|
+
var usingSegments = usesSegmentsSync(storage);
|
|
35
36
|
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
36
37
|
log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
37
38
|
if (usingSegments) {
|
|
@@ -46,10 +47,10 @@ export function pollingManagerCSFactory(params) {
|
|
|
46
47
|
var mySegmentsSyncTask = mySegmentsSyncTaskFactory(splitApi.fetchMemberships, storage, readiness, settings, matchingKey);
|
|
47
48
|
// smart ready
|
|
48
49
|
function smartReady() {
|
|
49
|
-
if (!readiness.isReady() && !
|
|
50
|
+
if (!readiness.isReady() && !usesSegmentsSync(storage))
|
|
50
51
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
51
52
|
}
|
|
52
|
-
if (!
|
|
53
|
+
if (!usesSegmentsSync(storage))
|
|
53
54
|
setTimeout(smartReady, 0);
|
|
54
55
|
else
|
|
55
56
|
readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
|
|
@@ -63,7 +64,7 @@ export function pollingManagerCSFactory(params) {
|
|
|
63
64
|
start: function () {
|
|
64
65
|
log.info(POLLING_START);
|
|
65
66
|
splitsSyncTask.start();
|
|
66
|
-
if (
|
|
67
|
+
if (usesSegmentsSync(storage))
|
|
67
68
|
startMySegmentsSyncTasks();
|
|
68
69
|
},
|
|
69
70
|
// Stop periodic fetching (polling)
|
|
@@ -2,6 +2,7 @@ import { timeout } from '../../../utils/promise/timeout';
|
|
|
2
2
|
import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
3
3
|
import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants';
|
|
4
4
|
import { MEMBERSHIPS_LS_UPDATE } from '../../streaming/constants';
|
|
5
|
+
import { usesSegmentsSync } from '../../../storages/AbstractSplitsCacheSync';
|
|
5
6
|
/**
|
|
6
7
|
* factory of MySegments updater, a task that:
|
|
7
8
|
* - fetches mySegments using `mySegmentsFetcher`
|
|
@@ -9,7 +10,7 @@ import { MEMBERSHIPS_LS_UPDATE } from '../../streaming/constants';
|
|
|
9
10
|
* - uses `segmentsEventEmitter` to emit events related to segments data updates
|
|
10
11
|
*/
|
|
11
12
|
export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmentsEventEmitter, requestTimeoutBeforeReady, retriesOnFailureBeforeReady, matchingKey) {
|
|
12
|
-
var
|
|
13
|
+
var segments = storage.segments, largeSegments = storage.largeSegments;
|
|
13
14
|
var readyOnAlreadyExistentState = true;
|
|
14
15
|
var startingUp = true;
|
|
15
16
|
/** timeout and telemetry decorator for `splitChangesFetcher` promise */
|
|
@@ -30,8 +31,10 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
|
|
|
30
31
|
shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
|
|
31
32
|
shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
|
|
32
33
|
}
|
|
34
|
+
if (storage.save)
|
|
35
|
+
storage.save();
|
|
33
36
|
// Notify update if required
|
|
34
|
-
if ((
|
|
37
|
+
if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
35
38
|
readyOnAlreadyExistentState = false;
|
|
36
39
|
segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
|
|
37
40
|
}
|
|
@@ -42,7 +42,7 @@ export function parseSegments(ruleEntity, matcherType) {
|
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* If there are defined filters and one feature flag doesn't match with them, its status is changed to 'ARCHIVE' to avoid storing it
|
|
45
|
-
* If there
|
|
45
|
+
* If there is `bySet` filter, `byName` and `byPrefix` filters are ignored
|
|
46
46
|
*
|
|
47
47
|
* @param featureFlag - feature flag to be evaluated
|
|
48
48
|
* @param filters - splitFiltersValidation bySet | byName
|
|
@@ -149,6 +149,8 @@ export function splitChangesUpdaterFactory(log, splitChangesFetcher, storage, sp
|
|
|
149
149
|
segments.registerSegments(setToArray(usedSegments))
|
|
150
150
|
]).then(function (_a) {
|
|
151
151
|
var ffChanged = _a[0], rbsChanged = _a[1];
|
|
152
|
+
if (storage.save)
|
|
153
|
+
storage.save();
|
|
152
154
|
if (splitsEventEmitter) {
|
|
153
155
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
154
156
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
|
@@ -4,6 +4,7 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
|
|
|
4
4
|
import { isConsentGranted } from '../consent';
|
|
5
5
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
6
6
|
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
7
|
+
import { usesSegmentsSync } from '../storages/AbstractSplitsCacheSync';
|
|
7
8
|
/**
|
|
8
9
|
* Online SyncManager factory.
|
|
9
10
|
* Can be used for server-side API, and client-side API with or without multiple clients.
|
|
@@ -65,35 +66,39 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
65
66
|
*/
|
|
66
67
|
start: function () {
|
|
67
68
|
running = true;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
// @TODO once event, impression and telemetry storages support persistence, call when `validateCache` promise is resolved
|
|
70
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
71
|
+
return Promise.resolve(storage.validateCache ? storage.validateCache() : false).then(function (isCacheLoaded) {
|
|
72
|
+
if (!running)
|
|
73
|
+
return;
|
|
74
|
+
if (startFirstTime) {
|
|
75
|
+
// Emits SDK_READY_FROM_CACHE
|
|
76
|
+
if (isCacheLoaded)
|
|
77
|
+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
78
|
+
}
|
|
79
|
+
// start syncing splits and segments
|
|
80
|
+
if (pollingManager) {
|
|
81
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
82
|
+
if (syncEnabled) {
|
|
83
|
+
if (pushManager) {
|
|
84
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
85
|
+
if (startFirstTime) {
|
|
86
|
+
pollingManager.syncAll();
|
|
87
|
+
}
|
|
88
|
+
pushManager.start();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
pollingManager.start();
|
|
81
92
|
}
|
|
82
|
-
pushManager.start();
|
|
83
93
|
}
|
|
84
94
|
else {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
else {
|
|
89
|
-
if (startFirstTime) {
|
|
90
|
-
pollingManager.syncAll();
|
|
95
|
+
if (startFirstTime) {
|
|
96
|
+
pollingManager.syncAll();
|
|
97
|
+
}
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
submitterManager.start(!isConsentGranted(settings));
|
|
96
|
-
startFirstTime = false;
|
|
100
|
+
startFirstTime = false;
|
|
101
|
+
});
|
|
97
102
|
},
|
|
98
103
|
/**
|
|
99
104
|
* Method used to stop/pause the syncManager.
|
|
@@ -127,7 +132,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
127
132
|
if (pushManager) {
|
|
128
133
|
if (pollingManager.isRunning()) {
|
|
129
134
|
// if doing polling, we must start the periodic fetch of data
|
|
130
|
-
if (
|
|
135
|
+
if (usesSegmentsSync(storage))
|
|
131
136
|
mySegmentsSyncTask.start();
|
|
132
137
|
}
|
|
133
138
|
else {
|
|
@@ -137,7 +142,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
else {
|
|
140
|
-
if (
|
|
145
|
+
if (usesSegmentsSync(storage))
|
|
141
146
|
mySegmentsSyncTask.start();
|
|
142
147
|
}
|
|
143
148
|
}
|
|
@@ -6,7 +6,7 @@ import { CONSENT_DECLINED, DEDUPED, QUEUED } from '../utils/constants';
|
|
|
6
6
|
* Impressions tracker stores impressions in cache and pass them to the listener and integrations manager if provided.
|
|
7
7
|
*/
|
|
8
8
|
export function impressionsTrackerFactory(settings, impressionsCache, noneStrategy, strategy, whenInit, integrationsManager, telemetryCache) {
|
|
9
|
-
var log = settings.log, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
|
|
9
|
+
var log = settings.log, impressionListener = settings.impressionListener, _a = settings.runtime, ip = _a.ip, hostname = _a.hostname, version = settings.version;
|
|
10
10
|
return {
|
|
11
11
|
track: function (impressions, attributes) {
|
|
12
12
|
if (settings.userConsent === CONSENT_DECLINED)
|
|
@@ -39,7 +39,7 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
// @TODO next block might be handled by the integration manager. In that case, the metadata object doesn't need to be passed in the constructor
|
|
42
|
-
if (
|
|
42
|
+
if (impressionListener || integrationsManager) {
|
|
43
43
|
var _loop_1 = function (i) {
|
|
44
44
|
var impressionData = {
|
|
45
45
|
// copy of impression, to avoid unexpected behavior if modified by integrations or impressionListener
|
|
@@ -56,8 +56,8 @@ export function impressionsTrackerFactory(settings, impressionsCache, noneStrate
|
|
|
56
56
|
if (integrationsManager)
|
|
57
57
|
integrationsManager.handleImpression(impressionData);
|
|
58
58
|
try { // @ts-ignore. An exception on the listeners should not break the SDK.
|
|
59
|
-
if (
|
|
60
|
-
|
|
59
|
+
if (impressionListener)
|
|
60
|
+
impressionListener.logImpression(impressionData);
|
|
61
61
|
}
|
|
62
62
|
catch (err) {
|
|
63
63
|
log.error(ERROR_IMPRESSIONS_LISTENER, [err]);
|