Skip to content

Commit

Permalink
Merge branch 'development' into include-default-treatment-in-split-view
Browse files Browse the repository at this point in the history
  • Loading branch information
nthorn552 authored Jul 13, 2023
2 parents a7f8cc3 + aa82069 commit 0bd7a5a
Show file tree
Hide file tree
Showing 19 changed files with 2,084 additions and 1,885 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.8.3 (June 29, 2023)
- Updated some transitive dependencies for vulnerability fixes.
- Updated SDK_READY_TIMED_OUT event to be emitted immediately when a connection error occurs using pluggable storage (i.e., when the wrapper `connect` promise is rejected) in consumer and partial consumer modes.
- Bugfix - The `destroy` method has been updated to immediately flag the SDK client as destroyed, to prevent unexpected behaviours when `getTreatment` and `track` methods are called synchronously after `destroy` method is called.

1.8.2 (May 15, 2023)
- Updated terminology on the SDKs codebase to be more aligned with current standard without causing a breaking change. The core change is the term split for feature flag on things like logs and IntelliSense comments.
- Updated split storage modules to optimize some operations when using Redis and pluggable storages.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Split has built and maintains SDKs for:

* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK)
* Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK)
* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities)
* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK)
* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK)
* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK)
Expand Down
3,824 changes: 1,994 additions & 1,830 deletions package-lock.json

Large diffs are not rendered by default.

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": "1.8.2",
"version": "1.8.3",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
55 changes: 40 additions & 15 deletions src/readiness/__tests__/readinessManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ test('READINESS MANAGER / Ready event should be fired once', () => {
expect(counter).toBe(1); // should be called once
});

