@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.
Files changed (62) hide show
  1. package/CHANGES.txt +1 -0
  2. package/cjs/services/authProvider.js +2 -2
  3. package/cjs/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
  4. package/cjs/services/splitApi.js +2 -2
  5. package/cjs/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -1
  6. package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -1
  7. package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +2 -1
  8. package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +2 -1
  9. package/cjs/storages/inRedis/DefinitionsCacheInRedis.js +3 -1
  10. package/cjs/storages/inRedis/RBSegmentsCacheInRedis.js +3 -1
  11. package/cjs/storages/pluggable/DefinitionsCachePluggable.js +3 -1
  12. package/cjs/storages/pluggable/RBSegmentsCachePluggable.js +3 -1
  13. package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  14. package/cjs/sync/polling/updaters/definitionChangesUpdater.js +14 -4
  15. package/cjs/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +5 -3
  16. package/cjs/sync/streaming/parseUtils.js +4 -4
  17. package/cjs/sync/streaming/pushManager.js +4 -3
  18. package/esm/services/authProvider.js +1 -1
  19. package/esm/{sync/streaming/AuthClient/index.js → services/fetchAuth.js} +2 -2
  20. package/esm/services/splitApi.js +1 -1
  21. package/esm/storages/inLocalStorage/DefinitionsCacheInLocal.js +2 -1
  22. package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +2 -1
  23. package/esm/storages/inMemory/DefinitionsCacheInMemory.js +2 -1
  24. package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +2 -1
  25. package/esm/storages/inRedis/DefinitionsCacheInRedis.js +3 -1
  26. package/esm/storages/inRedis/RBSegmentsCacheInRedis.js +3 -1
  27. package/esm/storages/pluggable/DefinitionsCachePluggable.js +3 -1
  28. package/esm/storages/pluggable/RBSegmentsCachePluggable.js +3 -1
  29. package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
  30. package/esm/sync/polling/updaters/definitionChangesUpdater.js +14 -4
  31. package/esm/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.js +6 -4
  32. package/esm/sync/streaming/parseUtils.js +2 -2
  33. package/esm/sync/streaming/pushManager.js +4 -3
  34. package/package.json +1 -1
  35. package/src/dtos/types.ts +4 -4
  36. package/src/services/authProvider.ts +2 -2
  37. package/src/{sync/streaming/AuthClient/index.ts → services/fetchAuth.ts} +3 -4
  38. package/src/services/splitApi.ts +1 -1
  39. package/src/services/types.ts +21 -4
  40. package/src/storages/AbstractDefinitionsCacheAsync.ts +3 -3
  41. package/src/storages/AbstractDefinitionsCacheSync.ts +2 -2
  42. package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +2 -2
  43. package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +3 -3
  44. package/src/storages/inMemory/DefinitionsCacheInMemory.ts +2 -2
  45. package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +2 -2
  46. package/src/storages/inRedis/DefinitionsCacheInRedis.ts +6 -4
  47. package/src/storages/inRedis/RBSegmentsCacheInRedis.ts +7 -5
  48. package/src/storages/pluggable/DefinitionsCachePluggable.ts +4 -2
  49. package/src/storages/pluggable/RBSegmentsCachePluggable.ts +5 -3
  50. package/src/storages/types.ts +6 -6
  51. package/src/sync/polling/fetchers/splitChangesFetcher.ts +5 -5
  52. package/src/sync/polling/types.ts +22 -1
  53. package/src/sync/polling/updaters/definitionChangesUpdater.ts +26 -20
  54. package/src/sync/streaming/SSEClient/index.ts +1 -1
  55. package/src/sync/streaming/SSEClient/types.ts +1 -1
  56. package/src/sync/streaming/UpdateWorkers/DefinitionsUpdateWorker.ts +15 -11
  57. package/src/sync/streaming/UpdateWorkers/types.ts +6 -0
  58. package/src/sync/streaming/parseUtils.ts +3 -4
  59. package/src/sync/streaming/pushManager.ts +4 -3
  60. package/cjs/sync/streaming/AuthClient/types.js +0 -2
  61. package/esm/sync/streaming/AuthClient/types.js +0 -1
  62. package/src/sync/streaming/AuthClient/types.ts +0 -19
@@ -1,4 +1,4 @@
1
- import { IFetchAuth } from '../sync/streaming/AuthClient/types';
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: number): Promise<boolean | void>
13
+ protected abstract setChangeNumber(changeNumber?: number): MaybeThenable<boolean | void>
14
14
 
