@splitsoftware/splitio-commons 2.2.1-rc.3 → 2.2.1-rc.5

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 (152) hide show
  1. package/CHANGES.txt +5 -2
  2. package/README.md +1 -0
  3. package/cjs/consent/sdkUserConsent.js +5 -3
  4. package/cjs/evaluator/combiners/and.js +2 -6
  5. package/cjs/evaluator/combiners/ifelseif.js +6 -6
  6. package/cjs/evaluator/condition/index.js +6 -5
  7. package/cjs/evaluator/index.js +7 -7
  8. package/cjs/evaluator/matchers/index.js +3 -1
  9. package/cjs/evaluator/matchers/matcherTypes.js +1 -0
  10. package/cjs/evaluator/matchers/rbsegment.js +56 -0
  11. package/cjs/evaluator/matchersTransform/index.js +4 -0
  12. package/cjs/evaluator/parser/index.js +2 -2
  13. package/cjs/evaluator/value/sanitize.js +1 -0
  14. package/cjs/listeners/browser.js +2 -5
  15. package/cjs/logger/constants.js +4 -3
  16. package/cjs/logger/messages/debug.js +3 -2
  17. package/cjs/logger/messages/warn.js +1 -1
  18. package/cjs/services/splitApi.js +3 -4
  19. package/cjs/services/splitHttpClient.js +3 -1
  20. package/cjs/storages/AbstractSplitsCacheSync.js +5 -2
  21. package/cjs/storages/KeyBuilder.js +9 -0
  22. package/cjs/storages/KeyBuilderCS.js +3 -0
  23. package/cjs/storages/KeyBuilderSS.js +3 -0
  24. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +117 -0
  25. package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
  26. package/cjs/storages/inLocalStorage/index.js +5 -1
  27. package/cjs/storages/inLocalStorage/validateCache.js +2 -1
  28. package/cjs/storages/inMemory/InMemoryStorage.js +3 -0
  29. package/cjs/storages/inMemory/InMemoryStorageCS.js +4 -0
  30. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +61 -0
  31. package/cjs/storages/inMemory/SplitsCacheInMemory.js +1 -0
  32. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +64 -0
  33. package/cjs/storages/inRedis/index.js +5 -3
  34. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +64 -0
  35. package/cjs/storages/pluggable/index.js +2 -0
  36. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +54 -4
  37. package/cjs/sync/polling/pollingManagerCS.js +7 -7
  38. package/cjs/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  39. package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  40. package/cjs/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  41. package/cjs/sync/polling/updaters/splitChangesUpdater.js +59 -33
  42. package/cjs/sync/streaming/SSEHandler/index.js +1 -0
  43. package/cjs/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +106 -77
  44. package/cjs/sync/streaming/constants.js +2 -1
  45. package/cjs/sync/streaming/pushManager.js +3 -16
  46. package/cjs/sync/syncManagerOnline.js +2 -2
  47. package/cjs/utils/constants/index.js +6 -2
  48. package/esm/consent/sdkUserConsent.js +5 -3
  49. package/esm/evaluator/combiners/and.js +2 -6
  50. package/esm/evaluator/combiners/ifelseif.js +7 -7
  51. package/esm/evaluator/condition/index.js +6 -5
  52. package/esm/evaluator/index.js +7 -7
  53. package/esm/evaluator/matchers/index.js +3 -1
  54. package/esm/evaluator/matchers/matcherTypes.js +1 -0
  55. package/esm/evaluator/matchers/rbsegment.js +52 -0
  56. package/esm/evaluator/matchersTransform/index.js +4 -0
  57. package/esm/evaluator/parser/index.js +2 -2
  58. package/esm/evaluator/value/sanitize.js +1 -0
  59. package/esm/listeners/browser.js +2 -5
  60. package/esm/logger/constants.js +1 -0
  61. package/esm/logger/messages/debug.js +3 -2
  62. package/esm/logger/messages/warn.js +1 -1
  63. package/esm/services/splitApi.js +3 -4
  64. package/esm/services/splitHttpClient.js +3 -1
  65. package/esm/storages/AbstractSplitsCacheSync.js +5 -2
  66. package/esm/storages/KeyBuilder.js +9 -0
  67. package/esm/storages/KeyBuilderCS.js +3 -0
  68. package/esm/storages/KeyBuilderSS.js +3 -0
  69. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +114 -0
  70. package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +9 -14
  71. package/esm/storages/inLocalStorage/index.js +5 -1
  72. package/esm/storages/inLocalStorage/validateCache.js +2 -1
  73. package/esm/storages/inMemory/InMemoryStorage.js +3 -0
  74. package/esm/storages/inMemory/InMemoryStorageCS.js +4 -0
  75. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +58 -0
  76. package/esm/storages/inMemory/SplitsCacheInMemory.js +1 -0
  77. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +61 -0
  78. package/esm/storages/inRedis/index.js +5 -3
  79. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +61 -0
  80. package/esm/storages/pluggable/index.js +2 -0
  81. package/esm/sync/polling/fetchers/splitChangesFetcher.js +54 -4
  82. package/esm/sync/polling/pollingManagerCS.js +7 -7
  83. package/esm/sync/polling/syncTasks/splitsSyncTask.js +1 -1
  84. package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
  85. package/esm/sync/polling/updaters/segmentChangesUpdater.js +1 -1
  86. package/esm/sync/polling/updaters/splitChangesUpdater.js +59 -33
  87. package/esm/sync/streaming/SSEHandler/index.js +2 -1
  88. package/esm/sync/streaming/UpdateWorkers/SplitsUpdateWorker.js +102 -73
  89. package/esm/sync/streaming/constants.js +1 -0
  90. package/esm/sync/streaming/pushManager.js +6 -19
  91. package/esm/sync/syncManagerOnline.js +2 -2
  92. package/esm/utils/constants/index.js +5 -1
  93. package/package.json +1 -1
  94. package/src/consent/sdkUserConsent.ts +3 -2
  95. package/src/dtos/types.ts +37 -8
  96. package/src/evaluator/Engine.ts +1 -1
  97. package/src/evaluator/combiners/and.ts +5 -4
  98. package/src/evaluator/combiners/ifelseif.ts +7 -9
  99. package/src/evaluator/condition/engineUtils.ts +1 -1
  100. package/src/evaluator/condition/index.ts +12 -12
  101. package/src/evaluator/index.ts +7 -7
  102. package/src/evaluator/matchers/index.ts +3 -1
  103. package/src/evaluator/matchers/matcherTypes.ts +1 -0
  104. package/src/evaluator/matchers/rbsegment.ts +74 -0
  105. package/src/evaluator/matchersTransform/index.ts +3 -0
  106. package/src/evaluator/parser/index.ts +3 -3
  107. package/src/evaluator/types.ts +2 -2
  108. package/src/evaluator/value/index.ts +2 -2
  109. package/src/evaluator/value/sanitize.ts +5 -4
  110. package/src/listeners/browser.ts +2 -3
  111. package/src/logger/constants.ts +1 -0
  112. package/src/logger/messages/debug.ts +3 -2
  113. package/src/logger/messages/warn.ts +1 -1
  114. package/src/sdkManager/index.ts +1 -1
  115. package/src/services/splitApi.ts +3 -4
  116. package/src/services/splitHttpClient.ts +3 -1
  117. package/src/services/types.ts +1 -1
  118. package/src/storages/AbstractSplitsCacheSync.ts +6 -3
  119. package/src/storages/KeyBuilder.ts +12 -0
  120. package/src/storages/KeyBuilderCS.ts +4 -0
  121. package/src/storages/KeyBuilderSS.ts +4 -0
  122. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +136 -0
  123. package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +10 -14
  124. package/src/storages/inLocalStorage/index.ts +5 -1
  125. package/src/storages/inLocalStorage/validateCache.ts +3 -1
  126. package/src/storages/inMemory/InMemoryStorage.ts +3 -0
  127. package/src/storages/inMemory/InMemoryStorageCS.ts +4 -0
  128. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +68 -0
  129. package/src/storages/inMemory/SplitsCacheInMemory.ts +1 -0
  130. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +79 -0
  131. package/src/storages/inRedis/index.ts +5 -3
  132. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +76 -0
  133. package/src/storages/pluggable/index.ts +2 -0
  134. package/src/storages/types.ts +33 -1
  135. package/src/sync/polling/fetchers/splitChangesFetcher.ts +65 -4
  136. package/src/sync/polling/fetchers/types.ts +1 -0
  137. package/src/sync/polling/pollingManagerCS.ts +7 -7
  138. package/src/sync/polling/syncTasks/splitsSyncTask.ts +1 -1
  139. package/src/sync/polling/types.ts +2 -2
  140. package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -2
  141. package/src/sync/polling/updaters/segmentChangesUpdater.ts +1 -1
  142. package/src/sync/polling/updaters/splitChangesUpdater.ts +70 -43
  143. package/src/sync/streaming/SSEHandler/index.ts +2 -1
  144. package/src/sync/streaming/SSEHandler/types.ts +2 -2
  145. package/src/sync/streaming/UpdateWorkers/SplitsUpdateWorker.ts +98 -68
  146. package/src/sync/streaming/constants.ts +1 -0
  147. package/src/sync/streaming/parseUtils.ts +2 -2
  148. package/src/sync/streaming/pushManager.ts +6 -18
  149. package/src/sync/streaming/types.ts +3 -2
  150. package/src/sync/syncManagerOnline.ts +2 -2
  151. package/src/utils/constants/index.ts +6 -1
  152. package/src/utils/lang/index.ts +1 -1
