@splitsoftware/splitio-commons 3.1.1-rc.11 → 3.1.1-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -0
- package/cjs/services/authProvider.js +2 -2
- package/cjs/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
- package/cjs/services/splitApi.js +2 -2
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/cjs/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +4 -1
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -3
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +4 -1
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +6 -3
- package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +12 -8
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +21 -11
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +12 -8
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +21 -11
- package/cjs/sync/offline/splitsParser/definitionChangesBuilder.js +37 -0
- package/cjs/sync/offline/splitsParser/splitsParserFromSettings.js +5 -32
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +26 -46
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +22 -15
- package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +6 -4
- package/cjs/sync/streaming/parseUtils.js +4 -4
- package/cjs/sync/streaming/pushManager.js +4 -3
- package/esm/services/authProvider.js +1 -1
- package/esm/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
- package/esm/services/splitApi.js +1 -1
- package/esm/storages/AbstractDefinitionsCacheAsync.js +2 -1
- package/esm/storages/AbstractDefinitionsCacheSync.js +2 -2
- package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +4 -1
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -3
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +4 -1
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +6 -3
- package/esm/storages/inRedis/DefinitionsCacheInRedis.js +12 -8
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +21 -11
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +12 -8
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +21 -11
- package/esm/sync/offline/splitsParser/definitionChangesBuilder.js +33 -0
- package/esm/sync/offline/splitsParser/splitsParserFromSettings.js +6 -33
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +23 -43
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +22 -15
- package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +8 -6
- package/esm/sync/streaming/parseUtils.js +2 -2
- package/esm/sync/streaming/pushManager.js +4 -3
- package/package.json +1 -1
- package/src/dtos/types.ts +4 -4
- package/src/services/authProvider.ts +2 -2
- package/src/{sync/streaming/AuthClient/index.ts → services/fetchAuth.ts} +3 -4
- package/src/services/splitApi.ts +1 -1
- package/src/services/types.ts +21 -4
- package/src/storages/AbstractDefinitionsCacheAsync.ts +5 -4
- package/src/storages/AbstractDefinitionsCacheSync.ts +4 -4
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +5 -3
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +7 -5
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +3 -2
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +5 -4
- package/src/storages/inRedis/DefinitionsCacheInRedis.ts +15 -11
- package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +26 -15
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +14 -9
- package/src/storages/pluggable/RBSegmentsCachePluggable.ts +24 -13
- package/src/storages/types.ts +6 -6
- package/src/sync/offline/splitsParser/definitionChangesBuilder.ts +49 -0
- package/src/sync/offline/splitsParser/splitsParserFromSettings.ts +8 -38
- package/src/sync/offline/splitsParser/types.ts +7 -3
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +25 -45
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +5 -5
- package/src/sync/polling/types.ts +22 -1
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +35 -33
- package/src/sync/streaming/SSEClient/index.ts +1 -1
- package/src/sync/streaming/SSEClient/types.ts +1 -1
- package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +17 -13
- package/src/sync/streaming/UpdateWorkers/types.ts +6 -0
- package/src/sync/streaming/parseUtils.ts +3 -4
- package/src/sync/streaming/pushManager.ts +4 -3
- package/src/sync/syncManagerOnline.ts +0 -1
- package/cjs/sync/streaming/AuthClient/types.js +0 -2
- package/esm/sync/streaming/AuthClient/types.js +0 -1
- package/src/sync/streaming/AuthClient/types.ts +0 -19
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { forOwn } from '../../../utils/lang';
|
|
2
1
|
import { syncTaskFactory } from '../../syncTask';
|
|
3
|
-
import { CONTROL } from '../../../utils/constants';
|
|
4
2
|
import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, FLAGS_UPDATE, SEGMENTS_UPDATE } from '../../../readiness/constants';
|
|
5
3
|
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';
|
|
6
4
|
/**
|
|
@@ -10,53 +8,35 @@ export function fromObjectUpdaterFactory(splitsParser, storage, readiness, setti
|
|
|
10
8
|
var log = settings.log, definitions = storage.definitions;
|
|
11
9
|
var startingUp = true;
|
|
12
10
|
return function objectUpdater() {
|
|
13
|
-
var
|
|
14
|
-
var loadError = null;
|
|
15
|
-
var splitsMock = {};
|
|
11
|
+
var changes;
|
|
16
12
|
try {
|
|
17
|
-
|
|
13
|
+
changes = splitsParser(settings);
|
|
18
14
|
}
|
|
19
15
|
catch (err) {
|
|
20
|
-
loadError = err;
|
|
21
16
|
log.error(ERROR_SYNC_OFFLINE_LOADING, [err]);
|
|
22
17
|
}
|
|
23
|
-
if (!
|
|
24
|
-
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(splitsMock)]);
|
|
25
|
-
forOwn(splitsMock, function (val, name) {
|
|
26
|
-
// @ts-ignore Split changeNumber and seed is undefined in localhost mode
|
|
27
|
-
splits.push({
|
|
28
|
-
name: name,
|
|
29
|
-
status: 'ACTIVE',
|
|
30
|
-
killed: false,
|
|
31
|
-
trafficAllocation: 100,
|
|
32
|
-
defaultTreatment: CONTROL,
|
|
33
|
-
conditions: val.conditions || [],
|
|
34
|
-
configurations: val.configurations,
|
|
35
|
-
trafficTypeName: val.trafficTypeName
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
return Promise.all([
|
|
39
|
-
definitions.clear(),
|
|
40
|
-
definitions.update(splits, [], Date.now())
|
|
41
|
-
]).then(function () {
|
|
42
|
-
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [] });
|
|
43
|
-
if (startingUp) {
|
|
44
|
-
startingUp = false;
|
|
45
|
-
Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
|
|
46
|
-
// Emits SDK_READY_FROM_CACHE
|
|
47
|
-
if (!cacheMetadata.initialCacheLoad) {
|
|
48
|
-
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
|
|
49
|
-
}
|
|
50
|
-
// Emits SDK_READY
|
|
51
|
-
readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return true;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
18
|
+
if (!changes || !changes.d)
|
|
58
19
|
return Promise.resolve(true);
|
|
59
|
-
|
|
20
|
+
var _a = changes.d, updated = _a.updated, removed = _a.removed, till = _a.till;
|
|
21
|
+
log.debug(SYNC_OFFLINE_DATA, [JSON.stringify(changes.d)]);
|
|
22
|
+
// On start, clear definitions that might be in the storage from a previous session (InLocalStorage), since the parser only tracks its own changes.
|
|
23
|
+
return Promise.resolve(startingUp && definitions.clear()).then(function () {
|
|
24
|
+
return definitions.update(updated, removed, till);
|
|
25
|
+
}).then(function (names) {
|
|
26
|
+
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: names });
|
|
27
|
+
if (startingUp) {
|
|
28
|
+
startingUp = false;
|
|
29
|
+
Promise.resolve(storage.validateCache ? storage.validateCache() : { initialCacheLoad: true /* Fallback: assume initial load when validateCache doesn't exist */ }).then(function (cacheMetadata) {
|
|
30
|
+
// Emits SDK_READY_FROM_CACHE
|
|
31
|
+
if (!cacheMetadata.initialCacheLoad) {
|
|
32
|
+
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, cacheMetadata);
|
|
33
|
+
}
|
|
34
|
+
// Emits SDK_READY
|
|
35
|
+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED, { type: SEGMENTS_UPDATE, names: [] });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
60
40
|
};
|
|
61
41
|
}
|
|
62
42
|
/**
|
|
@@ -13,7 +13,7 @@ function isSplitChangesLegacyResponse(data) {
|
|
|
13
13
|
function partitionByStatus(items, till, since) {
|
|
14
14
|
var updated = [], removed = [];
|
|
15
15
|
items.forEach(function (item) { return item.status === 'ARCHIVED' ? removed.push(item.name) : updated.push(item); });
|
|
16
|
-
return { updated: updated, removed: removed, till: till, since: since };
|
|
16
|
+
return { updated: updated, removed: removed, till: till, since: since !== null && since !== void 0 ? since : undefined };
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Factory of SplitChanges fetcher.
|
|
@@ -5,6 +5,7 @@ import { startsWith } from '../../../utils/lang';
|
|
|
5
5
|
import { IN_RULE_BASED_SEGMENT, IN_SEGMENT, RULE_BASED_SEGMENT, STANDARD_SEGMENT } from '../../../utils/constants';
|
|
6
6
|
import { setToArray } from '../../../utils/lang/sets';
|
|
7
7
|
import { RB_SEGMENT_UPDATE } from '../../streaming/constants';
|
|
8
|
+
import { thenable } from '../../../utils/promise/thenable';
|
|
8
9
|
/**
|
|
9
10
|
* Collect segments from a raw FF or RBS definition.
|
|
10
11
|
* Exported for testing purposes.
|
|
@@ -67,9 +68,8 @@ export function computeMutation(update, segments, filters) {
|
|
|
67
68
|
else {
|
|
68
69
|
accum.removed.push(ruleEntity.name);
|
|
69
70
|
}
|
|
70
|
-
accum.names.push(ruleEntity.name);
|
|
71
71
|
return accum;
|
|
72
|
-
}, { updated: [], removed: update.removed
|
|
72
|
+
}, { updated: [], removed: update.removed });
|
|
73
73
|
}
|
|
74
74
|
function convertInstantUpdateToDefinitionChanges(instantUpdate) {
|
|
75
75
|
var payload = instantUpdate.payload, changeNumber = instantUpdate.changeNumber;
|
|
@@ -110,7 +110,7 @@ export function definitionChangesUpdaterFactory(log, definitionChangesFetcher, s
|
|
|
110
110
|
* @param noCache - true to revalidate data to fetch
|
|
111
111
|
* @param till - query param to bypass CDN requests
|
|
112
112
|
*/
|
|
113
|
-
return function definitionChangesUpdater(noCache, till, instantUpdate) {
|
|
113
|
+
return function definitionChangesUpdater(noCache, till, instantUpdate, pcns) {
|
|
114
114
|
/**
|
|
115
115
|
* @param since - current changeNumber at definitionsCache
|
|
116
116
|
* @param retry - current number of retry attempts
|
|
@@ -118,8 +118,14 @@ export function definitionChangesUpdaterFactory(log, definitionChangesFetcher, s
|
|
|
118
118
|
function _definitionChangesUpdater(sinces, retry) {
|
|
119
119
|
if (retry === void 0) { retry = 0; }
|
|
120
120
|
var since = sinces[0], rbSince = sinces[1];
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// If provided, `since` and `rbSince` must not be higher than it, to avoid missing an update that the scoped `pcn` indicates hasn't been fetched yet.
|
|
122
|
+
if (pcns) {
|
|
123
|
+
if (pcns.since !== undefined && pcns.since < since)
|
|
124
|
+
since = pcns.since;
|
|
125
|
+
if (pcns.rbSince !== undefined && pcns.rbSince < rbSince)
|
|
126
|
+
rbSince = pcns.rbSince;
|
|
127
|
+
}
|
|
128
|
+
var definitionChangesRequest = instantUpdate ?
|
|
123
129
|
instantUpdate.type === RB_SEGMENT_UPDATE ?
|
|
124
130
|
{ rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
|
|
125
131
|
// IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
|
|
@@ -128,34 +134,35 @@ export function definitionChangesUpdaterFactory(log, definitionChangesFetcher, s
|
|
|
128
134
|
{ d: convertInstantUpdateToDefinitionChanges(instantUpdate) } :
|
|
129
135
|
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
|
|
130
136
|
}) :
|
|
131
|
-
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator)
|
|
137
|
+
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
|
|
138
|
+
if (thenable(definitionChangesRequest))
|
|
139
|
+
log.debug(SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
|
|
140
|
+
return Promise.resolve(definitionChangesRequest)
|
|
132
141
|
.then(function (definitionChanges) {
|
|
133
142
|
var usedSegments = new Set();
|
|
134
143
|
var updatedDefinitions = [];
|
|
135
|
-
var ffUpdate = false;
|
|
136
144
|
if (definitionChanges.d) {
|
|
137
|
-
var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed
|
|
138
|
-
updatedDefinitions = names;
|
|
145
|
+
var _a = computeMutation(definitionChanges.d, usedSegments, splitFiltersValidation), updated = _a.updated, removed = _a.removed;
|
|
139
146
|
log.debug(SYNC_UPDATE, [definitionChangesFetcher.type, updated.length, removed.length]);
|
|
140
|
-
|
|
147
|
+
updatedDefinitions = definitions.update(updated, removed, definitionChanges.d.till);
|
|
141
148
|
}
|
|
142
|
-
var
|
|
149
|
+
var updatedRBSNames = [];
|
|
143
150
|
if (definitionChanges.rbs) {
|
|
144
151
|
var _b = computeMutation(definitionChanges.rbs, usedSegments), updated = _b.updated, removed = _b.removed;
|
|
145
152
|
log.debug(SYNC_UPDATE, ['rule-based segments', updated.length, removed.length]);
|
|
146
|
-
|
|
153
|
+
updatedRBSNames = rbSegments.update(updated, removed, definitionChanges.rbs.till);
|
|
147
154
|
}
|
|
148
|
-
return Promise.all([
|
|
155
|
+
return Promise.all([updatedDefinitions, updatedRBSNames,
|
|
149
156
|
// @TODO if at least 1 segment fetch fails due to 404 and other segments are updated in the storage, SDK_UPDATE is not emitted
|
|
150
157
|
segments.registerSegments(setToArray(usedSegments))
|
|
151
158
|
]).then(function (_a) {
|
|
152
|
-
var
|
|
159
|
+
var updatedDefinitions = _a[0], updatedRBSNames = _a[1];
|
|
153
160
|
if (storage.save)
|
|
154
161
|
storage.save();
|
|
155
162
|
startingUp = false;
|
|
156
163
|
if (definitionsEventEmitter) {
|
|
157
164
|
// To emit SDK_DEFINITIONS_ARRIVED for server-side SDK, we must wait for all registered segments to be fetched
|
|
158
|
-
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((
|
|
165
|
+
return Promise.resolve(!definitionsEventEmitter.definitionsArrived || ((updatedDefinitions.length > 0 || updatedRBSNames.length > 0) && (!segmentsSyncTask || segmentsSyncTask.execute(true))))
|
|
159
166
|
.catch(function () { return false; } /** noop. just to handle a possible `checkAllSegmentsExist` rejection, before emitting SDK event */)
|
|
160
167
|
.then(function (emitSplitsArrivedEvent) {
|
|
161
168
|
// emit SDK events
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
|
|
2
|
-
import { SDK_DEFINITIONS_ARRIVED } from '../../../readiness/constants';
|
|
2
|
+
import { SDK_DEFINITIONS_ARRIVED, FLAGS_UPDATE } from '../../../readiness/constants';
|
|
3
3
|
import { Backoff } from '../../../utils/Backoff';
|
|
4
4
|
import { SPLITS } from '../../../utils/constants';
|
|
5
5
|
import { RB_SEGMENT_UPDATE } from '../constants';
|
|
6
|
-
import {
|
|
6
|
+
import { parseUpdatePayload } from '../parseUtils';
|
|
7
7
|
import { FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT, FETCH_BACKOFF_MAX_RETRIES } from './constants';
|
|
8
8
|
/**
|
|
9
|
-
* DefinitionsUpdateWorker factory
|
|
9
|
+
* DefinitionsUpdateWorker factory for Flags SDKs, where notifications pcn and changeNumber are environment-scoped
|
|
10
10
|
*/
|
|
11
|
-
export function DefinitionsUpdateWorker(
|
|
11
|
+
export function DefinitionsUpdateWorker(params, pollingManager) {
|
|
12
|
+
var log = params.settings.log, storage = params.storage, telemetryTracker = params.telemetryTracker, definitionsEventEmitter = params.readiness.definitions;
|
|
13
|
+
var definitionsSyncTask = pollingManager.definitionsSyncTask;
|
|
12
14
|
var ff = DefinitionsUpdateWorker(storage.definitions);
|
|
13
15
|
var rbs = DefinitionsUpdateWorker(storage.rbSegments);
|
|
14
16
|
function DefinitionsUpdateWorker(cache) {
|
|
@@ -93,7 +95,7 @@ export function DefinitionsUpdateWorker(log, storage, definitionsSyncTask, defin
|
|
|
93
95
|
put: function (parsedData) {
|
|
94
96
|
if (parsedData.d && parsedData.c !== undefined) {
|
|
95
97
|
try {
|
|
96
|
-
var payload =
|
|
98
|
+
var payload = parseUpdatePayload(parsedData.c, parsedData.d);
|
|
97
99
|
if (payload) {
|
|
98
100
|
(parsedData.type === RB_SEGMENT_UPDATE ? rbs : ff).put(parsedData, payload);
|
|
99
101
|
return;
|
|
@@ -116,7 +118,7 @@ export function DefinitionsUpdateWorker(log, storage, definitionsSyncTask, defin
|
|
|
116
118
|
var changeNumber = _a.changeNumber, splitName = _a.splitName, defaultTreatment = _a.defaultTreatment;
|
|
117
119
|
if (storage.definitions.killLocally(splitName, defaultTreatment, changeNumber)) {
|
|
118
120
|
// trigger an SDK_UPDATE if Split was killed locally
|
|
119
|
-
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, true);
|
|
121
|
+
definitionsEventEmitter.emit(SDK_DEFINITIONS_ARRIVED, { type: FLAGS_UPDATE, names: [splitName] }, true /* isSplitKill */);
|
|
120
122
|
}
|
|
121
123
|
// queues the SplitChanges fetch (only if changeNumber is newer)
|
|
122
124
|
ff.put({ changeNumber: changeNumber });
|
|
@@ -73,9 +73,9 @@ export function isInBitmap(bitmap, hash64hex) {
|
|
|
73
73
|
return (bitmap[internal] & 1 << offset) > 0;
|
|
74
74
|
}
|
|
75
75
|
/**
|
|
76
|
-
* Parse
|
|
76
|
+
* Parse notifications for instant updates
|
|
77
77
|
*/
|
|
78
|
-
export function
|
|
78
|
+
export function parseUpdatePayload(compression, data) {
|
|
79
79
|
return compression > 0 ?
|
|
80
80
|
parseCompressedData(data, compression, false) :
|
|
81
81
|
JSON.parse(decodeFromBase64(data));
|
|
@@ -18,8 +18,9 @@ import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
|
|
|
18
18
|
* - for server-side if key is not provided in settings.
|
|
19
19
|
* - for client-side, with support for multiple clients, if key is provided in settings
|
|
20
20
|
*/
|
|
21
|
-
export function pushManagerFactory(params, pollingManager) {
|
|
22
|
-
|
|
21
|
+
export function pushManagerFactory(params, pollingManager, definitionsUpdateWorkerFactory) {
|
|
22
|
+
if (definitionsUpdateWorkerFactory === void 0) { definitionsUpdateWorkerFactory = DefinitionsUpdateWorker; }
|
|
23
|
+
var settings = params.settings, storage = params.storage, serviceApi = params.serviceApi, platform = params.platform, telemetryTracker = params.telemetryTracker;
|
|
23
24
|
// `userKey` is the matching key of main client in client-side SDK.
|
|
24
25
|
// It can be used to check if running on client-side or server-side SDK.
|
|
25
26
|
var userKey = checkIfServerSide(settings) ? undefined : getMatching(settings.core.key);
|
|
@@ -41,7 +42,7 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
41
42
|
// MySegmentsUpdateWorker (client-side) are initiated in `add` method
|
|
42
43
|
var segmentsUpdateWorker = userKey ? undefined : SegmentsUpdateWorker(log, pollingManager.segmentsSyncTask, storage.segments);
|
|
43
44
|
// For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
|
|
44
|
-
var definitionsUpdateWorker =
|
|
45
|
+
var definitionsUpdateWorker = definitionsUpdateWorkerFactory(params, pollingManager);
|
|
45
46
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
46
47
|
// Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
47
48
|
var clients = {};
|
package/package.json
CHANGED
package/src/dtos/types.ts
CHANGED
|
@@ -243,14 +243,14 @@ export interface IDefinition extends TargetingEntity {
|
|
|
243
243
|
/** Interface of the parsed JSON response of `/splitChanges` */
|
|
244
244
|
export interface IDefinitionChangesResponse {
|
|
245
245
|
d?: {
|
|
246
|
-
till
|
|
247
|
-
since?: number
|
|
246
|
+
till?: number,
|
|
247
|
+
since?: number,
|
|
248
248
|
updated: IDefinition[],
|
|
249
249
|
removed: string[],
|
|
250
250
|
},
|
|
251
251
|
rbs?: {
|
|
252
|
-
till
|
|
253
|
-
since?: number
|
|
252
|
+
till?: number,
|
|
253
|
+
since?: number,
|
|
254
254
|
updated: IRBSegment[],
|
|
255
255
|
removed: string[],
|
|
256
256
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ISplitHttpClient, NetworkError } from './types';
|
|
2
|
-
import { IJwtCredential } from '
|
|
3
|
-
import { fetchAuthFactory } from '
|
|
2
|
+
import { IJwtCredential } from './types';
|
|
3
|
+
import { fetchAuthFactory } from './fetchAuth';
|
|
4
4
|
import { Backoff } from '../utils/Backoff';
|
|
5
5
|
import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
|
|
6
6
|
import { ISettings } from '../types';
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { IJwtCredential, IFetchAuth } from './types';
|
|
2
|
-
import { objectAssign } from '
|
|
3
|
-
import { decodeJWTtoken } from '
|
|
4
|
-
import { IResponse } from '../../../services/types';
|
|
1
|
+
import { IJwtCredential, IFetchAuth, IResponse } from './types';
|
|
2
|
+
import { objectAssign } from '../utils/lang/objectAssign';
|
|
3
|
+
import { decodeJWTtoken } from '../utils/jwt';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Factory of authentication function.
|
package/src/services/splitApi.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
6
6
|
import { ITelemetryTracker } from '../trackers/types';
|
|
7
7
|
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MEMBERSHIPS } from '../utils/constants';
|
|
8
8
|
import { ERROR_TOO_MANY_SETS } from '../logger/constants';
|
|
9
|
-
import { fetchAuthFactory } from '
|
|
9
|
+
import { fetchAuthFactory } from './fetchAuth';
|
|
10
10
|
|
|
11
11
|
const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
12
12
|
|
package/src/services/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IDecodedJWTToken } from '../utils/jwt/types';
|
|
2
2
|
|
|
3
3
|
export type IRequestOptions = {
|
|
4
4
|
method?: string,
|
|
@@ -30,14 +30,31 @@ export type NetworkError = Error & { statusCode?: number }
|
|
|
30
30
|
// Reduced version of Fetch API
|
|
31
31
|
export type IFetch = (url: string, options?: IRequestOptions) => Promise<IResponse>
|
|
32
32
|
|
|
33
|
-
// IFetch specialization
|
|
34
|
-
export type IHealthCheckAPI = () => Promise<boolean>
|
|
35
|
-
|
|
36
33
|
export type ISplitHttpClient = (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader?: boolean) => Promise<IResponse>
|
|
37
34
|
|
|
38
35
|
// @TODO: remove `newVersionHeader` and `useJwt` arguments when all service endpoints are migrated to the new auth mechanism
|
|
39
36
|
export type ISecureSplitHttpClient = ((url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean, newVersionHeader?: boolean, useJwt?: boolean) => Promise<IResponse>) & { stop(): void }
|
|
40
37
|
|
|
38
|
+
export type IJwtCredential = {
|
|
39
|
+
token: string; // empty string ("") when `"pushEnabled": false`
|
|
40
|
+
decodedToken: IDecodedJWTToken
|
|
41
|
+
channels: { [channel: string]: string[] }
|
|
42
|
+
// /api/v2/auth fields
|
|
43
|
+
pushEnabled?: boolean | null;
|
|
44
|
+
connDelay?: number | null;
|
|
45
|
+
// /api/v3/auth fields
|
|
46
|
+
config?: {
|
|
47
|
+
streaming?: {
|
|
48
|
+
delay?: number | null;
|
|
49
|
+
enabled?: boolean | null;
|
|
50
|
+
} | null;
|
|
51
|
+
} | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type IFetchAuth = (userKeys?: string[]) => Promise<IJwtCredential>
|
|
55
|
+
|
|
56
|
+
export type IHealthCheckAPI = () => Promise<boolean>
|
|
57
|
+
|
|
41
58
|
export type IFetchDefinitionChanges = (since: number, noCache?: boolean, till?: number, rbSince?: number) => Promise<IResponse>
|
|
42
59
|
|
|
43
60
|
export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IDefinitionsCacheAsync } from './types';
|
|
2
|
-
import { IDefinition } from '../dtos/types';
|
|
2
|
+
import { IDefinition, MaybeThenable } from '../dtos/types';
|
|
3
3
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -10,15 +10,16 @@ export abstract class AbstractDefinitionsCacheAsync implements IDefinitionsCache
|
|
|
10
10
|
|
|
11
11
|
protected abstract add(definition: IDefinition): Promise<boolean>
|
|
12
12
|
protected abstract remove(name: string): Promise<boolean>
|
|
13
|
-
protected abstract setChangeNumber(changeNumber
|
|
13
|
+
protected abstract setChangeNumber(changeNumber?: number): MaybeThenable<boolean | void>
|
|
14
14
|
|
|
15
|
-
update(toAdd: IDefinition[], toRemove: string[], changeNumber
|
|
15
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): Promise<string[]> {
|
|
16
16
|
return Promise.all([
|
|
17
17
|
this.setChangeNumber(changeNumber),
|
|
18
18
|
Promise.all(toAdd.map(addedFF => this.add(addedFF))),
|
|
19
19
|
Promise.all(toRemove.map(removedFF => this.remove(removedFF)))
|
|
20
20
|
]).then(([, added, removed]) => {
|
|
21
|
-
return
|
|
21
|
+
return toRemove.filter((_, i) => removed[i])
|
|
22
|
+
.concat(toAdd.filter((_, i) => added[i]).map(addedFF => addedFF.name));
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -11,11 +11,11 @@ export abstract class AbstractDefinitionsCacheSync implements IDefinitionsCacheS
|
|
|
11
11
|
|
|
12
12
|
protected abstract add(definition: IDefinition): boolean
|
|
13
13
|
protected abstract remove(name: string): boolean
|
|
14
|
-
protected abstract setChangeNumber(changeNumber
|
|
14
|
+
protected abstract setChangeNumber(changeNumber?: number): boolean | void
|
|
15
15
|
|
|
16
|
-
update(toAdd: IDefinition[], toRemove: string[], changeNumber
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): string[] {
|
|
17
|
+
const updated = toRemove.filter(removedFF => this.remove(removedFF))
|
|
18
|
+
.concat(toAdd.filter(addedFF => this.add(addedFF)).map(addedFF => addedFF.name));
|
|
19
19
|
this.setChangeNumber(changeNumber);
|
|
20
20
|
return updated;
|
|
21
21
|
}
|
|
@@ -79,13 +79,15 @@ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
|
|
|
79
79
|
this.hasSync = false;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
add(definition: IDefinition) {
|
|
82
|
+
add(definition: IDefinition): boolean {
|
|
83
83
|
const name = definition.name;
|
|
84
84
|
const definitionKey = this.keys.buildDefinitionKey(name);
|
|
85
85
|
const definitionFromStorage = this.storage.getItem(definitionKey);
|
|
86
86
|
const previousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : null;
|
|
87
87
|
|
|
88
88
|
if (previousDefinition) {
|
|
89
|
+
if (previousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
90
|
+
|
|
89
91
|
this._decrementCounts(previousDefinition);
|
|
90
92
|
this.removeFromSets(previousDefinition.name, previousDefinition.sets);
|
|
91
93
|
}
|
|
@@ -115,9 +117,9 @@ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
|
|
|
115
117
|
return item && JSON.parse(item);
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
setChangeNumber(changeNumber
|
|
120
|
+
setChangeNumber(changeNumber?: number): boolean {
|
|
119
121
|
try {
|
|
120
|
-
this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
|
|
122
|
+
if (changeNumber !== undefined) this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
|
|
121
123
|
// update "last updated" timestamp with current time
|
|
122
124
|
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
123
125
|
this.hasSync = true;
|
|
@@ -25,16 +25,16 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
25
25
|
this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
|
|
29
|
+
const updated = toRemove.filter(name => this.remove(name))
|
|
30
|
+
.concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
|
|
31
31
|
this.setChangeNumber(changeNumber);
|
|
32
32
|
return updated;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
private setChangeNumber(changeNumber
|
|
35
|
+
private setChangeNumber(changeNumber?: number) {
|
|
36
36
|
try {
|
|
37
|
-
this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
37
|
+
if (changeNumber !== undefined) this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
38
38
|
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
39
39
|
} catch (e) {
|
|
40
40
|
this.log.error(LOG_PREFIX + e);
|
|
@@ -54,6 +54,8 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
|
|
|
54
54
|
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
|
|
55
55
|
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
|
|
56
56
|
|
|
57
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
58
|
+
|
|
57
59
|
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
|
|
58
60
|
|
|
59
61
|
let usesSegmentsDiff = 0;
|
|
@@ -31,6 +31,7 @@ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
|
|
|
31
31
|
const name = definition.name;
|
|
32
32
|
const previousDefinition = this.get(name);
|
|
33
33
|
if (previousDefinition) { // We had this Split already
|
|
34
|
+
if (previousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
34
35
|
|
|
35
36
|
const previousTtName = previousDefinition.trafficTypeName;
|
|
36
37
|
this.ttCache[previousTtName]--;
|
|
@@ -77,8 +78,8 @@ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
|
|
|
77
78
|
return this.definitionsCache[name] || null;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
setChangeNumber(changeNumber
|
|
81
|
-
this.changeNumber = changeNumber;
|
|
81
|
+
setChangeNumber(changeNumber?: number): boolean {
|
|
82
|
+
if (changeNumber !== undefined) this.changeNumber = changeNumber;
|
|
82
83
|
return true;
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -15,16 +15,17 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
|
|
|
15
15
|
this.segmentsCount = 0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.changeNumber = changeNumber;
|
|
18
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): string[] {
|
|
19
|
+
const updated = toRemove.filter(name => this.remove(name))
|
|
20
|
+
.concat(toAdd.filter(rbSegment => this.add(rbSegment)).map(rbSegment => rbSegment.name));
|
|
21
|
+
if (changeNumber !== undefined) this.changeNumber = changeNumber;
|
|
22
22
|
return updated;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
private add(rbSegment: IRBSegment): boolean {
|
|
26
26
|
const name = rbSegment.name;
|
|
27
27
|
const previous = this.get(name);
|
|
28
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
28
29
|
if (previous && usesSegments(previous)) this.segmentsCount--;
|
|
29
30
|
|
|
30
31
|
this.cache[name] = rbSegment;
|
|
@@ -91,6 +91,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
91
91
|
let parsedPreviousDefinition: IDefinition, stringifiedNewDefinition;
|
|
92
92
|
try {
|
|
93
93
|
parsedPreviousDefinition = definitionFromStorage ? JSON.parse(definitionFromStorage) : undefined;
|
|
94
|
+
if (parsedPreviousDefinition && parsedPreviousDefinition.changeNumber >= definition.changeNumber) return false;
|
|
95
|
+
|
|
94
96
|
stringifiedNewDefinition = JSON.stringify(definition);
|
|
95
97
|
} catch (e) {
|
|
96
98
|
throw new Error('Error parsing feature flag definition: ' + e);
|
|
@@ -104,8 +106,8 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
104
106
|
return this._incrementCounts(definition).then(() => {
|
|
105
107
|
if (parsedPreviousDefinition) return this._decrementCounts(parsedPreviousDefinition);
|
|
106
108
|
});
|
|
107
|
-
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets));
|
|
108
|
-
})
|
|
109
|
+
}).then(() => this._updateSets(name, parsedPreviousDefinition && parsedPreviousDefinition.sets, definition.sets)).then(() => true);
|
|
110
|
+
});
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
/**
|
|
@@ -115,11 +117,11 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
115
117
|
*/
|
|
116
118
|
remove(name: string) {
|
|
117
119
|
return this.get(name).then((definition) => {
|
|
118
|
-
if (definition)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
if (!definition) return false;
|
|
121
|
+
|
|
122
|
+
return this._decrementCounts(definition).then(() => this._updateSets(name, definition.sets)).then(() => {
|
|
123
|
+
return this.redis.del(this.keys.buildDefinitionKey(name)).then((status: number) => status === 1);
|
|
124
|
+
});
|
|
123
125
|
});
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -143,10 +145,12 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
143
145
|
* The returned promise is resolved when the operation success,
|
|
144
146
|
* or rejected if it fails.
|
|
145
147
|
*/
|
|
146
|
-
setChangeNumber(changeNumber
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
148
|
+
setChangeNumber(changeNumber?: number) {
|
|
149
|
+
if (changeNumber !== undefined) {
|
|
150
|
+
return this.redis.set(this.keys.buildDefinitionsTillKey(), changeNumber + '').then(
|
|
151
|
+
(status: string | null) => status === 'OK'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
/**
|
|
@@ -37,27 +37,38 @@ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
40
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<string[]> {
|
|
41
41
|
return Promise.all([
|
|
42
42
|
this.setChangeNumber(changeNumber),
|
|
43
|
-
Promise.all(toAdd.map(
|
|
44
|
-
|
|
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);
|
|
50
|
-
return this.redis.del(key).then((status: number) => status === 1);
|
|
51
|
-
}))
|
|
43
|
+
Promise.all(toAdd.map(rbSegment => this.add(rbSegment))),
|
|
44
|
+
Promise.all(toRemove.map(name => this.remove(name)))
|
|
52
45
|
]).then(([, added, removed]) => {
|
|
53
|
-
return
|
|
46
|
+
return toRemove.filter((_, i) => removed[i])
|
|
47
|
+
.concat(toAdd.filter((_, i) => added[i]).map(rbSegment => rbSegment.name));
|
|
54
48
|
});
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
private add(rbSegment: IRBSegment): Promise<boolean> {
|
|
52
|
+
const key = this.keys.buildRBSegmentKey(rbSegment.name);
|
|
53
|
+
return this.get(rbSegment.name).then(previous => {
|
|
54
|
+
if (previous && previous.changeNumber >= rbSegment.changeNumber) return false;
|
|
55
|
+
|
|
56
|
+
const stringifiedNewRBSegment = JSON.stringify(rbSegment);
|
|
57
|
+
return this.redis.set(key, stringifiedNewRBSegment).then(() => true);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private remove(name: string): Promise<boolean> {
|
|
62
|
+
const key = this.keys.buildRBSegmentKey(name);
|
|
63
|
+
return this.redis.del(key).then((status: number) => status === 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setChangeNumber(changeNumber?: number) {
|
|
67
|
+
if (changeNumber !== undefined) {
|
|
68
|
+
return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
|
|
69
|
+
(status: string | null) => status === 'OK'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
getChangeNumber(): Promise<number> {
|