@splitsoftware/splitio-commons 2.4.2-rc.2 → 2.5.0-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 +2 -10
- package/cjs/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/cjs/storages/AbstractSplitsCacheSync.js +2 -3
- package/cjs/storages/dataLoader.js +102 -43
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +23 -20
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/cjs/storages/inLocalStorage/index.js +13 -28
- package/cjs/storages/inLocalStorage/validateCache.js +23 -25
- package/cjs/storages/inMemory/InMemoryStorageCS.js +32 -14
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/cjs/sync/syncManagerOnline.js +24 -28
- package/cjs/utils/env/isLocalStorageAvailable.js +5 -28
- package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
- package/esm/storages/AbstractMySegmentsCacheSync.js +23 -31
- package/esm/storages/AbstractSplitsCacheSync.js +2 -3
- package/esm/storages/dataLoader.js +99 -41
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +16 -16
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +23 -20
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +37 -33
- package/esm/storages/inLocalStorage/index.js +14 -29
- package/esm/storages/inLocalStorage/validateCache.js +23 -25
- package/esm/storages/inMemory/InMemoryStorageCS.js +32 -14
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +4 -0
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +3 -2
- package/esm/sync/syncManagerOnline.js +24 -28
- package/esm/utils/env/isLocalStorageAvailable.js +3 -24
- package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
- package/package.json +1 -1
- package/src/sdkFactory/index.ts +3 -2
- package/src/storages/AbstractMySegmentsCacheSync.ts +20 -26
- package/src/storages/AbstractSplitsCacheSync.ts +2 -3
- package/src/storages/dataLoader.ts +107 -49
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +17 -18
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +24 -22
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +37 -34
- package/src/storages/inLocalStorage/index.ts +16 -33
- package/src/storages/inLocalStorage/validateCache.ts +23 -26
- package/src/storages/inMemory/InMemoryStorageCS.ts +37 -14
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +4 -0
- package/src/storages/types.ts +6 -20
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +2 -1
- package/src/sync/syncManagerOnline.ts +22 -27
- package/src/types.ts +0 -35
- package/src/utils/env/isLocalStorageAvailable.ts +3 -24
- package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
- package/types/splitio.d.ts +46 -27
- package/cjs/storages/inLocalStorage/storageAdapter.js +0 -48
- package/esm/storages/inLocalStorage/storageAdapter.js +0 -44
- package/src/storages/inLocalStorage/storageAdapter.ts +0 -50
|
@@ -3,19 +3,16 @@ import { isNaNNumber } from '../../utils/lang';
|
|
|
3
3
|
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
|
|
4
4
|
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
5
5
|
import { LOG_PREFIX, DEFINED } from './constants';
|
|
6
|
-
import { StorageAdapter } from '../types';
|
|
7
6
|
|
|
8
7
|
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
9
8
|
|
|
10
9
|
private readonly keys: MySegmentsKeyBuilder;
|
|
11
10
|
private readonly log: ILogger;
|
|
12
|
-
private readonly storage: StorageAdapter;
|
|
13
11
|
|
|
14
|
-
constructor(log: ILogger, keys: MySegmentsKeyBuilder
|
|
12
|
+
constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
|
|
15
13
|
super();
|
|
16
14
|
this.log = log;
|
|
17
15
|
this.keys = keys;
|
|
18
|
-
this.storage = storage;
|
|
19
16
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
20
17
|
}
|
|
21
18
|
|
|
@@ -23,8 +20,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
23
20
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
24
21
|
|
|
25
22
|
try {
|
|
26
|
-
if (
|
|
27
|
-
|
|
23
|
+
if (localStorage.getItem(segmentKey) === DEFINED) return false;
|
|
24
|
+
localStorage.setItem(segmentKey, DEFINED);
|
|
28
25
|
return true;
|
|
29
26
|
} catch (e) {
|
|
30
27
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -36,8 +33,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
36
33
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
37
34
|
|
|
38
35
|
try {
|
|
39
|
-
if (
|
|
40
|
-
|
|
36
|
+
if (localStorage.getItem(segmentKey) !== DEFINED) return false;
|
|
37
|
+
localStorage.removeItem(segmentKey);
|
|
41
38
|
return true;
|
|
42
39
|
} catch (e) {
|
|
43
40
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -46,16 +43,18 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
isInSegment(name: string): boolean {
|
|
49
|
-
return
|
|
46
|
+
return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
getRegisteredSegments(): string[] {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
// Scan current values from localStorage
|
|
51
|
+
return Object.keys(localStorage).reduce((accum, key) => {
|
|
52
|
+
let segmentName = this.keys.extractSegmentName(key);
|
|
53
|
+
|
|
54
|
+
if (segmentName) accum.push(segmentName);
|
|
55
|
+
|
|
56
|
+
return accum;
|
|
57
|
+
}, [] as string[]);
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
getKeysCount() {
|
|
@@ -64,8 +63,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
64
63
|
|
|
65
64
|
protected setChangeNumber(changeNumber?: number) {
|
|
66
65
|
try {
|
|
67
|
-
if (changeNumber)
|
|
68
|
-
else
|
|
66
|
+
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
67
|
+
else localStorage.removeItem(this.keys.buildTillKey());
|
|
69
68
|
} catch (e) {
|
|
70
69
|
this.log.error(e);
|
|
71
70
|
}
|
|
@@ -73,7 +72,7 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
|
|
|
73
72
|
|
|
74
73
|
getChangeNumber() {
|
|
75
74
|
const n = -1;
|
|
76
|
-
let value: string | number | null =
|
|
75
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
|
|
77
76
|
|
|
78
77
|
if (value !== null) {
|
|
79
78
|
value = parseInt(value, 10);
|
|
@@ -5,37 +5,34 @@ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
|
|
|
5
5
|
import { setToArray } from '../../utils/lang/sets';
|
|
6
6
|
import { usesSegments } from '../AbstractSplitsCacheSync';
|
|
7
7
|
import { KeyBuilderCS } from '../KeyBuilderCS';
|
|
8
|
-
import { IRBSegmentsCacheSync
|
|
8
|
+
import { IRBSegmentsCacheSync } from '../types';
|
|
9
9
|
import { LOG_PREFIX } from './constants';
|
|
10
10
|
|
|
11
11
|
export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
12
12
|
|
|
13
13
|
private readonly keys: KeyBuilderCS;
|
|
14
14
|
private readonly log: ILogger;
|
|
15
|
-
private readonly storage: StorageAdapter;
|
|
16
15
|
|
|
17
|
-
constructor(settings: ISettings, keys: KeyBuilderCS
|
|
16
|
+
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
18
17
|
this.keys = keys;
|
|
19
18
|
this.log = settings.log;
|
|
20
|
-
this.storage = storage;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
clear() {
|
|
24
22
|
this.getNames().forEach(name => this.remove(name));
|
|
25
|
-
|
|
23
|
+
localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
|
|
29
|
-
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
30
|
-
updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
31
27
|
this.setChangeNumber(changeNumber);
|
|
32
|
-
|
|
28
|
+
const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
29
|
+
return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
private setChangeNumber(changeNumber: number) {
|
|
36
33
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
35
|
+
localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
39
36
|
} catch (e) {
|
|
40
37
|
this.log.error(LOG_PREFIX + e);
|
|
41
38
|
}
|
|
@@ -43,19 +40,20 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
43
40
|
|
|
44
41
|
private updateSegmentCount(diff: number) {
|
|
45
42
|
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
46
|
-
const count = toNumber(
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
const count = toNumber(localStorage.getItem(segmentsCountKey)) + diff;
|
|
44
|
+
// @ts-expect-error
|
|
45
|
+
if (count > 0) localStorage.setItem(segmentsCountKey, count);
|
|
46
|
+
else localStorage.removeItem(segmentsCountKey);
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
private add(rbSegment: IRBSegment): boolean {
|
|
52
50
|
try {
|
|
53
51
|
const name = rbSegment.name;
|
|
54
52
|
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
|
|
55
|
-
const
|
|
56
|
-
const previous =
|
|
53
|
+
const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
|
|
54
|
+
const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
|
|
57
55
|
|
|
58
|
-
|
|
56
|
+
localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
59
57
|
|
|
60
58
|
let usesSegmentsDiff = 0;
|
|
61
59
|
if (previous && usesSegments(previous)) usesSegmentsDiff--;
|
|
@@ -74,7 +72,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
74
72
|
const rbSegment = this.get(name);
|
|
75
73
|
if (!rbSegment) return false;
|
|
76
74
|
|
|
77
|
-
|
|
75
|
+
localStorage.removeItem(this.keys.buildRBSegmentKey(name));
|
|
78
76
|
|
|
79
77
|
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
|
|
80
78
|
|
|
@@ -86,13 +84,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
private getNames(): string[] {
|
|
89
|
-
const len =
|
|
87
|
+
const len = localStorage.length;
|
|
90
88
|
const accum = [];
|
|
91
89
|
|
|
92
90
|
let cur = 0;
|
|
93
91
|
|
|
94
92
|
while (cur < len) {
|
|
95
|
-
const key =
|
|
93
|
+
const key = localStorage.key(cur);
|
|
96
94
|
|
|
97
95
|
if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
|
|
98
96
|
|
|
@@ -103,10 +101,14 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
103
101
|
}
|
|
104
102
|
|
|
105
103
|
get(name: string): IRBSegment | null {
|
|
106
|
-
const item =
|
|
104
|
+
const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
|
|
107
105
|
return item && JSON.parse(item);
|
|
108
106
|
}
|
|
109
107
|
|
|
108
|
+
getAll(): IRBSegment[] {
|
|
109
|
+
return this.getNames().map(key => this.get(key)!);
|
|
110
|
+
}
|
|
111
|
+
|
|
110
112
|
contains(names: Set<string>): boolean {
|
|
111
113
|
const namesArray = setToArray(names);
|
|
112
114
|
const namesInStorage = this.getNames();
|
|
@@ -115,7 +117,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
115
117
|
|
|
116
118
|
getChangeNumber(): number {
|
|
117
119
|
const n = -1;
|
|
118
|
-
let value: string | number | null =
|
|
120
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
|
|
119
121
|
|
|
120
122
|
if (value !== null) {
|
|
121
123
|
value = parseInt(value, 10);
|
|
@@ -127,7 +129,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
usesSegments(): boolean {
|
|
130
|
-
const storedCount =
|
|
132
|
+
const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
131
133
|
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
132
134
|
|
|
133
135
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
@@ -6,28 +6,29 @@ 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';
|
|
10
9
|
|
|
10
|
+
/**
|
|
11
|
+
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
|
|
12
|
+
*/
|
|
11
13
|
export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
12
14
|
|
|
13
15
|
private readonly keys: KeyBuilderCS;
|
|
14
16
|
private readonly log: ILogger;
|
|
15
17
|
private readonly flagSetsFilter: string[];
|
|
16
18
|
private hasSync?: boolean;
|
|
17
|
-
private readonly storage: StorageAdapter;
|
|
18
19
|
|
|
19
|
-
constructor(settings: ISettings, keys: KeyBuilderCS
|
|
20
|
+
constructor(settings: ISettings, keys: KeyBuilderCS) {
|
|
20
21
|
super();
|
|
21
22
|
this.keys = keys;
|
|
22
23
|
this.log = settings.log;
|
|
23
24
|
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
|
-
|
|
28
|
+
const count = toNumber(localStorage.getItem(key)) - 1;
|
|
29
|
+
// @ts-expect-error
|
|
30
|
+
if (count > 0) localStorage.setItem(key, count);
|
|
31
|
+
else localStorage.removeItem(key);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
private _decrementCounts(split: ISplit) {
|
|
@@ -47,11 +48,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
47
48
|
private _incrementCounts(split: ISplit) {
|
|
48
49
|
try {
|
|
49
50
|
const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
|
|
50
|
-
|
|
51
|
+
// @ts-expect-error
|
|
52
|
+
localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
|
|
51
53
|
|
|
52
54
|
if (usesSegments(split)) {
|
|
53
55
|
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
|
|
54
|
-
|
|
56
|
+
// @ts-expect-error
|
|
57
|
+
localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
|
|
55
58
|
}
|
|
56
59
|
} catch (e) {
|
|
57
60
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -65,15 +68,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
65
68
|
*/
|
|
66
69
|
clear() {
|
|
67
70
|
// collect item keys
|
|
68
|
-
const len =
|
|
71
|
+
const len = localStorage.length;
|
|
69
72
|
const accum = [];
|
|
70
73
|
for (let cur = 0; cur < len; cur++) {
|
|
71
|
-
const key =
|
|
74
|
+
const key = localStorage.key(cur);
|
|
72
75
|
if (key != null && this.keys.isSplitsCacheKey(key)) accum.push(key);
|
|
73
76
|
}
|
|
74
77
|
// remove items
|
|
75
78
|
accum.forEach(key => {
|
|
76
|
-
|
|
79
|
+
localStorage.removeItem(key);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
this.hasSync = false;
|
|
@@ -83,15 +86,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
83
86
|
try {
|
|
84
87
|
const name = split.name;
|
|
85
88
|
const splitKey = this.keys.buildSplitKey(name);
|
|
86
|
-
const
|
|
87
|
-
const previousSplit =
|
|
89
|
+
const splitFromLocalStorage = localStorage.getItem(splitKey);
|
|
90
|
+
const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
|
|
88
91
|
|
|
89
92
|
if (previousSplit) {
|
|
90
93
|
this._decrementCounts(previousSplit);
|
|
91
94
|
this.removeFromFlagSets(previousSplit.name, previousSplit.sets);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
localStorage.setItem(splitKey, JSON.stringify(split));
|
|
95
98
|
|
|
96
99
|
this._incrementCounts(split);
|
|
97
100
|
this.addToFlagSets(split);
|
|
@@ -108,7 +111,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
108
111
|
const split = this.getSplit(name);
|
|
109
112
|
if (!split) return false;
|
|
110
113
|
|
|
111
|
-
|
|
114
|
+
localStorage.removeItem(this.keys.buildSplitKey(name));
|
|
112
115
|
|
|
113
116
|
this._decrementCounts(split);
|
|
114
117
|
this.removeFromFlagSets(split.name, split.sets);
|
|
@@ -121,15 +124,15 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
getSplit(name: string): ISplit | null {
|
|
124
|
-
const item =
|
|
127
|
+
const item = localStorage.getItem(this.keys.buildSplitKey(name));
|
|
125
128
|
return item && JSON.parse(item);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
setChangeNumber(changeNumber: number): boolean {
|
|
129
132
|
try {
|
|
130
|
-
|
|
133
|
+
localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
|
|
131
134
|
// update "last updated" timestamp with current time
|
|
132
|
-
|
|
135
|
+
localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
133
136
|
this.hasSync = true;
|
|
134
137
|
return true;
|
|
135
138
|
} catch (e) {
|
|
@@ -140,7 +143,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
140
143
|
|
|
141
144
|
getChangeNumber(): number {
|
|
142
145
|
const n = -1;
|
|
143
|
-
let value: string | number | null =
|
|
146
|
+
let value: string | number | null = localStorage.getItem(this.keys.buildSplitsTillKey());
|
|
144
147
|
|
|
145
148
|
if (value !== null) {
|
|
146
149
|
value = parseInt(value, 10);
|
|
@@ -152,13 +155,13 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
getSplitNames(): string[] {
|
|
155
|
-
const len =
|
|
158
|
+
const len = localStorage.length;
|
|
156
159
|
const accum = [];
|
|
157
160
|
|
|
158
161
|
let cur = 0;
|
|
159
162
|
|
|
160
163
|
while (cur < len) {
|
|
161
|
-
const key =
|
|
164
|
+
const key = localStorage.key(cur);
|
|
162
165
|
|
|
163
166
|
if (key != null && this.keys.isSplitKey(key)) accum.push(this.keys.extractKey(key));
|
|
164
167
|
|
|
@@ -169,7 +172,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
trafficTypeExists(trafficType: string): boolean {
|
|
172
|
-
const ttCount = toNumber(
|
|
175
|
+
const ttCount = toNumber(localStorage.getItem(this.keys.buildTrafficTypeKey(trafficType)));
|
|
173
176
|
return isFiniteNumber(ttCount) && ttCount > 0;
|
|
174
177
|
}
|
|
175
178
|
|
|
@@ -177,7 +180,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
177
180
|
// If cache hasn't been synchronized with the cloud, assume we need them.
|
|
178
181
|
if (!this.hasSync) return true;
|
|
179
182
|
|
|
180
|
-
const storedCount =
|
|
183
|
+
const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
|
|
181
184
|
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
|
|
182
185
|
|
|
183
186
|
return isFiniteNumber(splitsWithSegmentsCount) ?
|
|
@@ -188,9 +191,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
188
191
|
getNamesByFlagSets(flagSets: string[]): Set<string>[] {
|
|
189
192
|
return flagSets.map(flagSet => {
|
|
190
193
|
const flagSetKey = this.keys.buildFlagSetKey(flagSet);
|
|
191
|
-
const
|
|
194
|
+
const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
192
195
|
|
|
193
|
-
return new Set(
|
|
196
|
+
return new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
194
197
|
});
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -203,12 +206,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
203
206
|
|
|
204
207
|
const flagSetKey = this.keys.buildFlagSetKey(featureFlagSet);
|
|
205
208
|
|
|
206
|
-
const
|
|
209
|
+
const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
207
210
|
|
|
208
|
-
const flagSetCache = new Set(
|
|
211
|
+
const flagSetCache = new Set(flagSetFromLocalStorage ? JSON.parse(flagSetFromLocalStorage) : []);
|
|
209
212
|
flagSetCache.add(featureFlag.name);
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
212
215
|
});
|
|
213
216
|
}
|
|
214
217
|
|
|
@@ -223,19 +226,19 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
|
|
|
223
226
|
private removeNames(flagSetName: string, featureFlagName: string) {
|
|
224
227
|
const flagSetKey = this.keys.buildFlagSetKey(flagSetName);
|
|
225
228
|
|
|
226
|
-
const
|
|
229
|
+
const flagSetFromLocalStorage = localStorage.getItem(flagSetKey);
|
|
227
230
|
|
|
228
|
-
if (!
|
|
231
|
+
if (!flagSetFromLocalStorage) return;
|
|
229
232
|
|
|
230
|
-
const flagSetCache = new Set(JSON.parse(
|
|
233
|
+
const flagSetCache = new Set(JSON.parse(flagSetFromLocalStorage));
|
|
231
234
|
flagSetCache.delete(featureFlagName);
|
|
232
235
|
|
|
233
236
|
if (flagSetCache.size === 0) {
|
|
234
|
-
|
|
237
|
+
localStorage.removeItem(flagSetKey);
|
|
235
238
|
return;
|
|
236
239
|
}
|
|
237
240
|
|
|
238
|
-
|
|
241
|
+
localStorage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache)));
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
}
|
|
@@ -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
|
|
4
|
+
import { IStorageFactoryParams, IStorageSync, IStorageSyncFactory } from '../types';
|
|
5
5
|
import { validatePrefix } from '../KeyBuilder';
|
|
6
6
|
import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
|
|
7
|
-
import { isLocalStorageAvailable
|
|
7
|
+
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
|
|
8
8
|
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
|
|
9
9
|
import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
|
|
10
10
|
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
|
|
@@ -15,24 +15,7 @@ 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';
|
|
19
18
|
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
|
-
}
|
|
36
19
|
|
|
37
20
|
/**
|
|
38
21
|
* InLocal storage factory for standalone client-side SplitFactory
|
|
@@ -42,19 +25,21 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
42
25
|
const prefix = validatePrefix(options.prefix);
|
|
43
26
|
|
|
44
27
|
function InLocalStorageCSFactory(params: IStorageFactoryParams): IStorageSync {
|
|
45
|
-
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
|
|
46
28
|
|
|
47
|
-
|
|
48
|
-
if (!
|
|
29
|
+
// Fallback to InMemoryStorage if LocalStorage API is not available
|
|
30
|
+
if (!isLocalStorageAvailable()) {
|
|
31
|
+
params.settings.log.warn(LOG_PREFIX + 'LocalStorage API is unavailable. Falling back to default MEMORY storage');
|
|
32
|
+
return InMemoryStorageCSFactory(params);
|
|
33
|
+
}
|
|
49
34
|
|
|
35
|
+
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
|
|
50
36
|
const matchingKey = getMatching(settings.core.key);
|
|
51
37
|
const keys = new KeyBuilderCS(prefix, matchingKey);
|
|
52
38
|
|
|
53
|
-
const splits = new SplitsCacheInLocal(settings, keys
|
|
54
|
-
const rbSegments = new RBSegmentsCacheInLocal(settings, keys
|
|
55
|
-
const segments = new MySegmentsCacheInLocal(log, keys
|
|
56
|
-
const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)
|
|
57
|
-
let validateCachePromise: Promise<boolean> | undefined;
|
|
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));
|
|
58
43
|
|
|
59
44
|
return {
|
|
60
45
|
splits,
|
|
@@ -68,12 +53,10 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
68
53
|
uniqueKeys: new UniqueKeysCacheInMemoryCS(),
|
|
69
54
|
|
|
70
55
|
validateCache() {
|
|
71
|
-
return
|
|
56
|
+
return validateCache(options, settings, keys, splits, rbSegments, segments, largeSegments);
|
|
72
57
|
},
|
|
73
58
|
|
|
74
|
-
destroy() {
|
|
75
|
-
return storage.save && storage.save();
|
|
76
|
-
},
|
|
59
|
+
destroy() { },
|
|
77
60
|
|
|
78
61
|
// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
|
|
79
62
|
shared(matchingKey: string) {
|
|
@@ -81,8 +64,8 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
|
|
|
81
64
|
return {
|
|
82
65
|
splits: this.splits,
|
|
83
66
|
rbSegments: this.rbSegments,
|
|
84
|
-
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)
|
|
85
|
-
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)
|
|
67
|
+
segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
|
|
68
|
+
largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
|
|
86
69
|
impressions: this.impressions,
|
|
87
70
|
impressionCounts: this.impressionCounts,
|
|
88
71
|
events: this.events,
|
|
@@ -7,7 +7,6 @@ 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';
|
|
11
10
|
|
|
12
11
|
const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10;
|
|
13
12
|
const MILLIS_IN_A_DAY = 86400000;
|
|
@@ -17,11 +16,11 @@ const MILLIS_IN_A_DAY = 86400000;
|
|
|
17
16
|
*
|
|
18
17
|
* @returns `true` if cache should be cleared, `false` otherwise
|
|
19
18
|
*/
|
|
20
|
-
function validateExpiration(options: SplitIO.InLocalStorageOptions,
|
|
19
|
+
function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) {
|
|
21
20
|
const { log } = settings;
|
|
22
21
|
|
|
23
22
|
// Check expiration
|
|
24
|
-
const lastUpdatedTimestamp = parseInt(
|
|
23
|
+
const lastUpdatedTimestamp = parseInt(localStorage.getItem(keys.buildLastUpdatedKey()) as string, 10);
|
|
25
24
|
if (!isNaNNumber(lastUpdatedTimestamp)) {
|
|
26
25
|
const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS;
|
|
27
26
|
const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays;
|
|
@@ -33,12 +32,12 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
|
|
|
33
32
|
|
|
34
33
|
// Check hash
|
|
35
34
|
const storageHashKey = keys.buildHashKey();
|
|
36
|
-
const storageHash =
|
|
35
|
+
const storageHash = localStorage.getItem(storageHashKey);
|
|
37
36
|
const currentStorageHash = getStorageHash(settings);
|
|
38
37
|
|
|
39
38
|
if (storageHash !== currentStorageHash) {
|
|
40
39
|
try {
|
|
41
|
-
|
|
40
|
+
localStorage.setItem(storageHashKey, currentStorageHash);
|
|
42
41
|
} catch (e) {
|
|
43
42
|
log.error(LOG_PREFIX + e);
|
|
44
43
|
}
|
|
@@ -51,7 +50,7 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
|
|
|
51
50
|
|
|
52
51
|
// Clear on init
|
|
53
52
|
if (options.clearOnInit) {
|
|
54
|
-
const lastClearTimestamp = parseInt(
|
|
53
|
+
const lastClearTimestamp = parseInt(localStorage.getItem(keys.buildLastClear()) as string, 10);
|
|
55
54
|
|
|
56
55
|
if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) {
|
|
57
56
|
log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache');
|
|
@@ -68,29 +67,27 @@ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: Sto
|
|
|
68
67
|
*
|
|
69
68
|
* @returns `true` if cache is ready to be used, `false` otherwise (cache was cleared or there is no cache)
|
|
70
69
|
*/
|
|
71
|
-
export function validateCache(options: SplitIO.InLocalStorageOptions,
|
|
70
|
+
export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean {
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const isThereCache = splits.getChangeNumber() > -1;
|
|
72
|
+
const currentTimestamp = Date.now();
|
|
73
|
+
const isThereCache = splits.getChangeNumber() > -1;
|
|
76
74
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
if (validateExpiration(options, settings, keys, currentTimestamp, isThereCache)) {
|
|
76
|
+
splits.clear();
|
|
77
|
+
rbSegments.clear();
|
|
78
|
+
segments.clear();
|
|
79
|
+
largeSegments.clear();
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return false;
|
|
81
|
+
// Update last clear timestamp
|
|
82
|
+
try {
|
|
83
|
+
localStorage.setItem(keys.buildLastClear(), currentTimestamp + '');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
settings.log.error(LOG_PREFIX + e);
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if ready from cache
|
|
92
|
+
return isThereCache;
|
|
96
93
|
}
|