@@ -0,0 +1,68 @@
1
+ import { IRBSegment } from '../../dtos/types';
2
+ import { setToArray } from '../../utils/lang/sets';
3
+ import { usesSegments } from '../AbstractSplitsCacheSync';
4
+ import { IRBSegmentsCacheSync } from '../types';
5
+
6
+ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
7
+
8
+ private cache: Record<string, IRBSegment> = {};
9
+ private changeNumber: number = -1;
10
+ private segmentsCount: number = 0;
11
+
12
+ clear() {
13
+ this.cache = {};
14
+ this.changeNumber = -1;
15
+ this.segmentsCount = 0;
16
+ }
17
+
18
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
19
+ this.changeNumber = changeNumber;
20
+ const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
21
+ return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated;
22
+ }
23
+
24
+ private add(rbSegment: IRBSegment): boolean {
25
+ const name = rbSegment.name;
26
+ const previous = this.get(name);
27
+ if (previous && usesSegments(previous)) this.segmentsCount--;
28
+
29
+ this.cache[name] = rbSegment;
30
+ if (usesSegments(rbSegment)) this.segmentsCount++;
31
+
32
+ return true;
33
+ }
34
+
35
+ private remove(name: string): boolean {
36
+ const rbSegment = this.get(name);
37
+ if (!rbSegment) return false;
38
+
39
+ delete this.cache[name];
40
+
41
+ if (usesSegments(rbSegment)) this.segmentsCount--;
42
+
43
+ return true;
44
+ }
45
+
46
+ private getNames(): string[] {
47
+ return Object.keys(this.cache);
48
+ }
49
+
50
+ get(name: string): IRBSegment | null {
51
+ return this.cache[name] || null;
52
+ }
53
+
54
+ contains(names: Set<string>): boolean {
55
+ const namesArray = setToArray(names);
56
+ const namesInStorage = this.getNames();
57
+ return namesArray.every(name => namesInStorage.indexOf(name) !== -1);
58
+ }
59
+
60
+ getChangeNumber(): number {
61
+ return this.changeNumber;
62
+ }
63
+
64
+ usesSegments(): boolean {
65
+ return this.segmentsCount > 0;
66
+ }
67
+
68
+ }
@@ -24,6 +24,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync {
24
24
  this.ttCache = {};
25
25
  this.changeNumber = -1;
26
26
  this.segmentsCount = 0;
27
+ this.flagSetsCache = {};
27
28
  }
