@splitsoftware/splitio-commons 1.16.1-rc.10 → 1.16.1-rc.11
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/cjs/logger/constants.js +2 -2
- package/cjs/logger/messages/warn.js +1 -1
- package/cjs/services/splitApi.js +1 -1
- package/cjs/storages/AbstractSegmentsCacheSync.js +41 -7
- package/cjs/storages/dataLoader.js +1 -1
- package/cjs/storages/inLocalStorage/MySegmentsCacheInLocal.js +19 -63
- package/cjs/storages/inMemory/MySegmentsCacheInMemory.js +5 -40
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +7 -17
- package/cjs/sync/streaming/AuthClient/index.js +1 -1
- package/cjs/sync/streaming/SSEHandler/index.js +5 -7
- package/cjs/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +8 -6
- package/cjs/sync/streaming/constants.js +3 -3
- package/cjs/sync/streaming/pushManager.js +21 -24
- package/cjs/utils/constants/index.js +3 -4
- package/esm/logger/constants.js +1 -1
- package/esm/logger/messages/warn.js +1 -1
- package/esm/services/splitApi.js +2 -2
- package/esm/storages/AbstractSegmentsCacheSync.js +41 -7
- package/esm/storages/dataLoader.js +1 -1
- package/esm/storages/inLocalStorage/MySegmentsCacheInLocal.js +19 -63
- package/esm/storages/inMemory/MySegmentsCacheInMemory.js +5 -40
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +7 -17
- package/esm/sync/streaming/AuthClient/index.js +1 -1
- package/esm/sync/streaming/SSEHandler/index.js +6 -8
- package/esm/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.js +8 -6
- package/esm/sync/streaming/constants.js +2 -2
- package/esm/sync/streaming/pushManager.js +24 -27
- package/esm/utils/constants/index.js +1 -2
- package/package.json +1 -1
- package/src/dtos/types.ts +9 -12
- package/src/logger/constants.ts +1 -1
- package/src/logger/messages/warn.ts +1 -1
- package/src/services/splitApi.ts +2 -2
- package/src/storages/AbstractSegmentsCacheSync.ts +52 -7
- package/src/storages/AbstractSplitsCacheSync.ts +1 -1
- package/src/storages/dataLoader.ts +1 -1
- package/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +15 -69
- package/src/storages/inMemory/MySegmentsCacheInMemory.ts +6 -46
- package/src/storages/types.ts +5 -4
- package/src/sync/polling/types.ts +8 -9
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +9 -14
- package/src/sync/streaming/AuthClient/index.ts +1 -1
- package/src/sync/streaming/SSEHandler/index.ts +9 -11
- package/src/sync/streaming/SSEHandler/types.ts +6 -6
- package/src/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.ts +8 -7
- package/src/sync/streaming/constants.ts +2 -2
- package/src/sync/streaming/parseUtils.ts +2 -2
- package/src/sync/streaming/pushManager.ts +26 -29
- package/src/sync/streaming/types.ts +6 -6
- package/src/sync/submitters/types.ts +3 -4
- package/src/utils/constants/index.ts +1 -2
- package/types/dtos/types.d.ts +8 -12
- package/types/logger/constants.d.ts +1 -1
- package/types/storages/AbstractSegmentsCacheSync.d.ts +8 -6
- package/types/storages/AbstractSplitsCacheSync.d.ts +1 -1
- package/types/storages/inLocalStorage/MySegmentsCacheInLocal.d.ts +1 -12
- package/types/storages/inMemory/MySegmentsCacheInMemory.d.ts +1 -9
- package/types/storages/types.d.ts +5 -4
- package/types/sync/polling/types.d.ts +8 -6
- package/types/sync/streaming/SSEHandler/types.d.ts +6 -6
- package/types/sync/streaming/UpdateWorkers/MySegmentsUpdateWorker.d.ts +1 -2
- package/types/sync/streaming/constants.d.ts +2 -2
- package/types/sync/streaming/parseUtils.d.ts +2 -2
- package/types/sync/streaming/types.d.ts +5 -5
- package/types/sync/submitters/types.d.ts +3 -4
- package/types/utils/constants/index.d.ts +1 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
2
|
/* eslint-disable no-unused-vars */
|
|
3
|
+
import { IMySegmentsResponse } from '../dtos/types';
|
|
4
|
+
import { MySegmentsData } from '../sync/polling/types';
|
|
3
5
|
import { ISegmentsCacheSync } from './types';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -28,7 +30,9 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
28
30
|
/**
|
|
29
31
|
* clear the cache.
|
|
30
32
|
*/
|
|
31
|
-
|
|
33
|
+
clear() {
|
|
34
|
+
this.resetSegments({});
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* For server-side synchronizer: add the given list of segments to the cache, with an empty list of keys. The segments that already exist are not modified.
|
|
@@ -49,16 +53,57 @@ export abstract class AbstractSegmentsCacheSync implements ISegmentsCacheSync {
|
|
|
49
53
|
abstract getKeysCount(): number
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
|
-
* For server-side synchronizer:
|
|
53
|
-
* For client-side synchronizer:
|
|
56
|
+
* For server-side synchronizer: change number of `name` segment.
|
|
57
|
+
* For client-side synchronizer: change number of mySegments.
|
|
54
58
|
*/
|
|
55
|
-
setChangeNumber(name
|
|
56
|
-
|
|
59
|
+
abstract setChangeNumber(name?: string, changeNumber?: number): boolean | void
|
|
57
60
|
abstract getChangeNumber(name: string): number
|
|
58
61
|
|
|
59
62
|
/**
|
|
60
63
|
* For server-side synchronizer: the method is not used.
|
|
61
|
-
* For client-side synchronizer:
|
|
64
|
+
* For client-side synchronizer: it resets or updates the cache.
|
|
62
65
|
*/
|
|
63
|
-
resetSegments(
|
|
66
|
+
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean {
|
|
67
|
+
this.setChangeNumber(undefined, segmentsData.cn);
|
|
68
|
+
|
|
69
|
+
const { added, removed } = segmentsData as MySegmentsData;
|
|
70
|
+
|
|
71
|
+
if (added && removed) {
|
|
72
|
+
let isDiff = false;
|
|
73
|
+
|
|
74
|
+
added.forEach(segment => {
|
|
75
|
+
isDiff = this.addToSegment(segment) || isDiff;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
removed.forEach(segment => {
|
|
79
|
+
isDiff = this.removeFromSegment(segment) || isDiff;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return isDiff;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort();
|
|
86
|
+
const storedSegmentKeys = this.getRegisteredSegments().sort();
|
|
87
|
+
|
|
88
|
+
// Extreme fast => everything is empty
|
|
89
|
+
if (!names.length && !storedSegmentKeys.length) return false;
|
|
90
|
+
|
|
91
|
+
let index = 0;
|
|
92
|
+
|
|
93
|
+
while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++;
|
|
94
|
+
|
|
95
|
+
// Quick path => no changes
|
|
96
|
+
if (index === names.length && index === storedSegmentKeys.length) return false;
|
|
97
|
+
|
|
98
|
+
// Slowest path => add and/or remove segments
|
|
99
|
+
for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) {
|
|
100
|
+
this.removeFromSegment(storedSegmentKeys[removeIndex]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (let addIndex = index; addIndex < names.length; addIndex++) {
|
|
104
|
+
this.addToSegment(names[addIndex]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
64
109
|
}
|
|
@@ -32,7 +32,7 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync {
|
|
|
32
32
|
return splits;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
abstract setChangeNumber(changeNumber: number): boolean
|
|
35
|
+
abstract setChangeNumber(changeNumber: number): boolean | void
|
|
36
36
|
|
|
37
37
|
abstract getChangeNumber(): number
|
|
38
38
|
|
|
@@ -50,6 +50,6 @@ export function dataLoaderFactory(preloadedData: SplitIO.PreloadedData): DataLoa
|
|
|
50
50
|
return Array.isArray(userIds) && userIds.indexOf(userId) > -1;
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
|
-
storage.segments.resetSegments(mySegmentsData);
|
|
53
|
+
storage.segments.resetSegments({ k: mySegmentsData.map(s => ({ n: s })) });
|
|
54
54
|
};
|
|
55
55
|
}
|
|
@@ -16,22 +16,11 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
16
16
|
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/**
|
|
20
|
-
* Removes list of segments from localStorage
|
|
21
|
-
* @NOTE this method is not being used at the moment.
|
|
22
|
-
*/
|
|
23
|
-
clear() {
|
|
24
|
-
this.log.info(LOG_PREFIX + 'Flushing MySegments data from localStorage');
|
|
25
|
-
|
|
26
|
-
// We cannot simply call `localStorage.clear()` since that implies removing user items from the storage
|
|
27
|
-
// We could optimize next sentence, since it implies iterating over all localStorage items
|
|
28
|
-
this.resetSegments([]);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
19
|
addToSegment(name: string): boolean {
|
|
32
20
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
33
21
|
|
|
34
22
|
try {
|
|
23
|
+
if (localStorage.getItem(segmentKey) === DEFINED) return false;
|
|
35
24
|
localStorage.setItem(segmentKey, DEFINED);
|
|
36
25
|
return true;
|
|
37
26
|
} catch (e) {
|
|
@@ -44,6 +33,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
44
33
|
const segmentKey = this.keys.buildSegmentNameKey(name);
|
|
45
34
|
|
|
46
35
|
try {
|
|
36
|
+
if (localStorage.getItem(segmentKey) !== DEFINED) return false;
|
|
47
37
|
localStorage.removeItem(segmentKey);
|
|
48
38
|
return true;
|
|
49
39
|
} catch (e) {
|
|
@@ -56,41 +46,22 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
56
46
|
return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
* Reset (update) the cached list of segments with the given list, removing and adding segments if necessary.
|
|
61
|
-
*
|
|
62
|
-
* @param {string[]} names list of segment names
|
|
63
|
-
* @returns boolean indicating if the cache was updated (i.e., given list was different from the cached one)
|
|
64
|
-
*/
|
|
65
|
-
resetSegments(names: string[], changeNumber?: number): boolean {
|
|
66
|
-
try {
|
|
67
|
-
if (changeNumber) {
|
|
68
|
-
localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
69
|
-
} else {
|
|
70
|
-
localStorage.removeItem(this.keys.buildTillKey());
|
|
71
|
-
}
|
|
72
|
-
} catch (e) {
|
|
73
|
-
this.log.error(e);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let isDiff = false;
|
|
77
|
-
let index;
|
|
78
|
-
|
|
49
|
+
getRegisteredSegments(): string[] {
|
|
79
50
|
// Scan current values from localStorage
|
|
80
|
-
|
|
51
|
+
return Object.keys(localStorage).reduce((accum, key) => {
|
|
81
52
|
let segmentName = this.keys.extractSegmentName(key);
|
|
82
53
|
|
|
83
54
|
if (segmentName) {
|
|
84
55
|
accum.push(segmentName);
|
|
85
56
|
} else {
|
|
86
|
-
// @TODO @BREAKING: This is only to clean up "old" keys. Remove this whole else code block
|
|
57
|
+
// @TODO @BREAKING: This is only to clean up "old" keys. Remove this whole else code block
|
|
87
58
|
segmentName = this.keys.extractOldSegmentKey(key);
|
|
88
59
|
|
|
89
60
|
if (segmentName) { // this was an old segment key, let's clean up.
|
|
90
61
|
const newSegmentKey = this.keys.buildSegmentNameKey(segmentName);
|
|
91
62
|
try {
|
|
92
63
|
// If the new format key is not there, create it.
|
|
93
|
-
if (!localStorage.getItem(newSegmentKey)
|
|
64
|
+
if (!localStorage.getItem(newSegmentKey)) {
|
|
94
65
|
localStorage.setItem(newSegmentKey, DEFINED);
|
|
95
66
|
// we are migrating a segment, let's track it.
|
|
96
67
|
accum.push(segmentName);
|
|
@@ -104,46 +75,21 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
|
|
|
104
75
|
|
|
105
76
|
return accum;
|
|
106
77
|
}, [] as string[]);
|
|
107
|
-
|
|
108
|
-
// Extreme fast => everything is empty
|
|
109
|
-
if (names.length === 0 && storedSegmentNames.length === names.length)
|
|
110
|
-
return isDiff;
|
|
111
|
-
|
|
112
|
-
// Quick path
|
|
113
|
-
if (storedSegmentNames.length !== names.length) {
|
|
114
|
-
isDiff = true;
|
|
115
|
-
|
|
116
|
-
storedSegmentNames.forEach(name => this.removeFromSegment(name));
|
|
117
|
-
names.forEach(name => this.addToSegment(name));
|
|
118
|
-
} else {
|
|
119
|
-
// Slowest path => we need to find at least 1 difference because
|
|
120
|
-
for (index = 0; index < names.length && storedSegmentNames.indexOf(names[index]) !== -1; index++) {
|
|
121
|
-
// TODO: why empty statement?
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (index < names.length) {
|
|
125
|
-
isDiff = true;
|
|
126
|
-
|
|
127
|
-
storedSegmentNames.forEach(name => this.removeFromSegment(name));
|
|
128
|
-
names.forEach(name => this.addToSegment(name));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return isDiff;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
getRegisteredSegments(): string[] {
|
|
136
|
-
return Object.keys(localStorage).reduce<string[]>((accum, key) => {
|
|
137
|
-
const segmentName = this.keys.extractSegmentName(key);
|
|
138
|
-
if (segmentName) accum.push(segmentName);
|
|
139
|
-
return accum;
|
|
140
|
-
}, []);
|
|
141
78
|
}
|
|
142
79
|
|
|
143
80
|
getKeysCount() {
|
|
144
81
|
return 1;
|
|
145
82
|
}
|
|
146
83
|
|
|
84
|
+
setChangeNumber(name?: string, changeNumber?: number) {
|
|
85
|
+
try {
|
|
86
|
+
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
|
|
87
|
+
else localStorage.removeItem(this.keys.buildTillKey());
|
|
88
|
+
} catch (e) {
|
|
89
|
+
this.log.error(e);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
147
93
|
getChangeNumber() {
|
|
148
94
|
const n = -1;
|
|
149
95
|
let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
|
|
@@ -9,17 +9,17 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
9
9
|
private segmentCache: Record<string, boolean> = {};
|
|
10
10
|
private cn?: number;
|
|
11
11
|
|
|
12
|
-
clear() {
|
|
13
|
-
this.segmentCache = {};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
12
|
addToSegment(name: string): boolean {
|
|
13
|
+
if (this.segmentCache[name]) return false;
|
|
14
|
+
|
|
17
15
|
this.segmentCache[name] = true;
|
|
18
16
|
|
|
19
17
|
return true;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
removeFromSegment(name: string): boolean {
|
|
21
|
+
if (!this.segmentCache[name]) return false;
|
|
22
|
+
|
|
23
23
|
delete this.segmentCache[name];
|
|
24
24
|
|
|
25
25
|
return true;
|
|
@@ -29,49 +29,9 @@ export class MySegmentsCacheInMemory extends AbstractSegmentsCacheSync {
|
|
|
29
29
|
return this.segmentCache[name] === true;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Reset (update) the cached list of segments with the given list, removing and adding segments if necessary.
|
|
34
|
-
* @NOTE based on the way we use segments in the browser, this way is the best option
|
|
35
|
-
*
|
|
36
|
-
* @param {string[]} names list of segment names
|
|
37
|
-
* @returns boolean indicating if the cache was updated (i.e., given list was different from the cached one)
|
|
38
|
-
*/
|
|
39
|
-
resetSegments(names: string[], changeNumber?: number): boolean {
|
|
40
|
-
this.cn = changeNumber;
|
|
41
|
-
let isDiff = false;
|
|
42
|
-
let index;
|
|
43
|
-
|
|
44
|
-
const storedSegmentKeys = Object.keys(this.segmentCache);
|
|
45
32
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return isDiff;
|
|
49
|
-
|
|
50
|
-
// Quick path
|
|
51
|
-
if (storedSegmentKeys.length !== names.length) {
|
|
52
|
-
isDiff = true;
|
|
53
|
-
|
|
54
|
-
this.segmentCache = {};
|
|
55
|
-
names.forEach(s => {
|
|
56
|
-
this.addToSegment(s);
|
|
57
|
-
});
|
|
58
|
-
} else {
|
|
59
|
-
// Slowest path => we need to find at least 1 difference because
|
|
60
|
-
for (index = 0; index < names.length && this.isInSegment(names[index]); index++) {
|
|
61
|
-
// TODO: why empty statement?
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (index < names.length) {
|
|
65
|
-
isDiff = true;
|
|
66
|
-
|
|
67
|
-
this.segmentCache = {};
|
|
68
|
-
names.forEach(s => {
|
|
69
|
-
this.addToSegment(s);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return isDiff;
|
|
33
|
+
setChangeNumber(name?: string, changeNumber?: number) {
|
|
34
|
+
this.cn = changeNumber;
|
|
75
35
|
}
|
|
76
36
|
|
|
77
37
|
getChangeNumber() {
|
package/src/storages/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { MaybeThenable, ISplit } from '../dtos/types';
|
|
1
|
+
import { MaybeThenable, ISplit, IMySegmentsResponse } from '../dtos/types';
|
|
2
|
+
import { MySegmentsData } from '../sync/polling/types';
|
|
2
3
|
import { EventDataType, HttpErrors, HttpLatencies, ImpressionDataType, LastSync, Method, MethodExceptions, MethodLatencies, MultiMethodExceptions, MultiMethodLatencies, MultiConfigs, OperationType, StoredEventWithMetadata, StoredImpressionWithMetadata, StreamingEvent, UniqueKeysPayloadCs, UniqueKeysPayloadSs, TelemetryUsageStatsPayload, UpdatesFromSSEEnum } from '../sync/submitters/types';
|
|
3
4
|
import { SplitIO, ImpressionDTO, ISettings } from '../types';
|
|
4
5
|
import { ISet } from '../utils/lang/sets';
|
|
@@ -218,7 +219,7 @@ export interface ISplitsCacheSync extends ISplitsCacheBase {
|
|
|
218
219
|
removeSplits(names: string[]): boolean[],
|
|
219
220
|
getSplit(name: string): ISplit | null,
|
|
220
221
|
getSplits(names: string[]): Record<string, ISplit | null>,
|
|
221
|
-
setChangeNumber(changeNumber: number): boolean,
|
|
222
|
+
setChangeNumber(changeNumber: number): boolean | void,
|
|
222
223
|
getChangeNumber(): number,
|
|
223
224
|
getAll(): ISplit[],
|
|
224
225
|
getSplitNames(): string[],
|
|
@@ -268,9 +269,9 @@ export interface ISegmentsCacheSync extends ISegmentsCacheBase {
|
|
|
268
269
|
registerSegments(names: string[]): boolean
|
|
269
270
|
getRegisteredSegments(): string[]
|
|
270
271
|
getKeysCount(): number // only used for telemetry
|
|
271
|
-
setChangeNumber(name: string, changeNumber: number): boolean
|
|
272
|
+
setChangeNumber(name: string, changeNumber: number): boolean | void
|
|
272
273
|
getChangeNumber(name: string): number
|
|
273
|
-
resetSegments(
|
|
274
|
+
resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean // only for Sync Client-Side
|
|
274
275
|
clear(): void
|
|
275
276
|
}
|
|
276
277
|
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ISplit } from '../../dtos/types';
|
|
2
2
|
import { IReadinessManager } from '../../readiness/types';
|
|
3
3
|
import { IStorageSync } from '../../storages/types';
|
|
4
|
+
import { MEMBERSHIP_LS_UPDATE, MEMBERSHIP_MS_UPDATE } from '../streaming/types';
|
|
4
5
|
import { ITask, ISyncTask } from '../types';
|
|
5
6
|
|
|
6
7
|
export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }], boolean> { }
|
|
7
8
|
|
|
8
9
|
export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
|
|
9
10
|
|
|
10
|
-
export type MySegmentsData =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
add: boolean
|
|
17
|
-
}[]
|
|
11
|
+
export type MySegmentsData = {
|
|
12
|
+
type: MEMBERSHIP_MS_UPDATE | MEMBERSHIP_LS_UPDATE
|
|
13
|
+
cn: number
|
|
14
|
+
added: string[]
|
|
15
|
+
removed: string[]
|
|
16
|
+
}
|
|
18
17
|
|
|
19
18
|
export interface IMySegmentsSyncTask extends ISyncTask<[segmentsData?: MySegmentsData, noCache?: boolean], boolean> { }
|
|
20
19
|
|
|
@@ -6,6 +6,8 @@ import { SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
|
|
|
6
6
|
import { ILogger } from '../../../logger/types';
|
|
7
7
|
import { SYNC_MYSEGMENTS_FETCH_RETRY } from '../../../logger/constants';
|
|
8
8
|
import { MySegmentsData } from '../types';
|
|
9
|
+
import { IMembershipsResponse } from '../../../dtos/types';
|
|
10
|
+
import { MEMBERSHIP_LS_UPDATE } from '../../streaming/constants';
|
|
9
11
|
|
|
10
12
|
type IMySegmentsUpdater = (segmentList?: MySegmentsData, noCache?: boolean) => Promise<boolean>
|
|
11
13
|
|
|
@@ -36,23 +38,16 @@ export function mySegmentsUpdaterFactory(
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
// @TODO if allowing pluggable storages, handle async execution
|
|
39
|
-
function updateSegments(segmentsData: MySegmentsData) {
|
|
41
|
+
function updateSegments(segmentsData: IMembershipsResponse | MySegmentsData) {
|
|
40
42
|
|
|
41
43
|
let shouldNotifyUpdate;
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (cache!.isInSegment(name) !== add) {
|
|
47
|
-
shouldNotifyUpdate = true;
|
|
48
|
-
if (add) cache!.addToSegment(name);
|
|
49
|
-
else cache!.removeFromSegment(name);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
44
|
+
if ((segmentsData as MySegmentsData).type !== undefined) {
|
|
45
|
+
shouldNotifyUpdate = (segmentsData as MySegmentsData).type === MEMBERSHIP_LS_UPDATE ?
|
|
46
|
+
largeSegments!.resetSegments(segmentsData as MySegmentsData) :
|
|
47
|
+
segments.resetSegments(segmentsData as MySegmentsData);
|
|
52
48
|
} else {
|
|
53
|
-
|
|
54
|
-
shouldNotifyUpdate =
|
|
55
|
-
shouldNotifyUpdate = largeSegments!.resetSegments((segmentsData.ls?.k || []).map((segment) => segment.n), segmentsData.ls?.cn) || shouldNotifyUpdate;
|
|
49
|
+
shouldNotifyUpdate = segments.resetSegments((segmentsData as IMembershipsResponse).ms || {});
|
|
50
|
+
shouldNotifyUpdate = largeSegments!.resetSegments((segmentsData as IMembershipsResponse).ls || {}) || shouldNotifyUpdate;
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
// Notify update if required
|
|
@@ -14,7 +14,7 @@ export function authenticateFactory(fetchAuth: IFetchAuth): IAuthenticate {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Run authentication requests to Auth Server, and returns a promise that resolves with the decoded JTW token.
|
|
17
|
-
* @param {string[] | undefined} userKeys set of user Keys to track
|
|
17
|
+
* @param {string[] | undefined} userKeys set of user Keys to track membership updates. It is undefined for server-side API.
|
|
18
18
|
*/
|
|
19
19
|
return function authenticate(userKeys?: string[]): Promise<IAuthToken> {
|
|
20
20
|
return fetchAuth(userKeys)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { errorParser, messageParser } from './NotificationParser';
|
|
2
2
|
import { notificationKeeperFactory } from './NotificationKeeper';
|
|
3
|
-
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL,
|
|
3
|
+
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIP_MS_UPDATE, MEMBERSHIP_LS_UPDATE } from '../constants';
|
|
4
4
|
import { IPushEventEmitter } from '../types';
|
|
5
5
|
import { ISseEventHandler } from '../SSEClient/types';
|
|
6
|
-
import {
|
|
6
|
+
import { INotificationError, INotificationMessage } from './types';
|
|
7
7
|
import { ILogger } from '../../../logger/types';
|
|
8
8
|
import { STREAMING_PARSING_ERROR_FAILS, ERROR_STREAMING_SSE, STREAMING_PARSING_MESSAGE_FAILS, STREAMING_NEW_MESSAGE } from '../../../logger/constants';
|
|
9
9
|
import { ABLY_ERROR, NON_REQUESTED, SSE_CONNECTION_ERROR } from '../../../utils/constants';
|
|
@@ -75,26 +75,24 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
|
|
|
75
75
|
log.debug(STREAMING_NEW_MESSAGE, [data]);
|
|
76
76
|
|
|
77
77
|
// we only handle update events if streaming is up
|
|
78
|
-
|
|
79
|
-
const type = parsedData.type || parsedData.t;
|
|
80
|
-
if (!notificationKeeper.isStreamingUp() && [OCCUPANCY, CONTROL].indexOf(type) === -1) return;
|
|
78
|
+
if (!notificationKeeper.isStreamingUp() && [OCCUPANCY, CONTROL].indexOf(parsedData.type) === -1) return;
|
|
81
79
|
|
|
82
|
-
switch (type) {
|
|
80
|
+
switch (parsedData.type) {
|
|
83
81
|
/* update events */
|
|
84
82
|
case SPLIT_UPDATE:
|
|
85
83
|
case SEGMENT_UPDATE:
|
|
86
|
-
case
|
|
87
|
-
case
|
|
84
|
+
case MEMBERSHIP_MS_UPDATE:
|
|
85
|
+
case MEMBERSHIP_LS_UPDATE:
|
|
88
86
|
case SPLIT_KILL:
|
|
89
|
-
pushEmitter.emit(type, parsedData);
|
|
87
|
+
pushEmitter.emit(parsedData.type, parsedData);
|
|
90
88
|
break;
|
|
91
89
|
|
|
92
90
|
/* occupancy & control events, handled by NotificationManagerKeeper */
|
|
93
91
|
case OCCUPANCY:
|
|
94
|
-
notificationKeeper.handleOccupancyEvent(
|
|
92
|
+
notificationKeeper.handleOccupancyEvent(parsedData.metrics.publishers, channel, timestamp);
|
|
95
93
|
break;
|
|
96
94
|
case CONTROL:
|
|
97
|
-
notificationKeeper.handleControlEvent(
|
|
95
|
+
notificationKeeper.handleControlEvent(parsedData.controlType, channel, timestamp);
|
|
98
96
|
break;
|
|
99
97
|
|
|
100
98
|
default:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ControlType } from '../constants';
|
|
2
|
-
import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY,
|
|
2
|
+
import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIP_LS_UPDATE, MEMBERSHIP_MS_UPDATE } from '../types';
|
|
3
3
|
|
|
4
4
|
export enum Compression {
|
|
5
5
|
None = 0,
|
|
@@ -19,8 +19,8 @@ export interface KeyList {
|
|
|
19
19
|
r?: string[], // decimal hash64 of user keys
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
interface
|
|
23
|
-
|
|
22
|
+
interface IMembershipUpdateData<T extends string> {
|
|
23
|
+
type: T,
|
|
24
24
|
cn: number,
|
|
25
25
|
n?: string[],
|
|
26
26
|
c?: Compression,
|
|
@@ -31,9 +31,9 @@ interface IMySegmentsUpdateData<T extends string> {
|
|
|
31
31
|
s?: number, // seed for hash function
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export interface
|
|
34
|
+
export interface IMembershipMSUpdateData extends IMembershipUpdateData<MEMBERSHIP_MS_UPDATE> { }
|
|
35
35
|
|
|
36
|
-
export interface
|
|
36
|
+
export interface IMembershipLSUpdateData extends IMembershipUpdateData<MEMBERSHIP_LS_UPDATE> { }
|
|
37
37
|
|
|
38
38
|
export interface ISegmentUpdateData {
|
|
39
39
|
type: SEGMENT_UPDATE,
|
|
@@ -68,6 +68,6 @@ export interface IOccupancyData {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export type INotificationData =
|
|
71
|
+
export type INotificationData = IMembershipMSUpdateData | IMembershipLSUpdateData | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
|
|
72
72
|
export type INotificationMessage = { parsedData: INotificationData, channel: string, timestamp: number, data: string }
|
|
73
73
|
export type INotificationError = Event & { parsedData?: any, message?: string }
|
|
@@ -2,12 +2,12 @@ import { IMySegmentsSyncTask, MySegmentsData } from '../../polling/types';
|
|
|
2
2
|
import { Backoff } from '../../../utils/Backoff';
|
|
3
3
|
import { IUpdateWorker } from './types';
|
|
4
4
|
import { ITelemetryTracker } from '../../../trackers/types';
|
|
5
|
-
import {
|
|
5
|
+
import { MEMBERSHIPS } from '../../../utils/constants';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* MySegmentsUpdateWorker factory
|
|
9
9
|
*/
|
|
10
|
-
export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask, telemetryTracker: ITelemetryTracker
|
|
10
|
+
export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask, telemetryTracker: ITelemetryTracker): IUpdateWorker<[mySegmentsData?: Pick<MySegmentsData, 'type' | 'cn'>, payload?: Pick<MySegmentsData, 'added' | 'removed'>, delay?: number]> {
|
|
11
11
|
|
|
12
12
|
let maxChangeNumber = 0; // keeps the maximum changeNumber among queued events
|
|
13
13
|
let currentChangeNumber = -1;
|
|
@@ -37,7 +37,7 @@ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask,
|
|
|
37
37
|
syncTask.then((result) => {
|
|
38
38
|
if (!isHandlingEvent) return; // halt if `stop` has been called
|
|
39
39
|
if (result !== false) { // Unlike `Splits|SegmentsUpdateWorker`, we cannot use `mySegmentsCache.getChangeNumber` since `/mySegments` endpoint doesn't provide this value.
|
|
40
|
-
if (_segmentsData) telemetryTracker.trackUpdatesFromSSE(
|
|
40
|
+
if (_segmentsData) telemetryTracker.trackUpdatesFromSSE(MEMBERSHIPS);
|
|
41
41
|
currentChangeNumber = Math.max(currentChangeNumber, currentMaxChangeNumber); // use `currentMaxChangeNumber`, in case that `maxChangeNumber` was updated during fetch.
|
|
42
42
|
}
|
|
43
43
|
if (handleNewEvent) {
|
|
@@ -59,13 +59,14 @@ export function MySegmentsUpdateWorker(mySegmentsSyncTask: IMySegmentsSyncTask,
|
|
|
59
59
|
* @param segmentsData data for KeyList or SegmentRemoval instant updates
|
|
60
60
|
* @param delay optional time to wait for BoundedFetchRequest or BoundedFetchRequest updates
|
|
61
61
|
*/
|
|
62
|
-
put(
|
|
62
|
+
put(mySegmentsData: Pick<MySegmentsData, 'type' | 'cn'>, payload?: Pick<MySegmentsData, 'added' | 'removed'>, delay?: number) {
|
|
63
|
+
const { type, cn } = mySegmentsData;
|
|
63
64
|
// Ignore event if it is outdated or if there is a pending fetch request (_delay is set)
|
|
64
|
-
if (
|
|
65
|
+
if (cn <= currentChangeNumber || cn <= maxChangeNumber || _delay) return;
|
|
65
66
|
|
|
66
|
-
maxChangeNumber =
|
|
67
|
+
maxChangeNumber = cn;
|
|
67
68
|
handleNewEvent = true;
|
|
68
|
-
_segmentsData =
|
|
69
|
+
_segmentsData = payload && { type, cn, added: payload.added, removed: payload.removed };
|
|
69
70
|
_delay = delay;
|
|
70
71
|
|
|
71
72
|
if (backoff.timeoutID || !isHandlingEvent) __handleMySegmentsUpdateCall();
|
|
@@ -25,11 +25,11 @@ export const PUSH_SUBSYSTEM_UP = 'PUSH_SUBSYSTEM_UP';
|
|
|
25
25
|
export const PUSH_SUBSYSTEM_DOWN = 'PUSH_SUBSYSTEM_DOWN';
|
|
26
26
|
|
|
27
27
|
// Update-type push notifications, handled by NotificationProcessor
|
|
28
|
-
export const
|
|
28
|
+
export const MEMBERSHIP_MS_UPDATE = 'MEMBERSHIP_MS_UPDATE';
|
|
29
|
+
export const MEMBERSHIP_LS_UPDATE = 'MEMBERSHIP_LS_UPDATE';
|
|
29
30
|
export const SEGMENT_UPDATE = 'SEGMENT_UPDATE';
|
|
30
31
|
export const SPLIT_KILL = 'SPLIT_KILL';
|
|
31
32
|
export const SPLIT_UPDATE = 'SPLIT_UPDATE';
|
|
32
|
-
export const MY_LARGE_SEGMENTS_UPDATE = 'MY_LARGE_SEGMENTS_UPDATE';
|
|
33
33
|
|
|
34
34
|
// Control-type push notifications, handled by NotificationKeeper
|
|
35
35
|
export const CONTROL = 'CONTROL';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { algorithms } from '../../utils/decompress';
|
|
2
2
|
import { decodeFromBase64 } from '../../utils/base64';
|
|
3
3
|
import { hash } from '../../utils/murmur3/murmur3';
|
|
4
|
-
import { Compression,
|
|
4
|
+
import { Compression, IMembershipMSUpdateData, KeyList } from './SSEHandler/types';
|
|
5
5
|
import { ISplit } from '../../dtos/types';
|
|
6
6
|
|
|
7
7
|
const GZIP = 1;
|
|
@@ -91,7 +91,7 @@ export function parseFFUpdatePayload(compression: Compression, data: string): IS
|
|
|
91
91
|
|
|
92
92
|
const DEFAULT_MAX_INTERVAL = 60000;
|
|
93
93
|
|
|
94
|
-
export function getDelay(parsedData: Pick<
|
|
94
|
+
export function getDelay(parsedData: Pick<IMembershipMSUpdateData, 'i' | 'h' | 's'>, matchingKey: string) {
|
|
95
95
|
if (parsedData.h === 0) return 0;
|
|
96
96
|
|
|
97
97
|
const interval = parsedData.i || DEFAULT_MAX_INTERVAL;
|