@splitsoftware/splitio-commons 2.1.0-rc.2 → 2.1.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.
Files changed (174) hide show
  1. package/CHANGES.txt +2 -7
  2. package/README.md +1 -0
  3. package/cjs/evaluator/combiners/and.js +2 -6
  4. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  5. package/cjs/evaluator/condition/index.js +6 -5
  6. package/cjs/evaluator/index.js +7 -7
  7. package/cjs/evaluator/matchers/index.js +3 -1
  8. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  9. package/cjs/evaluator/matchers/rbsegment.js +43 -0
  10. package/cjs/evaluator/matchersTransform/index.js +4 -0
  11. package/cjs/evaluator/parser/index.js +2 -2
  12. package/cjs/evaluator/value/sanitize.js +1 -0
  13. package/cjs/logger/constants.js +5 -6
  14. package/cjs/logger/messages/debug.js +3 -4
  15. package/cjs/logger/messages/warn.js +1 -1
  16. package/cjs/readiness/readinessManager.js +0 -6
  17. package/cjs/services/splitApi.js +2 -2
  18. package/cjs/storages/AbstractSplitsCacheAsync.js +19 -1
  19. package/cjs/storages/AbstractSplitsCacheSync.js +17 -9
  20. package/cjs/storages/KeyBuilder.js +8 -15
  21. package/cjs/storages/KeyBuilderCS.js +11 -5
  22. package/cjs/storages/KeyBuilderSS.js +3 -0
  23. package/cjs/storages/dataLoader.js +3 -5
  24. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  25. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  26. package/cjs/storages/inLocalStorage/index.js +7 -5
  27. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  28. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  29. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  30. package/cjs/storages/inMemory/SplitsCacheInMemory.js +24 -31
  31. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  32. package/cjs/storages/inRedis/SplitsCacheInRedis.js +4 -21
  33. package/cjs/storages/inRedis/constants.js +1 -1
  34. package/cjs/storages/inRedis/index.js +2 -0
  35. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  36. package/cjs/storages/pluggable/SplitsCachePluggable.js +2 -19
  37. package/cjs/storages/pluggable/index.js +3 -2
  38. package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  39. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  40. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  41. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  42. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  43. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  44. package/cjs/sync/polling/updaters/splitChangesUpdater.js +62 -51
  45. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  46. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  47. package/cjs/sync/streaming/constants.js +2 -1
  48. package/cjs/sync/streaming/pushManager.js +3 -16
  49. package/cjs/sync/syncManagerOnline.js +5 -10
  50. package/cjs/trackers/uniqueKeysTracker.js +1 -1
  51. package/cjs/utils/constants/browser.js +5 -0
  52. package/cjs/utils/constants/index.js +3 -2
  53. package/cjs/utils/settingsValidation/storage/storageCS.js +1 -1
  54. package/esm/evaluator/combiners/and.js +2 -6
  55. package/esm/evaluator/combiners/ifelseif.js +7 -7
  56. package/esm/evaluator/condition/index.js +6 -5
  57. package/esm/evaluator/index.js +7 -7
  58. package/esm/evaluator/matchers/index.js +3 -1
  59. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  60. package/esm/evaluator/matchers/rbsegment.js +39 -0
  61. package/esm/evaluator/matchersTransform/index.js +4 -0
  62. package/esm/evaluator/parser/index.js +2 -2
  63. package/esm/evaluator/value/sanitize.js +1 -0
  64. package/esm/logger/constants.js +2 -3
  65. package/esm/logger/messages/debug.js +3 -4
  66. package/esm/logger/messages/warn.js +1 -1
  67. package/esm/readiness/readinessManager.js +0 -6
  68. package/esm/services/splitApi.js +2 -2
  69. package/esm/storages/AbstractSplitsCacheAsync.js +19 -1
  70. package/esm/storages/AbstractSplitsCacheSync.js +17 -9
  71. package/esm/storages/KeyBuilder.js +8 -15
  72. package/esm/storages/KeyBuilderCS.js +11 -5
  73. package/esm/storages/KeyBuilderSS.js +3 -0
  74. package/esm/storages/dataLoader.js +2 -4
  75. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  76. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +69 -15
  77. package/esm/storages/inLocalStorage/index.js +7 -5
  78. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  79. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  80. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  81. package/esm/storages/inMemory/SplitsCacheInMemory.js +24 -31
  82. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  83. package/esm/storages/inRedis/SplitsCacheInRedis.js +4 -21
  84. package/esm/storages/inRedis/constants.js +1 -1
  85. package/esm/storages/inRedis/index.js +2 -0
  86. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  87. package/esm/storages/pluggable/SplitsCachePluggable.js +2 -19
  88. package/esm/storages/pluggable/index.js +3 -2
  89. package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +14 -16
  90. package/esm/sync/polling/fetchers/splitChangesFetcher.js +2 -2
  91. package/esm/sync/polling/pollingManagerCS.js +7 -7
  92. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  93. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  94. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  95. package/esm/sync/polling/updaters/splitChangesUpdater.js +63 -52
  96. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  97. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  98. package/esm/sync/streaming/constants.js +1 -0
  99. package/esm/sync/streaming/pushManager.js +6 -19
  100. package/esm/sync/syncManagerOnline.js +5 -10
  101. package/esm/trackers/uniqueKeysTracker.js +1 -1
  102. package/esm/utils/constants/browser.js +2 -0
  103. package/esm/utils/constants/index.js +2 -1
  104. package/esm/utils/settingsValidation/storage/storageCS.js +1 -1
  105. package/package.json +1 -1
  106. package/src/dtos/types.ts +32 -8
  107. package/src/evaluator/Engine.ts +1 -1
  108. package/src/evaluator/combiners/and.ts +5 -4
  109. package/src/evaluator/combiners/ifelseif.ts +7 -9
  110. package/src/evaluator/condition/engineUtils.ts +1 -1
  111. package/src/evaluator/condition/index.ts +12 -12
  112. package/src/evaluator/index.ts +7 -7
  113. package/src/evaluator/matchers/index.ts +3 -1
  114. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  115. package/src/evaluator/matchers/rbsegment.ts +61 -0
  116. package/src/evaluator/matchersTransform/index.ts +3 -0
  117. package/src/evaluator/parser/index.ts +3 -3
  118. package/src/evaluator/types.ts +2 -2
  119. package/src/evaluator/value/index.ts +2 -2
  120. package/src/evaluator/value/sanitize.ts +5 -4
  121. package/src/logger/constants.ts +2 -3
  122. package/src/logger/messages/debug.ts +3 -4
  123. package/src/logger/messages/warn.ts +1 -1
  124. package/src/readiness/readinessManager.ts +0 -5
  125. package/src/sdkManager/index.ts +1 -1
  126. package/src/services/splitApi.ts +2 -2
  127. package/src/services/types.ts +1 -1
  128. package/src/storages/AbstractSplitsCacheAsync.ts +23 -5
  129. package/src/storages/AbstractSplitsCacheSync.ts +22 -15
  130. package/src/storages/KeyBuilder.ts +9 -17
  131. package/src/storages/KeyBuilderCS.ts +13 -6
  132. package/src/storages/KeyBuilderSS.ts +4 -0
  133. package/src/storages/dataLoader.ts +2 -5
  134. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  135. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +80 -16
  136. package/src/storages/inLocalStorage/index.ts +12 -8
  137. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  138. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  139. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  140. package/src/storages/inMemory/SplitsCacheInMemory.ts +22 -27
  141. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  142. package/src/storages/inRedis/SplitsCacheInRedis.ts +4 -21
  143. package/src/storages/inRedis/constants.ts +1 -1
  144. package/src/storages/inRedis/index.ts +2 -0
  145. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  146. package/src/storages/pluggable/SplitsCachePluggable.ts +2 -19
  147. package/src/storages/pluggable/index.ts +3 -2
  148. package/src/storages/types.ts +47 -18
  149. package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +19 -21
  150. package/src/sync/polling/fetchers/splitChangesFetcher.ts +2 -1
  151. package/src/sync/polling/fetchers/types.ts +1 -0
  152. package/src/sync/polling/pollingManagerCS.ts +7 -7
  153. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -2
  154. package/src/sync/polling/types.ts +2 -2
  155. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  156. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  157. package/src/sync/polling/updaters/splitChangesUpdater.ts +74 -63
  158. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  159. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  160. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  161. package/src/sync/streaming/constants.ts +1 -0
  162. package/src/sync/streaming/parseUtils.ts +2 -2
  163. package/src/sync/streaming/pushManager.ts +6 -18
  164. package/src/sync/streaming/types.ts +3 -2
  165. package/src/sync/syncManagerOnline.ts +5 -11
  166. package/src/trackers/uniqueKeysTracker.ts +1 -1
  167. package/src/utils/constants/browser.ts +2 -0
  168. package/src/utils/constants/index.ts +2 -1
  169. package/src/utils/lang/index.ts +2 -2
  170. package/src/utils/settingsValidation/storage/storageCS.ts +1 -1
  171. package/types/splitio.d.ts +1 -25
  172. package/cjs/storages/inLocalStorage/validateCache.js +0 -79
  173. package/esm/storages/inLocalStorage/validateCache.js +0 -75
  174. package/src/storages/inLocalStorage/validateCache.ts +0 -91