28
29
 
29
30
  addSplit(split: ISplit): boolean {
@@ -0,0 +1,79 @@
1
+ import { isNaNNumber } from '../../utils/lang';
2
+ import { IRBSegmentsCacheAsync } from '../types';
3
+ import { ILogger } from '../../logger/types';
4
+ import { IRBSegment } from '../../dtos/types';
5
+ import { LOG_PREFIX } from './constants';
6
+ import { setToArray } from '../../utils/lang/sets';
7
+ import { RedisAdapter } from './RedisAdapter';
8
+ import { KeyBuilderSS } from '../KeyBuilderSS';
9
+
10
+ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
11
+
12
+ private readonly log: ILogger;
13
+ private readonly keys: KeyBuilderSS;
14
+ private readonly redis: RedisAdapter;
15
+
16
+ constructor(log: ILogger, keys: KeyBuilderSS, redis: RedisAdapter) {
17
+ this.log = log;
18
+ this.keys = keys;
19
+ this.redis = redis;
20
+ }
21
+
22
+ get(name: string): Promise<IRBSegment | null> {
23
+ return this.redis.get(this.keys.buildRBSegmentKey(name))
24
+ .then(maybeRBSegment => maybeRBSegment && JSON.parse(maybeRBSegment));
25
+ }
26
+
27
+ private getNames(): Promise<string[]> {
28
+ return this.redis.keys(this.keys.searchPatternForRBSegmentKeys()).then(
29
+ (listOfKeys) => listOfKeys.map(this.keys.extractKey)
30
+ );
31
+ }
32
+
33
+ contains(names: Set<string>): Promise<boolean> {
34
+ const namesArray = setToArray(names);
35
+ return this.getNames().then(namesInStorage => {
36
+ return namesArray.every(name => namesInStorage.includes(name));
37
+ });
38
+ }
39
+
40
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean> {
41
+ return Promise.all([
42
+ this.setChangeNumber(changeNumber),
43
+ Promise.all(toAdd.map(toAdd => {
44
+ const key = this.keys.buildRBSegmentKey(toAdd.name);
45
+ const stringifiedNewRBSegment = JSON.stringify(toAdd);
46
+ return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
47
+ })),
48
+ Promise.all(toRemove.map(toRemove => {
49
+ const key = this.keys.buildRBSegmentKey(toRemove.name);
50
+ return this.redis.del(key).then(status => status === 1);
51
+ }))
52
+ ]).then(([, added, removed]) => {
53
+ return added.some(result => result) || removed.some(result => result);
54
+ });
55
+ }
56
+
57
+ setChangeNumber(changeNumber: number) {
58
+ return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
59
+ status => status === 'OK'
60
+ );
61
+ }
62
+
63
+ getChangeNumber(): Promise<number> {
64
+ return this.redis.get(this.keys.buildRBSegmentsTillKey()).then((value: string | null) => {
65
+ const i = parseInt(value as string, 10);
66
+
67
+ return isNaNNumber(i) ? -1 : i;
68
+ }).catch((e) => {
69
+ this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e);
70
+ return -1;
71
+ });
72
+ }
73
+
74
+ // @TODO implement if required by DataLoader or producer mode
75
+ clear() {
76
+ return Promise.resolve();
77
+ }
78
+
79
+ }
@@ -11,6 +11,7 @@ import { TelemetryCacheInRedis } from './TelemetryCacheInRedis';
11
11
  import { UniqueKeysCacheInRedis } from './UniqueKeysCacheInRedis';
