Skip to content

Commit 3e2e2a9

Browse files
Merge pull request #393 from splitio/rb_segments_streaming
[Rule-based segments] Handle streaming notification
2 parents 17bbd36 + 781d8ce commit 3e2e2a9

File tree

20 files changed

+153
-131
lines changed

20 files changed

+153
-131
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Split has built and maintains SDKs for:
2727
* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK)
2828
* Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK)
2929
* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities)
30+
* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://help.split.io/hc/en-us/articles/26988707417869-Elixir-Thin-Client-SDK)
3031
* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/8096158017165-Flutter-plugin)
3132
* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK)
3233
* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "message",
3+
"data": "{\"id\":\"mc4i3NENoA:0:0\",\"clientId\":\"NDEzMTY5Mzg0MA==:MTM2ODE2NDMxNA==\",\"timestamp\":1457552621899,\"encoding\":\"json\",\"channel\":\"NzM2MDI5Mzc0_NDEzMjQ1MzA0Nw==_splits\",\"data\":\"{\\\"type\\\":\\\"RB_SEGMENT_UPDATE\\\",\\\"changeNumber\\\":1457552620999}\"}"
4+
}

src/logger/messages/warn.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const codesWarn: [number, string][] = codesError.concat([
3333
[c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS + ': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
3434

3535
[c.STREAMING_PARSING_MEMBERSHIPS_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching Memberships due to an error processing %s notification: %s'],
36-
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'],
36+
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing %s notification: %s'],
3737
[c.WARN_INVALID_FLAGSET, '%s: you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. %s was discarded.'],
3838
[c.WARN_LOWERCASE_FLAGSET, '%s: flag set %s should be all lowercase - converting string to lowercase.'],
3939
[c.WARN_FLAGSET_WITHOUT_FLAGS, '%s: you passed %s flag set that does not contain cached feature flag names. Please double check what flag sets are in use in the Split user interface.'],

src/storages/__tests__/KeyBuilder.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,16 @@ test('KEYS / latency and exception keys (telemetry)', () => {
106106
test('getStorageHash', () => {
107107
expect(getStorageHash({
108108
core: { authorizationKey: '<fake-token-rfc>' },
109-
sync: { __splitFiltersValidation: { queryString: '&names=p1__split,p2__split' }, flagSpecVersion: '1.2' }
110-
} as ISettings)).toBe('7ccd6b31');
109+
sync: { __splitFiltersValidation: { queryString: '&names=p1__split,p2__split' }, flagSpecVersion: '1.3' }
110+
} as ISettings)).toBe('2ce5cc38');
111111

112112
expect(getStorageHash({
113113
core: { authorizationKey: '<fake-token-rfc>' },
114-
sync: { __splitFiltersValidation: { queryString: '&names=p2__split,p3__split' }, flagSpecVersion: '1.2' }
115-
} as ISettings)).toBe('2a25d0e1');
114+
sync: { __splitFiltersValidation: { queryString: '&names=p2__split,p3__split' }, flagSpecVersion: '1.3' }
115+
} as ISettings)).toBe('e65079c6');
116116

117117
expect(getStorageHash({
118118
core: { authorizationKey: '<fake-token-rfc>' },
119-
sync: { __splitFiltersValidation: { queryString: null }, flagSpecVersion: '1.2' }
120-
} as ISettings)).toBe('db8943b4');
119+
sync: { __splitFiltersValidation: { queryString: null }, flagSpecVersion: '1.3' }
120+
} as ISettings)).toBe('193e6f3f');
121121
});

src/storages/__tests__/RBSegmentsCacheSync.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe.each([cacheInMemory, cacheInLocal])('Rule-based segments cache sync (Me
6161
});
6262

6363
test('usesSegments should track segments usage correctly', () => {
64-
expect(cache.usesSegments()).toBe(true); // Initially true when changeNumber is -1
64+
expect(cache.usesSegments()).toBe(false); // No rbSegments, so false
6565

6666
cache.update([rbSegment], [], 1); // rbSegment doesn't have IN_SEGMENT matcher
6767
expect(cache.usesSegments()).toBe(false);
@@ -70,6 +70,6 @@ describe.each([cacheInMemory, cacheInLocal])('Rule-based segments cache sync (Me
7070
expect(cache.usesSegments()).toBe(true);
7171

7272
cache.clear();
73-
expect(cache.usesSegments()).toBe(true); // True after clear since changeNumber is -1
73+
expect(cache.usesSegments()).toBe(false); // False after clear since there are no rbSegments
7474
});
7575
});

src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
1212

1313
private readonly keys: KeyBuilderCS;
1414
private readonly log: ILogger;
15-
private hasSync?: boolean;
1615

1716
constructor(settings: ISettings, keys: KeyBuilderCS) {
1817
this.keys = keys;
@@ -22,7 +21,6 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
2221
clear() {
2322
this.getNames().forEach(name => this.remove(name));
2423
localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
25-
this.hasSync = false;
2624
}
2725

2826
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
@@ -35,7 +33,6 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
3533
try {
3634
localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
3735
localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
38-
this.hasSync = true;
3936
} catch (e) {
4037
this.log.error(LOG_PREFIX + e);
4138
}
@@ -128,9 +125,6 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
128125
}
129126