@@ -1,5 +1,5 @@
1
1
  import { ISplitsCacheSync } from './types';
2
- import { ISplit } from '../dtos/types';
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';
5
5
 
@@ -9,16 +9,14 @@ import { IN_SEGMENT, IN_LARGE_SEGMENT } from '../utils/constants';
9
9
  */
10
10
  export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
11
11
 
12
- abstract addSplit(name: string, split: ISplit): boolean
12
+ protected abstract addSplit(split: ISplit): boolean
13
+ protected abstract removeSplit(name: string): boolean
14
+ protected abstract setChangeNumber(changeNumber: number): boolean | void
13
15
 
14
- addSplits(entries: [string, ISplit][]): boolean[] {
15
- return entries.map(keyValuePair => this.addSplit(keyValuePair[0], keyValuePair[1]));
16
- }
17
-
18
- abstract removeSplit(name: string): boolean
19
-
20
- removeSplits(names: string[]): boolean[] {
21
- return names.map(name => this.removeSplit(name));
16
+ update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean {
17
+ this.setChangeNumber(changeNumber);
18
+ const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result);
19
+ return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated;
22
20
  }
23
21
 
24
22
  abstract getSplit(name: string): ISplit | null
@@ -31,8 +29,6 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
31
29
  return splits;