12
12
  import { ImpressionCountsCacheInRedis } from './ImpressionCountsCacheInRedis';
13
13
  import { metadataBuilder } from '../utils';
14
+ import { RBSegmentsCacheInRedis } from './RBSegmentsCacheInRedis';
14
15
 
15
16
  export interface InRedisStorageOptions {
16
17
  prefix?: string
@@ -34,7 +35,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
34
35
  const prefix = validatePrefix(options.prefix);
35
36
 
36
37
  function InRedisStorageFactory(params: IStorageFactoryParams): IStorageAsync {
37
- if (!RD) throw new Error('The SDK Redis storage is not available. Your runtime environment must support CommonJS (`require`) to import the ioredis dependency.');
38
+ if (!RD) throw new Error('The SDK Redis storage is unavailable. Make sure your runtime environment supports CommonJS (`require`) so the `ioredis` dependency can be imported.');
38
39
 
39
40
  const { onReadyFromCacheCb, onReadyCb, settings, settings: { log } } = params;
40
41
  const metadata = metadataBuilder(settings);
@@ -44,10 +45,10 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
44
45
  const impressionCountsCache = new ImpressionCountsCacheInRedis(log, keys.buildImpressionsCountKey(), redisClient);
45
46
  const uniqueKeysCache = new UniqueKeysCacheInRedis(log, keys.buildUniqueKeysKey(), redisClient);
46
47
 
47
- // RedisAdapter queues operations before connection
48
+ // RedisAdapter lets queue operations before connected
48
49
  onReadyFromCacheCb();
49
50
 
50
- // subscription to Redis connect event in order to emit SDK_READY event on consumer mode
51
+ // Subscription to Redis connect event in order to emit SDK_READY event on consumer mode
51
52
  redisClient.on('connect', () => {
52
53
  onReadyCb();
53
54
  impressionCountsCache.start();
@@ -59,6 +60,7 @@ export function InRedisStorage(options: InRedisStorageOptions = {}): IStorageAsy
59
60
 
60
61
  return {
61
62
  splits: new SplitsCacheInRedis(log, keys, redisClient, settings.sync.__splitFiltersValidation),
63
+ rbSegments: new RBSegmentsCacheInRedis(log, keys, redisClient),
62
64
  segments: new SegmentsCacheInRedis(log, keys, redisClient),
63
65
  impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
64
66
  impressionCounts: impressionCountsCache,
@@ -0,0 +1,76 @@
1
+ import { isNaNNumber } from '../../utils/lang';
2
+ import { KeyBuilder } from '../KeyBuilder';
3
+ import { IPluggableStorageWrapper, IRBSegmentsCacheAsync } from '../types';
4
+ import { ILogger } from '../../logger/types';
5
+ import { IRBSegment } from '../../dtos/types';
6
+ import { LOG_PREFIX } from './constants';
7
+ import { setToArray } from '../../utils/lang/sets';
8
+
9
+ export class RBSegmentsCachePluggable implements IRBSegmentsCacheAsync {
10
+
11
+ private readonly log: ILogger;
12
+ private readonly keys: KeyBuilder;
13
+ private readonly wrapper: IPluggableStorageWrapper;
14
+
15
+ constructor(log: ILogger, keys: KeyBuilder, wrapper: IPluggableStorageWrapper) {
16
+ this.log = log;
17
+ this.keys = keys;
18
+ this.wrapper = wrapper;
19
+ }
20
+
21
+ get(name: string): Promise<IRBSegment | null> {
22
+ return this.wrapper.get(this.keys.buildRBSegmentKey(name))
23
+ .then(maybeRBSegment => maybeRBSegment && JSON.parse(maybeRBSegment));
24
+ }
25
+
26
+ private getNames(): Promise<string[]> {
27
+ return this.wrapper.getKeysByPrefix(this.keys.buildRBSegmentKeyPrefix()).then(
28
+ (listOfKeys) => listOfKeys.map(this.keys.extractKey)
29
+ );
30
+ }
31
+
32
+ contains(names: Set<string>): Promise<boolean> {
33
+ const namesArray = setToArray(names);
34
+ return this.getNames().then(namesInStorage => {
35
+ return namesArray.every(name => namesInStorage.includes(name));
36
+ });
37
+ }
38
+
39
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean> {
40
+ return Promise.all([
41
+ this.setChangeNumber(changeNumber),
42
+ Promise.all(toAdd.map(toAdd => {
43
+ const key = this.keys.buildRBSegmentKey(toAdd.name);
44
+ const stringifiedNewRBSegment = JSON.stringify(toAdd);
45
+ return this.wrapper.set(key, stringifiedNewRBSegment).then(() => true);
46
+ })),
47
+ Promise.all(toRemove.map(toRemove => {
48
+ const key = this.keys.buildRBSegmentKey(toRemove.name);
49
+ return this.wrapper.del(key);
50
+ }))
51
+ ]).then(([, added, removed]) => {
52
+ return added.some(result => result) || removed.some(result => result);
53
+ });
54
+ }
55
+
56
+ setChangeNumber(changeNumber: number) {
57
+ return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
58
+ }
59
+
60
+ getChangeNumber(): Promise<number> {
61
+ return this.wrapper.get(this.keys.buildRBSegmentsTillKey()).then((value) => {
62
+ const i = parseInt(value as string, 10);
63
+
64
+ return isNaNNumber(i) ? -1 : i;
65
+ }).catch((e) => {
66
+ this.log.error(LOG_PREFIX + 'Could not retrieve changeNumber from storage. Error: ' + e);
67
+ return -1;
68
+ });
69
+ }
70
+
71
+ // @TODO implement if required by DataLoader or producer mode
72
+ clear() {
73
+ return Promise.resolve();
74
+ }
75
+
76
+ }
@@ -20,6 +20,7 @@ import { UniqueKeysCacheInMemory } from '../inMemory/UniqueKeysCacheInMemory';
20
20
  import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
21
21
  import { metadataBuilder } from '../utils';
22
22
  import { LOG_PREFIX } from '../pluggable/constants';
23
+ import { RBSegmentsCachePluggable } from './RBSegmentsCachePluggable';
23
24
 
24
25
  const NO_VALID_WRAPPER = 'Expecting pluggable storage `wrapper` in options, but no valid wrapper instance was provided.';
25
26
  const NO_VALID_WRAPPER_INTERFACE = 'The provided wrapper instance doesn’t follow the expected interface. Check our docs.';
@@ -117,6 +118,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
117
118
 
118
119
  return {
119
120
  splits: new SplitsCachePluggable(log, keys, wrapper, settings.sync.__splitFiltersValidation),
121
+ rbSegments: new RBSegmentsCachePluggable(log, keys, wrapper),
120
122
  segments: new SegmentsCachePluggable(log, keys, wrapper),
121
123
  impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
122
124
  impressionCounts: impressionCountsCache,
@@ -1,5 +1,5 @@
1
1
  import SplitIO from '../../types/splitio';
2
- import { MaybeThenable, ISplit, IMySegmentsResponse } from '../dtos/types';
2
+ import { MaybeThenable, ISplit, IRBSegment, IMySegmentsResponse } from '../dtos/types';
3
3
  import { MySegmentsData } from '../sync/polling/types';
4
4
  import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
5
5
  import { ISettings } from '../types';
@@ -221,6 +221,34 @@ export interface ISplitsCacheAsync extends ISplitsCacheBase {
221
221
  getNamesByFlagSets(flagSets: string[]): Promise<Set<string>[]>
222
222
  }
223
223
 
224
+ /** Rule-Based Segments cache */
225
+
226
+ export interface IRBSegmentsCacheBase {
227
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): MaybeThenable<boolean>,
228
+ get(name: string): MaybeThenable<IRBSegment | null>,
229
+ getChangeNumber(): MaybeThenable<number>,
230
+ clear(): MaybeThenable<boolean | void>,
231
+ contains(names: Set<string>): MaybeThenable<boolean>,
232
+ }
233
+
234
+ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
235
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean,
236
+ get(name: string): IRBSegment | null,
237
+ getChangeNumber(): number,
238
+ clear(): void,
239
+ contains(names: Set<string>): boolean,
240
+ // Used only for smart pausing in client-side standalone. Returns true if the storage contains a RBSegment using segments or large segments matchers
241
+ usesSegments(): boolean,
242
+ }
243
+
244
+ export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
245
+ update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): Promise<boolean>,
246
+ get(name: string): Promise<IRBSegment | null>,
247
+ getChangeNumber(): Promise<number>,
248
+ clear(): Promise<boolean | void>,
249
+ contains(names: Set<string>): Promise<boolean>,
250
+ }
251
+
224
252
  /** Segments cache */
225
253
 
226
254
  export interface ISegmentsCacheBase {
@@ -419,6 +447,7 @@ export interface ITelemetryCacheAsync extends ITelemetryEvaluationProducerAsync,
419
447
 
420
448
  export interface IStorageBase<
421
449
  TSplitsCache extends ISplitsCacheBase = ISplitsCacheBase,
450
+ TRBSegmentsCache extends IRBSegmentsCacheBase = IRBSegmentsCacheBase,
422
451
  TSegmentsCache extends ISegmentsCacheBase = ISegmentsCacheBase,
423
452
  TImpressionsCache extends IImpressionsCacheBase = IImpressionsCacheBase,
424
453
  TImpressionsCountCache extends IImpressionCountsCacheBase = IImpressionCountsCacheBase,
@@ -427,6 +456,7 @@ export interface IStorageBase<
427
456
  TUniqueKeysCache extends IUniqueKeysCacheBase = IUniqueKeysCacheBase
428
457
  > {
429
458
  splits: TSplitsCache,
459
+ rbSegments: TRBSegmentsCache,
430
460
  segments: TSegmentsCache,
431
461
  impressions: TImpressionsCache,
432
462
  impressionCounts: TImpressionsCountCache,
@@ -439,6 +469,7 @@ export interface IStorageBase<
439
469
 
440
470
  export interface IStorageSync extends IStorageBase<
441
471
  ISplitsCacheSync,
472
+ IRBSegmentsCacheSync,
442
473
  ISegmentsCacheSync,
443
474
  IImpressionsCacheSync,
444
475
  IImpressionCountsCacheSync,
@@ -453,6 +484,7 @@ export interface IStorageSync extends IStorageBase<
453
484
 
454
485
  export interface IStorageAsync extends IStorageBase<
455
486
  ISplitsCacheAsync,
487
+ IRBSegmentsCacheAsync,
456
488
  ISegmentsCacheAsync,
457
489
  IImpressionsCacheAsync | IImpressionsCacheSync,
458
490
  IImpressionCountsCacheBase,
@@ -1,24 +1,85 @@
1
+ import { ISettings } from '../../../types';
2
+ import { ISplitChangesResponse } from '../../../dtos/types';
1
3
  import { IFetchSplitChanges, IResponse } from '../../../services/types';
4
+ import { IStorageBase } from '../../../storages/types';
5
+ import { FLAG_SPEC_VERSION } from '../../../utils/constants';
6
+ import { base } from '../../../utils/settingsValidation';
2
7
  import { ISplitChangesFetcher } from './types';
8
+ import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants';
9
+
10
+ const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side
11
+ const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side
12
+
13
+ function sdkEndpointOverridden(settings: ISettings) {
14
+ return settings.urls.sdk !== base.urls.sdk;
15
+ }
3
16
 
4
17
  /**
5
18
  * Factory of SplitChanges fetcher.
6
19
  * SplitChanges fetcher is a wrapper around `splitChanges` API service that parses the response and handle errors.
7
20
  */
8
- export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges): ISplitChangesFetcher {
21
+ // @TODO breaking: drop support for Split Proxy below v5.10.0 and simplify the implementation
22
+ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges, settings: ISettings, storage: Pick<IStorageBase, 'splits' | 'rbSegments'>): ISplitChangesFetcher {
23
+
24
+ const log = settings.log;
25
+ const PROXY_CHECK_INTERVAL_MILLIS = settings.core.key !== undefined ? PROXY_CHECK_INTERVAL_MILLIS_CS : PROXY_CHECK_INTERVAL_MILLIS_SS;
26
+ let lastProxyCheckTimestamp: number | undefined;
9
27
 
10
28
  return function splitChangesFetcher(
11
29
  since: number,
12
30
  noCache?: boolean,
13
31
  till?: number,
32
+ rbSince?: number,
14
33
  // Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
15
34
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
16
- ) {
35
+ ): Promise<ISplitChangesResponse> {
36
+
37
+ // Recheck proxy
38
+ if (lastProxyCheckTimestamp && (Date.now() - lastProxyCheckTimestamp) > PROXY_CHECK_INTERVAL_MILLIS) {
39
+ settings.sync.flagSpecVersion = FLAG_SPEC_VERSION;
40
+ }
41
+
42
+ let splitsPromise = fetchSplitChanges(since, noCache, till, settings.sync.flagSpecVersion === FLAG_SPEC_VERSION ? rbSince : undefined)
43
+ // Handle proxy error with spec 1.3
44
+ .catch((err) => {
45
+ if (err.statusCode === 400 && sdkEndpointOverridden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) {
46
+ log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. Retrying with spec 1.2. If you are using Split Proxy, please upgrade to latest version');
47
+ lastProxyCheckTimestamp = Date.now();
48
+ settings.sync.flagSpecVersion = '1.2'; // fallback to 1.2 spec
49
+ return fetchSplitChanges(since, noCache, till); // retry request without rbSince
50
+ }
51
+ throw err;
52
+ });
17
53
 
18
- let splitsPromise = fetchSplitChanges(since, noCache, till);
19
54
  if (decorator) splitsPromise = decorator(splitsPromise);
20
55
 
21
- return splitsPromise.then(resp => resp.json());
56
+ return splitsPromise
57
+ .then(resp => resp.json())
58
+ .then(data => {
59
+ // Using flag spec version 1.2
60
+ if (data.splits) {
61
+ return {
62
+ ff: {
63
+ d: data.splits,
64
+ s: data.since,
65
+ t: data.till
66
+ }
67
+ };
68
+ }
69
+
70
+ // Proxy recovery
71
+ if (lastProxyCheckTimestamp) {
72
+ log.info(LOG_PREFIX_SYNC_SPLITS + 'Proxy error recovered');
73
+ lastProxyCheckTimestamp = undefined;
74
+ return splitChangesFetcher(-1, undefined, undefined, -1)
75
+ .then((splitChangesResponse: ISplitChangesResponse) =>
76
+ Promise.all([storage.splits.clear(), storage.rbSegments.clear()])
77
+ .then(() => splitChangesResponse)
78
+ );
79
+ }
80
+
81
+ return data;
82
+ });
22
83
  };
23
84
 
24
85
  }
@@ -5,6 +5,7 @@ export type ISplitChangesFetcher = (
5
5
  since: number,
6
6
  noCache?: boolean,
7
7
  till?: number,
8
+ rbSince?: number,
8
9
  decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
9
10
  ) => Promise<ISplitChangesResponse>
10
11
 
@@ -43,10 +43,10 @@ export function pollingManagerCSFactory(
43
43
  // smart pausing
44
44
  readiness.splits.on(SDK_SPLITS_ARRIVED, () => {
45
45
  if (!splitsSyncTask.isRunning()) return; // noop if not doing polling
46
- const splitsHaveSegments = storage.splits.usesSegments();
47
- if (splitsHaveSegments !== mySegmentsSyncTask.isRunning()) {
48
- log.info(POLLING_SMART_PAUSING, [splitsHaveSegments ? 'ON' : 'OFF']);
49
- if (splitsHaveSegments) {
46
+ const usingSegments = storage.splits.usesSegments() || storage.rbSegments.usesSegments();
47
+ if (usingSegments !== mySegmentsSyncTask.isRunning()) {
48
+ log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
49
+ if (usingSegments) {
50
50
  startMySegmentsSyncTasks();
51
51
  } else {
52
52
  stopMySegmentsSyncTasks();
@@ -59,9 +59,9 @@ export function pollingManagerCSFactory(
59
59
 
60
60
  // smart ready
61
61
  function smartReady() {
62
- if (!readiness.isReady() && !storage.splits.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
62
+ if (!readiness.isReady() && !storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
63
63
  }
64
- if (!storage.splits.usesSegments()) setTimeout(smartReady, 0);
64
+ if (!storage.splits.usesSegments() && !storage.rbSegments.usesSegments()) setTimeout(smartReady, 0);
65
65
  else readiness.splits.once(SDK_SPLITS_ARRIVED, smartReady);
66
66
 
67
67
  mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
@@ -77,7 +77,7 @@ export function pollingManagerCSFactory(
77
77
  log.info(POLLING_START);
78
78
 
79
79
  splitsSyncTask.start();
80
- if (storage.splits.usesSegments()) startMySegmentsSyncTasks();
80
+ if (storage.splits.usesSegments() || storage.rbSegments.usesSegments()) startMySegmentsSyncTasks();
81
81
  },
82
82
 
83
83
  // Stop periodic fetching (polling)
@@ -21,7 +21,7 @@ export function splitsSyncTaskFactory(
21
21
  settings.log,
22
22
  splitChangesUpdaterFactory(
23
23
  settings.log,
24
- splitChangesFetcherFactory(fetchSplitChanges),
24
+ splitChangesFetcherFactory(fetchSplitChanges, settings, storage),
25
25
  storage,
26
26
  settings.sync.__splitFiltersValidation,
27
27
  readiness.splits,
@@ -1,10 +1,10 @@
1
- import { ISplit } from '../../dtos/types';
1
+ import { IRBSegment, ISplit } from '../../dtos/types';
2
2
  import { IReadinessManager } from '../../readiness/types';
3
3
  import { IStorageSync } from '../../storages/types';
4
4
  import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../streaming/types';
5
5
  import { ITask, ISyncTask } from '../types';
6
6
 
7
- export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }], boolean> { }
7
+ export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit | IRBSegment, changeNumber: number }], boolean> { }
8
8
 
9
9
  export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
10
10
 
@@ -27,7 +27,7 @@ export function mySegmentsUpdaterFactory(
27
27
  matchingKey: string
28
28
  ): IMySegmentsUpdater {
29
29
 
30
- const { splits, segments, largeSegments } = storage;
30
+ const { splits, rbSegments, segments, largeSegments } = storage;
31
31
  let readyOnAlreadyExistentState = true;
32
32
  let startingUp = true;
33
33
 
@@ -51,7 +51,7 @@ export function mySegmentsUpdaterFactory(
51
51
  }
52
52
 
53
53
  // Notify update if required
54
- if (splits.usesSegments() && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
54
+ if ((splits.usesSegments() || rbSegments.usesSegments()) && (shouldNotifyUpdate || readyOnAlreadyExistentState)) {
55
55
  readyOnAlreadyExistentState = false;
56
56
  segmentsEventEmitter.emit(SDK_SEGMENTS_ARRIVED);
57
57
  }
@@ -51,7 +51,7 @@ export function segmentChangesUpdaterFactory(
51
51
  * Returned promise will not be rejected.
52
52
  *
53
53
  * @param fetchOnlyNew - if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
54
- * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
54
+ * This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE or RB_SEGMENT_UPDATE notifications.
55
55
  * @param segmentName - segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
56
56
  * @param noCache - true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
57
57
  * @param till - till target for the provided segmentName, for CDN bypass.