@splitsoftware/splitio-commons 2.5.0 → 2.5.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +12 -0
- 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/updaters/mySegmentsUpdater.js +2 -0
- package/cjs/sync/polling/updaters/splitChangesUpdater.js +2 -0
- package/cjs/sync/syncManagerOnline.js +28 -24
- package/cjs/utils/env/isLocalStorageAvailable.js +22 -1
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- 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/updaters/mySegmentsUpdater.js +2 -0
- package/esm/sync/polling/updaters/splitChangesUpdater.js +2 -0
- package/esm/sync/syncManagerOnline.js +28 -24
- package/esm/utils/env/isLocalStorageAvailable.js +19 -0
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -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/updaters/mySegmentsUpdater.ts +2 -0
- package/src/sync/polling/updaters/splitChangesUpdater.ts +3 -1
- package/src/sync/syncManagerOnline.ts +27 -22
- package/src/utils/env/isLocalStorageAvailable.ts +20 -0
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +30 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ImpressionsCacheInMemory } from '../inMemory/ImpressionsCacheInMemory';
|
|
2
2
|
import { ImpressionCountsCacheInMemory } from '../inMemory/ImpressionCountsCacheInMemory';
|
|
3
3
|
import { EventsCacheInMemory } from '../inMemory/EventsCacheInMemory';
|
|
4
|
-
import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types';
|
|
4
|
+
import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory, StorageAdapter } from '../types';
|
|
5
5
|
import { validatePrefix } from '../KeyBuilder';
|
|
6
6
|
import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
7
|
-
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
7
|
+
import { isLocalStorageAvailable, isValidStorageWrapper, isWebStorage } from '../../utils/env/isLocalStorageAvailable';
|
|
8
8
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
9
9
|
import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
|
|
10
10
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
@@ -15,7 +15,24 @@ import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/Telem
|
|
|
15
15
|
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
|
|
16
16
|
import { getMatching } from '../../utils/key';
|
|
17
17
|
import { validateCache } from './validateCache';
|
|
18
|
+
import { ILogger } from '../../logger/types';
|
|
18
19
|
import SplitIO from '../../../types/splitio';
|
|
20
|
+
import { storageAdapter } from './storageAdapter';
|
|
21
|
+
|
|
22
|
+
function validateStorage(log: ILogger, prefix: string, wrapper?: SplitIO.StorageWrapper): StorageAdapter | undefined {
|
|
23
|
+
if (wrapper) {
|
|
24
|
+
if (isValidStorageWrapper(wrapper)) {
|
|
25
|
+
return isWebStorage(wrapper) ?
|
|
26
|
+
wrapper as StorageAdapter: // localStorage and sessionStorage don't need adapter
|
|
27
|
+
storageAdapter(log, prefix, wrapper);
|
|
28
|
+
}
|
|
29
|
+
log.warn(LOG_PREFIX + 'Invalid storage provided. Falling back to LocalStorage API');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isLocalStorageAvailable()) return localStorage;
|
|
33
|
+
|
|
34
|
+
log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
35
|
+
}
|
|
19
36
|
|
|
20
37
|
/**
|
|
21
38
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
@@ -25,21 +42,19 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
25
42
|
const prefix = validatePrefix(options.prefix);
|
|
26
43
|
|
|
27
44
|
function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
|
|
45
|
+
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
|
|
28
46
|
|
|
29
|
-
|
|
30
|
-
if (!
|
|
31
|
-
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
32
|
-
return InMemoryStorageCSFactory(params);
|
|
33
|
-
}
|
|
47
|
+
const storage = validateStorage(log, prefix, options.wrapper);
|
|
48
|
+
if (!storage) return InMemoryStorageCSFactory(params);
|
|
34
49
|
|
|
35
|
-
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
|
|
36
50
|
const matchingKey = getMatching(settings.core.key);
|
|
37
51
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
38
52
|
|
|
39
|
-
const splits = new SplitsCacheInLocal(settings, keys);
|
|
40
|
-
const rbSegments = new RBSegmentsCacheInLocal(settings, keys);
|
|
41
|
-
const segments = new MySegmentsCacheInLocal(log, keys);
|
|
42
|
-
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
|
|
53
|
+
const splits = new SplitsCacheInLocal(settings, keys, storage);
|
|
54
|
+
const rbSegments = new RBSegmentsCacheInLocal(settings, keys, storage);
|
|
55
|
+
const segments = new MySegmentsCacheInLocal(log, keys, storage);
|
|
56
|
+
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage);
|
|
57
|
+
let validateCachePromise: Promise<boolean> | undefined;
|
|
43
58
|
|
|
44
59
|
return {
|
|
45
60
|
splits,
|
|
@@ -53,10 +68,16 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
53
68
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
54
69
|
|
|
55
70
|
validateCache() {
|
|
56
|
-
return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
|
|
71
|
+
return validateCachePromise || (validateCachePromise = validateCache(options, storage, settings, keys, splits, rbSegments, segments, largeSegments));
|
|
57
72
|
},
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
save() {
|
|
75
|
+
return storage.save && storage.save();
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
destroy() {
|
|
79
|
+
return storage.whenSaved && storage.whenSaved();
|
|
80
|
+
},
|
|
60
81
|
|
|
61
82
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
62
83
|
shared(matchingKey: string) {
|
|
@@ -64,8 +85,8 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
64
85
|
return {
|
|
65
86
|
splits: this.splits,
|
|
66
87
|
rbSegments: this.rbSegments,
|
|
67
|
-
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
|
|
68
|
-
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
|
|
88
|
+
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey), storage),
|
|
89
|
+
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey), storage),
|
|
69
90
|
impressions: this.impressions,
|
|
70
91
|
impressionCounts: this.impressionCounts,
|
|
71
92
|
events: this.events,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ILogger } from '../../logger/types';
|
|
2
|
+
import SplitIO from '../../../types/splitio';
|
|
3
|
+
import { LOG_PREFIX } from './constants';
|
|
4
|
+
import { StorageAdapter } from '../types';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export function storageAdapter(log: ILogger, prefix: string, wrapper: SplitIO.StorageWrapper): Required<StorageAdapter> {
|
|
8
|
+
let keys: string[] = [];
|
|
9
|
+
let cache: Record<string, string> = {};
|
|
10
|
+
|
|
11
|
+
let loadPromise: Promise<void> | undefined;
|
|
12
|
+
let savePromise = Promise.resolve();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
load() {
|
|
16
|
+
return loadPromise || (loadPromise = Promise.resolve().then(() => {
|
|
17
|
+
return wrapper.getItem(prefix);
|
|
18
|
+
}).then((storedCache) => {
|
|
19
|
+
cache = JSON.parse(storedCache || '{}');
|
|
20
|
+
keys = Object.keys(cache);
|
|
21
|
+
}).catch((e) => {
|
|
22
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `getItem` method, with error: ' + e);
|
|
23
|
+
}));
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
save() {
|
|
27
|
+
return savePromise = savePromise.then(() => {
|
|
28
|
+
return Promise.resolve(wrapper.setItem(prefix, JSON.stringify(cache)));
|
|
29
|
+
}).catch((e) => {
|
|
30
|
+
log.error(LOG_PREFIX + 'Rejected promise calling wrapper `setItem` method, with error: ' + e);
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
whenSaved() {
|
|
35
|
+
return savePromise;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
get length() {
|
|
39
|
+
return keys.length;
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
getItem(key: string) {
|
|
43
|
+
return cache[key] || null;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
key(index: number) {
|
|
47
|
+
return keys[index] || null;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
removeItem(key: string) {
|
|
51
|
+
const index = keys.indexOf(key);
|
|
52
|
+
if (index === -1) return;
|
|
53
|
+
keys.splice(index, 1);
|
|
54
|
+
delete cache[key];
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
setItem(key: string, value: string) {
|
|
58
|
+
if (keys.indexOf(key) === -1) keys.push(key);
|
|
59
|
+
cache[key] = value;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -7,6 +7,7 @@ import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
|
|
|
7
7
|
import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
8
8
|
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
9
9
|
import SplitIO from '../../../types/splitio';
|
|
10
|
+
import { StorageAdapter } from '../types';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
|
|
12
13
|
const MILLIS_IN_A_DAY = 86400000;
|
|
@@ -16,11 +17,11 @@ const MILLIS_IN_A_DAY = 86400000;
|
|
|
16
17
|
*
|
|
17
18
|
* @returns `true` if cache should be cleared, `false` otherwise
|
|
18
19
|
*/
|
|
19
|
-
function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
|
|
20
|
+
function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
|
|
20
21
|
const { log, initialRolloutPlan } = settings;
|
|
21
22
|
|
|
22
23
|
// Check expiration
|
|
23
|
-
const lastUpdatedTimestamp = parseInt(
|
|
24
|
+
const lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()) as string, 10);
|
|
24
25
|
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
25
26
|
const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
26
27
|
const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
@@ -32,12 +33,12 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
|
|
|
32
33
|
|
|
33
34
|
// Check hash
|
|
34
35
|
const storageHashKey = keys.buildHashKey();
|
|
35
|
-
const storageHash =
|
|
36
|
+
const storageHash = storage.getItem(storageHashKey);
|
|
36
37
|
const currentStorageHash = getStorageHash(settings);
|
|
37
38
|
|
|
38
39
|
if (storageHash !== currentStorageHash) {
|
|
39
40
|
try {
|
|
40
|
-
|
|
41
|
+
storage.setItem(storageHashKey, currentStorageHash);
|
|
41
42
|
} catch (e) {
|
|
42
43
|
log.error(LOG_PREFIX + e);
|
|
43
44
|
}
|
|
@@ -50,7 +51,7 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
|
|
|
50
51
|
|
|
51
52
|
// Clear on init
|
|
52
53
|
if (options.clearOnInit) {
|
|
53
|
-
const lastClearTimestamp = parseInt(
|
|
54
|
+
const lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()) as string, 10);
|
|
54
55
|
|
|
55
56
|
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
56
57
|
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
@@ -67,27 +68,32 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: IS
|
|
|
67
68
|
*
|
|
68
69
|
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
69
70
|
*/
|
|
70
|
-
export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
|
|
71
|
+
export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise<boolean> {
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
return Promise.resolve(storage.load && storage.load()).then(() => {
|
|
74
|
+
const currentTimestamp = Date.now();
|
|
75
|
+
const isThereCache = splits.getChangeNumber() > -1;
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) {
|
|
78
|
+
splits.clear();
|
|
79
|
+
rbSegments.clear();
|
|
80
|
+
segments.clear();
|
|
81
|
+
largeSegments.clear();
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
// Update last clear timestamp
|
|
84
|
+
try {
|
|
85
|
+
storage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
86
|
+
} catch (e) {
|
|
87
|
+
settings.log.error(LOG_PREFIX + e);
|
|
88
|
+
}
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
// Persist clear
|
|
91
|
+
if (storage.save) storage.save();
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
// Check if ready from cache
|
|
97
|
+
return isThereCache;
|
|
98
|
+
});
|
|
93
99
|
}
|
package/src/storages/types.ts
CHANGED
|
@@ -4,6 +4,23 @@ import { MySegmentsData } from '../sync/polling/types';
|
|
|
4
4
|
import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
|
|
5
5
|
import { ISettings } from '../types';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Internal interface based on a subset of the Web Storage API interface
|
|
9
|
+
* (https://developer.mozilla.org/en-US/docs/Web/API/Storage) used by the SDK
|
|
10
|
+
*/
|
|
11
|
+
export interface StorageAdapter {
|
|
12
|
+
// Methods to support async storages
|
|
13
|
+
load?: () => Promise<void>;
|
|
14
|
+
save?: () => Promise<void>;
|
|
15
|
+
whenSaved?: () => Promise<void>;
|
|
16
|
+
// Methods based on https://developer.mozilla.org/en-US/docs/Web/API/Storage
|
|
17
|
+
readonly length: number;
|
|
18
|
+
key(index: number): string | null;
|
|
19
|
+
getItem(key: string): string | null;
|
|
20
|
+
removeItem(key: string): void;
|
|
21
|
+
setItem(key: string, value: string): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
/**
|
|
8
25
|
* Interface of a pluggable storage wrapper.
|
|
9
26
|
*/
|
|
@@ -467,6 +484,7 @@ export interface IStorageBase<
|
|
|
467
484
|
uniqueKeys: TUniqueKeysCache,
|
|
468
485
|
destroy(): void | Promise<void>,
|
|
469
486
|
shared?: (matchingKey: string, onReadyCb?: (error?: any) => void) => this
|
|
487
|
+
save?: () => void | Promise<void>,
|
|
470
488
|
}
|
|
471
489
|
|
|
472
490
|
export interface IStorageSync extends IStorageBase<
|
|
@@ -480,7 +498,7 @@ export interface IStorageSync extends IStorageBase<
|
|
|
480
498
|
IUniqueKeysCacheSync
|
|
481
499
|
> {
|
|
482
500
|
// Defined in client-side
|
|
483
|
-
validateCache?: () => boolean
|
|
501
|
+
validateCache?: () => Promise<boolean>,
|
|
484
502
|
largeSegments?: ISegmentsCacheSync,
|
|
485
503
|
}
|
|
486
504
|
|
|
@@ -59,8 +59,7 @@ export function fromObjectUpdaterFactory(
|
|
|
59
59
|
|
|
60
60
|
if (startingUp) {
|
|
61
61
|
startingUp = false;
|
|
62
|
-
|
|
63
|
-
Promise.resolve().then(() => {
|
|
62
|
+
Promise.resolve(storage.validateCache ? storage.validateCache() : false).then((isCacheLoaded) => {
|
|
64
63
|
// Emits SDK_READY_FROM_CACHE
|
|
65
64
|
if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
66
65
|
// Emits SDK_READY
|
|
@@ -51,6 +51,8 @@ export function mySegmentsUpdaterFactory(
|
|
|
51
51
|
shouldNotifyUpdate = largeSegments!.resetSegments((segmentsData as IMembershipsResponse).ls || {}) || shouldNotifyUpdate;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
if (storage.save) storage.save();
|
|
55
|
+
|
|
54
56
|
// Notify update if required
|
|
55
57
|
if (usesSegmentsSync(storage) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
|
|
56
58
|
readyOnAlreadyExistentState = false;
|
|
@@ -117,7 +117,7 @@ export function computeMutation<T extends ISplit | IRBSegment>(rules: Array<T>,
|
|
|
117
117
|
export function splitChangesUpdaterFactory(
|
|
118
118
|
log: ILogger,
|
|
119
119
|
splitChangesFetcher: ISplitChangesFetcher,
|
|
120
|
-
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments'>,
|
|
120
|
+
storage: Pick<IStorageBase, 'splits' | 'rbSegments' | 'segments' | 'save'>,
|
|
121
121
|
splitFiltersValidation: ISplitFiltersValidation,
|
|
122
122
|
splitsEventEmitter?: ISplitsEventEmitter,
|
|
123
123
|
requestTimeoutBeforeReady: number = 0,
|
|
@@ -185,6 +185,8 @@ export function splitChangesUpdaterFactory(
|
|
|
185
185
|
// @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
|
|
186
186
|
segments.registerSegments(setToArray(usedSegments))
|
|
187
187
|
]).then(([ffChanged, rbsChanged]) => {
|
|
188
|
+
if (storage.save) storage.save();
|
|
189
|
+
|
|
188
190
|
if (splitsEventEmitter) {
|
|
189
191
|
// To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched
|
|
190
192
|
return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments))))
|
|
@@ -89,36 +89,41 @@ export function syncManagerOnlineFactory(
|
|
|
89
89
|
start() {
|
|
90
90
|
running = true;
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (isCacheLoaded) Promise.resolve().then(() => { readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); });
|
|
95
|
-
}
|
|
92
|
+
// @TODO once event, impression and telemetry storages support persistence, call when `validateCache` promise is resolved
|
|
93
|
+
submitterManager.start(!isConsentGranted(settings));
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
return Promise.resolve(storage.validateCache ? storage.validateCache() : false).then((isCacheLoaded) => {
|
|
96
|
+
if (!running) return;
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
|
|
98
|
+
if (startFirstTime) {
|
|
99
|
+
// Emits SDK_READY_FROM_CACHE
|
|
100
|
+
if (isCacheLoaded) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
|
|
101
|
+
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// start syncing splits and segments
|
|
105
|
+
if (pollingManager) {
|
|
106
|
+
|
|
107
|
+
// If synchronization is disabled pushManager and pollingManager should not start
|
|
108
|
+
if (syncEnabled) {
|
|
109
|
+
if (pushManager) {
|
|
110
|
+
// Doesn't call `syncAll` when the syncManager is resuming
|
|
111
|
+
if (startFirstTime) {
|
|
112
|
+
pollingManager.syncAll();
|
|
113
|
+
}
|
|
114
|
+
pushManager.start();
|
|
115
|
+
} else {
|
|
116
|
+
pollingManager.start();
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
104
119
|
if (startFirstTime) {
|
|
105
120
|
pollingManager.syncAll();
|
|
106
121
|
}
|
|
107
|
-
pushManager.start();
|
|
108
|
-
} else {
|
|
109
|
-
pollingManager.start();
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
if (startFirstTime) {
|
|
113
|
-
pollingManager.syncAll();
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// start periodic data recording (events, impressions, telemetry).
|
|
119
|
-
submitterManager.start(!isConsentGranted(settings));
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
startFirstTime = false;
|
|
126
|
+
});
|
|
122
127
|
},
|
|
123
128
|
|
|
124
129
|
/**
|
|
@@ -9,3 +9,23 @@ export function isLocalStorageAvailable(): boolean {
|
|
|
9
9
|
return false;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
export function isValidStorageWrapper(wrapper: any): boolean {
|
|
14
|
+
return wrapper !== null &&
|
|
15
|
+
typeof wrapper === 'object' &&
|
|
16
|
+
typeof wrapper.setItem === 'function' &&
|
|
17
|
+
typeof wrapper.getItem === 'function' &&
|
|
18
|
+
typeof wrapper.removeItem === 'function';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isWebStorage(wrapper: any): boolean {
|
|
22
|
+
if (typeof wrapper.length === 'number') {
|
|
23
|
+
try {
|
|
24
|
+
wrapper.key(0);
|
|
25
|
+
return true;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
@@ -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.validateCache = () => true; // to emit SDK_READY_FROM_CACHE
|
|
11
|
+
result.validateCache = () => Promise.resolve(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
|
@@ -463,6 +463,24 @@ interface IClientSideSyncSharedSettings extends IClientSideSharedSettings, ISync
|
|
|
463
463
|
*/
|
|
464
464
|
declare namespace SplitIO {
|
|
465
465
|
|
|
466
|
+
interface StorageWrapper {
|
|
467
|
+
/**
|
|
468
|
+
* Returns the value associated with the given key, or null if the key does not exist.
|
|
469
|
+
* If the operation is asynchronous, returns a Promise.
|
|
470
|
+
*/
|
|
471
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
472
|
+
/**
|
|
473
|
+
* Sets the value for the given key, creating a new key/value pair if key does not exist.
|
|
474
|
+
* If the operation is asynchronous, returns a Promise.
|
|
475
|
+
*/
|
|
476
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
477
|
+
/**
|
|
478
|
+
* Removes the key/value pair for the given key, if the key exists.
|
|
479
|
+
* If the operation is asynchronous, returns a Promise.
|
|
480
|
+
*/
|
|
481
|
+
removeItem(key: string): void | Promise<void>;
|
|
482
|
+
}
|
|
483
|
+
|
|
466
484
|
/**
|
|
467
485
|
* EventEmitter interface based on a subset of the Node.js EventEmitter methods.
|
|
468
486
|
*/
|
|
@@ -978,6 +996,12 @@ declare namespace SplitIO {
|
|
|
978
996
|
* @defaultValue `false`
|
|
979
997
|
*/
|
|
980
998
|
clearOnInit?: boolean;
|
|
999
|
+
/**
|
|
1000
|
+
* Optional storage wrapper to persist rollout plan related data. If not provided, the SDK will use the default localStorage Web API.
|
|
1001
|
+
*
|
|
1002
|
+
* @defaultValue `window.localStorage`
|
|
1003
|
+
*/
|
|
1004
|
+
wrapper?: StorageWrapper;
|
|
981
1005
|
}
|
|
982
1006
|
/**
|
|
983
1007
|
* Storage for asynchronous (consumer) SDK.
|
|
@@ -1339,6 +1363,12 @@ declare namespace SplitIO {
|
|
|
1339
1363
|
* @defaultValue `false`
|
|
1340
1364
|
*/
|
|
1341
1365
|
clearOnInit?: boolean;
|
|
1366
|
+
/**
|
|
1367
|
+
* Optional storage wrapper to persist rollout plan related data. If not provided, the SDK will use the default localStorage Web API.
|
|
1368
|
+
*
|
|
1369
|
+
* @defaultValue `window.localStorage`
|
|
1370
|
+
*/
|
|
1371
|
+
wrapper?: StorageWrapper;
|
|
1342
1372
|
};
|
|
1343
1373
|
}
|
|
1344
1374
|
/**
|