Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "0.1.1-canary.15",
"version": "0.1.1-canary.17",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/mocks/message.STREAMING_RESET.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"type": "message",
"data": "{\"timestamp\":1457552660000,\"data\":\"{\\\"type\\\":\\\"STREAMING_RESET\\\"}\"}"
"data": "{\"timestamp\":1457552660000,\"data\":\"{\\\"type\\\":\\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_RESET\\\"}\"}"
}
16 changes: 9 additions & 7 deletions src/readiness/readinessManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function readinessManagerFactory(

// emit SDK_READY_FROM_CACHE
let isReadyFromCache = false;
if (splits.splitsCacheLoaded) setTimeout(checkIsReadyFromCache, 0); // don't check status inmediately, to allow attach listeners
if (splits.splitsCacheLoaded) isReadyFromCache = true; // ready from cache, but doesn't emit SDK_READY_FROM_CACHE
else splits.once(SDK_SPLITS_CACHE_LOADED, checkIsReadyFromCache);

// emit SDK_READY_TIMED_OUT
Expand All @@ -62,13 +62,15 @@ export function readinessManagerFactory(
let isDestroyed = false;

function checkIsReadyFromCache() {
// @TODO add condition to emit SDK_READY_FROM_CACHE only if SDK_READY has not been emitted
if (!isReadyFromCache && splits.splitsCacheLoaded) {
// Make it async, in case user callbacks throw an exception
setTimeout(() => {
isReadyFromCache = true;
isReadyFromCache = true;
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
if (!isReady) {
try {
gate.emit(SDK_READY_FROM_CACHE);
}, 0);
} catch (e) {
// throws user callback exceptions in next tick
setTimeout(() => { throw e; }, 0);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/storages/AbstractSplitsCacheSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default abstract class AbstractSplitsCacheSync implements ISplitsCacheSyn
* It is used as condition to emit SDK_SPLITS_CACHE_LOADED, and then SDK_READY_FROM_CACHE.
*/
checkCache(): boolean {
return this.getChangeNumber() > -1;
return false;
}

/**
Expand Down
24 changes: 15 additions & 9 deletions src/sync/offline/syncTasks/fromObjectSyncTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import syncTaskFactory from '../../syncTask';
import { ISyncTask } from '../../types';
import { ISettings } from '../../../types';
import { CONTROL } from '../../../utils/constants';
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../../readiness/constants';
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../../../readiness/constants';
import { SYNC_OFFLINE_DATA, ERROR_SYNC_OFFLINE_LOADING } from '../../../logger/constants';

/**
Expand All @@ -20,8 +20,8 @@ export function fromObjectUpdaterFactory(
settings: ISettings,
): () => Promise<boolean> {

const log = settings.log;
let firstTime = true;
const log = settings.log, splitsCache = storage.splits;
let startingUp = true;

return function objectUpdater() {
const splits: [string, string][] = [];
Expand Down Expand Up @@ -54,14 +54,20 @@ export function fromObjectUpdaterFactory(
});

return Promise.all([
storage.splits.clear(),
storage.splits.addSplits(splits)
splitsCache.clear(), // required to sync removed splits from mock
splitsCache.setChangeNumber(Date.now()),
splitsCache.addSplits(splits)
]).then(() => {
readiness.splits.emit(SDK_SPLITS_ARRIVED);
// Only emits SDK_SEGMENTS_ARRIVED the first time for SDK_READY
if (firstTime) {
firstTime = false;
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);

if (startingUp) {
startingUp = false;
Promise.resolve(splitsCache.checkCache()).then(cacheReady => {
// Emits SDK_READY_FROM_CACHE
if (cacheReady) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
// Emits SDK_READY
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
});
}
return true;
});
Expand Down
8 changes: 8 additions & 0 deletions src/sync/streaming/SSEHandler/NotificationKeeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ export default function notificationKeeperFactory(pushEmitter: IPushEventEmitter
},

handleControlEvent(controlType: ControlType, channel: string, timestamp: number) {
/* STREAMING_RESET control event is handled by PushManager directly since it doesn't require
* tracking timestamp and state like OCCUPANCY or CONTROL. It also ignores previous
* OCCUPANCY and CONTROL notifications, and whether PUSH_SUBSYSTEM_DOWN has been emitted or not */
if (controlType === ControlType.STREAMING_RESET) {
pushEmitter.emit(controlType);
return;
}

for (let i = 0; i < channels.length; i++) {
const c = channels[i];
if (c.regex.test(channel)) {
Expand Down
8 changes: 4 additions & 4 deletions src/sync/streaming/SSEHandler/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-nocheck
import SSEHandlerFactory from '..';
import { PUSH_SUBSYSTEM_UP, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, PUSH_RETRYABLE_ERROR, MY_SEGMENTS_UPDATE, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MY_SEGMENTS_UPDATE_V2, STREAMING_RESET } from '../../constants';
import { PUSH_SUBSYSTEM_UP, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, PUSH_RETRYABLE_ERROR, MY_SEGMENTS_UPDATE, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MY_SEGMENTS_UPDATE_V2, ControlType } from '../../constants';
import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock';

// update messages
Expand Down Expand Up @@ -80,7 +80,7 @@ test('`handleOpen` and `handlerMessage` for OCCUPANCY notifications (Notificatio

expect(pushEmitter.emit).toHaveBeenLastCalledWith(SPLIT_UPDATE, { type: 'SPLIT_UPDATE', changeNumber: 1457552620999 }); // must handle update message if streaming on after an OCCUPANCY event
sseHandler.handleMessage(streamingReset);
expect(pushEmitter.emit).toHaveBeenLastCalledWith(STREAMING_RESET); // must handle streaming reset
expect(pushEmitter.emit).toHaveBeenLastCalledWith(ControlType.STREAMING_RESET); // must handle streaming reset
expect(pushEmitter.emit).toBeCalledTimes(7); // must not emit PUSH_SUBSYSTEM_UP if streaming is already on and another channel has publishers
});

Expand Down Expand Up @@ -114,7 +114,7 @@ test('`handlerMessage` for CONTROL notifications (NotificationKeeper)', () => {
sseHandler.handleMessage({ data: '{ "data": "{\\"type\\":\\"SPLIT_UPDATE\\",\\"changeNumber\\":1457552620999 }" }' });
expect(pushEmitter.emit).toHaveBeenLastCalledWith(SPLIT_UPDATE, { type: 'SPLIT_UPDATE', changeNumber: 1457552620999 }); // must handle update message if streaming on after a CONTROL event
sseHandler.handleMessage(streamingReset);
expect(pushEmitter.emit).toHaveBeenLastCalledWith(STREAMING_RESET); // must handle streaming reset
expect(pushEmitter.emit).toHaveBeenLastCalledWith(ControlType.STREAMING_RESET); // must handle streaming reset

sseHandler.handleMessage(controlStreamingDisabledSec); // testing STREAMING_DISABLED with second region
expect(pushEmitter.emit).toHaveBeenLastCalledWith(PUSH_NONRETRYABLE_ERROR); // must emit PUSH_NONRETRYABLE_ERROR if received a STREAMING_DISABLED control message
Expand Down Expand Up @@ -168,7 +168,7 @@ test('`handlerMessage` for update notifications (NotificationProcessor) and stre
expect(pushEmitter.emit).toHaveBeenLastCalledWith(MY_SEGMENTS_UPDATE_V2, ...expectedParams); // must emit MY_SEGMENTS_UPDATE_V2 with the message parsed data

sseHandler.handleMessage(streamingReset);
expect(pushEmitter.emit).toHaveBeenLastCalledWith(STREAMING_RESET); // must emit STREAMING_RESET
expect(pushEmitter.emit).toHaveBeenLastCalledWith(ControlType.STREAMING_RESET); // must emit STREAMING_RESET

});

Expand Down
11 changes: 2 additions & 9 deletions src/sync/streaming/SSEHandler/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { errorParser, messageParser } from './NotificationParser';
import notificationKeeperFactory from './NotificationKeeper';
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, STREAMING_RESET, MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE } from '../constants';
import { PUSH_RETRYABLE_ERROR, PUSH_NONRETRYABLE_ERROR, OCCUPANCY, CONTROL, MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE } from '../constants';
import { IPushEventEmitter } from '../types';
import { ISseEventHandler } from '../SSEClient/types';
import { INotificationError, INotificationMessage } from './types';
Expand Down Expand Up @@ -68,7 +68,7 @@ export default function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventE
log.debug(STREAMING_NEW_MESSAGE, [data]);

// we only handle update events if streaming is up.
if (!notificationKeeper.isStreamingUp() && [OCCUPANCY, CONTROL, STREAMING_RESET].indexOf(parsedData.type) === -1)
if (!notificationKeeper.isStreamingUp() && [OCCUPANCY, CONTROL].indexOf(parsedData.type) === -1)
return;

switch (parsedData.type) {
Expand All @@ -91,13 +91,6 @@ export default function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventE
notificationKeeper.handleControlEvent(parsedData.controlType, channel, timestamp);
break;

/* STREAMING_RESET event is handled by PushManager directly since it doesn't require
tracking timestamp and state like OCCUPANCY or CONTROL. It also ignores previous
OCCUPANCY and CONTROL notifications, and whether PUSH_SUBSYSTEM_DOWN has been emitted or not */
case STREAMING_RESET:
pushEmitter.emit(STREAMING_RESET);
break;

default:
break;
}
Expand Down
8 changes: 2 additions & 6 deletions src/sync/streaming/SSEHandler/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ControlType } from '../constants';
import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, STREAMING_RESET } from '../types';
import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY } from '../types';

export interface IMySegmentsUpdateData {
type: MY_SEGMENTS_UPDATE,
Expand Down Expand Up @@ -65,11 +65,7 @@ export interface IOccupancyData {
}
}

export interface IStreamingResetData {
type: STREAMING_RESET
}

export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData | IStreamingResetData
export type INotificationData = IMySegmentsUpdateData | IMySegmentsUpdateV2Data | ISegmentUpdateData | ISplitUpdateData | ISplitKillData | IControlData | IOccupancyData
export type INotificationMessage = { parsedData: INotificationData, channel: string, timestamp: number, data: string }
export type INotificationError = Event & { parsedData?: any, message?: string }

Expand Down
3 changes: 1 addition & 2 deletions src/sync/streaming/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ export const SPLIT_UPDATE = 'SPLIT_UPDATE';
export const CONTROL = 'CONTROL';
export const OCCUPANCY = 'OCCUPANCY';

export const STREAMING_RESET = 'STREAMING_RESET';

export enum ControlType {
STREAMING_DISABLED = 'STREAMING_DISABLED',
STREAMING_PAUSED = 'STREAMING_PAUSED',
STREAMING_RESUMED = 'STREAMING_RESUMED',
STREAMING_RESET = 'STREAMING_RESET',
}
8 changes: 4 additions & 4 deletions src/sync/streaming/pushManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import SSEClient from './SSEClient';
import { IFetchAuth } from '../../services/types';
import { ISettings } from '../../types';
import { getMatching } from '../../utils/key';
import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, STREAMING_RESET } from './constants';
import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
import { IPlatform } from '../../sdkFactory/types';
import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 } from '../../logger/constants';
import { KeyList, UpdateStrategy } from './SSEHandler/types';
Expand Down Expand Up @@ -98,8 +98,8 @@ export default function pushManagerFactory(
// Set token refresh 10 minutes before `expirationTime - issuedAt`
const decodedToken = authData.decodedToken;
const refreshTokenDelay = decodedToken.exp - decodedToken.iat - SECONDS_BEFORE_EXPIRATION;
// connDelay is present in AuthV2 but not in AuthV1
const connDelay = authData.connDelay || 0;
// Default connDelay of 60 secs
const connDelay = typeof authData.connDelay === 'number' && authData.connDelay >= 0 ? authData.connDelay : 60;

log.info(STREAMING_REFRESH_TOKEN, [refreshTokenDelay, connDelay]);

Expand Down Expand Up @@ -212,7 +212,7 @@ export default function pushManagerFactory(

/** STREAMING_RESET notification. Unlike a PUSH_RETRYABLE_ERROR, it doesn't emit PUSH_SUBSYSTEM_DOWN to fallback polling */

pushEmitter.on(STREAMING_RESET, function handleStreamingReset() {
pushEmitter.on(ControlType.STREAMING_RESET, function handleStreamingReset() {
if (disconnected) return; // should never happen

// Minimum required clean-up.
Expand Down
5 changes: 2 additions & 3 deletions src/sync/streaming/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IFetchAuth } from '../../services/types';
import { IStorageSync } from '../../storages/types';
import { IEventEmitter, ISettings } from '../../types';
import { IPlatform } from '../../sdkFactory/types';
import { ControlType } from './constants';

// Internal SDK events, subscribed by SyncManager and PushManager
export type PUSH_SUBSYSTEM_UP = 'PUSH_SUBSYSTEM_UP'
Expand All @@ -24,9 +25,7 @@ export type SPLIT_UPDATE = 'SPLIT_UPDATE';
export type CONTROL = 'CONTROL';
export type OCCUPANCY = 'OCCUPANCY';

export type STREAMING_RESET = 'STREAMING_RESET';

export type IPushEvent = PUSH_SUBSYSTEM_UP | PUSH_SUBSYSTEM_DOWN | PUSH_NONRETRYABLE_ERROR | PUSH_RETRYABLE_ERROR | MY_SEGMENTS_UPDATE | MY_SEGMENTS_UPDATE_V2 | SEGMENT_UPDATE | SPLIT_UPDATE | SPLIT_KILL | STREAMING_RESET
export type IPushEvent = PUSH_SUBSYSTEM_UP | PUSH_SUBSYSTEM_DOWN | PUSH_NONRETRYABLE_ERROR | PUSH_RETRYABLE_ERROR | MY_SEGMENTS_UPDATE | MY_SEGMENTS_UPDATE_V2 | SEGMENT_UPDATE | SPLIT_UPDATE | SPLIT_KILL | ControlType.STREAMING_RESET

type IParsedData<T extends IPushEvent> =
T extends MY_SEGMENTS_UPDATE ? IMySegmentsUpdateData :
Expand Down
2 changes: 1 addition & 1 deletion src/sync/submitters/metricsSyncTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ export function latenciesSyncTaskFactory(
): ISyncTask {

// don't retry metrics.
return submitterSyncTaskFactory(log, postMetricsLatencies, latenciesCache, metricsRefreshRate, 'latency metrics', latencyTracker, fromCache<number[]>('latencies'));
return submitterSyncTaskFactory(log, postMetricsLatencies, latenciesCache, metricsRefreshRate, 'latency metrics', latencyTracker, fromCache<number[]>('latencies'), 0, true);
}
3 changes: 2 additions & 1 deletion src/sync/submitters/submitterSyncTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export function submitterSyncTaskFactory<TState extends { length?: number }>(
latencyTracker?: ITimeTracker,
fromCacheToPayload?: (cacheData: TState) => any,
maxRetries: number = 0,
debugLogs?: boolean
): ISyncTask<[], void> {

let retries = 0;
Expand All @@ -27,7 +28,7 @@ export function submitterSyncTaskFactory<TState extends { length?: number }>(
const data = sourceCache.state();

const dataCount: number | '' = typeof data.length === 'number' ? data.length : '';
log.info(SUBMITTERS_PUSH, [dataCount, dataName]);
log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCount, dataName]);
const latencyTrackerStop = latencyTracker && latencyTracker.start();

const jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data);
Expand Down
6 changes: 3 additions & 3 deletions src/utils/settingsValidation/__tests__/splitFilters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_F

describe('validateSplitFilters', () => {

let defaultOutput = {
const defaultOutput = {
validFilters: [],
queryString: null,
groupedFilters: { byName: [], byPrefix: [] }
Expand All @@ -38,11 +38,11 @@ describe('validateSplitFilters', () => {

test('Returns object with null queryString, if `splitFilters` contains invalid filters or contains filters with no values or invalid values', () => {

let splitFilters: any[] = [
const splitFilters: any[] = [
{ type: 'byName', values: [] },
{ type: 'byName', values: [] },
{ type: 'byPrefix', values: [] }];
let output = {
const output = {
validFilters: [...splitFilters],
queryString: null,
groupedFilters: { byName: [], byPrefix: [] }
Expand Down
1 change: 0 additions & 1 deletion src/utils/settingsValidation/splitFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode:
}

// Validate filters and group their values by filter type inside `groupedFilters` object
// Assign the valid filters to the output of the validator by using filter function
res.validFilters = maybeSplitFilters.filter((filter, index) => {
if (filter && validateFilterType(filter.type) && Array.isArray(filter.values)) {
res.groupedFilters[filter.type as SplitIO.SplitFilterType] = res.groupedFilters[filter.type as SplitIO.SplitFilterType].concat(filter.values);
Expand Down