test('READINESS MANAGER / Ready event should be fired once', (done) => {
test('READINESS MANAGER / Ready from cache event should be fired once', (done) => {
const readinessManager = readinessManagerFactory(EventEmitter);
let counter = 0;

Expand Down Expand Up @@ -143,26 +143,51 @@ test('READINESS MANAGER / Segment updates should not be propagated', (done) => {
});
});

test('READINESS MANAGER / Timeout ready event', (done) => {
const readinessManager = readinessManagerFactory(EventEmitter, 10);
describe('READINESS MANAGER / Timeout ready event', () => {
let readinessManager: IReadinessManager;
let timeoutCounter: number;

let timeoutCounter = 0;
beforeEach(() => {
// Schedule timeout to be fired before SDK_READY
readinessManager = readinessManagerFactory(EventEmitter, 10);
timeoutCounter = 0;

readinessManager.gate.on(SDK_READY_TIMED_OUT, () => {
expect(readinessManager.hasTimedout()).toBe(true);
if (!readinessManager.isReady()) timeoutCounter++;
readinessManager.gate.on(SDK_READY_TIMED_OUT, () => {
expect(readinessManager.hasTimedout()).toBe(true);
if (!readinessManager.isReady()) timeoutCounter++;
});

setTimeout(() => {
readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
}, 20);
});

readinessManager.gate.on(SDK_READY, () => {
expect(readinessManager.isReady()).toBe(true);
expect(timeoutCounter).toBe(1); // Timeout was scheduled to be fired quickly
done();
test('should be fired once', (done) => {
readinessManager.gate.on(SDK_READY, () => {
expect(readinessManager.isReady()).toBe(true);
expect(timeoutCounter).toBe(1);
done();
});

readinessManager.gate.on(SDK_READY_TIMED_OUT, () => {
// Calling timeout again should not re-trigger the event
readinessManager.timeout();
setTimeout(readinessManager.timeout);
});
});

setTimeout(() => {
readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
}, 50);
test('should be fired once if called explicitly', (done) => {
readinessManager.gate.on(SDK_READY, () => {
expect(readinessManager.isReady()).toBe(true);
expect(timeoutCounter).toBe(1);
done();
});

// Calling timeout multiple times triggers the event only once
readinessManager.timeout();
setTimeout(readinessManager.timeout);
});
});

test('READINESS MANAGER / Cancel timeout if ready fired', (done) => {
Expand Down
14 changes: 10 additions & 4 deletions src/readiness/readinessManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ export function readinessManagerFactory(

// emit SDK_READY_TIMED_OUT
let hasTimedout = false;

function timeout() {
if (hasTimedout) return;
hasTimedout = true;
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
}

let readyTimeoutId: ReturnType<typeof setTimeout>;
if (readyTimeout > 0) {
readyTimeoutId = setTimeout(() => {
hasTimedout = true;
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
}, readyTimeout);
readyTimeoutId = setTimeout(timeout, readyTimeout);
}

// emit SDK_READY and SDK_UPDATE
Expand Down Expand Up @@ -108,6 +112,8 @@ export function readinessManagerFactory(
return readinessManagerFactory(EventEmitter, readyTimeout, splits);
},

timeout,

destroy() {
isDestroyed = true;

Expand Down
1 change: 1 addition & 0 deletions src/readiness/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface IReadinessManager {
isDestroyed(): boolean,
isOperational(): boolean,

timeout(): void,
destroy(): void,

/** for client-side */
Expand Down
6 changes: 3 additions & 3 deletions src/sdkClient/clientAttributesDecoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
},

/**
* Returns the attribute with the given key
* Returns the attribute with the given name
*
* @param {string} attributeName Attribute name
* @returns {Object} Attribute with the given key
* @returns {Object} Attribute with the given name
*/
getAttribute(attributeName: string) {
log.debug(`retrieved attribute ${attributeName}`);
Expand Down Expand Up @@ -100,7 +100,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
},

/**
* Removes from client's in memory attributes storage the attribute with the given key
* Removes from client's in memory attributes storage the attribute with the given name
*
* @param {string} attributeName
* @returns {boolean} true if attribute was removed and false otherways
Expand Down
8 changes: 4 additions & 4 deletions src/sdkClient/clientInputValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
const key = validateKey(log, maybeKey, methodName);
const splitOrSplits = multi ? validateSplits(log, maybeFeatureFlagNameOrNames, methodName) : validateSplit(log, maybeFeatureFlagNameOrNames, methodName);
const attributes = validateAttributes(log, maybeAttributes, methodName);
const isOperational = validateIfNotDestroyed(log, readinessManager, methodName);
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);

validateIfOperational(log, readinessManager, methodName);

const valid = isOperational && key && splitOrSplits && attributes !== false;
const valid = isNotDestroyed && key && splitOrSplits && attributes !== false;

return {
valid,
Expand Down Expand Up @@ -105,9 +105,9 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
const event = validateEvent(log, maybeEvent, 'track');
const eventValue = validateEventValue(log, maybeEventValue, 'track');
const { properties, size } = validateEventProperties(log, maybeProperties, 'track');
const isOperational = validateIfNotDestroyed(log, readinessManager, 'track');
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, 'track');

if (isOperational && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
if (isNotDestroyed && key && tt && event && eventValue !== false && properties !== false) { // @ts-expect-error
return client.track(key, tt, event, eventValue, properties, size);
} else {
return isSync ? false : Promise.resolve(false);
Expand Down
5 changes: 4 additions & 1 deletion src/sdkClient/sdkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
},
destroy() {
// Mark the SDK as destroyed immediately
sdkReadinessManager.readinessManager.destroy();

// record stat before flushing data
if (!isSharedClient) telemetryTracker.sessionLength();

Expand All @@ -61,12 +64,12 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo

return __flush().then(() => {
// Cleanup event listeners
sdkReadinessManager.readinessManager.destroy();
signalListener && signalListener.stop();

// Release the SDK Key if it is the main client
if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);

// @TODO stop only if last client is destroyed
if (uniqueKeysTracker) uniqueKeysTracker.stop();

// Cleanup storage
Expand Down
5 changes: 4 additions & 1 deletion src/sdkClient/sdkClientMethodCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl

const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => {
if (err) return;
if (err) {
sharedSdkReadiness.readinessManager.timeout();
return;
}
// Emit SDK_READY in consumer mode for shared clients
sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
});
Expand Down
5 changes: 4 additions & 1 deletion src/sdkClient/sdkClientMethodCSWithTT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl

const sharedSdkReadiness = sdkReadinessManager.shared(readyTimeout);
const sharedStorage = storage.shared && storage.shared(matchingKey, (err) => {
if (err) return;
if (err) {
sharedSdkReadiness.readinessManager.timeout();
return;
}
// Emit SDK_READY in consumer mode for shared clients
sharedSdkReadiness.readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
});
Expand Down
6 changes: 5 additions & 1 deletion src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
const storage = storageFactory({
settings,
onReadyCb: (error) => {
if (error) return; // Don't emit SDK_READY if storage failed to connect. Error message is logged by wrapperAdapter
if (error) {
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
readiness.timeout();
return;
}
readiness.splits.emit(SDK_SPLITS_ARRIVED);
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
},
Expand Down
2 changes: 1 addition & 1 deletion src/storages/pluggable/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function PluggableStorage(options: PluggableStorageOptions): IStorageAsyn
}).catch((e) => {
e = e || new Error('Error connecting wrapper');
onReadyCb(e);
return e;
return e; // Propagate error for shared clients
});

return {
Expand Down
1 change: 0 additions & 1 deletion src/sync/polling/updaters/segmentChangesUpdater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ export function segmentChangesUpdaterFactory(

for (let index = 0; index < segmentNames.length; index++) {
updaters.push(updateSegment(segmentNames[index], noCache, till, fetchOnlyNew));

}

return Promise.all(updaters).then(shouldUpdateFlags => {
Expand Down
19 changes: 2 additions & 17 deletions src/sync/streaming/pushManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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, ControlType } from './constants';
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';
import { isInBitmap, parseBitmap, parseFFUpdatePayload, parseKeyList } from './parseUtils';
import { isInBitmap, parseBitmap, parseKeyList } from './parseUtils';
import { ISet, _Set } from '../../utils/lang/sets';
import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
import { IAuthTokenPushEnabled } from './AuthClient/types';
Expand Down Expand Up @@ -221,22 +221,7 @@ export function pushManagerFactory(
/** Functions related to synchronization (Queues and Workers in the spec) */

pushEmitter.on(SPLIT_KILL, splitsUpdateWorker.killSplit);
pushEmitter.on(SPLIT_UPDATE, (parsedData) => {
if (parsedData.d && parsedData.c !== undefined) {
try {
const payload = parseFFUpdatePayload(parsedData.c, parsedData.d);
if (payload) {
// @TODO replace splitsUpdateWorker.put method with instant ff processor and updater
// splitsUpdateWorker.putWithPayload(payload);
// return;
}
} catch (e) {
// @TODO define a error code for feature flags parsing
log.debug(e);
}
}
splitsUpdateWorker.put(parsedData);
});
pushEmitter.on(SPLIT_UPDATE, splitsUpdateWorker.put);

if (userKey) {
pushEmitter.on(MY_SEGMENTS_UPDATE, function handleMySegmentsUpdate(parsedData, channel) {
Expand Down
2 changes: 1 addition & 1 deletion src/trackers/eventTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function eventTrackerFactory(
setTimeout(function () {
// copy of event, to avoid unexpected behaviour if modified by integrations
const eventDataCopy = objectAssign({}, eventData);
if (eventData.properties) eventDataCopy.properties = objectAssign({}, eventData.properties);
if (properties) eventDataCopy.properties = objectAssign({}, properties);
// integrationsManager does not throw errors (they are internally handled by each integration module)
integrationsManager.handleEvent(eventDataCopy);
}, 0);
Expand Down
2 changes: 1 addition & 1 deletion src/trackers/impressionsTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function impressionsTrackerFactory(

const impressionsToListenerCount = impressionsToListener.length;

if ( impressionsToStore.length>0 ){
if (impressionsToStore.length > 0) {
const res = impressionsCache.track(impressionsToStore);

// If we're on an async storage, handle error and log it.
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,14 +1193,14 @@ export namespace SplitIO {
*/
setAttribute(attributeName: string, attributeValue: AttributeType): boolean,
/**
* Returns the attribute with the given key
* Returns the attribute with the given name
*
* @param {string} attributeName Attribute name
* @returns {AttributeType} Attribute with the given key
* @returns {AttributeType} Attribute with the given name
*/
getAttribute(attributeName: string): AttributeType,
/**
* Removes from client's in memory attributes storage the attribute with the given key.
* Removes from client's in memory attributes storage the attribute with the given name.
*
* @param {string} attributeName
* @returns {boolean} true if attribute was removed and false otherwise
Expand Down

0 comments on commit 0bd7a5a

Please sign in to comment.