@splitsoftware/splitio-commons 2.4.2-rc.3 → 2.5.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -11
- package/cjs/evaluator/convertions/index.js +9 -1
- package/cjs/evaluator/matchersTransform/index.js +2 -3
- package/cjs/sdkClient/sdkClientMethodCS.js +5 -1
- package/cjs/sdkFactory/index.js +8 -2
- package/cjs/storages/getRolloutPlan.js +69 -0
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/cjs/storages/inLocalStorage/index.js +13 -31
- package/cjs/storages/inLocalStorage/validateCache.js +25 -30
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/cjs/storages/setRolloutPlan.js +66 -0
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +0 -2
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +0 -2
- package/cjs/sync/syncManagerOnline.js +24 -28
- package/cjs/utils/env/isLocalStorageAvailable.js +5 -28
- package/cjs/utils/inputValidation/index.js +1 -3
- package/cjs/utils/settingsValidation/index.js +4 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/evaluator/convertions/index.js +7 -0
- package/esm/evaluator/matchersTransform/index.js +3 -4
- package/esm/sdkClient/sdkClientMethodCS.js +5 -1
- package/esm/sdkFactory/index.js +8 -2
- package/esm/storages/getRolloutPlan.js +65 -0
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +21 -17
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/esm/storages/inLocalStorage/index.js +14 -32
- package/esm/storages/inLocalStorage/validateCache.js +25 -30
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/esm/storages/setRolloutPlan.js +61 -0
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +0 -2
- package/esm/sync/polling/updaters/splitChangesUpdater.js +0 -2
- package/esm/sync/syncManagerOnline.js +24 -28
- package/esm/utils/env/isLocalStorageAvailable.js +3 -24
- package/esm/utils/inputValidation/index.js +0 -1
- package/esm/utils/settingsValidation/index.js +4 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/evaluator/convertions/index.ts +10 -0
- package/src/evaluator/matchersTransform/index.ts +3 -4
- package/src/sdkClient/sdkClientMethodCS.ts +7 -1
- package/src/sdkFactory/index.ts +12 -4
- package/src/storages/getRolloutPlan.ts +72 -0
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +17 -18
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +22 -19
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +37 -34
- package/src/storages/inLocalStorage/index.ts +16 -37
- package/src/storages/inLocalStorage/validateCache.ts +25 -31
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -0
- package/src/storages/setRolloutPlan.ts +71 -0
- package/src/storages/types.ts +25 -23
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +2 -1
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +0 -2
- package/src/sync/polling/updaters/splitChangesUpdater.ts +1 -3
- package/src/sync/syncManagerOnline.ts +22 -27
- package/src/types.ts +2 -35
- package/src/utils/env/isLocalStorageAvailable.ts +3 -24
- package/src/utils/inputValidation/index.ts +0 -1
- package/src/utils/settingsValidation/index.ts +4 -0
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +36 -44
- package/cjs/storages/dataLoader.js +0 -50
- package/cjs/storages/inLocalStorage/storageAdapter.js +0 -54
- package/cjs/utils/inputValidation/preloadedData.js +0 -59
- package/esm/storages/dataLoader.js +0 -46
- package/esm/storages/inLocalStorage/storageAdapter.js +0 -50
- package/esm/utils/inputValidation/preloadedData.js +0 -55
- package/src/storages/dataLoader.ts +0 -55
- package/src/storages/inLocalStorage/storageAdapter.ts +0 -62
- package/src/utils/inputValidation/preloadedData.ts +0 -57
|
@@ -31,8 +31,6 @@ export function mySegmentsUpdaterFactory(log, mySegmentsFetcher, storage, segmen
|
|
|
31
31
|
shouldNotifyUpdate = segments.resetSegments(segmentsData.ms || {});
|
|
32
32
|
shouldNotifyUpdate = largeSegments.resetSegments(segmentsData.ls || {}) || shouldNotifyUpdate;
|
|
33
33
|
}
|
|
34
|
-
if (storage.save)
|
|
35
|
-
storage.save();
|
|
36
34
|
// Notify update if required
|
|
37
35
|
if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
38
36
|
readyOnAlreadyExistentState = false;
|
|
@@ -149,8 +149,6 @@ 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();
|
|
154
152
|
if (splitsEventEmitter) {
|
|
155
153
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
156
154
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
|
@@ -66,39 +66,35 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
66
66
|
*/
|
|
67
67
|
start: function () {
|
|
68
68
|
running = true;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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();
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
69
|
+
if (startFirstTime) {
|
|
70
|
+
var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
71
|
+
if (isCacheLoaded)
|
|
72
|
+
Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
73
|
+
}
|
|
74
|
+
// start syncing splits and segments
|
|
75
|
+
if (pollingManager) {
|
|
76
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
77
|
+
if (syncEnabled) {
|
|
78
|
+
if (pushManager) {
|
|
79
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
95
80
|
if (startFirstTime) {
|
|
96
81
|
pollingManager.syncAll();
|
|
97
82
|
}
|
|
83
|
+
pushManager.start();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
pollingManager.start();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
if (startFirstTime) {
|
|
91
|
+
pollingManager.syncAll();
|
|
98
92
|
}
|
|
99
93
|
}
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
}
|
|
95
|
+
// start periodic data recording (events, impressions, telemetry).
|
|
96
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
97
|
+
startFirstTime = false;
|
|
102
98
|
},
|
|
103
99
|
/**
|
|
104
100
|
* Method used to stop/pause the syncManager.
|
|
@@ -1,33 +1,12 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
1
2
|
export function isLocalStorageAvailable() {
|
|
2
|
-
try {
|
|
3
|
-
// eslint-disable-next-line no-undef
|
|
4
|
-
return isValidStorageWrapper(localStorage);
|
|
5
|
-
}
|
|
6
|
-
catch (e) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export function isValidStorageWrapper(wrapper) {
|
|
11
3
|
var mod = '__SPLITSOFTWARE__';
|
|
12
4
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
wrapper.removeItem(mod);
|
|
5
|
+
localStorage.setItem(mod, mod);
|
|
6
|
+
localStorage.removeItem(mod);
|
|
16
7
|
return true;
|
|
17
8
|
}
|
|
18
9
|
catch (e) {
|
|
19
10
|
return false;
|
|
20
11
|
}
|
|
21
12
|
}
|
|
22
|
-
export function isWebStorage(wrapper) {
|
|
23
|
-
if (typeof wrapper.length === 'number') {
|
|
24
|
-
try {
|
|
25
|
-
wrapper.key(0);
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
@@ -10,5 +10,4 @@ export { validateTrafficType } from './trafficType';
|
|
|
10
10
|
export { validateIfNotDestroyed, validateIfOperational } from './isOperational';
|
|
11
11
|
export { validateSplitExistence } from './splitExistence';
|
|
12
12
|
export { validateTrafficTypeExistence } from './trafficTypeExistence';
|
|
13
|
-
export { validatePreloadedData } from './preloadedData';
|
|
14
13
|
export { validateEvaluationOptions } from './eventProperties';
|
|
@@ -5,6 +5,7 @@ import { STANDALONE_MODE, OPTIMIZED, LOCALHOST_MODE, DEBUG, FLAG_SPEC_VERSION }
|
|
|
5
5
|
import { validImpressionsMode } from './impressionsMode';
|
|
6
6
|
import { validateKey } from '../inputValidation/key';
|
|
7
7
|
import { ERROR_MIN_CONFIG_PARAM, LOG_PREFIX_CLIENT_INSTANTIATION } from '../../logger/constants';
|
|
8
|
+
import { validateRolloutPlan } from '../../storages/setRolloutPlan';
|
|
8
9
|
// Exported for telemetry
|
|
9
10
|
export var base = {
|
|
10
11
|
// Define which kind of object you want to retrieve from SplitFactory
|
|
@@ -128,6 +129,9 @@ export function settingsValidation(config, validationParams) {
|
|
|
128
129
|
// @ts-ignore, modify readonly prop
|
|
129
130
|
if (storage)
|
|
130
131
|
withDefaults.storage = storage(withDefaults);
|
|
132
|
+
// @ts-ignore, modify readonly prop
|
|
133
|
+
if (withDefaults.initialRolloutPlan)
|
|
134
|
+
withDefaults.initialRolloutPlan = validateRolloutPlan(log, withDefaults);
|
|
131
135
|
// Validate key and TT (for client-side)
|
|
132
136
|
var maybeKey = withDefaults.core.key;
|
|
133
137
|
if (validationParams.acceptKey) {
|
|
@@ -3,7 +3,7 @@ import { ERROR_STORAGE_INVALID } from '../../../logger/constants';
|
|
|
3
3
|
import { LOCALHOST_MODE, STANDALONE_MODE, STORAGE_PLUGGABLE, STORAGE_LOCALSTORAGE, STORAGE_MEMORY } from '../../../utils/constants';
|
|
4
4
|
export function __InLocalStorageMockFactory(params) {
|
|
5
5
|
var result = InMemoryStorageCSFactory(params);
|
|
6
|
-
result.validateCache = function () { return
|
|
6
|
+
result.validateCache = function () { return true; }; // to emit SDK_READY_FROM_CACHE
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
9
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IBetweenMatcherData } from '../../dtos/types';
|
|
2
|
+
|
|
1
3
|
export function zeroSinceHH(millisSinceEpoch: number): number {
|
|
2
4
|
return new Date(millisSinceEpoch).setUTCHours(0, 0, 0, 0);
|
|
3
5
|
}
|
|
@@ -5,3 +7,11 @@ export function zeroSinceHH(millisSinceEpoch: number): number {
|
|
|
5
7
|
export function zeroSinceSS(millisSinceEpoch: number): number {
|
|
6
8
|
return new Date(millisSinceEpoch).setUTCSeconds(0, 0);
|
|
7
9
|
}
|
|
10
|
+
|
|
11
|
+
export function betweenDateTimeTransform(betweenMatcherData: IBetweenMatcherData): IBetweenMatcherData {
|
|
12
|
+
return {
|
|
13
|
+
dataType: betweenMatcherData.dataType,
|
|
14
|
+
start: zeroSinceSS(betweenMatcherData.start),
|
|
15
|
+
end: zeroSinceSS(betweenMatcherData.end)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -3,7 +3,7 @@ import { matcherTypes, matcherTypesMapper, matcherDataTypes } from '../matchers/
|
|
|
3
3
|
import { segmentTransform } from './segment';
|
|
4
4
|
import { whitelistTransform } from './whitelist';
|
|
5
5
|
import { numericTransform } from './unaryNumeric';
|
|
6
|
-
import { zeroSinceHH, zeroSinceSS } from '../convertions';
|
|
6
|
+
import { zeroSinceHH, zeroSinceSS, betweenDateTimeTransform } from '../convertions';
|
|
7
7
|
import { IBetweenMatcherData, IInLargeSegmentMatcherData, IInSegmentMatcherData, ISplitMatcher, IUnaryNumericMatcherData } from '../../dtos/types';
|
|
8
8
|
import { IMatcherDto } from '../types';
|
|
9
9
|
|
|
@@ -32,7 +32,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
32
32
|
let type = matcherTypesMapper(matcherType);
|
|
33
33
|
// As default input data type we use string (even for ALL_KEYS)
|
|
34
34
|
let dataType = matcherDataTypes.STRING;
|
|
35
|
-
let value
|
|
35
|
+
let value;
|
|
36
36
|
|
|
37
37
|
if (type === matcherTypes.IN_SEGMENT) {
|
|
38
38
|
value = segmentTransform(userDefinedSegmentMatcherData as IInSegmentMatcherData);
|
|
@@ -60,8 +60,7 @@ export function matchersTransform(matchers: ISplitMatcher[]): IMatcherDto[] {
|
|
|
60
60
|
dataType = matcherDataTypes.NUMBER;
|
|
61
61
|
|
|
62
62
|
if (value.dataType === 'DATETIME') {
|
|
63
|
-
value
|
|
64
|
-
value.end = zeroSinceSS(value.end);
|
|
63
|
+
value = betweenDateTimeTransform(value);
|
|
65
64
|
dataType = matcherDataTypes.DATETIME;
|
|
66
65
|
}
|
|
67
66
|
} else if (type === matcherTypes.BETWEEN_SEMVER) {
|
|
@@ -9,13 +9,15 @@ import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING, L
|
|
|
9
9
|
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
|
|
10
10
|
import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
11
11
|
import { buildInstanceId } from './identity';
|
|
12
|
+
import { setRolloutPlan } from '../storages/setRolloutPlan';
|
|
13
|
+
import { ISegmentsCacheSync } from '../storages/types';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Factory of client method for the client-side API variant where TT is ignored.
|
|
15
17
|
* Therefore, clients don't have a bound TT for the track method.
|
|
16
18
|
*/
|
|
17
19
|
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.IBrowserClient {
|
|
18
|
-
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log } } = params;
|
|
20
|
+
const { clients, storage, syncManager, sdkReadinessManager, settings: { core: { key }, log, initialRolloutPlan } } = params;
|
|
19
21
|
|
|
20
22
|
const mainClientInstance = clientCSDecorator(
|
|
21
23
|
log,
|
|
@@ -56,6 +58,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
|
|
|
56
58
|
sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
57
59
|
});
|
|
58
60
|
|
|
61
|
+
if (sharedStorage && initialRolloutPlan) {
|
|
62
|
+
setRolloutPlan(log, initialRolloutPlan, { segments: sharedStorage.segments as ISegmentsCacheSync, largeSegments: sharedStorage.largeSegments as ISegmentsCacheSync }, matchingKey);
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
// 3 possibilities:
|
|
60
66
|
// - Standalone mode: both syncManager and sharedSyncManager are defined
|
|
61
67
|
// - Consumer mode: both syncManager and sharedSyncManager are undefined
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -14,6 +14,9 @@ import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized
|
|
|
14
14
|
import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
|
|
15
15
|
import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
|
|
16
16
|
import { DEBUG, OPTIMIZED } from '../utils/constants';
|
|
17
|
+
import { setRolloutPlan } from '../storages/setRolloutPlan';
|
|
18
|
+
import { IStorageSync } from '../storages/types';
|
|
19
|
+
import { getMatching } from '../utils/key';
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Modular SDK factory
|
|
@@ -24,7 +27,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
24
27
|
syncManagerFactory, SignalListener, impressionsObserverFactory,
|
|
25
28
|
integrationsManagerFactory, sdkManagerFactory, sdkClientMethodFactory,
|
|
26
29
|
filterAdapterFactory, lazyInit } = params;
|
|
27
|
-
const { log, sync: { impressionsMode } } = settings;
|
|
30
|
+
const { log, sync: { impressionsMode }, initialRolloutPlan, core: { key } } = settings;
|
|
28
31
|
|
|
29
32
|
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
|
|
30
33
|
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
|
|
@@ -43,7 +46,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
43
46
|
|
|
44
47
|
const storage = storageFactory({
|
|
45
48
|
settings,
|
|
46
|
-
onReadyCb
|
|
49
|
+
onReadyCb(error) {
|
|
47
50
|
if (error) {
|
|
48
51
|
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
|
|
49
52
|
readiness.timeout();
|
|
@@ -52,11 +55,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
52
55
|
readiness.splits.emit(SDK_SPLITS_ARRIVED);
|
|
53
56
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
54
57
|
},
|
|
55
|
-
onReadyFromCacheCb
|
|
58
|
+
onReadyFromCacheCb() {
|
|
56
59
|
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
57
60
|
}
|
|
58
61
|
});
|
|
59
|
-
|
|
62
|
+
|
|
63
|
+
if (initialRolloutPlan) {
|
|
64
|
+
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
|
|
65
|
+
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
const clients: Record<string, SplitIO.IBasicClient> = {};
|
|
61
69
|
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
|
|
62
70
|
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import SplitIO from '../../types/splitio';
|
|
2
|
+
import { IStorageSync } from './types';
|
|
3
|
+
import { setToArray } from '../utils/lang/sets';
|
|
4
|
+
import { getMatching } from '../utils/key';
|
|
5
|
+
import { ILogger } from '../logger/types';
|
|
6
|
+
import { RolloutPlan } from './types';
|
|
7
|
+
import { IMembershipsResponse, IMySegmentsResponse } from '../dtos/types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets the rollout plan snapshot from the given synchronous storage.
|
|
11
|
+
*/
|
|
12
|
+
export function getRolloutPlan(log: ILogger, storage: IStorageSync, options: SplitIO.RolloutPlanOptions = {}): RolloutPlan {
|
|
13
|
+
|
|
14
|
+
const { keys, exposeSegments } = options;
|
|
15
|
+
const { splits, segments, rbSegments } = storage;
|
|
16
|
+
|
|
17
|
+
log.debug(`storage: get feature flags${keys ? `, and memberships for keys ${keys}` : ''}${exposeSegments ? ', and segments' : ''}`);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
splitChanges: {
|
|
21
|
+
ff: {
|
|
22
|
+
t: splits.getChangeNumber(),
|
|
23
|
+
s: -1,
|
|
24
|
+
d: splits.getAll(),
|
|
25
|
+
},
|
|
26
|
+
rbs: {
|
|
27
|
+
t: rbSegments.getChangeNumber(),
|
|
28
|
+
s: -1,
|
|
29
|
+
d: rbSegments.getAll(),
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
segmentChanges: exposeSegments ? // @ts-ignore accessing private prop
|
|
33
|
+
Object.keys(segments.segmentCache).map(segmentName => ({
|
|
34
|
+
name: segmentName, // @ts-ignore
|
|
35
|
+
added: setToArray(segments.segmentCache[segmentName] as Set<string>),
|
|
36
|
+
removed: [],
|
|
37
|
+
since: -1,
|
|
38
|
+
till: segments.getChangeNumber(segmentName)!
|
|
39
|
+
})) :
|
|
40
|
+
undefined,
|
|
41
|
+
memberships: keys ?
|
|
42
|
+
keys.reduce<Record<string, IMembershipsResponse>>((prev, key) => {
|
|
43
|
+
const matchingKey = getMatching(key);
|
|
44
|
+
if (storage.shared) { // Client-side segments
|
|
45
|
+
const sharedStorage = storage.shared(matchingKey);
|
|
46
|
+
prev[matchingKey] = {
|
|
47
|
+
ms: { // @ts-ignore
|
|
48
|
+
k: Object.keys(sharedStorage.segments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
49
|
+
},
|
|
50
|
+
ls: sharedStorage.largeSegments ? { // @ts-ignore
|
|
51
|
+
k: Object.keys(sharedStorage.largeSegments.segmentCache).map(segmentName => ({ n: segmentName })),
|
|
52
|
+
} : undefined
|
|
53
|
+
};
|
|
54
|
+
} else { // Server-side segments
|
|
55
|
+
prev[matchingKey] = {
|
|
56
|
+
ms: { // @ts-ignore
|
|
57
|
+
k: Object.keys(storage.segments.segmentCache).reduce<IMySegmentsResponse['k']>((prev, segmentName) => { // @ts-ignore
|
|
58
|
+
return storage.segments.segmentCache[segmentName].has(matchingKey) ?
|
|
59
|
+
prev!.concat({ n: segmentName }) :
|
|
60
|
+
prev;
|
|
61
|
+
}, [])
|
|
62
|
+
},
|
|
63
|
+
ls: {
|
|
64
|
+
k: []
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return prev;
|
|
69
|
+
}, {}) :
|
|
70
|
+
undefined
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -3,19 +3,16 @@ import { isNaNNumber } from '../../utils/lang';
|
|
|
3
3
|
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
4
4
|
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
5
5
|
import { LOG_PREFIX, DEFINED } from './constants';
|
|
6
|
-
import { StorageAdapter } from '../types';
|
|
7
6
|
|
|
8
7
|
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
9
8
|
|
|
10
9
|
private readonly keys: MySegmentsKeyBuilder;
|
|
11
10
|
private readonly log: ILogger;
|
|
12
|
-
private readonly storage: StorageAdapter;
|
|
13
11
|
|
|
14
|
-
constructor(log: ILogger, keys: MySegmentsKeyBuilder
|
|
12
|
+
constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
|
|
15
13
|
super();
|
|
16
14
|
this.log = log;
|
|
17
15
|
this.keys = keys;
|
|
18
|
-
this.storage = storage;
|
|
19
16
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
20
17
|
}
|
|
21
18
|
|
|
@@ -23,8 +20,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
23
20
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
24
21
|
|
|
25
22
|
try {
|
|
26
|
-
if (
|
|
27
|
-
|
|
23
|
+
if (localStorage.getItem(segmentKey) === DEFINED) return false;
|
|
24
|
+
localStorage.setItem(segmentKey, DEFINED);
|
|
28
25
|
return true;
|
|
29
26
|
} catch (e) {
|
|
30
27
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -36,8 +33,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
36
33
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
37
34
|
|
|
38
35
|
try {
|
|
39
|
-
if (
|
|
40
|
-
|
|
36
|
+
if (localStorage.getItem(segmentKey) !== DEFINED) return false;
|
|
37
|
+
localStorage.removeItem(segmentKey);
|
|
41
38
|
return true;
|
|
42
39
|
} catch (e) {
|
|
43
40
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -46,16 +43,18 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
isInSegment(name: string): boolean {
|
|
49
|
-
return
|
|
46
|
+
return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
getRegisteredSegments(): string[] {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
// Scan current values from localStorage
|
|
51
|
+
return Object.keys(localStorage).reduce((accum, key) => {
|
|
52
|
+
let segmentName = this.keys.extractSegmentName(key);
|
|
53
|
+
|
|
54
|
+
if (segmentName) accum.push(segmentName);
|
|
55
|
+
|
|
56
|
+
return accum;
|
|
57
|
+
}, [] as string[]);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
getKeysCount() {
|
|
@@ -64,8 +63,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
64
63
|
|
|
65
64
|
protected setChangeNumber(changeNumber?: number) {
|
|
66
65
|
try {
|
|
67
|
-
if (changeNumber)
|
|
68
|
-
else
|
|
66
|
+
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
67
|
+
else localStorage.removeItem(this.keys.buildTillKey());
|
|
69
68
|
} catch (e) {
|
|
70
69
|
this.log.error(e);
|
|
71
70
|
}
|
|
@@ -73,7 +72,7 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
73
72
|
|
|
74
73
|
getChangeNumber() {
|
|
75
74
|
const n = -1;
|
|
76
|
-
let value: string | number | null =
|
|
75
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
|
|
77
76
|
|
|
78
77
|
if (value !== null) {
|
|
79
78
|
value = parseInt(value, 10);
|
|
@@ -5,24 +5,22 @@ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
|
|
|
5
5
|
import { setToArray } from '../../utils/lang/sets';
|
|
6
6
|
import { usesSegments } from '../AbstractSplitsCacheSync';
|
|
7
7
|
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
8
|
-
import { IRBSegmentsCacheSync
|
|
8
|
+
import { IRBSegmentsCacheSync } from '../types';
|
|
9
9
|
import { LOG_PREFIX } from './constants';
|
|
10
10
|
|
|
11
11
|
export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
12
12
|
|
|
13
13
|
private readonly keys: KeyBuilderCS;
|
|
14
14
|
private readonly log: ILogger;
|
|
15
|
-
private readonly storage: StorageAdapter;
|
|
16
15
|
|
|
17
|
-
constructor(settings: ISettings, keys: KeyBuilderCS
|
|
16
|
+
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
18
17
|
this.keys = keys;
|
|
19
18
|
this.log = settings.log;
|
|
20
|
-
this.storage = storage;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
clear() {
|
|
24
22
|
this.getNames().forEach(name => this.remove(name));
|
|
25
|
-
|
|
23
|
+
localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
@@ -33,8 +31,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
33
31
|
|
|
34
32
|
private setChangeNumber(changeNumber: number) {
|
|
35
33
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
35
|
+
localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
38
36
|
} catch (e) {
|
|
39
37
|
this.log.error(LOG_PREFIX + e);
|
|
40
38
|
}
|
|
@@ -42,19 +40,20 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
42
40
|
|
|
43
41
|
private updateSegmentCount(diff: number) {
|
|
44
42
|
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
45
|
-
const count = toNumber(
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
const count = toNumber(localStorage.getItem(segmentsCountKey)) + diff;
|
|
44
|
+
// @ts-expect-error
|
|
45
|
+
if (count > 0) localStorage.setItem(segmentsCountKey, count);
|
|
46
|
+
else localStorage.removeItem(segmentsCountKey);
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
private add(rbSegment: IRBSegment): boolean {
|
|
51
50
|
try {
|
|
52
51
|
const name = rbSegment.name;
|
|
53
52
|
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
|
|
54
|
-
const
|
|
55
|
-
const previous =
|
|
53
|
+
const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
|
|
54
|
+
const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
|
|
56
55
|
|
|
57
|
-
|
|
56
|
+
localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
58
57
|
|
|
59
58
|
let usesSegmentsDiff = 0;
|
|
60
59
|
if (previous && usesSegments(previous)) usesSegmentsDiff--;
|
|
@@ -73,7 +72,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
73
72
|
const rbSegment = this.get(name);
|
|
74
73
|
if (!rbSegment) return false;
|
|
75
74
|
|
|
76
|
-
|
|
75
|
+
localStorage.removeItem(this.keys.buildRBSegmentKey(name));
|
|
77
76
|
|
|
78
77
|
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
|
|
79
78
|
|
|
@@ -85,13 +84,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
private getNames(): string[] {
|
|
88
|
-
const len =
|
|
87
|
+
const len = localStorage.length;
|
|
89
88
|
const accum = [];
|
|
90
89
|
|
|
91
90
|
let cur = 0;
|
|
92
91
|
|
|
93
92
|
while (cur < len) {
|
|
94
|
-
const key =
|
|
93
|
+
const key = localStorage.key(cur);
|
|
95
94
|
|
|
96
95
|
if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
|
|
97
96
|
|
|
@@ -102,10 +101,14 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
get(name: string): IRBSegment | null {
|
|
105
|
-
const item =
|
|
104
|
+
const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
|
|
106
105
|
return item && JSON.parse(item);
|
|
107
106
|
}
|
|
108
107
|
|
|
108
|
+
getAll(): IRBSegment[] {
|
|
109
|
+
return this.getNames().map(key => this.get(key)!);
|
|
110
|
+
}
|
|
111
|
+
|
|
109
112
|
contains(names: Set<string>): boolean {
|
|
110
113
|
const namesArray = setToArray(names);
|
|
111
114
|
const namesInStorage = this.getNames();
|
|
@@ -114,7 +117,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
114
117
|
|
|
115
118
|
getChangeNumber(): number {
|
|
116
119
|
const n = -1;
|
|
117
|
-
let value: string | number | null =
|
|
120
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
|
|
118
121
|
|
|
119
122
|
if (value !== null) {
|
|
120
123
|
value = parseInt(value, 10);
|
|
@@ -126,7 +129,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
usesSegments(): boolean {
|
|
129
|
-
const storedCount =
|
|
132
|
+
const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
130
133
|
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
131
134
|
|
|
132
135
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|