130127
usesSegments(): boolean {
131-
// If cache hasn't been synchronized, assume we need segments
132-
if (!this.hasSync) return true;
133-
134128
const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
135129
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
136130

src/storages/inMemory/RBSegmentsCacheInMemory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync {
6262
}
6363

6464
usesSegments(): boolean {
65-
return this.getChangeNumber() === -1 || this.segmentsCount > 0;
65+
return this.segmentsCount > 0;
6666
}
6767

6868
}

src/sync/polling/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { ISplit } from '../../dtos/types';
1+
import { IRBSegment, ISplit } from '../../dtos/types';
22
import { IReadinessManager } from '../../readiness/types';
33
import { IStorageSync } from '../../storages/types';
44
import { MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../streaming/types';
55
import { ITask, ISyncTask } from '../types';
66

7-
export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit, changeNumber: number }], boolean> { }
7+
export interface ISplitsSyncTask extends ISyncTask<[noCache?: boolean, till?: number, splitUpdateNotification?: { payload: ISplit | IRBSegment, changeNumber: number }], boolean> { }
88

99
export interface ISegmentsSyncTask extends ISyncTask<[fetchOnlyNew?: boolean, segmentName?: string, noCache?: boolean, till?: number], boolean> { }
1010

src/sync/polling/updaters/segmentChangesUpdater.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function segmentChangesUpdaterFactory(
5151
* Returned promise will not be rejected.
5252
*
5353
* @param fetchOnlyNew - if true, only fetch the segments that not exists, i.e., which `changeNumber` is equal to -1.
54-
* This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE notifications.
54+
* This param is used by SplitUpdateWorker on server-side SDK, to fetch new registered segments on SPLIT_UPDATE or RB_SEGMENT_UPDATE notifications.
5555
* @param segmentName - segment name to fetch. By passing `undefined` it fetches the list of segments registered at the storage
5656
* @param noCache - true to revalidate data to fetch on a SEGMENT_UPDATE notifications.
5757
* @param till - till target for the provided segmentName, for CDN bypass.

src/sync/streaming/SSEHandler/__tests__/index.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// @ts-nocheck
22
import { SSEHandlerFactory } from '..';
3-
import { PUSH_SUBSYSTEM_UP, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, PUSH_RETRYABLE_ERROR, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, ControlType } from '../../constants';
3+
import { PUSH_SUBSYSTEM_UP, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, PUSH_RETRYABLE_ERROR, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, RB_SEGMENT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, ControlType } from '../../constants';
44
import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock';
55

66
// update messages
77
import splitUpdateMessage from '../../../../__tests__/mocks/message.SPLIT_UPDATE.1457552620999.json';
8+
import rbsegmentUpdateMessage from '../../../../__tests__/mocks/message.RB_SEGMENT_UPDATE.1457552620999.json';
89
import splitKillMessage from '../../../../__tests__/mocks/message.SPLIT_KILL.1457552650000.json';
910
import segmentUpdateMessage from '../../../../__tests__/mocks/message.SEGMENT_UPDATE.1457552640000.json';
1011

@@ -144,6 +145,10 @@ test('`handlerMessage` for update notifications (NotificationProcessor) and stre
144145
sseHandler.handleMessage(splitUpdateMessage);
145146
expect(pushEmitter.emit).toHaveBeenLastCalledWith(SPLIT_UPDATE, ...expectedParams); // must emit SPLIT_UPDATE with the message change number
146147

148+
expectedParams = [{ type: 'RB_SEGMENT_UPDATE', changeNumber: 1457552620999 }];
149+
sseHandler.handleMessage(rbsegmentUpdateMessage);
150+
expect(pushEmitter.emit).toHaveBeenLastCalledWith(RB_SEGMENT_UPDATE, ...expectedParams); // must emit RB_SEGMENT_UPDATE with the message change number
151+
147152
expectedParams = [{ type: 'SPLIT_KILL', changeNumber: 1457552650000, splitName: 'whitelist', defaultTreatment: 'not_allowed' }];
148153
sseHandler.handleMessage(splitKillMessage);
149154
expect(pushEmitter.emit).toHaveBeenLastCalledWith(SPLIT_KILL, ...expectedParams); // must emit SPLIT_KILL with the message change number, split name and default treatment

src/sync/streaming/SSEHandler/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { errorParser, messageParser } from './NotificationParser';
22
import { notificationKeeperFactory } from './NotificationKeeper';
3-
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE } from '../constants';
3+
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, RB_SEGMENT_UPDATE } from '../constants';
44
import { IPushEventEmitter } from '../types';
55
import { ISseEventHandler } from '../SSEClient/types';
66
import { INotificationError, INotificationMessage } from './types';
@@ -84,6 +84,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
8484
case MEMBERSHIPS_MS_UPDATE:
8585
case MEMBERSHIPS_LS_UPDATE:
8686
case SPLIT_KILL:
87+
case RB_SEGMENT_UPDATE:
8788
pushEmitter.emit(parsedData.type, parsedData);
8889
break;
8990

src/sync/streaming/SSEHandler/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ControlType } from '../constants';
2-
import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE } from '../types';
2+
import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE, RB_SEGMENT_UPDATE } from '../types';
33

44
export enum Compression {
55
None = 0,
@@ -42,7 +42,7 @@ export interface ISegmentUpdateData {
4242
}
4343

4444
export interface ISplitUpdateData {
45-
type: SPLIT_UPDATE,
45+
type: SPLIT_UPDATE | RB_SEGMENT_UPDATE,
4646
changeNumber: number,
4747
pcn?: number,
4848
d?: string,

0 commit comments

Comments
 (0)