32
30
  }
33
31
 
34
- abstract setChangeNumber(changeNumber: number): boolean | void
35
-
36
32
  abstract getChangeNumber(): number
37
33
 
38
34
  getAll(): ISplit[] {
@@ -47,6 +43,14 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
47
43
 
48
44
  abstract clear(): void
49
45
 
46
+ /**
47
+ * Check if the splits information is already stored in cache. This data can be preloaded.
48
+ * It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
49
+ */
50
+ checkCache(): boolean {
51
+ return false;
52
+ }
53
+
50
54
  /**
51
55
  * Kill `name` split and set `defaultTreatment` and `changeNumber`.
52
56
  * Used for SPLIT_KILL push notifications.
@@ -63,7 +67,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
63
67
  newSplit.defaultTreatment = defaultTreatment;
64
68
  newSplit.changeNumber = changeNumber;
65
69
 
66
- return this.addSplit(name, newSplit);
70
+ return this.addSplit(newSplit);
67
71
  }
68
72
  return false;
69
73
  }
@@ -76,8 +80,8 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
76
80
  * Given a parsed split, it returns a boolean flagging if its conditions use segments matchers (rules & whitelists).
77
81
  * This util is intended to simplify the implementation of `splitsCache::usesSegments` method
78
82
  */
79
- export function usesSegments(split: ISplit) {
80
- const conditions = split.conditions || [];
83
+ export function usesSegments(ruleEntity: ISplit | IRBSegment) {
84
+ const conditions = ruleEntity.conditions || [];
81
85
  for (let i = 0; i < conditions.length; i++) {
82
86
  const matchers = conditions[i].matcherGroup.matchers;
83
87
 
@@ -87,5 +91,8 @@ export function usesSegments(split: ISplit) {
87
91
  }
88
92
  }
89
93
 
94
+ const excluded = (ruleEntity as IRBSegment).excluded;
95
+ if (excluded && excluded.segments && excluded.segments.length > 0) return true;
96
+
90
97
  return false;
91
98
  }
@@ -1,5 +1,4 @@
1
1
  import { ISettings } from '../types';
2
- import { startsWith } from '../utils/lang';
3
2
  import { hash } from '../utils/murmur3/murmur3';
4
3
 
5
4
  const everythingAtTheEnd = /[^.]+$/;
@@ -34,22 +33,20 @@ export class KeyBuilder {
34
33
  return `${this.prefix}.splits.till`;
35
34
  }
36
35
 
37
- // NOT USED
38
- // buildSplitsReady() {
39
- // return `${this.prefix}.splits.ready`;
40
- // }
36
+ buildSplitKeyPrefix() {
37
+ return `${this.prefix}.split.`;
38
+ }
41
39
 
42
- isSplitKey(key: string) {
43
- return startsWith(key, `${this.prefix}.split.`);
40
+ buildRBSegmentKey(rbsegmentName: string) {
41
+ return `${this.prefix}.rbsegment.${rbsegmentName}`;
44
42
  }
45
43
 
46
- buildSplitKeyPrefix() {
47
- return `${this.prefix}.split.`;
44
+ buildRBSegmentsTillKey() {
45
+ return `${this.prefix}.rbsegments.till`;
48
46
  }
49
47
 
50
- // Only used by InLocalStorage.
51
- buildSplitsWithSegmentCountKey() {
52
- return `${this.prefix}.splits.usingSegments`;
48
+ buildRBSegmentKeyPrefix() {
49
+ return `${this.prefix}.rbsegment.`;
53
50
  }
54
51
 
55
52
  buildSegmentNameKey(segmentName: string) {
@@ -60,11 +57,6 @@ export class KeyBuilder {
60
57
  return `${this.prefix}.segment.${segmentName}.till`;
61
58
  }
62
59
 
63
- // NOT USED
64
- // buildSegmentsReady() {
65
- // return `${this.prefix}.segments.ready`;
66
- // }
67
-
68
60
  extractKey(builtKey: string) {
69
61
  const s = builtKey.match(everythingAtTheEnd);
70
62
 
@@ -15,7 +15,7 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
15
15
  constructor(prefix: string, matchingKey: string) {
16
16
  super(prefix);
17
17
  this.matchingKey = matchingKey;
18
- this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.(splits?|trafficType|flagSet)\\.`);
18
+ this.regexSplitsCacheKey = new RegExp(`^${prefix}\\.(splits?|trafficType|flagSet|rbsegment)\\.`);
19
19
  }
20
20
 
21
21
  /**
@@ -28,8 +28,7 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
28
28
  extractSegmentName(builtSegmentKeyName: string) {
29
29
  const prefix = `${this.prefix}.${this.matchingKey}.segment.`;
30
30
 
31
- if (startsWith(builtSegmentKeyName, prefix))
32
- return builtSegmentKeyName.substr(prefix.length);
31
+ if (startsWith(builtSegmentKeyName, prefix)) return builtSegmentKeyName.slice(prefix.length);
33
32
  }
34
33
 
35
34
  buildLastUpdatedKey() {
@@ -44,8 +43,16 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
44
43
  return `${this.prefix}.${this.matchingKey}.segments.till`;
45
44
  }
46
45
 
47
- buildLastClear() {
48
- return `${this.prefix}.lastClear`;
46
+ isSplitKey(key: string) {
47
+ return startsWith(key, `${this.prefix}.split.`);
48
+ }
49
+
50
+ isRBSegmentKey(key: string) {
51
+ return startsWith(key, `${this.prefix}.rbsegment.`);
52
+ }
53
+
54
+ buildSplitsWithSegmentCountKey() {
55
+ return `${this.prefix}.splits.usingSegments`;
49
56
  }
50
57
  }
51
58
 
@@ -58,7 +65,7 @@ export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string):
58
65
  extractSegmentName(builtSegmentKeyName: string) {
59
66
  const p = `${prefix}.${matchingKey}.largeSegment.`;
60
67
 
61
- if (startsWith(builtSegmentKeyName, p)) return builtSegmentKeyName.substr(p.length);
68
+ if (startsWith(builtSegmentKeyName, p)) return builtSegmentKeyName.slice(p.length);
62
69
  },
63
70
 
64
71
  buildTillKey() {
@@ -53,6 +53,10 @@ export class KeyBuilderSS extends KeyBuilder {
53
53
  return `${this.buildSplitKeyPrefix()}*`;
54
54
  }
55
55
 
56
+ searchPatternForRBSegmentKeys() {
57
+ return `${this.buildRBSegmentKeyPrefix()}*`;
58
+ }
59
+
56
60
  /* Telemetry keys */
57
61
 
58
62
  buildLatencyKey(method: Method, bucket: number) {
@@ -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
  *
@@ -37,10 +35,9 @@ export function dataLoaderFactory(preloadedData: PreloadedData): DataLoader {
37
35
 
38
36
  // cleaning up the localStorage data, since some cached splits might need be part of the preloaded data
39
37
  storage.splits.clear();
40
- storage.splits.setChangeNumber(since);
41
38
 
42
39
  // splitsData in an object where the property is the split name and the pertaining value is a stringified json of its data
43
- storage.splits.addSplits(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])));
40
+ storage.splits.update(Object.keys(splitsData).map(splitName => JSON.parse(splitsData[splitName])), [], since);
44
41
 
45
42
  // add mySegments data
46
43
  let mySegmentsData = preloadedData.mySegmentsData && preloadedData.mySegmentsData[userId];
@@ -0,0 +1,136 @@
1
+ import { IRBSegment } from '../../dtos/types';
2
+ import { ILogger } from '../../logger/types';
3
+ import { ISettings } from '../../types';
4
+ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
5
+ import { setToArray } from '../../utils/lang/sets';
6
+ import { usesSegments } from '../AbstractSplitsCacheSync';
7
+ import { KeyBuilderCS } from '../KeyBuilderCS';
8
+ import { IRBSegmentsCacheSync } from '../types';
9
+ import { LOG_PREFIX } from './constants';
10
+
11
+ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
12
+
13
+ private readonly keys: KeyBuilderCS;
14
+ private readonly log: ILogger;
15
+
16
+ constructor(settings: ISettings, keys: KeyBuilderCS) {
17
+ this.keys = keys;
18
+ this.log = settings.log;
19
+ }
20
+
21
+ clear() {
22
+ this.getNames().forEach(name => this.remove(name));
23
+ localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
24
+ }
25
+
26
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
27
+ this.setChangeNumber(changeNumber);
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;
30
+ }
31
+
32
+ private setChangeNumber(changeNumber: number) {
33
+ try {
34
+ localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
35
+ localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36
+ } catch (e) {
37
+ this.log.error(LOG_PREFIX + e);
38
+ }
39
+ }
40
+
41
+ private updateSegmentCount(diff: number) {
42
+ const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
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);
47
+ }
48
+
49
+ private add(rbSegment: IRBSegment): boolean {
50
+ try {
51
+ const name = rbSegment.name;
52
+ const rbSegmentKey = this.keys.buildRBSegmentKey(name);
53
+ const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
54
+ const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
55
+
56
+ localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57
+
58
+ let usesSegmentsDiff = 0;
59
+ if (previous && usesSegments(previous)) usesSegmentsDiff--;
60
+ if (usesSegments(rbSegment)) usesSegmentsDiff++;
61
+ if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff);
62
+
63
+ return true;
64
+ } catch (e) {
65
+ this.log.error(LOG_PREFIX + e);
66
+ return false;
67
+ }
68
+ }
69
+
70
+ private remove(name: string): boolean {
71
+ try {
72
+ const rbSegment = this.get(name);
73
+ if (!rbSegment) return false;
74
+
75
+ localStorage.removeItem(this.keys.buildRBSegmentKey(name));
76
+
77
+ if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
78
+
79
+ return true;
80
+ } catch (e) {
81
+ this.log.error(LOG_PREFIX + e);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ private getNames(): string[] {
87
+ const len = localStorage.length;
88
+ const accum = [];
89
+
90
+ let cur = 0;
91
+
92
+ while (cur < len) {
93
+ const key = localStorage.key(cur);
94
+
95
+ if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
96
+
97
+ cur++;
98
+ }
99
+
100
+ return accum;
101
+ }
102
+
103
+ get(name: string): IRBSegment | null {
104
+ const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
105
+ return item && JSON.parse(item);
106
+ }
107
+
108
+ contains(names: Set<string>): boolean {
109
+ const namesArray = setToArray(names);
110
+ const namesInStorage = this.getNames();
111
+ return namesArray.every(name => namesInStorage.indexOf(name) !== -1);
112
+ }
113
+
114
+ getChangeNumber(): number {
115
+ const n = -1;
116
+ let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
117
+
118
+ if (value !== null) {
119
+ value = parseInt(value, 10);
120
+
121
+ return isNaNNumber(value) ? n : value;
122
+ }
123
+
124
+ return n;
125
+ }
126
+
127
+ usesSegments(): boolean {
128
+ const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
129
+ const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
130
+
131
+ return isFiniteNumber(splitsWithSegmentsCount) ?
132
+ splitsWithSegmentsCount > 0 :
133
+ true;
134
+ }
135
+
136
+ }
@@ -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) {
@@ -49,16 +57,14 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
49
57
 
50
58
  private _incrementCounts(split: ISplit) {
51
59
  try {
52
- if (split) {
53
- const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
54
- // @ts-expect-error
55
- localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
60
+ const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName);
61
+ // @ts-expect-error
62
+ localStorage.setItem(ttKey, toNumber(localStorage.getItem(ttKey)) + 1);
56
63
 
57
- if (usesSegments(split)) {
58
- const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
59
- // @ts-expect-error
60
- localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
61
- }
64
+ if (usesSegments(split)) {
65
+ const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
66
+ // @ts-expect-error
67
+ localStorage.setItem(segmentsCountKey, toNumber(localStorage.getItem(segmentsCountKey)) + 1);
62
68
  }
63
69
  } catch (e) {
64
70
  this.log.error(LOG_PREFIX + e);
@@ -71,6 +77,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
71
77
  * We cannot simply call `localStorage.clear()` since that implies removing user items from the storage.
72
78
  */
73
79
  clear() {
80
+ this.log.info(LOG_PREFIX + 'Flushing Splits data from localStorage');
81
+
74
82
  // collect item keys
75
83
  const len = localStorage.length;
76
84
  const accum = [];
@@ -86,8 +94,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
86
94
  this.hasSync = false;
87
95
  }
88
96
 
89
- addSplit(name: string, split: ISplit) {
97
+ addSplit(split: ISplit) {
90
98
  try {
99
+ const name = split.name;
91
100
  const splitKey = this.keys.buildSplitKey(name);
92
101
  const splitFromLocalStorage = localStorage.getItem(splitKey);
93
102
  const previousSplit = splitFromLocalStorage ? JSON.parse(splitFromLocalStorage) : null;
@@ -110,6 +119,8 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
110
119
  removeSplit(name: string): boolean {
111
120
  try {
112
121
  const split = this.getSplit(name);
122
+ if (!split) return false;
123
+
113
124
  localStorage.removeItem(this.keys.buildSplitKey(name));
114
125
 
115
126
  this._decrementCounts(split);
@@ -122,12 +133,25 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
122
133
  }
123
134
  }
124
135
 
125
- getSplit(name: string) {
136
+ getSplit(name: string): ISplit | null {
126
137
  const item = localStorage.getItem(this.keys.buildSplitKey(name));
127
138
  return item && JSON.parse(item);
128
139
  }
129
140
 
130
141
  setChangeNumber(changeNumber: number): boolean {
142
+
143
+ // when using a new split query, we must update it at the store
144
+ if (this.updateNewFilter) {
145
+ this.log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache');
146
+ const storageHashKey = this.keys.buildHashKey();
147
+ try {
148
+ localStorage.setItem(storageHashKey, this.storageHash);
149
+ } catch (e) {
150
+ this.log.error(LOG_PREFIX + e);
151
+ }
152
+ this.updateNewFilter = false;
153
+ }
154
+
131
155
  try {
132
156
  localStorage.setItem(this.keys.buildSplitsTillKey(), changeNumber + '');
133
157
  // update "last updated" timestamp with current time
@@ -182,11 +206,51 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
182
206
  const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
183
207
  const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
184
208
 
185
- if (isFiniteNumber(splitsWithSegmentsCount)) {
186
- return splitsWithSegmentsCount > 0;
187
- } else {
188
- return true;
209
+ return isFiniteNumber(splitsWithSegmentsCount) ?
210
+ splitsWithSegmentsCount > 0 :
211
+ true;
212
+ }
213
+
214
+ /**
215
+ * Check if the splits information is already stored in browser LocalStorage.
216
+ * In this function we could add more code to check if the data is valid.
217
+ * @override
218
+ */
219
+ checkCache(): boolean {
220
+ return this.getChangeNumber() > -1;
221
+ }
222
+
223
+ /**
224
+ * Clean Splits cache if its `lastUpdated` timestamp is older than the given `expirationTimestamp`,
225
+ *
226
+ * @param expirationTimestamp - if the value is not a number, data will not be cleaned
227
+ */
228
+ private _checkExpiration(expirationTimestamp?: number) {
229
+ let value: string | number | null = localStorage.getItem(this.keys.buildLastUpdatedKey());
230
+ if (value !== null) {
231
+ value = parseInt(value, 10);
232
+ if (!isNaNNumber(value) && expirationTimestamp && value < expirationTimestamp) this.clear();
233
+ }
234
+ }
235
+
236
+ // @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
237
+ private _checkFilterQuery() {
238
+ const storageHashKey = this.keys.buildHashKey();
239
+ const storageHash = localStorage.getItem(storageHashKey);
240
+
241
+ if (storageHash !== this.storageHash) {
242
+ try {
243
+ // mark cache to update the new query filter on first successful splits fetch
244
+ this.updateNewFilter = true;
245
+
246
+ // if there is cache, clear it
247
+ if (this.checkCache()) this.clear();
248
+
249
+ } catch (e) {
250
+ this.log.error(LOG_PREFIX + e);
251
+ }
189
252
  }
253
+ // if the filter didn't change, nothing is done
190
254
  }
191
255
 
192
256
  getNamesByFlagSets(flagSets: string[]): Set<string>[] {
@@ -7,19 +7,23 @@ 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
- import { validateCache } from './validateCache';
17
- import SplitIO from '../../../types/splitio';
17
+ import { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal';
18
+
19
+ export interface InLocalStorageOptions {
20
+ prefix?: string
21
+ }
18
22
 
19
23
  /**
20
24
  * InLocal storage factory for standalone client-side SplitFactory
21
25
  */
22
- export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): IStorageSyncFactory {
26
+ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyncFactory {
23
27
 
24
28
  const prefix = validatePrefix(options.prefix);
25
29
 
@@ -34,13 +38,16 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
34
38
  const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
35
39
  const matchingKey = getMatching(settings.core.key);
36
40
  const keys = new KeyBuilderCS(prefix, matchingKey);
41
+ const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
37
42
 
38
- const splits = new SplitsCacheInLocal(settings, keys);
43
+ const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
44
+ const rbSegments = new RBSegmentsCacheInLocal(settings, keys);
39
45
  const segments = new MySegmentsCacheInLocal(log, keys);
40
46
  const largeSegments = new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey));
41
47
 
42
48
  return {
43
49
  splits,
50
+ rbSegments,
44
51
  segments,
45
52
  largeSegments,
46
53
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
@@ -49,10 +56,6 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
49
56
  telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
50
57
  uniqueKeys: new UniqueKeysCacheInMemoryCS(),
51
58
 
52
- validateCache() {
53
- return validateCache(options, settings, keys, splits, segments, largeSegments);
54
- },
55
-
56
59
  destroy() { },
57
60
 
58
61
  // When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
@@ -60,6 +63,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt
60
63
 
61
64
  return {
62
65
  splits: this.splits,
66
+ rbSegments: this.rbSegments,
63
67
  segments: new MySegmentsCacheInLocal(log, new KeyBuilderCS(prefix, matchingKey)),
64
68
  largeSegments: new MySegmentsCacheInLocal(log, myLargeSegmentsKeyBuilder(prefix, matchingKey)),
65
69
  impressions: this.impressions,
@@ -7,6 +7,7 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
10
+ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
10
11
 
11
12
  /**
12
13
  * InMemory storage factory for standalone server-side SplitFactory
@@ -17,10 +18,12 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
17
18
  const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { __splitFiltersValidation } } } = params;
18
19
 
19
20
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
21
+ const rbSegments = new RBSegmentsCacheInMemory();
20
22
  const segments = new SegmentsCacheInMemory();
21
23
 
22
24
  const storage = {
23
25
  splits,
26
+ rbSegments,
24
27
  segments,
25
28
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
26
29
  impressionCounts: new ImpressionCountsCacheInMemory(),
@@ -7,6 +7,7 @@ import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
7
7
  import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
8
8
  import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
9
9
  import { UniqueKeysCacheInMemoryCS } from './UniqueKeysCacheInMemoryCS';
10
+ import { RBSegmentsCacheInMemory } from './RBSegmentsCacheInMemory';
10
11
 
11
12
  /**
12
13
  * InMemory storage factory for standalone client-side SplitFactory
@@ -17,11 +18,13 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
17
18
  const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize }, sync: { __splitFiltersValidation } } } = params;
18
19
 
19
20
  const splits = new SplitsCacheInMemory(__splitFiltersValidation);
21
+ const rbSegments = new RBSegmentsCacheInMemory();
20
22
  const segments = new MySegmentsCacheInMemory();
21
23
  const largeSegments = new MySegmentsCacheInMemory();
22
24
 
23
25
  const storage = {
24
26
  splits,
27
+ rbSegments,
25
28
  segments,
26
29
  largeSegments,
27
30
  impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
@@ -36,6 +39,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
36
39
  shared() {
37
40
  return {
38
41
  splits: this.splits,
42
+ rbSegments: this.rbSegments,
39
43
  segments: new MySegmentsCacheInMemory(),
40
44
  largeSegments: new MySegmentsCacheInMemory(),
41
45
  impressions: this.impressions,