@splitsoftware/splitio-commons 3.1.1-rc.11 → 3.1.1-rc.12
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 +1 -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/inLocalStorage/DefinitionsCacheInLocal.js +2 -1
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -1
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -1
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +2 -1
- package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +3 -1
- package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +3 -1
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +3 -1
- package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +3 -1
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +14 -4
- package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +5 -3
- 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/inLocalStorage/DefinitionsCacheInLocal.js +2 -1
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -1
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -1
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +2 -1
- package/esm/storages/inRedis/DefinitionsCacheInRedis.js +3 -1
- package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +3 -1
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +3 -1
- package/esm/storages/pluggable/RBSegmentsCachePluggable.js +3 -1
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +14 -4
- package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +6 -4
- 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 +3 -3
- package/src/storages/AbstractDefinitionsCacheSync.ts +2 -2
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +2 -2
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +3 -3
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +2 -2
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +2 -2
- package/src/storages/inRedis/DefinitionsCacheInRedis.ts +6 -4
- package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +7 -5
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +4 -2
- package/src/storages/pluggable/RBSegmentsCachePluggable.ts +5 -3
- package/src/storages/types.ts +6 -6
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +5 -5
- package/src/sync/polling/types.ts +22 -1
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +26 -20
- 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 +15 -11
- 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/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
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,9 +10,9 @@ 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<boolean> {
|
|
16
16
|
return Promise.all([
|
|
17
17
|
this.setChangeNumber(changeNumber),
|
|
18
18
|
Promise.all(toAdd.map(addedFF => this.add(addedFF))),
|
|
@@ -11,9 +11,9 @@ 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
|
|
16
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber?: number): boolean {
|
|
17
17
|
let updated = toAdd.map(addedFF => this.add(addedFF)).some(result => result);
|
|
18
18
|
updated = toRemove.map(removedFF => this.remove(removedFF)).some(result => result) || updated;
|
|
19
19
|
this.setChangeNumber(changeNumber);
|
|
@@ -115,9 +115,9 @@ export class DefinitionsCacheInLocal extends AbstractDefinitionsCacheSync {
|
|
|
115
115
|
return item && JSON.parse(item);
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
setChangeNumber(changeNumber
|
|
118
|
+
setChangeNumber(changeNumber?: number): boolean {
|
|
119
119
|
try {
|
|
120
|
-
this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
|
|
120
|
+
if (changeNumber !== undefined) this.storage.setItem(this.keys.buildDefinitionsTillKey(), changeNumber + '');
|
|
121
121
|
// update "last updated" timestamp with current time
|
|
122
122
|
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
|
|
123
123
|
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
|
|
28
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): boolean {
|
|
29
29
|
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
30
30
|
updated = toRemove.map(toRemove => this.remove(toRemove)).some(result => result) || updated;
|
|
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);
|
|
@@ -77,8 +77,8 @@ export class DefinitionsCacheInMemory extends AbstractDefinitionsCacheSync {
|
|
|
77
77
|
return this.definitionsCache[name] || null;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
setChangeNumber(changeNumber
|
|
81
|
-
this.changeNumber = changeNumber;
|
|
80
|
+
setChangeNumber(changeNumber?: number): boolean {
|
|
81
|
+
if (changeNumber !== undefined) this.changeNumber = changeNumber;
|
|
82
82
|
return true;
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -15,10 +15,10 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
|
|
|
15
15
|
this.segmentsCount = 0;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
18
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): boolean {
|
|
19
19
|
let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result);
|
|
20
20
|
updated = toRemove.map(toRemove => this.remove(toRemove)).some(result => result) || updated;
|
|
21
|
-
this.changeNumber = changeNumber;
|
|
21
|
+
if (changeNumber !== undefined) this.changeNumber = changeNumber;
|
|
22
22
|
return updated;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -143,10 +143,12 @@ export class DefinitionsCacheInRedis extends AbstractDefinitionsCacheAsync {
|
|
|
143
143
|
* The returned promise is resolved when the operation success,
|
|
144
144
|
* or rejected if it fails.
|
|
145
145
|
*/
|
|
146
|
-
setChangeNumber(changeNumber
|
|
147
|
-
|
|
148
|
-
(
|
|
149
|
-
|
|
146
|
+
setChangeNumber(changeNumber?: number) {
|
|
147
|
+
if (changeNumber !== undefined) {
|
|
148
|
+
return this.redis.set(this.keys.buildDefinitionsTillKey(), changeNumber + '').then(
|
|
149
|
+
(status: string | null) => status === 'OK'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
/**
|
|
@@ -37,7 +37,7 @@ 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<boolean> {
|
|
41
41
|
return Promise.all([
|
|
42
42
|
this.setChangeNumber(changeNumber),
|
|
43
43
|
Promise.all(toAdd.map(toAdd => {
|
|
@@ -54,10 +54,12 @@ export class RBSegmentsCacheInRedis implements IRBSegmentsCacheAsync {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
setChangeNumber(changeNumber
|
|
58
|
-
|
|
59
|
-
(
|
|
60
|
-
|
|
57
|
+
setChangeNumber(changeNumber?: number) {
|
|
58
|
+
if (changeNumber !== undefined) {
|
|
59
|
+
return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
|
|
60
|
+
(status: string | null) => status === 'OK'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
getChangeNumber(): Promise<number> {
|
|
@@ -202,8 +202,10 @@ export class DefinitionsCachePluggable extends AbstractDefinitionsCacheAsync {
|
|
|
202
202
|
* The returned promise is resolved when the operation success,
|
|
203
203
|
* or rejected if it fails (e.g., wrapper operation fails).
|
|
204
204
|
*/
|
|
205
|
-
setChangeNumber(changeNumber
|
|
206
|
-
|
|
205
|
+
setChangeNumber(changeNumber?: number) {
|
|
206
|
+
if (changeNumber !== undefined) {
|
|
207
|
+
return this.wrapper.set(this.keys.buildDefinitionsTillKey(), changeNumber + '');
|
|
208
|
+
}
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
/**
|
|
@@ -36,7 +36,7 @@ export class RBSegmentsCachePluggable implements IRBSegmentsCacheAsync {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
39
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<boolean> {
|
|
40
40
|
return Promise.all([
|
|
41
41
|
this.setChangeNumber(changeNumber),
|
|
42
42
|
Promise.all(toAdd.map(toAdd => {
|
|
@@ -53,8 +53,10 @@ export class RBSegmentsCachePluggable implements IRBSegmentsCacheAsync {
|
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
setChangeNumber(changeNumber
|
|
57
|
-
|
|
56
|
+
setChangeNumber(changeNumber?: number) {
|
|
57
|
+
if (changeNumber !== undefined) {
|
|
58
|
+
return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
|
|
59
|
+
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
getChangeNumber(): Promise<number> {
|
package/src/storages/types.ts
CHANGED
|
@@ -195,7 +195,7 @@ export interface IPluggableStorageWrapper {
|
|
|
195
195
|
/** Splits cache */
|
|
196
196
|
|
|
197
197
|
export interface IDefinitionsCacheBase {
|
|
198
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber
|
|
198
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): MaybeThenable<boolean>,
|
|
199
199
|
get(name: string): MaybeThenable<IDefinition | null>,
|
|
200
200
|
getMany(names: string[]): MaybeThenable<Record<string, IDefinition | null>>, // `fetchMany` in spec
|
|
201
201
|
// should never reject or throw an exception. Instead return -1 by default, assuming no splits are present in the storage.
|
|
@@ -212,7 +212,7 @@ export interface IDefinitionsCacheBase {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
export interface IDefinitionsCacheSync extends IDefinitionsCacheBase {
|
|
215
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber
|
|
215
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): boolean,
|
|
216
216
|
get(name: string): IDefinition | null,
|
|
217
217
|
getMany(names: string[]): Record<string, IDefinition | null>,
|
|
218
218
|
getChangeNumber(): number,
|
|
@@ -226,7 +226,7 @@ export interface IDefinitionsCacheSync extends IDefinitionsCacheBase {
|
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
export interface IDefinitionsCacheAsync extends IDefinitionsCacheBase {
|
|
229
|
-
update(toUpsert: IDefinition[], toRemove: string[], changeNumber
|
|
229
|
+
update(toUpsert: IDefinition[], toRemove: string[], changeNumber?: number): Promise<boolean>,
|
|
230
230
|
get(name: string): Promise<IDefinition | null>,
|
|
231
231
|
getMany(names: string[]): Promise<Record<string, IDefinition | null>>,
|
|
232
232
|
getChangeNumber(): Promise<number>,
|
|
@@ -242,7 +242,7 @@ export interface IDefinitionsCacheAsync extends IDefinitionsCacheBase {
|
|
|
242
242
|
/** Rule-Based Segments cache */
|
|
243
243
|
|
|
244
244
|
export interface IRBSegmentsCacheBase {
|
|
245
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
245
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): MaybeThenable<boolean>,
|
|
246
246
|
get(name: string): MaybeThenable<IRBSegment | null>,
|
|
247
247
|
getChangeNumber(): MaybeThenable<number>,
|
|
248
248
|
clear(): MaybeThenable<boolean | void>,
|
|
@@ -250,7 +250,7 @@ export interface IRBSegmentsCacheBase {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
|
|
253
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
253
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): boolean,
|
|
254
254
|
get(name: string): IRBSegment | null,
|
|
255
255
|
getChangeNumber(): number,
|
|
256
256
|
getAll(): IRBSegment[],
|
|
@@ -261,7 +261,7 @@ export interface IRBSegmentsCacheSync extends IRBSegmentsCacheBase {
|
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
export interface IRBSegmentsCacheAsync extends IRBSegmentsCacheBase {
|
|
264
|
-
update(toAdd: IRBSegment[], toRemove: string[], changeNumber
|
|
264
|
+
update(toAdd: IRBSegment[], toRemove: string[], changeNumber?: number): Promise<boolean>,
|
|
265
265
|
get(name: string): Promise<IRBSegment | null>,
|
|
266
266
|
getChangeNumber(): Promise<number>,
|
|
267
267
|
clear(): Promise<boolean | void>,
|
|
@@ -21,12 +21,12 @@ export type ISplit = IDefinition;
|
|
|
21
21
|
export interface ISplitChangesResponse {
|
|
22
22
|
ff?: {
|
|
23
23
|
t: number,
|
|
24
|
-
s?: number,
|
|
24
|
+
s?: number | null,
|
|
25
25
|
d?: ISplit[] | null,
|
|
26
26
|
},
|
|
27
27
|
rbs?: {
|
|
28
28
|
t: number,
|
|
29
|
-
s?: number,
|
|
29
|
+
s?: number | null,
|
|
30
30
|
d?: IRBSegment[] | null,
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -34,7 +34,7 @@ export interface ISplitChangesResponse {
|
|
|
34
34
|
/** JSON response of `/splitChanges` for flag spec version 1.2 or below */
|
|
35
35
|
export interface ISplitChangesLegacyResponse {
|
|
36
36
|
till: number,
|
|
37
|
-
since?: number,
|
|
37
|
+
since?: number | null,
|
|
38
38
|
splits: ISplit[]
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -42,10 +42,10 @@ function isSplitChangesLegacyResponse(data: ISplitChangesResponse | ISplitChange
|
|
|
42
42
|
return (data as ISplitChangesLegacyResponse).splits != null;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function partitionByStatus<T extends { status?: string; name: string }>(items: T[], till: number, since?: number) {
|
|
45
|
+
function partitionByStatus<T extends { status?: string; name: string }>(items: T[], till: number, since?: number | null) {
|
|
46
46
|
const updated: T[] = [], removed: string[] = [];
|
|
47
47
|
items.forEach(item => item.status === 'ARCHIVED' ? removed.push(item.name) : updated.push(item));
|
|
48
|
-
return { updated, removed, till, since };
|
|
48
|
+
return { updated, removed, till, since: since ?? undefined };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -4,7 +4,28 @@ 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
|
|
7
|
+
export type InstantUpdate = {
|
|
8
|
+
payload: IDefinition | IRBSegment,
|
|
9
|
+
/**
|
|
10
|
+
* Environment-scoped change number to track as the storage change number.
|
|
11
|
+
* If not provided, the storage change number is preserved. That's the case of config instant
|
|
12
|
+
* updates, where the notification change number is scoped to the config rather than to the
|
|
13
|
+
* environment.
|
|
14
|
+
*/
|
|
15
|
+
changeNumber?: number,
|
|
16
|
+
type: string
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `pcn` of CONFIG_UPDATE and RB_SEGMENT_UPDATE notifications for the Configs SDK, used as lower bounds for the `since` and `rbSince` fetch params.
|
|
21
|
+
* Added for robustness, although in practice `pcn` should never be lower than the current storage change number.
|
|
22
|
+
*/
|
|
23
|
+
export type PreviousChangeNumbers = {
|
|
24
|
+
since?: number, // `pcn` of CONFIG_UPDATE notifications
|
|
25
|
+
rbSince?: number // `pcn` of RB_SEGMENT_UPDATE notifications
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface IDefinitionsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, instantUpdate?: InstantUpdate, pcns?: PreviousChangeNumbers], boolean> { }
|
|
8
29
|
|
|
9
30
|
export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
|
|
10
31
|
|
|
@@ -11,11 +11,10 @@ import { IN_RULE_BASED_SEGMENT, IN_SEGMENT, RULE_BASED_SEGMENT, STANDARD_SEGMENT
|
|
|
11
11
|
import { setToArray } from '../../../utils/lang/sets';
|
|
12
12
|
import { RB_SEGMENT_UPDATE } from '../../streaming/constants';
|
|
13
13
|
import { SdkUpdateMetadata } from '../../../../types/splitio';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { InstantUpdate, ISegmentsSyncTask, PreviousChangeNumbers } from '../types';
|
|
15
|
+
import { thenable } from '../../../utils/promise/thenable';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
type DefinitionChangesUpdater = (noCache?: boolean, till?: number, instantUpdate?: InstantUpdate) => Promise<boolean>
|
|
17
|
+
type DefinitionChangesUpdater = (noCache?: boolean, till?: number, instantUpdate?: InstantUpdate, pcns?: PreviousChangeNumbers) => Promise<boolean>
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Collect segments from a raw FF or RBS definition.
|
|
@@ -142,28 +141,35 @@ export function definitionChangesUpdaterFactory(
|
|
|
142
141
|
* @param noCache - true to revalidate data to fetch
|
|
143
142
|
* @param till - query param to bypass CDN requests
|
|
144
143
|
*/
|
|
145
|
-
return function definitionChangesUpdater(noCache?: boolean, till?: number, instantUpdate?: InstantUpdate) {
|
|
144
|
+
return function definitionChangesUpdater(noCache?: boolean, till?: number, instantUpdate?: InstantUpdate, pcns?: PreviousChangeNumbers) {
|
|
146
145
|
|
|
147
146
|
/**
|
|
148
147
|
* @param since - current changeNumber at definitionsCache
|
|
149
148
|
* @param retry - current number of retry attempts
|
|
150
149
|
*/
|
|
151
150
|
function _definitionChangesUpdater(sinces: [number, number], retry = 0): Promise<boolean> {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
151
|
+
let [since, rbSince] = sinces;
|
|
152
|
+
|
|
153
|
+
// 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.
|
|
154
|
+
if (pcns) {
|
|
155
|
+
if (pcns.since !== undefined && pcns.since < since) since = pcns.since;
|
|
156
|
+
if (pcns.rbSince !== undefined && pcns.rbSince < rbSince) rbSince = pcns.rbSince;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const definitionChangesRequest = instantUpdate ?
|
|
160
|
+
instantUpdate.type === RB_SEGMENT_UPDATE ?
|
|
161
|
+
{ rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['rbs'] } :
|
|
162
|
+
// IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
|
|
163
|
+
Promise.resolve(rbSegments.contains(parseSegments(instantUpdate.payload, IN_RULE_BASED_SEGMENT))).then((contains) => {
|
|
164
|
+
return contains ?
|
|
165
|
+
{ d: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['d'] } :
|
|
166
|
+
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
|
|
167
|
+
}) :
|
|
168
|
+
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
|
|
169
|
+
|
|
170
|
+
if (thenable(definitionChangesRequest)) log.debug(SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
|
|
171
|
+
|
|
172
|
+
return Promise.resolve(definitionChangesRequest)
|
|
167
173
|
.then((definitionChanges: IDefinitionChangesResponse) => {
|
|
168
174
|
const usedSegments = new Set<string>();
|
|
169
175
|
|
|
@@ -5,7 +5,7 @@ import { ISettings } from '../../../types';
|
|
|
5
5
|
import { checkIfServerSide } from '../../../utils/key';
|
|
6
6
|
import { isString } from '../../../utils/lang';
|
|
7
7
|
import { objectAssign } from '../../../utils/lang/objectAssign';
|
|
8
|
-
import { IJwtCredential } from '
|
|
8
|
+
import { IJwtCredential } from '../../../services/types';
|
|
9
9
|
import { ISSEClient, ISseEventHandler } from './types';
|
|
10
10
|
|
|
11
11
|
const ABLY_API_VERSION = '1.1';
|
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
import { IRBSegment, IDefinition } from '../../../dtos/types';
|
|
2
2
|
import { STREAMING_PARSING_SPLIT_UPDATE } from '../../../logger/constants';
|
|
3
|
-
import { ILogger } from '../../../logger/types';
|
|
4
3
|
import { SDK_DEFINITIONS_ARRIVED } from '../../../readiness/constants';
|
|
5
|
-
import {
|
|
6
|
-
import { IRBSegmentsCacheSync, IDefinitionsCacheSync
|
|
7
|
-
import { ITelemetryTracker } from '../../../trackers/types';
|
|
4
|
+
import { ISdkFactoryContextSync } from '../../../sdkFactory/types';
|
|
5
|
+
import { IRBSegmentsCacheSync, IDefinitionsCacheSync } from '../../../storages/types';
|
|
8
6
|
import { Backoff } from '../../../utils/Backoff';
|
|
9
7
|
import { SPLITS } from '../../../utils/constants';
|
|
10
|
-
import {
|
|
11
|
-
import { InstantUpdate } from '../../polling/
|
|
8
|
+
import { IPollingManager } from '../../polling/types';
|
|
9
|
+
import { InstantUpdate } from '../../polling/types';
|
|
12
10
|
import { RB_SEGMENT_UPDATE } from '../constants';
|
|
13
|
-
import {
|
|
11
|
+
import { parseUpdatePayload } from '../parseUtils';
|
|
14
12
|
import { ISplitKillData, ISplitUpdateData } from '../SSEHandler/types';
|
|
15
13
|
import { FETCH_BACKOFF_BASE, FETCH_BACKOFF_MAX_WAIT, FETCH_BACKOFF_MAX_RETRIES } from './constants';
|
|
16
|
-
import {
|
|
14
|
+
import { IDefinitionsUpdateWorker } from './types';
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
|
-
* DefinitionsUpdateWorker factory
|
|
17
|
+
* DefinitionsUpdateWorker factory for Flags SDKs, where notifications pcn and changeNumber are environment-scoped
|
|
20
18
|
*/
|
|
21
|
-
export function DefinitionsUpdateWorker(
|
|
19
|
+
export function DefinitionsUpdateWorker(
|
|
20
|
+
params: Pick<ISdkFactoryContextSync, 'settings' | 'storage' | 'telemetryTracker' | 'readiness'>,
|
|
21
|
+
pollingManager: Pick<IPollingManager, 'definitionsSyncTask'>,
|
|
22
|
+
): IDefinitionsUpdateWorker {
|
|
23
|
+
|
|
24
|
+
const { settings: { log }, storage, telemetryTracker, readiness: { definitions: definitionsEventEmitter } } = params;
|
|
25
|
+
const { definitionsSyncTask } = pollingManager;
|
|
22
26
|
|
|
23
27
|
const ff = DefinitionsUpdateWorker(storage.definitions);
|
|
24
28
|
const rbs = DefinitionsUpdateWorker(storage.rbSegments);
|
|
@@ -108,7 +112,7 @@ export function DefinitionsUpdateWorker(log: ILogger, storage: IStorageSync, def
|
|
|
108
112
|
put(parsedData) {
|
|
109
113
|
if (parsedData.d && parsedData.c !== undefined) {
|
|
110
114
|
try {
|
|
111
|
-
const payload =
|
|
115
|
+
const payload = parseUpdatePayload<IDefinition | IRBSegment>(parsedData.c, parsedData.d);
|
|
112
116
|
if (payload) {
|
|
113
117
|
(parsedData.type === RB_SEGMENT_UPDATE ? rbs : ff).put(parsedData, payload);
|
|
114
118
|
return;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { ISplitKillData, ISplitUpdateData } from '../SSEHandler/types';
|
|
2
|
+
|
|
1
3
|
export interface IUpdateWorker<T extends any[]> {
|
|
2
4
|
stop(): void // clear scheduled tasks (backoff)
|
|
3
5
|
put(...args: T): void // handle new update event
|
|
4
6
|
}
|
|
7
|
+
|
|
8
|
+
export interface IDefinitionsUpdateWorker extends IUpdateWorker<[updateData: ISplitUpdateData]> {
|
|
9
|
+
killDefinition: (event: ISplitKillData) => void
|
|
10
|
+
}
|
|
@@ -2,7 +2,6 @@ import { algorithms } from '../../utils/decompress';
|
|
|
2
2
|
import { decodeFromBase64 } from '../../utils/base64';
|
|
3
3
|
import { hash } from '../../utils/murmur3/murmur3';
|
|
4
4
|
import { Compression, IMembershipMSUpdateData } from './SSEHandler/types';
|
|
5
|
-
import { IRBSegment, IDefinition } from '../../dtos/types';
|
|
6
5
|
|
|
7
6
|
const GZIP = 1;
|
|
8
7
|
const ZLIB = 2;
|
|
@@ -80,11 +79,11 @@ export function isInBitmap(bitmap: Uint8Array, hash64hex: string) {
|
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
/**
|
|
83
|
-
* Parse
|
|
82
|
+
* Parse notifications for instant updates
|
|
84
83
|
*/
|
|
85
|
-
export function
|
|
84
|
+
export function parseUpdatePayload<T>(compression: Compression, data: string): T | undefined {
|
|
86
85
|
return compression > 0 ?
|
|
87
|
-
parseCompressedData<
|
|
86
|
+
parseCompressedData<T>(data, compression, false) :
|
|
88
87
|
JSON.parse(decodeFromBase64(data));
|
|
89
88
|
}
|
|
90
89
|
|
|
@@ -15,7 +15,7 @@ import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STRE
|
|
|
15
15
|
import { IMembershipMSUpdateData, IMembershipLSUpdateData, KeyList, UpdateStrategy } from './SSEHandler/types';
|
|
16
16
|
import { getDelay, isInBitmap, parseBitmap, parseCompressedData } from './parseUtils';
|
|
17
17
|
import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
|
|
18
|
-
import { IJwtCredential } from '
|
|
18
|
+
import { IJwtCredential } from '../../services/types';
|
|
19
19
|
import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
|
|
20
20
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
21
21
|
|
|
@@ -27,9 +27,10 @@ import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
|
27
27
|
export function pushManagerFactory(
|
|
28
28
|
params: ISdkFactoryContextSync,
|
|
29
29
|
pollingManager: IPollingManager,
|
|
30
|
+
definitionsUpdateWorkerFactory = DefinitionsUpdateWorker
|
|
30
31
|
): IPushManager | undefined {
|
|
31
32
|
|
|
32
|
-
const { settings, storage, serviceApi,
|
|
33
|
+
const { settings, storage, serviceApi, platform, telemetryTracker } = params;
|
|
33
34
|
|
|
34
35
|
// `userKey` is the matching key of main client in client-side SDK.
|
|
35
36
|
// It can be used to check if running on client-side or server-side SDK.
|
|
@@ -54,7 +55,7 @@ export function pushManagerFactory(
|
|
|
54
55
|
// MySegmentsUpdateWorker (client-side) are initiated in `add` method
|
|
55
56
|
const segmentsUpdateWorker = userKey ? undefined : SegmentsUpdateWorker(log, pollingManager.segmentsSyncTask as ISegmentsSyncTask, storage.segments);
|
|
56
57
|
// For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
|
|
57
|
-
const definitionsUpdateWorker =
|
|
58
|
+
const definitionsUpdateWorker = definitionsUpdateWorkerFactory(params, pollingManager);
|
|
58
59
|
|
|
59
60
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
60
61
|
// Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { IDecodedJWTToken } from '../../../utils/jwt/types';
|
|
2
|
-
|
|
3
|
-
export type IJwtCredential = {
|
|
4
|
-
token: string; // empty string ("") when `"pushEnabled": false`
|
|
5
|
-
decodedToken: IDecodedJWTToken
|
|
6
|
-
channels: { [channel: string]: string[] }
|
|
7
|
-
// /api/v2/auth fields
|
|
8
|
-
pushEnabled?: boolean | null;
|
|
9
|
-
connDelay?: number | null;
|
|
10
|
-
// /api/v3/auth fields
|
|
11
|
-
config?: {
|
|
12
|
-
streaming?: {
|
|
13
|
-
delay?: number | null;
|
|
14
|
-
enabled?: boolean | null;
|
|
15
|
-
} | null;
|
|
16
|
-
} | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type IFetchAuth = (userKeys?: string[]) => Promise<IJwtCredential>
|