15
- update(toAdd: IDefinition[], toRemove: string[], changeNumber: number): Promise<boolean> {
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: number): boolean | void
14
+ protected abstract setChangeNumber(changeNumber?: number): boolean | void
15
15
 
16
- update(toAdd: IDefinition[], toRemove: string[], changeNumber: number): boolean {
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: number): boolean {
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: number): boolean {
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: number) {
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: number): boolean {
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: number): boolean {
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: number): Promise<boolean> {
147
- return this.redis.set(this.keys.buildDefinitionsTillKey(), changeNumber + '').then(
148
- (status: string | null) => status === 'OK'
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: number): Promise<boolean> {
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: number) {
58
- return this.redis.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '').then(
59
- (status: string | null) => status === 'OK'
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: number) {
206
- return this.wrapper.set(this.keys.buildDefinitionsTillKey(), changeNumber + '');
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: number): Promise<boolean> {
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: number) {
57
- return this.wrapper.set(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
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> {
@@ -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: number): MaybeThenable<boolean>,
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: number): boolean,
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: number): Promise<boolean>,
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: number): MaybeThenable<boolean>,
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: number): boolean,
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: number): Promise<boolean>,
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 interface IDefinitionsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, definitionUpdateNotification?: { payload: IDefinition | IRBSegment, changeNumber: number }], boolean> { }
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 { ISplit } from '../fetchers/splitChangesFetcher';
15
- import { ISegmentsSyncTask } from '../types';
14
+ import { InstantUpdate, ISegmentsSyncTask, PreviousChangeNumbers } from '../types';
15
+ import { thenable } from '../../../utils/promise/thenable';
16
16
 
17
- export type InstantUpdate = { payload: ISplit | IRBSegment, changeNumber: number, type: string };
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
- const [since, rbSince] = sinces;
153
- log.debug(SYNC_FETCH, [definitionChangesFetcher.type, since, rbSince]);
154
-
155
- return Promise.resolve(
156
- instantUpdate ?
157
- instantUpdate.type === RB_SEGMENT_UPDATE ?
158
- { rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['rbs'] } :
159
- // IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
160
- Promise.resolve(rbSegments.contains(parseSegments(instantUpdate.payload, IN_RULE_BASED_SEGMENT))).then((contains) => {
161
- return contains ?
162
- { d: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['d'] } :
163
- definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
164
- }) :
165
- definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator)
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 '../AuthClient/types';
8
+ import { IJwtCredential } from '../../../services/types';
9
9
  import { ISSEClient, ISseEventHandler } from './types';
10
10
 
11
11
  const ABLY_API_VERSION = '1.1';
@@ -1,4 +1,4 @@
1
- import { IJwtCredential } from '../AuthClient/types';
1
+ import { IJwtCredential } from '../../../services/types';
2
2
 
3
3
  export interface ISseEventHandler {
4
4
  handleError: (ev: Event) => any;
@@ -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 { IDefinitionsEventEmitter } from '../../../readiness/types';
6
- import { IRBSegmentsCacheSync, IDefinitionsCacheSync, IStorageSync } from '../../../storages/types';
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 { IDefinitionsSyncTask } from '../../polling/types';
11
- import { InstantUpdate } from '../../polling/updaters/definitionChangesUpdater';
8
+ import { IPollingManager } from '../../polling/types';
9
+ import { InstantUpdate } from '../../polling/types';
12
10
  import { RB_SEGMENT_UPDATE } from '../constants';
13
- import { parseFFUpdatePayload } from '../parseUtils';
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 { IUpdateWorker } from './types';
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(log: ILogger, storage: IStorageSync, definitionsSyncTask: IDefinitionsSyncTask, definitionsEventEmitter: IDefinitionsEventEmitter, telemetryTracker: ITelemetryTracker): IUpdateWorker<[updateData: ISplitUpdateData]> & { killDefinition(event: ISplitKillData): void } {
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 = parseFFUpdatePayload(parsedData.c, parsedData.d);
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 feature flags notifications for instant feature flag updates
82
+ * Parse notifications for instant updates
84
83
  */
85
- export function parseFFUpdatePayload(compression: Compression, data: string): IDefinition | IRBSegment | undefined {
84
+ export function parseUpdatePayload<T>(compression: Compression, data: string): T | undefined {
86
85
  return compression > 0 ?
87
- parseCompressedData<IDefinition | IRBSegment>(data, compression, false) :
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 './AuthClient/types';
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, readiness, platform, telemetryTracker } = params;
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 = DefinitionsUpdateWorker(log, storage, pollingManager.definitionsSyncTask, readiness.definitions, telemetryTracker);
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,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -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>