@splitsoftware/splitio-commons 2.1.0-rc.2 → 2.1.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 +2 -7
- package/cjs/readiness/readinessManager.js +0 -6
- package/cjs/storages/AbstractSplitsCacheAsync.js +7 -0
- package/cjs/storages/AbstractSplitsCacheSync.js +7 -0
- package/cjs/storages/KeyBuilderCS.js +0 -3
- package/cjs/storages/dataLoader.js +2 -3
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/cjs/storages/inLocalStorage/index.js +3 -5
- package/cjs/storages/inRedis/constants.js +1 -1
- package/cjs/storages/pluggable/index.js +1 -2
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +10 -1
- package/cjs/sync/syncManagerOnline.js +3 -8
- package/cjs/trackers/uniqueKeysTracker.js +1 -1
- package/cjs/utils/constants/browser.js +5 -0
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/readiness/readinessManager.js +0 -6
- package/esm/storages/AbstractSplitsCacheAsync.js +7 -0
- package/esm/storages/AbstractSplitsCacheSync.js +7 -0
- package/esm/storages/KeyBuilderCS.js +0 -3
- package/esm/storages/dataLoader.js +1 -2
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +57 -1
- package/esm/storages/inLocalStorage/index.js +3 -5
- package/esm/storages/inRedis/constants.js +1 -1
- package/esm/storages/pluggable/index.js +1 -2
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +2 -3
- package/esm/sync/polling/updaters/splitChangesUpdater.js +11 -2
- package/esm/sync/syncManagerOnline.js +3 -8
- package/esm/trackers/uniqueKeysTracker.js +1 -1
- package/esm/utils/constants/browser.js +2 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/readiness/readinessManager.ts +0 -5
- package/src/storages/AbstractSplitsCacheAsync.ts +8 -0
- package/src/storages/AbstractSplitsCacheSync.ts +8 -0
- package/src/storages/KeyBuilderCS.ts +0 -4
- package/src/storages/dataLoader.ts +1 -3
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +66 -1
- package/src/storages/inLocalStorage/index.ts +8 -8
- package/src/storages/inRedis/constants.ts +1 -1
- package/src/storages/pluggable/index.ts +1 -2
- package/src/storages/types.ts +4 -1
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +5 -6
- package/src/sync/polling/updaters/splitChangesUpdater.ts +11 -2
- package/src/sync/syncManagerOnline.ts +3 -9
- package/src/trackers/uniqueKeysTracker.ts +1 -1
- package/src/utils/constants/browser.ts +2 -0
- package/src/utils/lang/index.ts +1 -1
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +1 -25
- package/cjs/storages/inLocalStorage/validateCache.js +0 -79
- package/esm/storages/inLocalStorage/validateCache.js +0 -75
- package/src/storages/inLocalStorage/validateCache.ts +0 -91
|
@@ -3,7 +3,6 @@ import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
|
|
|
3
3
|
import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
|
|
4
4
|
import { isConsentGranted } from '../consent';
|
|
5
5
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
6
|
-
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
7
6
|
/**
|
|
8
7
|
* Online SyncManager factory.
|
|
9
8
|
* Can be used for server-side API, and client-side API with or without multiple clients.
|
|
@@ -17,7 +16,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
17
16
|
* SyncManager factory for modular SDK
|
|
18
17
|
*/
|
|
19
18
|
return function (params) {
|
|
20
|
-
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker
|
|
19
|
+
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker;
|
|
21
20
|
/** Polling Manager */
|
|
22
21
|
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
23
22
|
/** Push Manager */
|
|
@@ -65,11 +64,6 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
65
64
|
*/
|
|
66
65
|
start: function () {
|
|
67
66
|
running = true;
|
|
68
|
-
if (startFirstTime) {
|
|
69
|
-
var isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
70
|
-
if (isCacheLoaded)
|
|
71
|
-
Promise.resolve().then(function () { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
72
|
-
}
|
|
73
67
|
// start syncing splits and segments
|
|
74
68
|
if (pollingManager) {
|
|
75
69
|
// If synchronization is disabled pushManager and pollingManager should not start
|
|
@@ -78,6 +72,7 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
78
72
|
// Doesn't call `syncAll` when the syncManager is resuming
|
|
79
73
|
if (startFirstTime) {
|
|
80
74
|
pollingManager.syncAll();
|
|
75
|
+
startFirstTime = false;
|
|
81
76
|
}
|
|
82
77
|
pushManager.start();
|
|
83
78
|
}
|
|
@@ -88,12 +83,12 @@ export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFacto
|
|
|
88
83
|
else {
|
|
89
84
|
if (startFirstTime) {
|
|
90
85
|
pollingManager.syncAll();
|
|
86
|
+
startFirstTime = false;
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
// start periodic data recording (events, impressions, telemetry).
|
|
95
91
|
submitterManager.start(!isConsentGranted(settings));
|
|
96
|
-
startFirstTime = false;
|
|
97
92
|
},
|
|
98
93
|
/**
|
|
99
94
|
* Method used to stop/pause the syncManager.
|
|
@@ -5,7 +5,7 @@ var noopFilterAdapter = {
|
|
|
5
5
|
clear: function () { }
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Tracks uniques keys
|
|
9
9
|
* Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
|
|
10
10
|
* or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
|
|
11
11
|
*
|
|
@@ -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.
|
|
6
|
+
result.splits.checkCache = 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
|
@@ -3,7 +3,6 @@ import { ISettings } from '../types';
|
|
|
3
3
|
import SplitIO from '../../types/splitio';
|
|
4
4
|
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
|
|
5
5
|
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, ISplitsEventEmitter } from './types';
|
|
6
|
-
import { STORAGE_LOCALSTORAGE } from '../utils/constants';
|
|
7
6
|
|
|
8
7
|
function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISplitsEventEmitter {
|
|
9
8
|
const splitsEventEmitter = objectAssign(new EventEmitter(), {
|
|
@@ -115,10 +114,6 @@ export function readinessManagerFactory(
|
|
|
115
114
|
isReady = true;
|
|
116
115
|
try {
|
|
117
116
|
syncLastUpdate();
|
|
118
|
-
if (!isReadyFromCache && settings.storage?.type === STORAGE_LOCALSTORAGE) {
|
|
119
|
-
isReadyFromCache = true;
|
|
120
|
-
gate.emit(SDK_READY_FROM_CACHE);
|
|
121
|
-
}
|
|
122
117
|
gate.emit(SDK_READY);
|
|
123
118
|
} catch (e) {
|
|
124
119
|
// throws user callback exceptions in next tick
|
|
@@ -27,6 +27,14 @@ export abstract class AbstractSplitsCacheAsync implements ISplitsCacheAsync {
|
|
|
27
27
|
return Promise.resolve(true);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Check if the splits information is already stored in cache.
|
|
32
|
+
* Noop, just keeping the interface. This is used by client-side implementations only.
|
|
33
|
+
*/
|
|
34
|
+
checkCache(): Promise<boolean> {
|
|
35
|
+
return Promise.resolve(false);
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/**
|
|
31
39
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
32
40
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -47,6 +47,14 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
47
47
|
|
|
48
48
|
abstract clear(): void
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Check if the splits information is already stored in cache. This data can be preloaded.
|
|
52
|
+
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
|
|
53
|
+
*/
|
|
54
|
+
checkCache(): boolean {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
/**
|
|
51
59
|
* Kill `name` split and set `defaultTreatment` and `changeNumber`.
|
|
52
60
|
* Used for SPLIT_KILL push notifications.
|
|
@@ -43,10 +43,6 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
|
|
|
43
43
|
buildTillKey() {
|
|
44
44
|
return `${this.prefix}.${this.matchingKey}.segments.till`;
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
buildLastClear() {
|
|
48
|
-
return `${this.prefix}.lastClear`;
|
|
49
|
-
}
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string): MySegmentsKeyBuilder {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { PreloadedData } from '../types';
|
|
2
|
+
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser';
|
|
2
3
|
import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types';
|
|
3
4
|
|
|
4
|
-
// This value might be eventually set via a config parameter
|
|
5
|
-
const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days
|
|
6
|
-
|
|
7
5
|
/**
|
|
8
6
|
* Factory of client-side storage loader
|
|
9
7
|
*
|
|
@@ -5,6 +5,7 @@ import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
|
5
5
|
import { ILogger } from '../../logger/types';
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
8
|
+
import { getStorageHash } from '../KeyBuilder';
|
|
8
9
|
import { setToArray } from '../../utils/lang/sets';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -14,14 +15,21 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
14
15
|
|
|
15
16
|
private readonly keys: KeyBuilderCS;
|
|
16
17
|
private readonly log: ILogger;
|
|
18
|
+
private readonly storageHash: string;
|
|
17
19
|
private readonly flagSetsFilter: string[];
|
|
18
20
|
private hasSync?: boolean;
|
|
21
|
+
private updateNewFilter?: boolean;
|
|
19
22
|
|
|
20
|
-
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
23
|
+
constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
|
|
21
24
|
super();
|
|
22
25
|
this.keys = keys;
|
|
23
26
|
this.log = settings.log;
|
|
27
|
+
this.storageHash = getStorageHash(settings);
|
|
24
28
|
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
29
|
+
|
|
30
|
+
this._checkExpiration(expirationTimestamp);
|
|
31
|
+
|
|
32
|
+
this._checkFilterQuery();
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
private _decrementCount(key: string) {
|
|
@@ -71,6 +79,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
71
79
|
* We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
|
|
72
80
|
*/
|
|
73
81
|
clear() {
|
|
82
|
+
this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
|
|
83
|
+
|
|
74
84
|
// collect item keys
|
|
75
85
|
const len = localStorage.length;
|
|
76
86
|
const accum = [];
|
|
@@ -128,6 +138,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
setChangeNumber(changeNumber: number): boolean {
|
|
141
|
+
|
|
142
|
+
// when using a new split query, we must update it at the store
|
|
143
|
+
if (this.updateNewFilter) {
|
|
144
|
+
this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
|
|
145
|
+
const storageHashKey = this.keys.buildHashKey();
|
|
146
|
+
try {
|
|
147
|
+
localStorage.setItem(storageHashKey, this.storageHash);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
this.log.error(LOG_PREFIX + e);
|
|
150
|
+
}
|
|
151
|
+
this.updateNewFilter = false;
|
|
152
|
+
}
|
|
153
|
+
|
|
131
154
|
try {
|
|
132
155
|
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
133
156
|
// update "last updated" timestamp with current time
|
|
@@ -189,6 +212,48 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
189
212
|
}
|
|
190
213
|
}
|
|
191
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Check if the splits information is already stored in browser LocalStorage.
|
|
217
|
+
* In this function we could add more code to check if the data is valid.
|
|
218
|
+
* @override
|
|
219
|
+
*/
|
|
220
|
+
checkCache(): boolean {
|
|
221
|
+
return this.getChangeNumber() > -1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
|
|
226
|
+
*
|
|
227
|
+
* @param expirationTimestamp - if the value is not a number, data will not be cleaned
|
|
228
|
+
*/
|
|
229
|
+
private _checkExpiration(expirationTimestamp?: number) {
|
|
230
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
|
|
231
|
+
if (value !== null) {
|
|
232
|
+
value = parseInt(value, 10);
|
|
233
|
+
if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
|
|
238
|
+
private _checkFilterQuery() {
|
|
239
|
+
const storageHashKey = this.keys.buildHashKey();
|
|
240
|
+
const storageHash = localStorage.getItem(storageHashKey);
|
|
241
|
+
|
|
242
|
+
if (storageHash !== this.storageHash) {
|
|
243
|
+
try {
|
|
244
|
+
// mark cache to update the new query filter on first successful splits fetch
|
|
245
|
+
this.updateNewFilter = true;
|
|
246
|
+
|
|
247
|
+
// if there is cache, clear it
|
|
248
|
+
if (this.checkCache()) this.clear();
|
|
249
|
+
|
|
250
|
+
} catch (e) {
|
|
251
|
+
this.log.error(LOG_PREFIX + e);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// if the filter didn't change, nothing is done
|
|
255
|
+
}
|
|
256
|
+
|
|
192
257
|
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
193
258
|
return flagSets.map(flagSet => {
|
|
194
259
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
@@ -7,19 +7,22 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
|
7
7
|
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
8
8
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
9
9
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
10
|
+
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
|
|
10
11
|
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
|
|
11
12
|
import { LOG_PREFIX } from './constants';
|
|
12
13
|
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
|
|
13
14
|
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
|
|
14
15
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
15
16
|
import { getMatching } from '../../utils/key';
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
export interface InLocalStorageOptions {
|
|
19
|
+
prefix?: string
|
|
20
|
+
}
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
21
24
|
*/
|
|
22
|
-
export function InLocalStorage(options:
|
|
25
|
+
export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {
|
|
23
26
|
|
|
24
27
|
const prefix = validatePrefix(options.prefix);
|
|
25
28
|
|
|
@@ -34,8 +37,9 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
34
37
|
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
|
|
35
38
|
const matchingKey = getMatching(settings.core.key);
|
|
36
39
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
40
|
+
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
|
|
37
41
|
|
|
38
|
-
const splits = new SplitsCacheInLocal(settings, keys);
|
|
42
|
+
const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
|
|
39
43
|
const segments = new MySegmentsCacheInLocal(log, keys);
|
|
40
44
|
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
41
45
|
|
|
@@ -49,10 +53,6 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
49
53
|
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
|
|
50
54
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
51
55
|
|
|
52
|
-
validateCache() {
|
|
53
|
-
return validateCache(options, settings, keys, splits, segments, largeSegments);
|
|
54
|
-
},
|
|
55
|
-
|
|
56
56
|
destroy() { },
|
|
57
57
|
|
|
58
58
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const LOG_PREFIX = 'storage:redis: ';
|
|
2
2
|
export const DEFAULT_CACHE_SIZE = 30000;
|
|
3
|
-
export const REFRESH_RATE = 300000; //
|
|
3
|
+
export const REFRESH_RATE = 300000; // 300000 ms = start after 5 mins
|
|
4
4
|
export const TTL_REFRESH = 3600; // 1hr
|
|
@@ -88,8 +88,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
|
|
|
88
88
|
// Connects to wrapper and emits SDK_READY event on main client
|
|
89
89
|
const connectPromise = wrapper.connect().then(() => {
|
|
90
90
|
if (isSynchronizer) {
|
|
91
|
-
//
|
|
92
|
-
// In standalone or producer mode, clear storage if SDK key, flags filter criteria or flags spec version was modified
|
|
91
|
+
// In standalone or producer mode, clear storage if SDK key or feature flag filter has changed
|
|
93
92
|
return wrapper.get(keys.buildHashKey()).then((hash) => {
|
|
94
93
|
const currentHash = getStorageHash(settings);
|
|
95
94
|
if (hash !== currentHash) {
|
package/src/storages/types.ts
CHANGED
|
@@ -191,6 +191,8 @@ export interface ISplitsCacheBase {
|
|
|
191
191
|
// only for Client-Side. Returns true if the storage is not synchronized yet (getChangeNumber() === -1) or contains a FF using segments or large segments
|
|
192
192
|
usesSegments(): MaybeThenable<boolean>,
|
|
193
193
|
clear(): MaybeThenable<boolean | void>,
|
|
194
|
+
// should never reject or throw an exception. Instead return false by default, to avoid emitting SDK_READY_FROM_CACHE.
|
|
195
|
+
checkCache(): MaybeThenable<boolean>,
|
|
194
196
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): MaybeThenable<boolean>,
|
|
195
197
|
getNamesByFlagSets(flagSets: string[]): MaybeThenable<Set<string>[]>
|
|
196
198
|
}
|
|
@@ -207,6 +209,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
207
209
|
trafficTypeExists(trafficType: string): boolean,
|
|
208
210
|
usesSegments(): boolean,
|
|
209
211
|
clear(): void,
|
|
212
|
+
checkCache(): boolean,
|
|
210
213
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): boolean,
|
|
211
214
|
getNamesByFlagSets(flagSets: string[]): Set<string>[]
|
|
212
215
|
}
|
|
@@ -223,6 +226,7 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
|
|
|
223
226
|
trafficTypeExists(trafficType: string): Promise<boolean>,
|
|
224
227
|
usesSegments(): Promise<boolean>,
|
|
225
228
|
clear(): Promise<boolean | void>,
|
|
229
|
+
checkCache(): Promise<boolean>,
|
|
226
230
|
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean>,
|
|
227
231
|
getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
|
|
228
232
|
}
|
|
@@ -453,7 +457,6 @@ export interface IStorageSync extends IStorageBase<
|
|
|
453
457
|
IUniqueKeysCacheSync
|
|
454
458
|
> {
|
|
455
459
|
// Defined in client-side
|
|
456
|
-
validateCache?: () => boolean, // @TODO support async
|
|
457
460
|
largeSegments?: ISegmentsCacheSync,
|
|
458
461
|
}
|
|
459
462
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { forOwn } from '../../../utils/lang';
|
|
2
2
|
import { IReadinessManager } from '../../../readiness/types';
|
|
3
|
-
import {
|
|
3
|
+
import { ISplitsCacheSync } from '../../../storages/types';
|
|
4
4
|
import { ISplitsParser } from '../splitsParser/types';
|
|
5
5
|
import { ISplit, ISplitPartial } from '../../../dtos/types';
|
|
6
6
|
import { syncTaskFactory } from '../../syncTask';
|
|
@@ -15,7 +15,7 @@ import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/c
|
|
|
15
15
|
*/
|
|
16
16
|
export function fromObjectUpdaterFactory(
|
|
17
17
|
splitsParser: ISplitsParser,
|
|
18
|
-
storage:
|
|
18
|
+
storage: { splits: ISplitsCacheSync },
|
|
19
19
|
readiness: IReadinessManager,
|
|
20
20
|
settings: ISettings,
|
|
21
21
|
): () => Promise<boolean> {
|
|
@@ -60,10 +60,9 @@ export function fromObjectUpdaterFactory(
|
|
|
60
60
|
|
|
61
61
|
if (startingUp) {
|
|
62
62
|
startingUp = false;
|
|
63
|
-
|
|
64
|
-
Promise.resolve().then(() => {
|
|
63
|
+
Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
|
|
65
64
|
// Emits SDK_READY_FROM_CACHE
|
|
66
|
-
if (
|
|
65
|
+
if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
67
66
|
// Emits SDK_READY
|
|
68
67
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
69
68
|
});
|
|
@@ -81,7 +80,7 @@ export function fromObjectUpdaterFactory(
|
|
|
81
80
|
*/
|
|
82
81
|
export function fromObjectSyncTaskFactory(
|
|
83
82
|
splitsParser: ISplitsParser,
|
|
84
|
-
storage:
|
|
83
|
+
storage: { splits: ISplitsCacheSync },
|
|
85
84
|
readiness: IReadinessManager,
|
|
86
85
|
settings: ISettings
|
|
87
86
|
): ISyncTask<[], boolean> {
|
|
@@ -3,7 +3,7 @@ import { ISplitChangesFetcher } from '../fetchers/types';
|
|
|
3
3
|
import { ISplit, ISplitChangesResponse, ISplitFiltersValidation } from '../../../dtos/types';
|
|
4
4
|
import { ISplitsEventEmitter } from '../../../readiness/types';
|
|
5
5
|
import { timeout } from '../../../utils/promise/timeout';
|
|
6
|
-
import { SDK_SPLITS_ARRIVED } from '../../../readiness/constants';
|
|
6
|
+
import { SDK_SPLITS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
|
|
7
7
|
import { ILogger } from '../../../logger/types';
|
|
8
8
|
import { SYNC_SPLITS_FETCH, SYNC_SPLITS_NEW, SYNC_SPLITS_REMOVED, SYNC_SPLITS_SEGMENTS, SYNC_SPLITS_FETCH_FAILS, SYNC_SPLITS_FETCH_RETRY } from '../../../logger/constants';
|
|
9
9
|
import { startsWith } from '../../../utils/lang';
|
|
@@ -153,7 +153,7 @@ export function splitChangesUpdaterFactory(
|
|
|
153
153
|
*/
|
|
154
154
|
function _splitChangesUpdater(since: number, retry = 0): Promise<boolean> {
|
|
155
155
|
log.debug(SYNC_SPLITS_FETCH, [since]);
|
|
156
|
-
|
|
156
|
+
const fetcherPromise = Promise.resolve(splitUpdateNotification ?
|
|
157
157
|
{ splits: [splitUpdateNotification.payload], till: splitUpdateNotification.changeNumber } :
|
|
158
158
|
splitChangesFetcher(since, noCache, till, _promiseDecorator)
|
|
159
159
|
)
|
|
@@ -200,6 +200,15 @@ export function splitChangesUpdaterFactory(
|
|
|
200
200
|
}
|
|
201
201
|
return false;
|
|
202
202
|
});
|
|
203
|
+
|
|
204
|
+
// After triggering the requests, if we have cached splits information let's notify that to emit SDK_READY_FROM_CACHE.
|
|
205
|
+
// Wrapping in a promise since checkCache can be async.
|
|
206
|
+
if (splitsEventEmitter && startingUp) {
|
|
207
|
+
Promise.resolve(splits.checkCache()).then(isCacheReady => {
|
|
208
|
+
if (isCacheReady) splitsEventEmitter.emit(SDK_SPLITS_CACHE_LOADED);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return fetcherPromise;
|
|
203
212
|
}
|
|
204
213
|
|
|
205
214
|
let sincePromise = Promise.resolve(splits.getChangeNumber()); // `getChangeNumber` never rejects or throws error
|
|
@@ -9,7 +9,6 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
|
|
|
9
9
|
import { isConsentGranted } from '../consent';
|
|
10
10
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
11
11
|
import { ISdkFactoryContextSync } from '../sdkFactory/types';
|
|
12
|
-
import { SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Online SyncManager factory.
|
|
@@ -29,7 +28,7 @@ export function syncManagerOnlineFactory(
|
|
|
29
28
|
*/
|
|
30
29
|
return function (params: ISdkFactoryContextSync): ISyncManagerCS {
|
|
31
30
|
|
|
32
|
-
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } },
|
|
31
|
+
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;
|
|
33
32
|
|
|
34
33
|
/** Polling Manager */
|
|
35
34
|
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
@@ -88,11 +87,6 @@ export function syncManagerOnlineFactory(
|
|
|
88
87
|
start() {
|
|
89
88
|
running = true;
|
|
90
89
|
|
|
91
|
-
if (startFirstTime) {
|
|
92
|
-
const isCacheLoaded = storage.validateCache ? storage.validateCache() : false;
|
|
93
|
-
if (isCacheLoaded) Promise.resolve().then(() => { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
90
|
// start syncing splits and segments
|
|
97
91
|
if (pollingManager) {
|
|
98
92
|
|
|
@@ -102,6 +96,7 @@ export function syncManagerOnlineFactory(
|
|
|
102
96
|
// Doesn't call `syncAll` when the syncManager is resuming
|
|
103
97
|
if (startFirstTime) {
|
|
104
98
|
pollingManager.syncAll();
|
|
99
|
+
startFirstTime = false;
|
|
105
100
|
}
|
|
106
101
|
pushManager.start();
|
|
107
102
|
} else {
|
|
@@ -110,14 +105,13 @@ export function syncManagerOnlineFactory(
|
|
|
110
105
|
} else {
|
|
111
106
|
if (startFirstTime) {
|
|
112
107
|
pollingManager.syncAll();
|
|
108
|
+
startFirstTime = false;
|
|
113
109
|
}
|
|
114
110
|
}
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
// start periodic data recording (events, impressions, telemetry).
|
|
118
114
|
submitterManager.start(!isConsentGranted(settings));
|
|
119
|
-
|
|
120
|
-
startFirstTime = false;
|
|
121
115
|
},
|
|
122
116
|
|
|
123
117
|
/**
|
|
@@ -10,7 +10,7 @@ const noopFilterAdapter = {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Tracks uniques keys
|
|
14
14
|
* Unique Keys Tracker will be in charge of checking if the MTK was already sent to the BE in the last period
|
|
15
15
|
* or schedule to be sent; if not it will be added in an internal cache and sent in the next post.
|
|
16
16
|
*
|
package/src/utils/lang/index.ts
CHANGED
|
@@ -120,7 +120,7 @@ export function isBoolean(val: any): boolean {
|
|
|
120
120
|
* Unlike `Number.isFinite`, it also tests Number object instances.
|
|
121
121
|
* Unlike global `isFinite`, it returns false if the value is not a number or Number object instance.
|
|
122
122
|
*/
|
|
123
|
-
export function isFiniteNumber(val: any):
|
|
123
|
+
export function isFiniteNumber(val: any): boolean {
|
|
124
124
|
if (val instanceof Number) val = val.valueOf();
|
|
125
125
|
return typeof val === 'number' ?
|
|
126
126
|
Number.isFinite ? Number.isFinite(val) : isFinite(val) :
|
|
@@ -8,7 +8,7 @@ import { IStorageFactoryParams, IStorageSync } from '../../../storages/types';
|
|
|
8
8
|
|
|
9
9
|
export function __InLocalStorageMockFactory(params: IStorageFactoryParams): IStorageSync {
|
|
10
10
|
const result = InMemoryStorageCSFactory(params);
|
|
11
|
-
result.
|
|
11
|
+
result.splits.checkCache = () => true; // to emit SDK_READY_FROM_CACHE
|
|
12
12
|
return result;
|
|
13
13
|
}
|
|
14
14
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/types/splitio.d.ts
CHANGED
|
@@ -910,18 +910,6 @@ declare namespace SplitIO {
|
|
|
910
910
|
* @defaultValue `'SPLITIO'`
|
|
911
911
|
*/
|
|
912
912
|
prefix?: string;
|
|
913
|
-
/**
|
|
914
|
-
* Number of days before cached data expires if it was not updated. If cache expires, it is cleared on initialization.
|
|
915
|
-
*
|
|
916
|
-
* @defaultValue `10`
|
|
917
|
-
*/
|
|
918
|
-
expirationDays?: number;
|
|
919
|
-
/**
|
|
920
|
-
* Optional settings to clear the cache. If set to `true`, the SDK clears the cached data on initialization, unless the cache was cleared within the last 24 hours.
|
|
921
|
-
*
|
|
922
|
-
* @defaultValue `false`
|
|
923
|
-
*/
|
|
924
|
-
clearOnInit?: boolean;
|
|
925
913
|
}
|
|
926
914
|
/**
|
|
927
915
|
* Storage for asynchronous (consumer) SDK.
|
|
@@ -1245,23 +1233,11 @@ declare namespace SplitIO {
|
|
|
1245
1233
|
*/
|
|
1246
1234
|
type?: BrowserStorage;
|
|
1247
1235
|
/**
|
|
1248
|
-
* Optional prefix to prevent any kind of data collision between SDK versions
|
|
1236
|
+
* Optional prefix to prevent any kind of data collision between SDK versions.
|
|
1249
1237
|
*
|
|
1250
1238
|
* @defaultValue `'SPLITIO'`
|
|
1251
1239
|
*/
|
|
1252
1240
|
prefix?: string;
|
|
1253
|
-
/**
|
|
1254
|
-
* Optional settings for the 'LOCALSTORAGE' storage type. It specifies the number of days before cached data expires if it was not updated. If cache expires, it is cleared on initialization.
|
|
1255
|
-
*
|
|
1256
|
-
* @defaultValue `10`
|
|
1257
|
-
*/
|
|
1258
|
-
expirationDays?: number;
|
|
1259
|
-
/**
|
|
1260
|
-
* Optional settings for the 'LOCALSTORAGE' storage type. If set to `true`, the SDK clears the cached data on initialization, unless the cache was cleared within the last 24 hours.
|
|
1261
|
-
*
|
|
1262
|
-
* @defaultValue `false`
|
|
1263
|
-
*/
|
|
1264
|
-
clearOnInit?: boolean;
|
|
1265
1241
|
};
|
|
1266
1242
|
}
|
|
1267
1243
|
/**
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateCache = void 0;
|
|
4
|
-
var lang_1 = require("../../utils/lang");
|
|
5
|
-
var KeyBuilder_1 = require("../KeyBuilder");
|
|
6
|
-
var constants_1 = require("./constants");
|
|
7
|
-
var DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
|
|
8
|
-
var MILLIS_IN_A_DAY = 86400000;
|
|
9
|
-
/**
|
|
10
|
-
* Validates if cache should be cleared and sets the cache `hash` if needed.
|
|
11
|
-
*
|
|
12
|
-
* @returns `true` if cache should be cleared, `false` otherwise
|
|
13
|
-
*/
|
|
14
|
-
function validateExpiration(options, settings, keys, currentTimestamp, isThereCache) {
|
|
15
|
-
var log = settings.log;
|
|
16
|
-
// Check expiration
|
|
17
|
-
var lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()), 10);
|
|
18
|
-
if (!(0, lang_1.isNaNNumber)(lastUpdatedTimestamp)) {
|
|
19
|
-
var cacheExpirationInDays = (0, lang_1.isFiniteNumber)(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
20
|
-
var expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
21
|
-
if (lastUpdatedTimestamp < expirationTimestamp) {
|
|
22
|
-
log.info(constants_1.LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache');
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Check hash
|
|
27
|
-
var storageHashKey = keys.buildHashKey();
|
|
28
|
-
var storageHash = localStorage.getItem(storageHashKey);
|
|
29
|
-
var currentStorageHash = (0, KeyBuilder_1.getStorageHash)(settings);
|
|
30
|
-
if (storageHash !== currentStorageHash) {
|
|
31
|
-
try {
|
|
32
|
-
localStorage.setItem(storageHashKey, currentStorageHash);
|
|
33
|
-
}
|
|
34
|
-
catch (e) {
|
|
35
|
-
log.error(constants_1.LOG_PREFIX + e);
|
|
36
|
-
}
|
|
37
|
-
if (isThereCache) {
|
|
38
|
-
log.info(constants_1.LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache');
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
return false; // No cache to clear
|
|
42
|
-
}
|
|
43
|
-
// Clear on init
|
|
44
|
-
if (options.clearOnInit) {
|
|
45
|
-
var lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()), 10);
|
|
46
|
-
if ((0, lang_1.isNaNNumber)(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
47
|
-
log.info(constants_1.LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Clean cache if:
|
|
54
|
-
* - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp`
|
|
55
|
-
* - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified
|
|
56
|
-
* - `clearOnInit` was set and cache was not cleared in the last 24 hours
|
|
57
|
-
*
|
|
58
|
-
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
59
|
-
*/
|
|
60
|
-
function validateCache(options, settings, keys, splits, segments, largeSegments) {
|
|
61
|
-
var currentTimestamp = Date.now();
|
|
62
|
-
var isThereCache = splits.getChangeNumber() > -1;
|
|
63
|
-
if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
|
|
64
|
-
splits.clear();
|
|
65
|
-
segments.clear();
|
|
66
|
-
largeSegments.clear();
|
|
67
|
-
// Update last clear timestamp
|
|
68
|
-
try {
|
|
69
|
-
localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
70
|
-
}
|
|
71
|
-
catch (e) {
|
|
72
|
-
settings.log.error(constants_1.LOG_PREFIX + e);
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
// Check if ready from cache
|
|
77
|
-
return isThereCache;
|
|
78
|
-
}
|
|
79
|
-
exports.validateCache = validateCache;
|