@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
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
/* eslint-disable no-undef */
|
|
2
1
|
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) {
|
|
3
11
|
var mod = '__SPLITSOFTWARE__';
|
|
4
12
|
try {
|
|
5
|
-
|
|
6
|
-
|
|
13
|
+
wrapper.setItem(mod, mod);
|
|
14
|
+
wrapper.getItem(mod);
|
|
15
|
+
wrapper.removeItem(mod);
|
|
7
16
|
return true;
|
|
8
17
|
}
|
|
9
18
|
catch (e) {
|
|
10
19
|
return false;
|
|
11
20
|
}
|
|
12
21
|
}
|
|
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
|
+
}
|
|
@@ -58,12 +58,6 @@ function validateSplitFilter(log, type, values, maxLength) {
|
|
|
58
58
|
/**
|
|
59
59
|
* Returns a string representing the URL encoded query component of /splitChanges URL.
|
|
60
60
|
*
|
|
61
|
-
* The possible formats of the query string are:
|
|
62
|
-
* - null: if all filters are empty
|
|
63
|
-
* - '&names=<comma-separated-values>': if only `byPrefix` filter is undefined
|
|
64
|
-
* - '&prefixes=<comma-separated-values>': if only `byName` filter is undefined
|
|
65
|
-
* - '&names=<comma-separated-values>&prefixes=<comma-separated-values>': if no one is undefined
|
|
66
|
-
*
|
|
67
61
|
* @param groupedFilters - object of filters. Each filter must be a list of valid, unique and ordered string values.
|
|
68
62
|
* @returns null or string with the `split filter query` component of the URL.
|
|
69
63
|
*/
|
|
@@ -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 true; }; // to emit SDK_READY_FROM_CACHE
|
|
6
|
+
result.validateCache = function () { return Promise.resolve(true); }; // to emit SDK_READY_FROM_CACHE
|
|
7
7
|
return result;
|
|
8
8
|
}
|
|
9
9
|
__InLocalStorageMockFactory.type = STORAGE_MEMORY;
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ export const codesError: [number, string][] = [
|
|
|
14
14
|
[c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock feature flags data. No changes will be applied to the current cache. %s'],
|
|
15
15
|
[c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'],
|
|
16
16
|
[c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'],
|
|
17
|
-
[c.ERROR_HTTP, '
|
|
17
|
+
[c.ERROR_HTTP, 'HTTP request failed with %s. URL: %s. Message: %s'],
|
|
18
18
|
// client status
|
|
19
19
|
[c.ERROR_CLIENT_LISTENER, 'A listener was added for %s on the SDK, which has already fired and won\'t be emitted again. The callback won\'t be executed.'],
|
|
20
20
|
[c.ERROR_CLIENT_DESTROYED, '%s: Client has already been destroyed - no calls possible.'],
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -107,7 +107,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
107
107
|
|
|
108
108
|
log.info(NEW_FACTORY);
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
// @ts-ignore
|
|
111
|
+
return objectAssign({
|
|
111
112
|
// Split evaluation and event tracking engine
|
|
112
113
|
client: clientMethod,
|
|
113
114
|
|
|
@@ -125,17 +126,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
125
126
|
destroy() {
|
|
126
127
|
hasInit = false;
|
|
127
128
|
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
__ctx: ctx
|
|
129
|
+
}
|
|
131
130
|
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
|
|
132
|
-
|
|
133
|
-
// append factory to global
|
|
134
|
-
if (typeof window === 'object') { // @ts-ignore
|
|
135
|
-
// eslint-disable-next-line no-undef
|
|
136
|
-
(window.__HARNESS_FME__ = window.__HARNESS_FME__ || []).push(factory);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// @ts-ignore
|
|
140
|
-
return factory;
|
|
141
131
|
}
|
|
@@ -70,7 +70,7 @@ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFet
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (!resp || resp.status !== 403) { // 403's log we'll be handled somewhere else.
|
|
73
|
-
log[logErrorsAsInfo ? 'info' : 'error'](ERROR_HTTP, [resp ? resp.status : '
|
|
73
|
+
log[logErrorsAsInfo ? 'info' : 'error'](ERROR_HTTP, [resp ? 'status code ' + resp.status : 'no status code', url, msg]);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const networkError: NetworkError = new Error(msg);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISplitsCacheSync } from './types';
|
|
1
|
+
import { ISplitsCacheSync, IStorageSync } from './types';
|
|
2
2
|
import { IRBSegment, ISplit } from '../dtos/types';
|
|
3
3
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
4
4
|
import { IN_SEGMENT, IN_LARGE_SEGMENT } from '../utils/constants';
|
|
@@ -88,3 +88,7 @@ export function usesSegments(ruleEntity: ISplit | IRBSegment) {
|
|
|
88
88
|
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
export function usesSegmentsSync(storage: Pick<IStorageSync, 'splits' | 'rbSegments'>) {
|
|
93
|
+
return storage.splits.usesSegments() || storage.rbSegments.usesSegments();
|
|
94
|
+
}
|
|
@@ -3,16 +3,19 @@ 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';
|
|
6
7
|
|
|
7
8
|
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
8
9
|
|
|
9
10
|
private readonly keys: MySegmentsKeyBuilder;
|
|
10
11
|
private readonly log: ILogger;
|
|
12
|
+
private readonly storage: StorageAdapter;
|
|
11
13
|
|
|
12
|
-
constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
|
|
14
|
+
constructor(log: ILogger, keys: MySegmentsKeyBuilder, storage: StorageAdapter) {
|
|
13
15
|
super();
|
|
14
16
|
this.log = log;
|
|
15
17
|
this.keys = keys;
|
|
18
|
+
this.storage = storage;
|
|
16
19
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -20,8 +23,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
20
23
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
21
24
|
|
|
22
25
|
try {
|
|
23
|
-
if (
|
|
24
|
-
|
|
26
|
+
if (this.storage.getItem(segmentKey) === DEFINED) return false;
|
|
27
|
+
this.storage.setItem(segmentKey, DEFINED);
|
|
25
28
|
return true;
|
|
26
29
|
} catch (e) {
|
|
27
30
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -33,8 +36,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
33
36
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
34
37
|
|
|
35
38
|
try {
|
|
36
|
-
if (
|
|
37
|
-
|
|
39
|
+
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
|
|
40
|
+
this.storage.removeItem(segmentKey);
|
|
38
41
|
return true;
|
|
39
42
|
} catch (e) {
|
|
40
43
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -43,18 +46,16 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
isInSegment(name: string): boolean {
|
|
46
|
-
return
|
|
49
|
+
return this.storage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
getRegisteredSegments(): string[] {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return accum;
|
|
57
|
-
}, [] as string[]);
|
|
53
|
+
const registeredSegments: string[] = [];
|
|
54
|
+
for (let i = 0, len = this.storage.length; i < len; i++) {
|
|
55
|
+
const segmentName = this.keys.extractSegmentName(this.storage.key(i)!);
|
|
56
|
+
if (segmentName) registeredSegments.push(segmentName);
|
|
57
|
+
}
|
|
58
|
+
return registeredSegments;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
getKeysCount() {
|
|
@@ -63,8 +64,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
63
64
|
|
|
64
65
|
protected setChangeNumber(changeNumber?: number) {
|
|
65
66
|
try {
|
|
66
|
-
if (changeNumber)
|
|
67
|
-
else
|
|
67
|
+
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
68
|
+
else this.storage.removeItem(this.keys.buildTillKey());
|
|
68
69
|
} catch (e) {
|
|
69
70
|
this.log.error(e);
|
|
70
71
|
}
|
|
@@ -72,7 +73,7 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
72
73
|
|
|
73
74
|
getChangeNumber() {
|
|
74
75
|
const n = -1;
|
|
75
|
-
let value: string | number | null =
|
|
76
|
+
let value: string | number | null = this.storage.getItem(this.keys.buildTillKey());
|
|
76
77
|
|
|
77
78
|
if (value !== null) {
|
|
78
79
|
value = parseInt(value, 10);
|
|
@@ -5,22 +5,24 @@ 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 } from '../types';
|
|
8
|
+
import { IRBSegmentsCacheSync, StorageAdapter } 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;
|
|
15
16
|
|
|
16
|
-
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
17
|
+
constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
|
|
17
18
|
this.keys = keys;
|
|
18
19
|
this.log = settings.log;
|
|
20
|
+
this.storage = storage;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
clear() {
|
|
22
24
|
this.getNames().forEach(name => this.remove(name));
|
|
23
|
-
|
|
25
|
+
this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
@@ -31,8 +33,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
31
33
|
|
|
32
34
|
private setChangeNumber(changeNumber: number) {
|
|
33
35
|
try {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
37
|
+
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
36
38
|
} catch (e) {
|
|
37
39
|
this.log.error(LOG_PREFIX + e);
|
|
38
40
|
}
|
|
@@ -40,20 +42,19 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
40
42
|
|
|
41
43
|
private updateSegmentCount(diff: number) {
|
|
42
44
|
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
43
|
-
const count = toNumber(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else localStorage.removeItem(segmentsCountKey);
|
|
45
|
+
const count = toNumber(this.storage.getItem(segmentsCountKey)) + diff;
|
|
46
|
+
if (count > 0) this.storage.setItem(segmentsCountKey, count + '');
|
|
47
|
+
else this.storage.removeItem(segmentsCountKey);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
private add(rbSegment: IRBSegment): boolean {
|
|
50
51
|
try {
|
|
51
52
|
const name = rbSegment.name;
|
|
52
53
|
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
|
|
53
|
-
const
|
|
54
|
-
const previous =
|
|
54
|
+
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
|
|
55
|
+
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
57
58
|
|
|
58
59
|
let usesSegmentsDiff = 0;
|
|
59
60
|
if (previous && usesSegments(previous)) usesSegmentsDiff--;
|
|
@@ -72,7 +73,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
72
73
|
const rbSegment = this.get(name);
|
|
73
74
|
if (!rbSegment) return false;
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
this.storage.removeItem(this.keys.buildRBSegmentKey(name));
|
|
76
77
|
|
|
77
78
|
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
|
|
78
79
|
|
|
@@ -84,13 +85,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
private getNames(): string[] {
|
|
87
|
-
const len =
|
|
88
|
+
const len = this.storage.length;
|
|
88
89
|
const accum = [];
|
|
89
90
|
|
|
90
91
|
let cur = 0;
|
|
91
92
|
|
|
92
93
|
while (cur < len) {
|
|
93
|
-
const key =
|
|
94
|
+
const key = this.storage.key(cur);
|
|
94
95
|
|
|
95
96
|
if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
|
|
96
97
|
|
|
@@ -101,7 +102,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
get(name: string): IRBSegment | null {
|
|
104
|
-
const item =
|
|
105
|
+
const item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
|
|
105
106
|
return item && JSON.parse(item);
|
|
106
107
|
}
|
|
107
108
|
|
|
@@ -113,7 +114,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
113
114
|
|
|
114
115
|
getChangeNumber(): number {
|
|
115
116
|
const n = -1;
|
|
116
|
-
let value: string | number | null =
|
|
117
|
+
let value: string | number | null = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
|
|
117
118
|
|
|
118
119
|
if (value !== null) {
|
|
119
120
|
value = parseInt(value, 10);
|
|
@@ -125,7 +126,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
usesSegments(): boolean {
|
|
128
|
-
const storedCount =
|
|
129
|
+
const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
129
130
|
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
130
131
|
|
|
131
132
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
@@ -6,29 +6,28 @@ import { ILogger } from '../../logger/types';
|
|
|
6
6
|
import { LOG_PREFIX } from './constants';
|
|
7
7
|
import { ISettings } from '../../types';
|
|
8
8
|
import { setToArray } from '../../utils/lang/sets';
|
|
9
|
+
import { StorageAdapter } from '../types';
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
12
|
-
*/
|
|
13
11
|
export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
14
12
|
|
|
15
13
|
private readonly keys: KeyBuilderCS;
|
|
16
14
|
private readonly log: ILogger;
|
|
17
15
|
private readonly flagSetsFilter: string[];
|
|
18
16
|
private hasSync?: boolean;
|
|
17
|
+
private readonly storage: StorageAdapter;
|
|
19
18
|
|
|
20
|
-
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
19
|
+
constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
|
|
21
20
|
super();
|
|
22
21
|
this.keys = keys;
|
|
23
22
|
this.log = settings.log;
|
|
24
23
|
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
|
|
24
|
+
this.storage = storage;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
private _decrementCount(key: string) {
|
|
28
|
-
const count = toNumber(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
else localStorage.removeItem(key);
|
|
28
|
+
const count = toNumber(this.storage.getItem(key)) - 1;
|
|
29
|
+
if (count > 0) this.storage.setItem(key, count + '');
|
|
30
|
+
else this.storage.removeItem(key);
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
private _decrementCounts(split: ISplit) {
|
|
@@ -48,13 +47,11 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
48
47
|
private _incrementCounts(split: ISplit) {
|
|
49
48
|
try {
|
|
50
49
|
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
51
|
-
|
|
52
|
-
localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
|
|
50
|
+
this.storage.setItem(ttKey, (toNumber(this.storage.getItem(ttKey)) + 1) + '');
|
|
53
51
|
|
|
54
52
|
if (usesSegments(split)) {
|
|
55
53
|
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
56
|
-
|
|
57
|
-
localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
|
|
54
|
+
this.storage.setItem(segmentsCountKey, (toNumber(this.storage.getItem(segmentsCountKey)) + 1) + '');
|
|
58
55
|
}
|
|
59
56
|
} catch (e) {
|
|
60
57
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -68,15 +65,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
68
65
|
*/
|
|
69
66
|
clear() {
|
|
70
67
|
// collect item keys
|
|
71
|
-
const len =
|
|
68
|
+
const len = this.storage.length;
|
|
72
69
|
const accum = [];
|
|
73
70
|
for (let cur = 0; cur < len; cur++) {
|
|
74
|
-
const key =
|
|
71
|
+
const key = this.storage.key(cur);
|
|
75
72
|
if (key != null && this.keys.isSplitsCacheKey(key)) accum.push(key);
|
|
76
73
|
}
|
|
77
74
|
// remove items
|
|
78
75
|
accum.forEach(key => {
|
|
79
|
-
|
|
76
|
+
this.storage.removeItem(key);
|
|
80
77
|
});
|
|
81
78
|
|
|
82
79
|
this.hasSync = false;
|
|
@@ -86,15 +83,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
86
83
|
try {
|
|
87
84
|
const name = split.name;
|
|
88
85
|
const splitKey = this.keys.buildSplitKey(name);
|
|
89
|
-
const
|
|
90
|
-
const previousSplit =
|
|
86
|
+
const splitFromStorage = this.storage.getItem(splitKey);
|
|
87
|
+
const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null;
|
|
91
88
|
|
|
92
89
|
if (previousSplit) {
|
|
93
90
|
this._decrementCounts(previousSplit);
|
|
94
91
|
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
this.storage.setItem(splitKey, JSON.stringify(split));
|
|
98
95
|
|
|
99
96
|
this._incrementCounts(split);
|
|
100
97
|
this.addToFlagSets(split);
|
|
@@ -111,7 +108,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
111
108
|
const split = this.getSplit(name);
|
|
112
109
|
if (!split) return false;
|
|
113
110
|
|
|
114
|
-
|
|
111
|
+
this.storage.removeItem(this.keys.buildSplitKey(name));
|
|
115
112
|
|
|
116
113
|
this._decrementCounts(split);
|
|
117
114
|
this.removeFromFlagSets(split.name, split.sets);
|
|
@@ -124,15 +121,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
getSplit(name: string): ISplit | null {
|
|
127
|
-
const item =
|
|
124
|
+
const item = this.storage.getItem(this.keys.buildSplitKey(name));
|
|
128
125
|
return item && JSON.parse(item);
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
setChangeNumber(changeNumber: number): boolean {
|
|
132
129
|
try {
|
|
133
|
-
|
|
130
|
+
this.storage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
134
131
|
// update "last updated" timestamp with current time
|
|
135
|
-
|
|
132
|
+
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
136
133
|
this.hasSync = true;
|
|
137
134
|
return true;
|
|
138
135
|
} catch (e) {
|
|
@@ -143,7 +140,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
143
140
|
|
|
144
141
|
getChangeNumber(): number {
|
|
145
142
|
const n = -1;
|
|
146
|
-
let value: string | number | null =
|
|
143
|
+
let value: string | number | null = this.storage.getItem(this.keys.buildSplitsTillKey());
|
|
147
144
|
|
|
148
145
|
if (value !== null) {
|
|
149
146
|
value = parseInt(value, 10);
|
|
@@ -155,13 +152,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
155
152
|
}
|
|
156
153
|
|
|
157
154
|
getSplitNames(): string[] {
|
|
158
|
-
const len =
|
|
155
|
+
const len = this.storage.length;
|
|
159
156
|
const accum = [];
|
|
160
157
|
|
|
161
158
|
let cur = 0;
|
|
162
159
|
|
|
163
160
|
while (cur < len) {
|
|
164
|
-
const key =
|
|
161
|
+
const key = this.storage.key(cur);
|
|
165
162
|
|
|
166
163
|
if (key != null && this.keys.isSplitKey(key)) accum.push(this.keys.extractKey(key));
|
|
167
164
|
|
|
@@ -172,7 +169,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
172
169
|
}
|
|
173
170
|
|
|
174
171
|
trafficTypeExists(trafficType: string): boolean {
|
|
175
|
-
const ttCount = toNumber(
|
|
172
|
+
const ttCount = toNumber(this.storage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
|
|
176
173
|
return isFiniteNumber(ttCount) && ttCount > 0;
|
|
177
174
|
}
|
|
178
175
|
|
|
@@ -180,7 +177,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
180
177
|
// If cache hasn't been synchronized with the cloud, assume we need them.
|
|
181
178
|
if (!this.hasSync) return true;
|
|
182
179
|
|
|
183
|
-
const storedCount =
|
|
180
|
+
const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
184
181
|
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
185
182
|
|
|
186
183
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
@@ -191,9 +188,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
191
188
|
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
192
189
|
return flagSets.map(flagSet => {
|
|
193
190
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
194
|
-
const
|
|
191
|
+
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
195
192
|
|
|
196
|
-
return new Set(
|
|
193
|
+
return new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
197
194
|
});
|
|
198
195
|
}
|
|
199
196
|
|
|
@@ -206,12 +203,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
206
203
|
|
|
207
204
|
const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
|
|
208
205
|
|
|
209
|
-
const
|
|
206
|
+
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
210
207
|
|
|
211
|
-
const flagSetCache = new Set(
|
|
208
|
+
const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []);
|
|
212
209
|
flagSetCache.add(featureFlag.name);
|
|
213
210
|
|
|
214
|
-
|
|
211
|
+
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
215
212
|
});
|
|
216
213
|
}
|
|
217
214
|
|
|
@@ -226,19 +223,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
226
223
|
private removeNames(flagSetName: string, featureFlagName: string) {
|
|
227
224
|
const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
|
|
228
225
|
|
|
229
|
-
const
|
|
226
|
+
const flagSetFromStorage = this.storage.getItem(flagSetKey);
|
|
230
227
|
|
|
231
|
-
if (!
|
|
228
|
+
if (!flagSetFromStorage) return;
|
|
232
229
|
|
|
233
|
-
const flagSetCache = new Set(JSON.parse(
|
|
230
|
+
const flagSetCache = new Set(JSON.parse(flagSetFromStorage));
|
|
234
231
|
flagSetCache.delete(featureFlagName);
|
|
235
232
|
|
|
236
233
|
if (flagSetCache.size === 0) {
|
|
237
|
-
|
|
234
|
+
this.storage.removeItem(flagSetKey);
|
|
238
235
|
return;
|
|
239
236
|
}
|
|
240
237
|
|
|
241
|
-
|
|
238
|
+
this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
242
239
|
}
|
|
243
240
|
|
|
244
241
|
}
|
|
@@ -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.SyncStorageWrapper | SplitIO.AsyncStorageWrapper): 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,
|