diff --git a/.eslintrc b/.eslintrc index abcc2c30..a6140fe5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -38,7 +38,8 @@ "@typescript-eslint/no-unused-vars": "error", "keyword-spacing": "error", "comma-style": "error", - "no-trailing-spaces": "error" + "no-trailing-spaces": "error", + "space-before-function-paren": ["error", {"named": "never"}] }, "overrides": [ diff --git a/CHANGES.txt b/CHANGES.txt index 4f11bbd5..b359b4d0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,39 +1,44 @@ +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. + - Updated some transitive dependencies for vulnerability fixes. + 1.8.1 (February 7, 2023) -- Updated a module import to remove a trailing comma that can cause issues with some bundlers. + - Updated a module import to remove a trailing comma that can cause issues with some bundlers. 1.8.0 (February 3, 2023) -- Added flush data method to client + - Added flush data method to client. 1.7.3 (December 16, 2022) -- Updated unique keys cache for Redis and Pluggable storages to optimize the usage of the underlying storage. -- Updated some transitive dependencies for vulnerability fixes. -- Bugfixing - Updated events and impressions cache in localhost mode in order to avoid memory leaks (Related to issue https://github.com/splitio/javascript-commons/issues/181). + - Updated unique keys cache for Redis and Pluggable storages to optimize the usage of the underlying storage. + - Updated some transitive dependencies for vulnerability fixes. + - Bugfixing - Updated events and impressions cache in localhost mode in order to avoid memory leaks (Related to issue https://github.com/splitio/javascript-commons/issues/181). 1.7.2 (October 14, 2022) -- Bugfixing - Handle `Navigator.sendBeacon` API exceptions in the browser, and fallback to regular Fetch/XHR transport in case of error. + - Bugfixing - Handle `Navigator.sendBeacon` API exceptions in the browser, and fallback to regular Fetch/XHR transport in case of error. 1.7.1 (October 5, 2022) -- Updated default value of `scheduler.featuresRefreshRate` config parameter to 60 seconds. + - Updated default value of `scheduler.featuresRefreshRate` config parameter to 60 seconds. 1.7.0 (October 4, 2022) -- Added a new impressions mode for the SDK called NONE, to be used in factory when there is no desire to capture impressions on an SDK factory to feed Split's analytics engine. Running NONE mode, the SDK will only capture unique keys evaluated for a particular feature flag instead of full blown impressions. -- Updated SDK telemetry to support pluggable storage, partial consumer mode, and synchronizer. -- Updated storage implementations to improve the performance of split evaluations (i.e., `getTreatment(s)` method calls) when using the default storage in memory. -- Updated evaluation flow to avoid unnecessarily storage calls when the SDK is not ready. + - Added a new impressions mode for the SDK called NONE, to be used in factory when there is no desire to capture impressions on an SDK factory to feed Split's analytics engine. Running NONE mode, the SDK will only capture unique keys evaluated for a particular feature flag instead of full blown impressions. + - Updated SDK telemetry to support pluggable storage, partial consumer mode, and synchronizer. + - Updated storage implementations to improve the performance of feature flag evaluations (i.e., `getTreatment(s)` method calls) when using the default storage in memory. + - Updated evaluation flow to avoid unnecessarily storage calls when the SDK is not ready. 1.6.1 (July 22, 2022) -- Updated GoogleAnalyticsToSplit integration to validate `autoRequire` config parameter and avoid some wrong warning logs when mapping GA hit fields to Split event properties. + - Updated GoogleAnalyticsToSplit integration to validate `autoRequire` config parameter and avoid some wrong warning logs when mapping GA hit fields to Split event properties. 1.6.0 (July 21, 2022) -- Added `autoRequire` configuration option to the Google Analytics to Split integration, which takes care of requiring the splitTracker plugin on trackers dynamically created by Google tag managers (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js). -- Updated browser listener to push remaining impressions and events on 'visibilitychange' and 'pagehide' DOM events, instead of 'unload', which is not reliable in modern mobile and desktop Web browsers. -- Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced. -- Bugfixing - Removed js-yaml dependency to avoid resolution to an incompatible version on certain npm versions when installing third-party dependencies that also define js-yaml as transitive dependency (Related to issue https://github.com/splitio/javascript-client/issues/662). + - Added `autoRequire` configuration option to the Google Analytics to Split integration, which takes care of requiring the splitTracker plugin on trackers dynamically created by Google tag managers (See https://help.split.io/hc/en-us/articles/360040838752#set-up-with-gtm-and-gtag.js). + - Updated browser listener to push remaining impressions and events on 'visibilitychange' and 'pagehide' DOM events, instead of 'unload', which is not reliable in modern mobile and desktop Web browsers. + - Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced. + - Bugfixing - Removed js-yaml dependency to avoid resolution to an incompatible version on certain npm versions when installing third-party dependencies that also define js-yaml as transitive dependency (Related to issue https://github.com/splitio/javascript-client/issues/662). 1.5.0 (June 29, 2022) -- Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online Split will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used. -- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown. -- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running. + - Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online, Split SDK will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used. + - Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown. + - Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running. 1.4.1 (June 13, 2022) - Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending. @@ -81,7 +86,7 @@ - Updated dependencies for vulnerability fixes. 0.1.0 (March 30, 2021) - - Initial public release. It includes common modules to be consumed by the different Split implementations written in JavaScript. Based on the original JS SDK in the `javascript-client` repository. + - Initial public release. It includes common modules to be consumed by the different Split SDK implementations written in JavaScript. Based on the original JS SDK in the `javascript-client` repository. - It's designed with a modular approach, with the following goals in mind: - Dependents should be able to include the modules that are needed for, as an example, a storage. - Dependents should be able to use the module that's specific for their runtime environment, allowing for better usage of native APIs as well as to build optimizations targeted by each platform. diff --git a/README.md b/README.md index 7d6d7a13..76da352b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![npm version](https://badge.fury.io/js/%40splitsoftware%2Fsplitio-commons.svg)](https://badge.fury.io/js/%40splitsoftware%2Fsplitio-commons) [![Build Status](https://github.com/splitio/javascript-commons/actions/workflows/ci.yml/badge.svg)](https://github.com/splitio/javascript-commons/actions/workflows/ci.yml) ## Overview -This library is designed to work with Split, the platform for controlled rollouts, which serves features to your users via a Split feature flag to manage your complete customer experience. +This library is designed to work with Split, the platform for controlled rollouts, which serves features to your users via feature flags to manage your complete customer experience. [![Twitter Follow](https://img.shields.io/twitter/follow/splitsoftware.svg?style=social&label=Follow&maxAge=1529000)](https://twitter.com/intent/follow?screen_name=splitsoftware) diff --git a/package-lock.json b/package-lock.json index 1b75f969..96e25423 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.8.1", + "version": "1.8.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "1.8.1", + "version": "1.8.2", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.1" diff --git a/package.json b/package.json index 45845999..6bc6e0e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.8.1", + "version": "1.8.2", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/integrations/ga/__tests__/GaToSplit.spec.ts b/src/integrations/ga/__tests__/GaToSplit.spec.ts index 3c3196e9..1417c6a1 100644 --- a/src/integrations/ga/__tests__/GaToSplit.spec.ts +++ b/src/integrations/ga/__tests__/GaToSplit.spec.ts @@ -101,7 +101,7 @@ test('defaultMapper', () => { }); const coreSettings = { - authorizationKey: 'apikey', + authorizationKey: 'sdkkey', key: 'key', trafficType: 'user', } as ISettings['core']; diff --git a/src/integrations/ga/__tests__/gaMock.ts b/src/integrations/ga/__tests__/gaMock.ts index a16f3c2e..2a72863d 100644 --- a/src/integrations/ga/__tests__/gaMock.ts +++ b/src/integrations/ga/__tests__/gaMock.ts @@ -1,9 +1,9 @@ export function modelMock(fieldsObject: UniversalAnalytics.FieldsObject) { return { - get (fieldName: string) { + get(fieldName: string) { return fieldsObject[fieldName as keyof UniversalAnalytics.FieldsObject]; }, - set (fieldNameOrObject: string | {}, fieldValue?: any) { + set(fieldNameOrObject: string | {}, fieldValue?: any) { if (typeof fieldNameOrObject === 'object') fieldsObject = { ...fieldsObject, ...fieldNameOrObject }; else diff --git a/src/logger/constants.ts b/src/logger/constants.ts index 1837e5fa..85c449a7 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -94,7 +94,7 @@ export const WARN_INTEGRATION_INVALID = 218; export const WARN_SPLITS_FILTER_IGNORED = 219; export const WARN_SPLITS_FILTER_INVALID = 220; export const WARN_SPLITS_FILTER_EMPTY = 221; -export const WARN_API_KEY = 222; +export const WARN_SDK_KEY = 222; export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223; export const ERROR_ENGINE_COMBINER_IFELSEIF = 300; @@ -136,7 +136,7 @@ export const LOG_PREFIX_SYNC = 'sync'; export const LOG_PREFIX_SYNC_MANAGER = LOG_PREFIX_SYNC + ':sync-manager: '; export const LOG_PREFIX_SYNC_OFFLINE = LOG_PREFIX_SYNC + ':offline: '; export const LOG_PREFIX_SYNC_STREAMING = LOG_PREFIX_SYNC + ':streaming: '; -export const LOG_PREFIX_SYNC_SPLITS = LOG_PREFIX_SYNC + ':split-changes: '; +export const LOG_PREFIX_SYNC_SPLITS = LOG_PREFIX_SYNC + ':featureflag-changes: '; export const LOG_PREFIX_SYNC_SEGMENTS = LOG_PREFIX_SYNC + ':segment-changes: '; export const LOG_PREFIX_SYNC_MYSEGMENTS = LOG_PREFIX_SYNC + ':my-segments: '; export const LOG_PREFIX_SYNC_POLLING = LOG_PREFIX_SYNC + ':polling-manager: '; diff --git a/src/logger/messages/debug.ts b/src/logger/messages/debug.ts index c57ea6e0..bca703bb 100644 --- a/src/logger/messages/debug.ts +++ b/src/logger/messages/debug.ts @@ -13,8 +13,8 @@ export const codesDebug: [number, string][] = codesInfo.concat([ [c.ENGINE_MATCHER_CONTAINS_ALL, c.LOG_PREFIX_ENGINE_MATCHER + '[containsAllMatcher] %s contains all elements of %s? %s'], [c.ENGINE_MATCHER_CONTAINS_ANY, c.LOG_PREFIX_ENGINE_MATCHER + '[containsAnyMatcher] %s contains at least an element of %s? %s'], [c.ENGINE_MATCHER_CONTAINS_STRING, c.LOG_PREFIX_ENGINE_MATCHER + '[containsStringMatcher] %s contains %s? %s'], - [c.ENGINE_MATCHER_DEPENDENCY, c.LOG_PREFIX_ENGINE_MATCHER + '[dependencyMatcher] parent split "%s" evaluated to "%s" with label "%s". %s evaluated treatment is part of [%s] ? %s.'], - [c.ENGINE_MATCHER_DEPENDENCY_PRE, c.LOG_PREFIX_ENGINE_MATCHER + '[dependencyMatcher] will evaluate parent split: "%s" with key: %s %s'], + [c.ENGINE_MATCHER_DEPENDENCY, c.LOG_PREFIX_ENGINE_MATCHER + '[dependencyMatcher] parent feature flag "%s" evaluated to "%s" with label "%s". %s evaluated treatment is part of [%s] ? %s.'], + [c.ENGINE_MATCHER_DEPENDENCY_PRE, c.LOG_PREFIX_ENGINE_MATCHER + '[dependencyMatcher] will evaluate parent feature flag: "%s" with key: %s %s'], [c.ENGINE_MATCHER_EQUAL, c.LOG_PREFIX_ENGINE_MATCHER + '[equalToMatcher] is %s equal to %s? %s'], [c.ENGINE_MATCHER_EQUAL_TO_SET, c.LOG_PREFIX_ENGINE_MATCHER + '[equalToSetMatcher] is %s equal to set %s? %s'], [c.ENGINE_MATCHER_ENDS_WITH, c.LOG_PREFIX_ENGINE_MATCHER + '[endsWithMatcher] %s ends with %s? %s'], @@ -35,15 +35,15 @@ export const codesDebug: [number, string][] = codesInfo.concat([ [c.RETRIEVE_CLIENT_EXISTING, 'Retrieving existing SDK client.'], [c.RETRIEVE_MANAGER, 'Retrieving manager instance.'], // synchronizer - [c.SYNC_OFFLINE_DATA, c.LOG_PREFIX_SYNC_OFFLINE + 'Splits data: \n%s'], - [c.SYNC_SPLITS_FETCH, c.LOG_PREFIX_SYNC_SPLITS + 'Spin up split update using since = %s'], - [c.SYNC_SPLITS_NEW, c.LOG_PREFIX_SYNC_SPLITS + 'New splits %s'], - [c.SYNC_SPLITS_REMOVED, c.LOG_PREFIX_SYNC_SPLITS + 'Removed splits %s'], + [c.SYNC_OFFLINE_DATA, c.LOG_PREFIX_SYNC_OFFLINE + 'Feature flags data: \n%s'], + [c.SYNC_SPLITS_FETCH, c.LOG_PREFIX_SYNC_SPLITS + 'Spin up feature flags update using since = %s'], + [c.SYNC_SPLITS_NEW, c.LOG_PREFIX_SYNC_SPLITS + 'New feature flags %s'], + [c.SYNC_SPLITS_REMOVED, c.LOG_PREFIX_SYNC_SPLITS + 'Removed feature flags %s'], [c.SYNC_SPLITS_SEGMENTS, c.LOG_PREFIX_SYNC_SPLITS + 'Segment names collected %s'], [c.STREAMING_NEW_MESSAGE, c.LOG_PREFIX_SYNC_STREAMING + 'New SSE message received, with data: %s.'], [c.SYNC_TASK_START, c.LOG_PREFIX_SYNC + ': Starting %s. Running each %s millis'], [c.SYNC_TASK_EXECUTE, c.LOG_PREFIX_SYNC + ': Running %s'], [c.SYNC_TASK_STOP, c.LOG_PREFIX_SYNC + ': Stopping %s'], // initialization / settings validation - [c.SETTINGS_SPLITS_FILTER, c.LOG_PREFIX_SETTINGS + ': splits filtering criteria is "%s".'] + [c.SETTINGS_SPLITS_FILTER, c.LOG_PREFIX_SETTINGS + ': feature flags filtering criteria is "%s".'] ]); diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts index 1eebbbd7..62691a72 100644 --- a/src/logger/messages/error.ts +++ b/src/logger/messages/error.ts @@ -2,7 +2,7 @@ import * as c from '../constants'; export const codesError: [number, string][] = [ // evaluator - [c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid Split, no valid rules found'], + [c.ERROR_ENGINE_COMBINER_IFELSEIF, c.LOG_PREFIX_ENGINE_COMBINER + 'Invalid feature flag, no valid rules found'], // SDK [c.ERROR_LOGLEVEL_INVALID, 'logger: Invalid Log Level - No changes to the logs will be applied.'], [c.ERROR_CLIENT_CANNOT_GET_READY, 'The SDK will not get ready. Reason: %s'], @@ -10,7 +10,7 @@ export const codesError: [number, string][] = [ [c.ERROR_IMPRESSIONS_LISTENER, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Impression listener logImpression method threw: %s.'], [c.ERROR_EVENTS_TRACKER, c.LOG_PREFIX_EVENTS_TRACKER + 'Failed to queue %s'], // synchronizer - [c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock Splits data, no changes will be applied to the current cache. %s'], + [c.ERROR_SYNC_OFFLINE_LOADING, c.LOG_PREFIX_SYNC_OFFLINE + 'There was an issue loading the mock feature flags data. No changes will be applied to the current cache. %s'], [c.ERROR_STREAMING_SSE, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to connect or error on streaming connection, with error message: %s'], [c.ERROR_STREAMING_AUTH, c.LOG_PREFIX_SYNC_STREAMING + 'Failed to authenticate for streaming. Error: %s.'], [c.ERROR_HTTP, 'Response status is not OK. Status: %s. URL: %s. Message: %s'], diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index 169e73ea..a7b62171 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -8,7 +8,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.CLIENT_READY_FROM_CACHE, READY_MSG + ' from cache'], [c.CLIENT_READY, READY_MSG], // SDK - [c.IMPRESSION, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Split: %s. Key: %s. Evaluation: %s. Label: %s'], + [c.IMPRESSION, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Feature flag: %s. Key: %s. Evaluation: %s. Label: %s'], [c.IMPRESSION_QUEUEING, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Queueing corresponding impression.'], [c.NEW_SHARED_CLIENT, 'New shared client instance created.'], [c.NEW_FACTORY, 'New Split SDK instance created.'], @@ -22,13 +22,13 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.POLLING_SMART_PAUSING, c.LOG_PREFIX_SYNC_POLLING + 'Turning segments data polling %s.'], [c.POLLING_START, c.LOG_PREFIX_SYNC_POLLING + 'Starting polling'], [c.POLLING_STOP, c.LOG_PREFIX_SYNC_POLLING + 'Stopping polling'], - [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of splits #%s. Reason: %s'], + [c.SYNC_SPLITS_FETCH_RETRY, c.LOG_PREFIX_SYNC_SPLITS + 'Retrying download of feature flags #%s. Reason: %s'], [c.SUBMITTERS_PUSH_FULL_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full %s queue and reseting timer.'], [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s.'], [c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'], [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'], [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting streaming.'], - [c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given Api key. Switching to polling mode.'], + [c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given SDK key. Switching to polling mode.'], [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'], [c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'], [c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'], diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 3b3f984e..8965ae4b 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -7,7 +7,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.ENGINE_VALUE_NO_ATTRIBUTES, c.LOG_PREFIX_ENGINE_VALUE + 'Defined attribute [%s], no attributes received.'], // synchronizer [c.SYNC_MYSEGMENTS_FETCH_RETRY, c.LOG_PREFIX_SYNC_MYSEGMENTS + 'Retrying download of segments #%s. Reason: %s'], - [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of Splits. %s'], + [c.SYNC_SPLITS_FETCH_FAILS, c.LOG_PREFIX_SYNC_SPLITS + 'Error while doing fetch of feature flags. %s'], [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'], [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'], [c.STREAMING_FALLBACK, c.LOG_PREFIX_SYNC_STREAMING + 'Falling back to polling mode. Reason: %s'], @@ -21,15 +21,15 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.WARN_TRIMMING_PROPERTIES, '%s: Event has more than 300 properties. Some of them will be trimmed when processed.'], [c.WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'], [c.WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'], - [c.WARN_NOT_EXISTENT_SPLIT, '%s: split "%s" does not exist in this environment, please double check what splits exist in the Split web console.'], + [c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'], [c.WARN_LOWERCASE_TRAFFIC_TYPE, '%s: traffic_type_name should be all lowercase - converting string to lowercase.'], - [c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding split in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split web console.'], + [c.WARN_NOT_EXISTENT_TT, '%s: traffic type "%s" does not have any corresponding feature flag in this environment, make sure you\'re tracking your events to a valid traffic type defined in the Split user interface.'], // initialization / settings validation [c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'], - [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': split filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'], - [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': split filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'], - [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': splitFilters configuration must be a non-empty array of filter objects.'], - [c.WARN_API_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'], + [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': feature flag filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'], + [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'], + [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': feature flag filter configuration must be a non-empty array of filter objects.'], + [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'], [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'], ]); diff --git a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts index f5c49f96..45fe70ff 100644 --- a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts +++ b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts @@ -3,16 +3,16 @@ import { loggerMock } from '../../logger/__tests__/sdkLogger.mock'; // mocked methods return the provided attributes object (2nd argument), to assert that it was properly passed const clientMock = { - getTreatment(maybeKey: any, maybeSplit: string, maybeAttributes?: any) { + getTreatment(maybeKey: any, maybeFeatureFlagName: string, maybeAttributes?: any) { return maybeAttributes; }, - getTreatmentWithConfig(maybeKey: any, maybeSplit: string, maybeAttributes?: any) { + getTreatmentWithConfig(maybeKey: any, maybeFeatureFlagName: string, maybeAttributes?: any) { return maybeAttributes; }, - getTreatments(maybeKey: any, maybeSplits: string[], maybeAttributes?: any) { + getTreatments(maybeKey: any, maybeFeatureFlagNames: string[], maybeAttributes?: any) { return maybeAttributes; }, - getTreatmentsWithConfig(maybeKey: any, maybeSplits: string[], maybeAttributes?: any) { + getTreatmentsWithConfig(maybeKey: any, maybeFeatureFlagNames: string[], maybeAttributes?: any) { return maybeAttributes; } }; @@ -75,7 +75,9 @@ describe('ATTRIBUTES DECORATION / validation', () => { test('Should return false if it is an invalid attributes map', () => { expect(client.setAttribute('', 'attributeValue')).toEqual(false); // It should be invalid if the attribute key is not a string + // @ts-expect-error expect(client.setAttribute('attributeKey1', new Date())).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. + // @ts-expect-error expect(client.setAttribute('attributeKey2', { 'some': 'object' })).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. expect(client.setAttribute('attributeKey3', Infinity)).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. diff --git a/src/sdkClient/__tests__/sdkClientMethod.spec.ts b/src/sdkClient/__tests__/sdkClientMethod.spec.ts index 4e3724db..27be5258 100644 --- a/src/sdkClient/__tests__/sdkClientMethod.spec.ts +++ b/src/sdkClient/__tests__/sdkClientMethod.spec.ts @@ -13,7 +13,7 @@ const paramMocks = [ syncManager: undefined, sdkReadinessManager: { sdkStatus: jest.fn(), readinessManager: { destroy: jest.fn() } }, signalListener: undefined, - settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'api key '} }, + settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory() }, // SyncManager (i.e., Sync SDK) and Signal listener @@ -22,7 +22,7 @@ const paramMocks = [ syncManager: { stop: jest.fn(), flush: jest.fn(() => Promise.resolve()) }, sdkReadinessManager: { sdkStatus: jest.fn(), readinessManager: { destroy: jest.fn() } }, signalListener: { stop: jest.fn() }, - settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'api key '} }, + settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} }, telemetryTracker: telemetryTrackerFactory() } ]; diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index f13e3b8b..11036519 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -13,10 +13,10 @@ import { isStorageSync } from '../trackers/impressionObserver/utils'; const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY }; -function treatmentsNotReady(splitNames: string[]) { +function treatmentsNotReady(featureFlagNames: string[]) { const evaluations: Record = {}; - splitNames.forEach(splitName => { - evaluations[splitName] = treatmentNotReady; + featureFlagNames.forEach(featureFlagName => { + evaluations[featureFlagName] = treatmentNotReady; }); return evaluations; } @@ -28,12 +28,12 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker, telemetryTracker } = params; const { log, mode } = settings; - function getTreatment(key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, withConfig = false) { + function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes: SplitIO.Attributes | undefined, withConfig = false) { const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT); const wrapUp = (evaluationResult: IEvaluationResult) => { const queue: ImpressionDTO[] = []; - const treatment = processEvaluation(evaluationResult, splitName, key, attributes, withConfig, `getTreatment${withConfig ? 'withConfig' : ''}`, queue); + const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, `getTreatment${withConfig ? 'withConfig' : ''}`, queue); impressionsTracker.track(queue, attributes); stopTelemetryTracker(queue[0] && queue[0].label); @@ -41,7 +41,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl }; const evaluation = readinessManager.isReady() || readinessManager.isReadyFromCache() ? - evaluateFeature(log, key, splitName, attributes, storage) : + evaluateFeature(log, key, featureFlagName, attributes, storage) : isStorageSync(settings) ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected treatmentNotReady : Promise.resolve(treatmentNotReady); // Promisify if async @@ -49,18 +49,18 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return thenable(evaluation) ? evaluation.then((res) => wrapUp(res)) : wrapUp(evaluation); } - function getTreatmentWithConfig(key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined) { - return getTreatment(key, splitName, attributes, true); + function getTreatmentWithConfig(key: SplitIO.SplitKey, featureFlagName: string, attributes: SplitIO.Attributes | undefined) { + return getTreatment(key, featureFlagName, attributes, true); } - function getTreatments(key: SplitIO.SplitKey, splitNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false) { + function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false) { const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS); const wrapUp = (evaluationResults: Record) => { const queue: ImpressionDTO[] = []; const treatments: Record = {}; - Object.keys(evaluationResults).forEach(splitName => { - treatments[splitName] = processEvaluation(evaluationResults[splitName], splitName, key, attributes, withConfig, `getTreatments${withConfig ? 'withConfig' : ''}`, queue); + Object.keys(evaluationResults).forEach(featureFlagName => { + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, `getTreatments${withConfig ? 'withConfig' : ''}`, queue); }); impressionsTracker.track(queue, attributes); @@ -69,22 +69,22 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl }; const evaluations = readinessManager.isReady() || readinessManager.isReadyFromCache() ? - evaluateFeatures(log, key, splitNames, attributes, storage) : + evaluateFeatures(log, key, featureFlagNames, attributes, storage) : isStorageSync(settings) ? // If the SDK is not ready, treatment may be incorrect due to having splits but not segments data, or storage is not connected - treatmentsNotReady(splitNames) : - Promise.resolve(treatmentsNotReady(splitNames)); // Promisify if async + treatmentsNotReady(featureFlagNames) : + Promise.resolve(treatmentsNotReady(featureFlagNames)); // Promisify if async return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations); } - function getTreatmentsWithConfig(key: SplitIO.SplitKey, splitNames: string[], attributes: SplitIO.Attributes | undefined) { - return getTreatments(key, splitNames, attributes, true); + function getTreatmentsWithConfig(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined) { + return getTreatments(key, featureFlagNames, attributes, true); } // Internal function function processEvaluation( evaluation: IEvaluationResult, - splitName: string, + featureFlagName: string, key: SplitIO.SplitKey, attributes: SplitIO.Attributes | undefined, withConfig: boolean, @@ -95,12 +95,12 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const bucketingKey = getBucketing(key); const { treatment, label, changeNumber, config = null } = evaluation; - log.info(IMPRESSION, [splitName, matchingKey, treatment, label]); + log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]); - if (validateSplitExistance(log, readinessManager, splitName, label, invokingMethodName)) { + if (validateSplitExistance(log, readinessManager, featureFlagName, label, invokingMethodName)) { log.info(IMPRESSION_QUEUEING); queue.push({ - feature: splitName, + feature: featureFlagName, keyName: matchingKey, treatment, time: Date.now(), diff --git a/src/sdkClient/clientAttributesDecoration.ts b/src/sdkClient/clientAttributesDecoration.ts index 8d160108..1f4afec5 100644 --- a/src/sdkClient/clientAttributesDecoration.ts +++ b/src/sdkClient/clientAttributesDecoration.ts @@ -18,20 +18,20 @@ export function clientAttributesDecoration = {}; attribute[attributeName] = attributeValue; if (!validateAttributesDeep(log, attribute, 'setAttribute')) return false; @@ -95,7 +95,7 @@ export function clientAttributesDecoration { + getAttributes() { return attributeStorage.getAll(); }, diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index de9f3594..b7fb98fd 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -30,10 +30,10 @@ export function clientInputValidationDecorator true).catch(() => false); diff --git a/src/services/splitHttpClient.ts b/src/services/splitHttpClient.ts index 25d70f0b..7eca4cc9 100644 --- a/src/services/splitHttpClient.ts +++ b/src/services/splitHttpClient.ts @@ -55,7 +55,7 @@ export function splitHttpClientFactory(settings: Pick { cache.addSplits([ // loop of addSplit ['split1', splitWithUserTT], ['split2', splitWithAccountTT], - ['split3', splitWithUserTT], // @ts-ignore - ['malformed', {}] + ['split3', splitWithUserTT], ]); cache.addSplit('split4', splitWithUserTT); diff --git a/src/storages/inMemory/AttributesCacheInMemory.ts b/src/storages/inMemory/AttributesCacheInMemory.ts index 0718294a..da7445a1 100644 --- a/src/storages/inMemory/AttributesCacheInMemory.ts +++ b/src/storages/inMemory/AttributesCacheInMemory.ts @@ -1,8 +1,9 @@ +import { SplitIO } from '../../types'; import { objectAssign } from '../../utils/lang/objectAssign'; export class AttributesCacheInMemory { - private attributesCache: Record = {}; + private attributesCache: Record = {}; /** @@ -12,7 +13,7 @@ export class AttributesCacheInMemory { * @param {Object} attributeValue attribute value * @returns {boolean} the attribute was stored */ - setAttribute(attributeName: string, attributeValue: Object): boolean { + setAttribute(attributeName: string, attributeValue: SplitIO.AttributeType) { this.attributesCache[attributeName] = attributeValue; return true; } @@ -23,7 +24,7 @@ export class AttributesCacheInMemory { * @param {string} attributeName attribute name * @returns {Object?} stored attribute value */ - getAttribute(attributeName: string): Object { + getAttribute(attributeName: string) { return this.attributesCache[attributeName]; } @@ -33,7 +34,7 @@ export class AttributesCacheInMemory { * @param {[string, Object]} attributes attributes to create or update * @returns {boolean} attributes were stored */ - setAttributes(attributes: Record): boolean { + setAttributes(attributes: Record) { this.attributesCache = objectAssign(this.attributesCache, attributes); return true; } @@ -43,7 +44,7 @@ export class AttributesCacheInMemory { * * @returns {Map} stored attributes */ - getAll(): Record { + getAll() { return this.attributesCache; } @@ -53,7 +54,7 @@ export class AttributesCacheInMemory { * @param {string} attributeName attribute to remove * @returns {boolean} attribute removed */ - removeAttribute(attributeName: string): boolean { + removeAttribute(attributeName: string) { if (Object.keys(this.attributesCache).indexOf(attributeName) >= 0) { delete this.attributesCache[attributeName]; return true; diff --git a/src/storages/inMemory/SplitsCacheInMemory.ts b/src/storages/inMemory/SplitsCacheInMemory.ts index c6b59bd9..ef0d9dcf 100644 --- a/src/storages/inMemory/SplitsCacheInMemory.ts +++ b/src/storages/inMemory/SplitsCacheInMemory.ts @@ -24,11 +24,9 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync { const previousSplit = this.getSplit(name); if (previousSplit) { // We had this Split already - if (previousSplit.trafficTypeName) { - const previousTtName = previousSplit.trafficTypeName; - this.ttCache[previousTtName]--; - if (!this.ttCache[previousTtName]) delete this.ttCache[previousTtName]; - } + const previousTtName = previousSplit.trafficTypeName; + this.ttCache[previousTtName]--; + if (!this.ttCache[previousTtName]) delete this.ttCache[previousTtName]; if (usesSegments(previousSplit)) { // Substract from segments count for the previous version of this Split. this.splitsWithSegmentsCount--; @@ -40,10 +38,7 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync { this.splitsCache[name] = split; // Update TT cache const ttName = split.trafficTypeName; - if (ttName) { // safeguard - if (!this.ttCache[ttName]) this.ttCache[ttName] = 0; - this.ttCache[ttName]++; - } + this.ttCache[ttName] = (this.ttCache[ttName] || 0) + 1; // Add to segments count for the new version of the Split if (usesSegments(split)) this.splitsWithSegmentsCount++; @@ -61,11 +56,8 @@ export class SplitsCacheInMemory extends AbstractSplitsCacheSync { delete this.splitsCache[name]; const ttName = split.trafficTypeName; - - if (ttName) { // safeguard - this.ttCache[ttName]--; // Update tt cache - if (!this.ttCache[ttName]) delete this.ttCache[ttName]; - } + this.ttCache[ttName]--; // Update tt cache + if (!this.ttCache[ttName]) delete this.ttCache[ttName]; // Update the segments count. if (usesSegments(split)) this.splitsWithSegmentsCount--; diff --git a/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts index ce645cd1..979906df 100644 --- a/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/SplitsCacheInMemory.spec.ts @@ -50,8 +50,7 @@ test('SPLITS CACHE / In Memory / trafficTypeExists and ttcache tests', () => { cache.addSplits([ // loop of addSplit ['split1', splitWithUserTT], ['split2', splitWithAccountTT], - ['split3', splitWithUserTT], // @ts-ignore - ['malformed', {}] + ['split3', splitWithUserTT], ]); cache.addSplit('split4', splitWithUserTT); diff --git a/src/storages/inRedis/SplitsCacheInRedis.ts b/src/storages/inRedis/SplitsCacheInRedis.ts index dd16cbbf..43f86481 100644 --- a/src/storages/inRedis/SplitsCacheInRedis.ts +++ b/src/storages/inRedis/SplitsCacheInRedis.ts @@ -45,19 +45,15 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync { } private _decrementCounts(split: ISplit) { - if (split.trafficTypeName) { - const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); - return this.redis.decr(ttKey).then(count => { - if (count === 0) return this.redis.del(ttKey); - }); - } + const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); + return this.redis.decr(ttKey).then(count => { + if (count === 0) return this.redis.del(ttKey); + }); } private _incrementCounts(split: ISplit) { - if (split.trafficTypeName) { - const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); - return this.redis.incr(ttKey); - } + const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); + return this.redis.incr(ttKey); } /** @@ -70,21 +66,24 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync { return this.redis.get(splitKey).then(splitFromStorage => { // handling parsing errors - let parsedPreviousSplit, newStringifiedSplit; + let parsedPreviousSplit: ISplit, newStringifiedSplit; try { parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined; newStringifiedSplit = JSON.stringify(split); } catch (e) { - throw new Error('Error parsing split definition: ' + e); + throw new Error('Error parsing feature flag definition: ' + e); } - return Promise.all([ - this.redis.set(splitKey, newStringifiedSplit), - this._incrementCounts(split), - // If it's an update, we decrement the traffic type of the existing split, - parsedPreviousSplit && this._decrementCounts(parsedPreviousSplit) - ]); - }).then(([status]) => status === 'OK'); + return this.redis.set(splitKey, newStringifiedSplit).then(() => { + // avoid unnecessary increment/decrement operations + if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName) return; + + // update traffic type counts + return this._incrementCounts(split).then(() => { + if (parsedPreviousSplit) return this._decrementCounts(parsedPreviousSplit); + }); + }); + }).then(() => true); } /** @@ -247,7 +246,7 @@ export class SplitsCacheInRedis extends AbstractSplitsCacheAsync { return Promise.resolve(splits); }) .catch(e => { - this.log.error(LOG_PREFIX + `Could not grab splits due to an error: ${e}.`); + this.log.error(LOG_PREFIX + `Could not grab feature flags due to an error: ${e}.`); return Promise.reject(e); }); } diff --git a/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts index 025de680..22d12190 100644 --- a/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/SplitsCacheInRedis.spec.ts @@ -64,8 +64,7 @@ describe('SPLITS CACHE REDIS', () => { await cache.addSplits([ ['split1', splitWithUserTT], ['split2', splitWithAccountTT], - ['split3', splitWithUserTT], // @ts-ignore - ['malformed', {}] + ['split3', splitWithUserTT], ]); await cache.addSplit('split4', splitWithUserTT); await cache.addSplit('split4', splitWithUserTT); // trying to add the same definition for an already added split will not have effect @@ -140,8 +139,6 @@ describe('SPLITS CACHE REDIS', () => { // Delete splits and TT keys await cache.removeSplits(['lol1', 'lol2']); - await connection.del(keysBuilder.buildTrafficTypeKey('account_tt')); - await connection.del(keysBuilder.buildTrafficTypeKey('user_tt')); expect(await connection.keys(`${prefix}*`)).toHaveLength(0); await connection.quit(); }); diff --git a/src/storages/pluggable/SplitsCachePluggable.ts b/src/storages/pluggable/SplitsCachePluggable.ts index 0a7e3366..47421987 100644 --- a/src/storages/pluggable/SplitsCachePluggable.ts +++ b/src/storages/pluggable/SplitsCachePluggable.ts @@ -29,19 +29,15 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync { } private _decrementCounts(split: ISplit) { - if (split.trafficTypeName) { - const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); - return this.wrapper.decr(ttKey).then(count => { - if (count === 0) return this.wrapper.del(ttKey); - }); - } + const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); + return this.wrapper.decr(ttKey).then(count => { + if (count === 0) return this.wrapper.del(ttKey); + }); } private _incrementCounts(split: ISplit) { - if (split.trafficTypeName) { - const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); - return this.wrapper.incr(ttKey); - } + const ttKey = this.keys.buildTrafficTypeKey(split.trafficTypeName); + return this.wrapper.incr(ttKey); } /** @@ -54,20 +50,23 @@ export class SplitsCachePluggable extends AbstractSplitsCacheAsync { return this.wrapper.get(splitKey).then(splitFromStorage => { // handling parsing error - let parsedPreviousSplit, stringifiedNewSplit; + let parsedPreviousSplit: ISplit, stringifiedNewSplit; try { parsedPreviousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : undefined; stringifiedNewSplit = JSON.stringify(split); } catch (e) { - throw new Error('Error parsing split definition: ' + e); + throw new Error('Error parsing feature flag definition: ' + e); } - return Promise.all([ - this.wrapper.set(splitKey, stringifiedNewSplit), - this._incrementCounts(split), - // If it's an update, we decrement the traffic type and segment count of the existing split, - parsedPreviousSplit && this._decrementCounts(parsedPreviousSplit) - ]); + return this.wrapper.set(splitKey, stringifiedNewSplit).then(() => { + // avoid unnecessary increment/decrement operations + if (parsedPreviousSplit && parsedPreviousSplit.trafficTypeName === split.trafficTypeName) return; + + // update traffic type counts + return this._incrementCounts(split).then(() => { + if (parsedPreviousSplit) return this._decrementCounts(parsedPreviousSplit); + }); + }); }).then(() => true); } diff --git a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts index a6999713..6cad1e2a 100644 --- a/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/SplitsCachePluggable.spec.ts @@ -76,8 +76,7 @@ describe('SPLITS CACHE PLUGGABLE', () => { await cache.addSplits([ ['split1', splitWithUserTT], ['split2', splitWithAccountTT], - ['split3', splitWithUserTT], // @ts-ignore - ['malformed', {}] + ['split3', splitWithUserTT], ]); await cache.addSplit('split4', splitWithUserTT); await cache.addSplit('split4', splitWithUserTT); // trying to add the same definition for an already added split will not have effect @@ -115,7 +114,8 @@ describe('SPLITS CACHE PLUGGABLE', () => { }); test('killLocally', async () => { - const cache = new SplitsCachePluggable(loggerMock, keysBuilder, wrapperMockFactory()); + const wrapper = wrapperMockFactory(); + const cache = new SplitsCachePluggable(loggerMock, keysBuilder, wrapper); await cache.addSplit('lol1', splitWithUserTT); await cache.addSplit('lol2', splitWithAccountTT); @@ -145,6 +145,9 @@ describe('SPLITS CACHE PLUGGABLE', () => { expect(updated).toBe(false); // killLocally resolves without update if changeNumber is old expect(lol1Split.defaultTreatment).not.toBe('some_treatment_2'); // existing split is not updated if given changeNumber is older + // Delete splits and TT keys + await cache.removeSplits(['lol1', 'lol2']); + expect(await wrapper.getKeysByPrefix('SPLITIO')).toHaveLength(0); }); }); diff --git a/src/sync/polling/updaters/segmentChangesUpdater.ts b/src/sync/polling/updaters/segmentChangesUpdater.ts index f9f04dcb..3494bd7c 100644 --- a/src/sync/polling/updaters/segmentChangesUpdater.ts +++ b/src/sync/polling/updaters/segmentChangesUpdater.ts @@ -98,7 +98,7 @@ export function segmentChangesUpdaterFactory( // If the operation is forbidden, it may be due to permissions. Destroy the SDK instance. // @TODO although factory status is destroyed, synchronization is not stopped if (readiness) readiness.destroy(); - log.error(`${LOG_PREFIX_INSTANTIATION}: you passed a client-side type authorizationKey, please grab an Api Key from the Split web console that is of type Server-side.`); + log.error(`${LOG_PREFIX_INSTANTIATION}: you passed a client-side type authorizationKey, please grab an SDK Key from the Split user interface that is of type server-side.`); } else { log.warn(`${LOG_PREFIX_SYNC_SEGMENTS}Error while doing fetch of segments. ${error}`); } diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index d34ba641..b2cbfb7e 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -139,7 +139,7 @@ export function pushManagerFactory( log.error(ERROR_STREAMING_AUTH, [error.message]); - // Handle 4XX HTTP errors: 401 (invalid API Key) or 400 (using incorrect API Key, i.e., client-side API Key on server-side) + // Handle 4XX HTTP errors: 401 (invalid SDK Key) or 400 (using incorrect SDK Key, i.e., client-side SDK Key on server-side) if (error.statusCode >= 400 && error.statusCode < 500) { telemetryTracker.streamingEvent(AUTH_REJECTION); pushEmitter.emit(PUSH_NONRETRYABLE_ERROR); diff --git a/src/sync/submitters/telemetrySubmitter.ts b/src/sync/submitters/telemetrySubmitter.ts index 041d1f26..a72d40b6 100644 --- a/src/sync/submitters/telemetrySubmitter.ts +++ b/src/sync/submitters/telemetrySubmitter.ts @@ -33,8 +33,8 @@ function getActiveFactories() { } function getRedundantActiveFactories() { - return Object.keys(usedKeysMap).reduce((acum, apiKey) => { - return acum + usedKeysMap[apiKey] - 1; + return Object.keys(usedKeysMap).reduce((acum, sdkKey) => { + return acum + usedKeysMap[sdkKey] - 1; }, 0); } diff --git a/src/types.ts b/src/types.ts index e2391ec4..5ecfa7ba 100644 --- a/src/types.ts +++ b/src/types.ts @@ -194,23 +194,23 @@ interface ISharedSettings { */ sync?: { /** - * List of Split filters. These filters are used to fetch a subset of the Splits definitions in your environment, in order to reduce the delay of the SDK to be ready. + * List of feature flag filters. These filters are used to fetch a subset of the feature flag definitions in your environment, in order to reduce the delay of the SDK to be ready. * This configuration is only meaningful when the SDK is working in "standalone" mode. * - * At the moment, two types of split filters are supported: by name and by prefix. + * At the moment, only one type of feature flag filter is supported: by name. + * * Example: * `splitFilter: [ - * { type: 'byName', values: ['my_split_1', 'my_split_2'] }, // will fetch splits named 'my_split_1' and 'my_split_2' - * { type: 'byPrefix', values: ['testing'] } // will fetch splits whose names start with 'testing__' prefix + * { type: 'byName', values: ['my_feature_flag_1', 'my_feature_flag_2'] }, // will fetch feature flags named 'my_feature_flag_1' and 'my_feature_flag_2' * ]` * @property {SplitIO.SplitFilter[]} splitFilters */ splitFilters?: SplitIO.SplitFilter[] /** - * Impressions Collection Mode. Option to determine how impressions are going to be sent to Split Servers. + * Impressions Collection Mode. Option to determine how impressions are going to be sent to Split servers. * Possible values are 'DEBUG' and 'OPTIMIZED'. * - DEBUG: will send all the impressions generated (recommended only for debugging purposes). - * - OPTIMIZED: will send unique impressions to Split Servers avoiding a considerable amount of traffic that duplicated impressions could generate. + * - OPTIMIZED: will send unique impressions to Split servers avoiding a considerable amount of traffic that duplicated impressions could generate. * @property {String} impressionsMode * @default 'OPTIMIZED' */ @@ -337,7 +337,7 @@ interface INodeBasicSettings extends ISharedSettings { */ core: { /** - * Your API key. More information: @see {@link https://help.split.io/hc/en-us/articles/360019916211-API-keys} + * Your SDK key. More information: @see {@link https://help.split.io/hc/en-us/articles/360019916211-API-keys} * @property {string} authorizationKey */ authorizationKey: string, @@ -362,14 +362,14 @@ interface INodeBasicSettings extends ISharedSettings { /** * The SDK mode. Possible values are "standalone" (which is the default) and "consumer". For "localhost" mode, use "localhost" as authorizationKey. * @property {SDKMode} mode - * @default standalone + * @default 'standalone' */ mode?: SDKMode, /** * Mocked features file path. For testing purposses only. For using this you should specify "localhost" as authorizationKey on core settings. * @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#localhost-mode} * @property {MockedFeaturesFilePath} features - * @default $HOME/.split + * @default '$HOME/.split' */ features?: SplitIO.MockedFeaturesFilePath, } @@ -452,17 +452,17 @@ interface IBasicSDK { */ export namespace SplitIO { /** - * Split treatment value, returned by getTreatment. + * Feature flag treatment value, returned by getTreatment. * @typedef {string} Treatment */ export type Treatment = string; /** - * Split treatment promise that will resolve to actual treatment value. + * Feature flag treatment promise that will resolve to actual treatment value. * @typedef {Promise} AsyncTreatment */ export type AsyncTreatment = Promise; /** - * An object with the treatments for a bulk of splits, returned by getTreatments. For example: + * An object with the treatments for a bulk of feature flags, returned by getTreatments. For example: * { * feature1: 'on', * feature2: 'off @@ -473,14 +473,14 @@ export namespace SplitIO { [featureName: string]: Treatment }; /** - * Split treatments promise that will resolve to the actual SplitIO.Treatments object. + * Feature flag treatments promise that will resolve to the actual SplitIO.Treatments object. * @typedef {Promise} AsyncTreatments */ export type AsyncTreatments = Promise; /** - * Split evaluation result with treatment and configuration, returned by getTreatmentWithConfig. + * Feature flag evaluation result with treatment and configuration, returned by getTreatmentWithConfig. * @typedef {Object} TreatmentWithConfig - * @property {string} treatment The treatment result + * @property {string} treatment The treatment string * @property {string | null} config The stringified version of the JSON config defined for that treatment, null if there is no config for the resulting treatment. */ export type TreatmentWithConfig = { @@ -488,13 +488,13 @@ export namespace SplitIO { config: string | null }; /** - * Split treatment promise that will resolve to actual treatment with config value. + * Feature flag treatment promise that will resolve to actual treatment with config value. * @typedef {Promise} AsyncTreatmentWithConfig */ export type AsyncTreatmentWithConfig = Promise; /** - * An object with the treatments with configs for a bulk of splits, returned by getTreatmentsWithConfig. - * Each existing configuration is a stringified version of the JSON you defined on the Split web console. For example: + * An object with the treatments with configs for a bulk of feature flags, returned by getTreatmentsWithConfig. + * Each existing configuration is a stringified version of the JSON you defined on the Split user interface. For example: * { * feature1: { treatment: 'on', config: null } * feature2: { treatment: 'off', config: '{"bannerText":"Click here."}' } @@ -505,7 +505,7 @@ export namespace SplitIO { [featureName: string]: TreatmentWithConfig }; /** - * Split treatments promise that will resolve to the actual SplitIO.TreatmentsWithConfig object. + * Feature flag treatments promise that will resolve to the actual SplitIO.TreatmentsWithConfig object. * @typedef {Promise} AsyncTreatmentsWithConfig */ export type AsyncTreatmentsWithConfig = Promise; @@ -515,15 +515,20 @@ export namespace SplitIO { */ export type Event = 'init::timeout' | 'init::ready' | 'init::cache-ready' | 'state::update'; /** - * Split attributes should be on object with values of type string or number (dates should be sent as millis since epoch). - * @typedef {Object.} Attributes + * Attributes should be on object with values of type string or number (dates should be sent as millis since epoch). + * @typedef {Object.} Attributes * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#attribute-syntax} */ export type Attributes = { - [attributeName: string]: string | number | boolean | Array + [attributeName: string]: AttributeType }; /** - * Split properties should be an object with values of type string, number, boolean or null. Size limit of ~31kb. + * Type of an attribute value + * @typedef {string | number | boolean | Array} AttributeType + */ + export type AttributeType = string | number | boolean | Array; + /** + * Properties should be an object with values of type string, number, boolean or null. Size limit of ~31kb. * @typedef {Object.} Attributes * @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#track */ @@ -563,43 +568,43 @@ export namespace SplitIO { export type ImpressionData = { impression: ImpressionDTO, attributes?: SplitIO.Attributes, - ip: string| false, + ip: string | false, hostname: string | false, sdkLanguageVersion: string }; /** - * Data corresponding to one Split view. + * Data corresponding to one feature flag view. * @typedef {Object} SplitView */ export type SplitView = { /** - * The name of the split. + * The name of the feature flag. * @property {string} name */ name: string, /** - * The traffic type of the split. + * The traffic type of the feature flag. * @property {string} trafficType */ trafficType: string, /** - * Whether the split is killed or not. + * Whether the feature flag is killed or not. * @property {boolean} killed */ killed: boolean, /** - * The list of treatments available for the split. + * The list of treatments available for the feature flag. * @property {Array} treatments */ treatments: Array, /** - * Current change number of the split. + * Current change number of the feature flag. * @property {number} changeNumber */ changeNumber: number, /** * Map of configurations per treatment. - * Each existing configuration is a stringified version of the JSON you defined on the Split web console. + * Each existing configuration is a stringified version of the JSON you defined on the Split user interface. * @property {Object.} configs */ configs: { @@ -607,7 +612,7 @@ export namespace SplitIO { } }; /** - * A promise that will be resolved with that SplitView. + * A promise that resolves to a feature flag view. * @typedef {Promise} SplitView */ export type SplitViewAsync = Promise; @@ -616,17 +621,17 @@ export namespace SplitIO { */ export type SplitViews = Array; /** - * A promise that will be resolved with an SplitIO.SplitViews array. + * A promise that resolves to an SplitIO.SplitViews array. * @typedef {Promise} SplitViewsAsync */ export type SplitViewsAsync = Promise; /** - * An array of split names. + * An array of feature flag names. * @typedef {Array} SplitNames */ export type SplitNames = Array; /** - * A promise that will be resolved with an array of split names. + * A promise that resolves to an array of feature flag names. * @typedef {Promise} SplitNamesAsync */ export type SplitNamesAsync = Promise; @@ -670,7 +675,7 @@ export namespace SplitIO { */ export type UrlSettings = { /** - * String property to override the base URL where the SDK will get feature flagging related data like a Split rollout plan or segments information. + * String property to override the base URL where the SDK will get rollout plan related data, like feature flags and segments definitions. * @property {string} sdk * @default 'https://sdk.split.io/api' */ @@ -706,7 +711,7 @@ export namespace SplitIO { */ export type SplitFilterType = 'byName' | 'byPrefix'; /** - * Defines a split filter, described by a type and list of values. + * Defines a feature flag filter, described by a type and list of values. */ export interface SplitFilter { /** @@ -715,7 +720,7 @@ export namespace SplitIO { */ type: SplitFilterType, /** - * List of values: split names for 'byName' filter type, and split prefixes for 'byPrefix' type. + * List of values: feature flag names for 'byName' filter type, and feature flag name prefixes for 'byPrefix' type. * @property {string[]} values */ values: string[], @@ -726,7 +731,7 @@ export namespace SplitIO { */ export type ImpressionsMode = 'OPTIMIZED' | 'DEBUG' | 'NONE' /** - * Defines the format of Split data to preload on the factory storage (cache). + * Defines the format of rollout plan data to preload on the factory storage (cache). */ export interface PreloadedData { /** @@ -741,7 +746,7 @@ export namespace SplitIO { */ since: number, /** - * Map of splits to their serialized definitions. + * Map of feature flags to their stringified definitions. */ splitsData: { [splitName: string]: string @@ -754,7 +759,7 @@ export namespace SplitIO { [key: string]: string[] }, /** - * Optional map of segments to their serialized definitions. + * Optional map of segments to their stringified definitions. * This property is ignored if `mySegmentsData` was provided. */ segmentsData?: { @@ -806,15 +811,15 @@ export namespace SplitIO { */ scheduler?: { /** - * The SDK polls Split servers for changes to feature roll-out plans. This parameter controls this polling period in seconds. + * The SDK polls Split servers for changes to feature flag definitions. This parameter controls this polling period in seconds. * @property {number} featuresRefreshRate - * @default 30 + * @default 60 */ featuresRefreshRate?: number, /** * The SDK sends information on who got what treatment at what time back to Split servers to power analytics. This parameter controls how often this data is sent to Split servers. The parameter should be in seconds. * @property {number} impressionsRefreshRate - * @default 60 + * @default 300 */ impressionsRefreshRate?: number, /** @@ -828,7 +833,7 @@ export namespace SplitIO { * The SDK sends diagnostic metrics to Split servers. This parameters controls this metric flush period in seconds. * @property {number} metricsRefreshRate * @default 120 - * @deprecated This parameter is ignored now. + * @deprecated This parameter is ignored now. Use `telemetryRefreshRate` instead. */ metricsRefreshRate?: number, /** @@ -877,7 +882,7 @@ export namespace SplitIO { */ core: { /** - * Your API key. More information: @see {@link https://help.split.io/hc/en-us/articles/360019916211-API-keys} + * Your SDK key. More information: @see {@link https://help.split.io/hc/en-us/articles/360019916211-API-keys} * @property {string} authorizationKey */ authorizationKey: string, @@ -1018,51 +1023,51 @@ export namespace SplitIO { */ export interface IClient extends IBasicClient { /** - * Returns a Treatment value, which will be (or eventually be) the treatment string for the given feature. + * Returns a Treatment value, which is the treatment string for the given feature. * @function getTreatment * @param {string} key - The string key representing the consumer. - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {Treatment} The treatment or treatment promise which will resolve to the treatment string. + * @returns {Treatment} The treatment string. */ - getTreatment(key: SplitKey, splitName: string, attributes?: Attributes): Treatment, + getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes): Treatment, /** - * Returns a TreatmentWithConfig value (a map of treatment and config), which will be (or eventually be) the map with treatment and config for the given feature. + * Returns a TreatmentWithConfig value, which is an object with both treatment and config string for the given feature. * @function getTreatmentWithConfig * @param {string} key - The string key representing the consumer. - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {TreatmentWithConfig} The TreatmentWithConfig or TreatmentWithConfig promise which will resolve to the map containing - * the treatment and the configuration stringified JSON (or null if there was no config for that treatment). + * @returns {TreatmentWithConfig} The TreatmentWithConfig, the object containing the treatment string and the + * configuration stringified JSON (or null if there was no config for that treatment). */ - getTreatmentWithConfig(key: SplitKey, splitName: string, attributes?: Attributes): TreatmentWithConfig, + getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes): TreatmentWithConfig, /** - * Returns a Treatments value, whick will be (or eventually be) an object with the treatments for the given features. + * Returns a Treatments value, which is an object map with the treatments for the given features. * @function getTreatments * @param {string} key - The string key representing the consumer. - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {Treatments} The treatments or treatments promise which will resolve to the treatments object. + * @returns {Treatments} The treatments object map. */ - getTreatments(key: SplitKey, splitNames: string[], attributes?: Attributes): Treatments, + getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): Treatments, /** - * Returns a TreatmentsWithConfig value, whick will be an object with the TreatmentWithConfig (a map with both treatment and config string) for the given features. + * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @function getTreatmentsWithConfig * @param {string} key - The string key representing the consumer. - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. * @returns {TreatmentsWithConfig} The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfig(key: SplitKey, splitNames: string[], attributes?: Attributes): TreatmentsWithConfig, + getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): TreatmentsWithConfig, /** - * Tracks an event to be fed to the results product on Split Webconsole. + * Tracks an event to be fed to the results product on Split user interface. * @function track * @param {SplitKey} key - The key that identifies the entity related to this event. * @param {string} trafficType - The traffic type of the entity related to this event. * @param {string} eventType - The event type corresponding to this event. * @param {number=} value - The value of this event. * @param {Properties=} properties - The properties of this event. Values can be string, number, boolean or null. - * @returns {boolean} Whether the event was added to the queue succesfully or not. + * @returns {boolean} Whether the event was added to the queue successfully or not. */ track(key: SplitIO.SplitKey, trafficType: string, eventType: string, value?: number, properties?: Properties): boolean, } @@ -1078,51 +1083,51 @@ export namespace SplitIO { * NOTE: Treatment will be a promise only in async storages, like REDIS. * @function getTreatment * @param {string} key - The string key representing the consumer. - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {AsyncTreatment} Treatment promise which will resolve to the treatment string. + * @returns {AsyncTreatment} Treatment promise that resolves to the treatment string. */ - getTreatment(key: SplitKey, splitName: string, attributes?: Attributes): AsyncTreatment, + getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes): AsyncTreatment, /** - * Returns a TreatmentWithConfig value, which will be (or eventually be) a map with both treatment and config string for the given feature. + * Returns a TreatmentWithConfig value, which will be (or eventually be) an object with both treatment and config string for the given feature. * For usage on NodeJS as we don't have only one key. * NOTE: Treatment will be a promise only in async storages, like REDIS. * @function getTreatmentWithConfig * @param {string} key - The string key representing the consumer. - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {AsyncTreatmentWithConfig} TreatmentWithConfig promise which will resolve to the TreatmentWithConfig object. + * @returns {AsyncTreatmentWithConfig} TreatmentWithConfig promise that resolves to the TreatmentWithConfig object. */ - getTreatmentWithConfig(key: SplitKey, splitName: string, attributes?: Attributes): AsyncTreatmentWithConfig, + getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes): AsyncTreatmentWithConfig, /** - * Returns a Treatments value, whick will be (or eventually be) an object with the treatments for the given features. + * Returns a Treatments value, which will be (or eventually be) an object map with the treatments for the given features. * For usage on NodeJS as we don't have only one key. * @function getTreatments * @param {string} key - The string key representing the consumer. - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {AsyncTreatments} Treatments promise which will resolve to the treatments object. + * @returns {AsyncTreatments} Treatments promise that resolves to the treatments object map. */ - getTreatments(key: SplitKey, splitNames: string[], attributes?: Attributes): AsyncTreatments, + getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): AsyncTreatments, /** - * Returns a Treatments value, whick will be (or eventually be) an object with all the maps of treatment and config string for the given features. + * Returns a TreatmentsWithConfig value, which will be (or eventually be) an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * For usage on NodeJS as we don't have only one key. * @function getTreatmentsWithConfig * @param {string} key - The string key representing the consumer. - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {AsyncTreatmentsWithConfig} TreatmentsWithConfig promise which will resolve to the map of TreatmentsWithConfig objects. + * @returns {AsyncTreatmentsWithConfig} TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ - getTreatmentsWithConfig(key: SplitKey, splitNames: string[], attributes?: Attributes): AsyncTreatmentsWithConfig, + getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): AsyncTreatmentsWithConfig, /** - * Tracks an event to be fed to the results product on Split Webconsole and returns a promise to signal when the event was successfully queued (or not). + * Tracks an event to be fed to the results product on Split user interface, and returns a promise to signal when the event was successfully queued (or not). * @function track * @param {SplitKey} key - The key that identifies the entity related to this event. * @param {string} trafficType - The traffic type of the entity related to this event. * @param {string} eventType - The event type corresponding to this event. * @param {number=} value - The value of this event. * @param {Properties=} properties - The properties of this event. Values can be string, number, boolean or null. - * @returns {Promise} A promise that resolves to a boolean indicating if the event was added to the queue succesfully or not. + * @returns {Promise} A promise that resolves to a boolean indicating if the event was added to the queue successfully or not. */ track(key: SplitIO.SplitKey, trafficType: string, eventType: string, value?: number, properties?: Properties): Promise } @@ -1133,84 +1138,86 @@ export namespace SplitIO { */ export interface ICsClient extends IBasicClient { /** - * Returns a Treatment value, which will be the treatment string for the given feature. + * Returns a Treatment value, which is the treatment string for the given feature. * @function getTreatment - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {Treatment} The treatment result. + * @returns {Treatment} The treatment string. */ - getTreatment(splitName: string, attributes?: Attributes): Treatment, + getTreatment(featureFlagName: string, attributes?: Attributes): Treatment, /** - * Returns a TreatmentWithConfig value, which will be a map of treatment and the config for that treatment. - * @function getTreatment - * @param {string} splitName - The string that represents the split we wan't to get the treatment. + * Returns a TreatmentWithConfig value, which is an object with both treatment and config string for the given feature. + * @function getTreatmentWithConfig + * @param {string} featureFlagName - The string that represents the feature flag we want to get the treatment. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {TreatmentWithConfig} The treatment or treatment promise which will resolve to the treatment string. + * @returns {TreatmentWithConfig} The map containing the treatment and the configuration stringified JSON (or null if there was no config for that treatment). */ - getTreatmentWithConfig(splitName: string, attributes?: Attributes): TreatmentWithConfig, + getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes): TreatmentWithConfig, /** - * Returns a Treatments value, whick will be (or eventually be) an object with the treatments for the given features. + * Returns a Treatments value, which is an object map with the treatments for the given features. * @function getTreatments - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. - * @returns {Treatments} The treatments or treatments promise which will resolve to the treatments object. + * @returns {Treatments} The treatments object map. */ - getTreatments(splitNames: string[], attributes?: Attributes): Treatments, + getTreatments(featureFlagNames: string[], attributes?: Attributes): Treatments, /** - * Returns a TreatmentsWithConfig value, whick will be an object with the TreatmentWithConfig (a map with both treatment and config string) for the given features. + * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @function getTreatmentsWithConfig - * @param {Array} splitNames - An array of the split names we wan't to get the treatments. + * @param {Array} featureFlagNames - An array of the feature flag names we want to get the treatments. * @param {Attributes=} attributes - An object of type Attributes defining the attributes for the given key. * @returns {TreatmentsWithConfig} The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfig(splitNames: string[], attributes?: Attributes): TreatmentsWithConfig, + getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes): TreatmentsWithConfig, /** - * Tracks an event to be fed to the results product on Split Webconsole. + * Tracks an event to be fed to the results product on Split user interface. * @function track - * @param {string} trafficType - The traffic type of the entity related to this event. NOTE: only has to be provided if the client doesn't have a traffic type + * @param {string} trafficType - The traffic type of the entity related to this event. * @param {string} eventType - The event type corresponding to this event. * @param {number=} value - The value of this event. * @param {Properties=} properties - The properties of this event. Values can be string, number, boolean or null. - * @returns {boolean} Whether the event was added to the queue succesfully or not. + * @returns {boolean} Whether the event was added to the queue successfully or not. */ - track(...args: [trafficType: string, eventType: string, value?: number, properties?: Properties] | [eventType: string, value?: number, properties?: Properties]): boolean, + track(trafficType: string, eventType: string, value?: number, properties?: Properties): boolean, /** * Add an attribute to client's in memory attributes storage - * @function setAttribute - * @param {string} attributeName Attrinute name - * @param {string, number, boolean, list} attributeValue Attribute value - * @returns {boolean} true if the attribute was stored and false otherways + * + * @param {string} attributeName Attribute name + * @param {AttributeType} attributeValue Attribute value + * @returns {boolean} true if the attribute was stored and false otherwise */ - setAttribute(attributeName: string, attributeValue: Object): boolean, + setAttribute(attributeName: string, attributeValue: AttributeType): boolean, /** * Returns the attribute with the given key - * @function getAttribute + * * @param {string} attributeName Attribute name - * @returns {Object} Attribute with the given key + * @returns {AttributeType} Attribute with the given key */ - getAttribute(attributeName: string): Object, + getAttribute(attributeName: string): AttributeType, /** - * Add to client's in memory attributes storage the attributes in 'attributes' - * @function setAttributes - * @param {Object} attributes Object with attributes to store - * @returns true if attributes were stored an false otherways + * Removes from client's in memory attributes storage the attribute with the given key. + * + * @param {string} attributeName + * @returns {boolean} true if attribute was removed and false otherwise */ - setAttributes(attributes: Record): boolean, + removeAttribute(attributeName: string): boolean, /** - * Return all the attributes stored in client's in memory attributes storage - * @function getAttributes - * @returns {Object} returns all the stored attributes + * Add to client's in memory attributes storage the attributes in 'attributes'. + * + * @param {Attributes} attributes Object with attributes to store + * @returns true if attributes were stored an false otherwise */ - getAttributes(): Record, + setAttributes(attributes: Attributes): boolean, /** - * Removes from client's in memory attributes storage the attribute with the given key - * @function removeAttribute - * @param {string} attributeName - * @returns {boolean} true if attribute was removed and false otherways + * Return all the attributes stored in client's in memory attributes storage. + * + * @returns {Attributes} returns all the stored attributes */ - removeAttribute(attributeName: string): boolean, + getAttributes(): Attributes, /** - * Remove all the stored attributes in the client's in memory attribute storage + * Remove all the stored attributes in the client's in memory attribute storage. + * + * @returns {boolean} true if all attribute were removed and false otherwise */ clearAttributes(): boolean } @@ -1221,24 +1228,24 @@ export namespace SplitIO { */ export interface IManager extends IStatusInterface { /** - * Get the array of Split names. + * Get the array of feature flag names. * @function names - * @returns {SplitNames} The lists of Split names. + * @returns {SplitNames} The list of feature flag names. */ - names(): SplitNames; + names(): SplitNames, /** - * Get the array of splits data in SplitView format. + * Get the array of feature flags data in SplitView format. * @function splits * @returns {SplitViews} The list of SplitIO.SplitView. */ - splits(): SplitViews; + splits(): SplitViews, /** * Get the data of a split in SplitView format. * @function split - * @param {string} splitName The name of the split we wan't to get info of. + * @param {string} featureFlagName The name of the feature flag we want to get info of. * @returns {SplitView} The SplitIO.SplitView of the given split. */ - split(splitName: string): SplitView; + split(featureFlagName: string): SplitView, } /** * Representation of a manager instance with asynchronous storage of the SDK. @@ -1247,23 +1254,23 @@ export namespace SplitIO { */ export interface IAsyncManager extends IStatusInterface { /** - * Get the array of Split names. + * Get the array of feature flag names. * @function names - * @returns {SplitNamesAsync} A promise that will resolve to the array of Splitio.SplitNames. + * @returns {SplitNamesAsync} A promise that resolves to the list of feature flag names. */ - names(): SplitNamesAsync; + names(): SplitNamesAsync, /** - * Get the array of splits data in SplitView format. + * Get the array of feature flags data in SplitView format. * @function splits - * @returns {SplitViewsAsync} A promise that will resolve to the SplitIO.SplitView list. + * @returns {SplitViewsAsync} A promise that resolves to the SplitIO.SplitView list. */ - splits(): SplitViewsAsync; + splits(): SplitViewsAsync, /** * Get the data of a split in SplitView format. * @function split - * @param {string} splitName The name of the split we wan't to get info of. - * @returns {SplitViewAsync} A promise that will resolve to the SplitIO.SplitView value. + * @param {string} featureFlagName The name of the feature flag we want to get info of. + * @returns {SplitViewAsync} A promise that resolves to the SplitIO.SplitView value. */ - split(splitName: string): SplitViewAsync; + split(featureFlagName: string): SplitViewAsync, } } diff --git a/src/utils/inputValidation/__tests__/apiKey.spec.ts b/src/utils/inputValidation/__tests__/apiKey.spec.ts index e9cd1c18..2273b105 100644 --- a/src/utils/inputValidation/__tests__/apiKey.spec.ts +++ b/src/utils/inputValidation/__tests__/apiKey.spec.ts @@ -1,4 +1,4 @@ -import { ERROR_EMPTY, ERROR_NULL, ERROR_INVALID, WARN_API_KEY } from '../../../logger/constants'; +import { ERROR_EMPTY, ERROR_NULL, ERROR_INVALID, WARN_SDK_KEY } from '../../../logger/constants'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { validateApiKey, validateAndTrackApiKey, releaseApiKey } from '../apiKey'; @@ -22,19 +22,19 @@ describe('validateApiKey', () => { afterEach(() => { loggerMock.mockClear(); }); - test('Should return the passed api key if it is a valid string without logging any errors', () => { - const validApiKey = 'qjok3snti4dgsticade5hfphmlucarsflv14'; + test('Should return the passed SDK key if it is a valid string without logging any errors', () => { + const validSdkKey = 'qjok3snti4dgsticade5hfphmlucarsflv14'; - expect(validateApiKey(loggerMock, validApiKey)).toBe(validApiKey); // It should return the passed string if it is valid. + expect(validateApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); // It should return the passed string if it is valid. expect(loggerMock.error).not.toBeCalled(); // Should not log any errors. }); - test('Should return false and log error if the api key is invalid', () => { + test('Should return false and log error if the SDK key is invalid', () => { for (let i = 0; i < invalidKeys.length; i++) { - const invalidApiKey = invalidKeys[i]['key']; + const invalidSdkKey = invalidKeys[i]['key']; const expectedLog = invalidKeys[i]['msg']; - expect(validateApiKey(loggerMock, invalidApiKey)).toBe(false); // Invalid strings should return false. + expect(validateApiKey(loggerMock, invalidSdkKey)).toBe(false); // Invalid strings should return false. expect(loggerMock.error.mock.calls[0][0]).toEqual(expectedLog); // The error should be logged for the invalid string. loggerMock.error.mockClear(); @@ -46,71 +46,71 @@ describe('validateAndTrackApiKey', () => { afterEach(() => { loggerMock.mockClear(); }); - test('Should log a warning if we are instantiating more than one factory (different api keys)', () => { - const validApiKey1 = 'qjok3snti4dgsticade5hfphmlucarsflv14'; - const validApiKey2 = 'qjok3snti4dgsticade5hfphmlucars92uih'; - const validApiKey3 = '84ynbsnti4dgsticade5hfphmlucars92uih'; + test('Should log a warning if we are instantiating more than one factory (different SDK keys)', () => { + const validSdkKey1 = 'qjok3snti4dgsticade5hfphmlucarsflv14'; + const validSdkKey2 = 'qjok3snti4dgsticade5hfphmlucars92uih'; + const validSdkKey3 = '84ynbsnti4dgsticade5hfphmlucars92uih'; - expect(validateAndTrackApiKey(loggerMock, validApiKey1)).toBe(validApiKey1); - expect(loggerMock.warn).not.toBeCalled(); // If this is the first api key we are registering, there is no warning. + expect(validateAndTrackApiKey(loggerMock, validSdkKey1)).toBe(validSdkKey1); + expect(loggerMock.warn).not.toBeCalled(); // If this is the first SDK key we are registering, there is no warning. - expect(validateAndTrackApiKey(loggerMock, validApiKey2)).toBe(validApiKey2); - expect(validateAndTrackApiKey(loggerMock, validApiKey3)).toBe(validApiKey3); - expect(loggerMock.warn).toBeCalledWith(WARN_API_KEY, ['an instance of the Split factory']); // we get a warning when we register a new api key. + expect(validateAndTrackApiKey(loggerMock, validSdkKey2)).toBe(validSdkKey2); + expect(validateAndTrackApiKey(loggerMock, validSdkKey3)).toBe(validSdkKey3); + expect(loggerMock.warn).toBeCalledWith(WARN_SDK_KEY, ['an instance of the Split factory']); // we get a warning when we register a new SDK key. // We will release the used keys and expect no warnings next time. - releaseApiKey(validApiKey1); - releaseApiKey(validApiKey2); - releaseApiKey(validApiKey3); + releaseApiKey(validSdkKey1); + releaseApiKey(validSdkKey2); + releaseApiKey(validSdkKey3); loggerMock.mockClear(); - expect(validateAndTrackApiKey(loggerMock, validApiKey1)).toBe(validApiKey1); + expect(validateAndTrackApiKey(loggerMock, validSdkKey1)).toBe(validSdkKey1); expect(loggerMock.warn).not.toBeCalled(); // If all the keys were released and we try again, there is no warning. - releaseApiKey(validApiKey1); // clean up the cache of api keys for next test + releaseApiKey(validSdkKey1); // clean up the cache of SDK keys for next test }); - test('Should log a warning if we are instantiating more than one factory (same api key)', () => { - const validApiKey = '84ynbsnti4dgsticade5hfphmlucars92uih'; + test('Should log a warning if we are instantiating more than one factory (same SDK key)', () => { + const validSdkKey = '84ynbsnti4dgsticade5hfphmlucars92uih'; - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); - expect(loggerMock.warn).not.toBeCalled(); // If this is the first api key we are registering, there is no warning. + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); + expect(loggerMock.warn).not.toBeCalled(); // If this is the first SDK key we are registering, there is no warning. - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); // Same key one more time, 2 instances plus new one. - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); // Same key one more time, 3 instances plus new one. - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); expect(loggerMock.warn.mock.calls).toEqual([ - [WARN_API_KEY, ['1 factory/ies with this API Key']], - [WARN_API_KEY, ['2 factory/ies with this API Key']], - [WARN_API_KEY, ['3 factory/ies with this API Key']] - ]); // We get a warning each time we register the same api key, with the number of instances we have. + [WARN_SDK_KEY, ['1 factory/ies with this SDK Key']], + [WARN_SDK_KEY, ['2 factory/ies with this SDK Key']], + [WARN_SDK_KEY, ['3 factory/ies with this SDK Key']] + ]); // We get a warning each time we register the same SDK key, with the number of instances we have. - // We will release the used api key leaving only 1 "use" on the cache. - releaseApiKey(validApiKey); - releaseApiKey(validApiKey); - releaseApiKey(validApiKey); + // We will release the used SDK key leaving only 1 "use" on the cache. + releaseApiKey(validSdkKey); + releaseApiKey(validSdkKey); + releaseApiKey(validSdkKey); loggerMock.mockClear(); // So we get the warning again. - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); - expect(loggerMock.warn).toBeCalledWith(WARN_API_KEY, ['1 factory/ies with this API Key']); + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); + expect(loggerMock.warn).toBeCalledWith(WARN_SDK_KEY, ['1 factory/ies with this SDK Key']); // Leave it with 0 - releaseApiKey(validApiKey); - releaseApiKey(validApiKey); + releaseApiKey(validSdkKey); + releaseApiKey(validSdkKey); loggerMock.mockClear(); - expect(validateAndTrackApiKey(loggerMock, validApiKey)).toBe(validApiKey); + expect(validateAndTrackApiKey(loggerMock, validSdkKey)).toBe(validSdkKey); expect(loggerMock.warn).not.toBeCalled(); // s users, there is no warning when we use it again. - releaseApiKey(validApiKey); // clean up the cache just in case a new test is added + releaseApiKey(validSdkKey); // clean up the cache just in case a new test is added }); }); diff --git a/src/utils/inputValidation/__tests__/preloadedData.spec.ts b/src/utils/inputValidation/__tests__/preloadedData.spec.ts index 381cba4a..79f1d1a4 100644 --- a/src/utils/inputValidation/__tests__/preloadedData.spec.ts +++ b/src/utils/inputValidation/__tests__/preloadedData.spec.ts @@ -9,7 +9,7 @@ const testCases = [ { input: { lastUpdated: 10, since: 10, splitsData: {} }, output: true, - warn: `${method}: preloadedData.splitsData doesn't contain split definitions.` + warn: `${method}: preloadedData.splitsData doesn't contain feature flag definitions.` }, { input: { lastUpdated: 10, since: 10, splitsData: { 'some_split': 'SPLIT DEFINITION' } }, @@ -94,19 +94,19 @@ const testCases = [ // should be false if splitsData property is invalid input: { lastUpdated: 10, since: 10, splitsData: undefined }, output: false, - error: `${method}: preloadedData.splitsData must be a map of split names to their serialized definitions.` + error: `${method}: preloadedData.splitsData must be a map of feature flag names to their stringified definitions.` }, { // should be false if splitsData property is invalid input: { lastUpdated: 10, since: 10, splitsData: ['DEFINITION'] }, output: false, - error: `${method}: preloadedData.splitsData must be a map of split names to their serialized definitions.` + error: `${method}: preloadedData.splitsData must be a map of feature flag names to their stringified definitions.` }, { // should be false if splitsData property is invalid input: { lastUpdated: 10, since: 10, splitsData: { some_split: undefined } }, output: false, - error: `${method}: preloadedData.splitsData must be a map of split names to their serialized definitions.` + error: `${method}: preloadedData.splitsData must be a map of feature flag names to their stringified definitions.` }, { // should be false if mySegmentsData property is invalid @@ -124,13 +124,13 @@ const testCases = [ // should be false if segmentsData property is invalid input: { lastUpdated: 10, since: 10, splitsData: { some_split: 'DEFINITION' }, segmentsData: ['DEFINITION'] }, output: false, - error: `${method}: preloadedData.segmentsData must be a map of segment names to their serialized definitions.` + error: `${method}: preloadedData.segmentsData must be a map of segment names to their stringified definitions.` }, { // should be false if segmentsData property is invalid input: { lastUpdated: 10, since: 10, splitsData: { some_split: 'DEFINITION' }, segmentsData: { some_segment: undefined } }, output: false, - error: `${method}: preloadedData.segmentsData must be a map of segment names to their serialized definitions.` + error: `${method}: preloadedData.segmentsData must be a map of segment names to their stringified definitions.` } ]; diff --git a/src/utils/inputValidation/__tests__/split.spec.ts b/src/utils/inputValidation/__tests__/split.spec.ts index 85d14be6..01bb381b 100644 --- a/src/utils/inputValidation/__tests__/split.spec.ts +++ b/src/utils/inputValidation/__tests__/split.spec.ts @@ -27,11 +27,11 @@ const trimmableSplits = [ ' split_name3' ]; -describe('INPUT VALIDATION for Split name', () => { +describe('INPUT VALIDATION for feature flag name', () => { afterEach(() => { loggerMock.mockClear(); }); - test('Should return the provided split name if it is a valid string without logging any errors', () => { + test('Should return the provided feature flag name if it is a valid string without logging any errors', () => { expect(validateSplit(loggerMock, 'splitName', 'some_method_splitName')).toBe('splitName'); // It should return the provided string if it is valid. expect(loggerMock.error.mock.calls[0]).not.toEqual('some_method_splitName'); // Should not log any errors. expect(validateSplit(loggerMock, 'split_name', 'some_method_splitName')).toBe('split_name'); // It should return the provided string if it is valid. @@ -42,11 +42,11 @@ describe('INPUT VALIDATION for Split name', () => { expect(loggerMock.warn).not.toBeCalled(); // It should have not logged any warnings. }); - test('Should trim split name if it is a valid string with trimmable spaces and log a warning (if those are enabled)', () => { + test('Should trim feature flag name if it is a valid string with trimmable spaces and log a warning (if those are enabled)', () => { for (let i = 0; i < trimmableSplits.length; i++) { const trimmableSplit = trimmableSplits[i]; - expect(validateSplit(loggerMock, trimmableSplit, 'some_method_splitName')).toBe(trimmableSplit.trim()); // It should return the trimmed version of the split name received. - expect(loggerMock.warn).toBeCalledWith(WARN_TRIMMING, ['some_method_splitName', 'split name', trimmableSplit]); // Should log a warning if those are enabled. + expect(validateSplit(loggerMock, trimmableSplit, 'some_method_splitName')).toBe(trimmableSplit.trim()); // It should return the trimmed version of the feature flag name received. + expect(loggerMock.warn).toBeCalledWith(WARN_TRIMMING, ['some_method_splitName', 'feature flag name', trimmableSplit]); // Should log a warning if those are enabled. loggerMock.warn.mockClear(); } @@ -54,14 +54,14 @@ describe('INPUT VALIDATION for Split name', () => { expect(loggerMock.error).not.toBeCalled(); // It should have not logged any errors. }); - test('Should return false and log error if split name is not a valid string', () => { + test('Should return false and log error if feature flag name is not a valid string', () => { for (let i = 0; i < invalidSplits.length; i++) { const invalidValue = invalidSplits[i]['split']; // @ts-ignore const expectedLog = invalidSplits[i]['msg']; expect(validateSplit(loggerMock, invalidValue, 'test_method')).toBe(false); // Invalid event types should always return false. - expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'split name']); // Should log the error for the invalid event type. + expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'feature flag name']); // Should log the error for the invalid event type. loggerMock.error.mockClear(); } diff --git a/src/utils/inputValidation/__tests__/splits.spec.ts b/src/utils/inputValidation/__tests__/splits.spec.ts index 221cbf1b..923b31b3 100644 --- a/src/utils/inputValidation/__tests__/splits.spec.ts +++ b/src/utils/inputValidation/__tests__/splits.spec.ts @@ -32,7 +32,7 @@ const invalidSplits = [ NaN ]; -describe('INPUT VALIDATION for Split names', () => { +describe('INPUT VALIDATION for feature flag names', () => { afterEach(() => { loggerMock.mockClear(); @@ -62,7 +62,7 @@ describe('INPUT VALIDATION for Split names', () => { test('Should return false and log an error for the array if it is invalid', () => { for (let i = 0; i < invalidSplits.length; i++) { expect(validateSplits(loggerMock, invalidSplits[i], 'test_method')).toBe(false); // It will return false as the array is of an incorrect type. - expect(loggerMock.error).toBeCalledWith(ERROR_EMPTY_ARRAY, ['test_method', 'split_names']); // Should log the error for the collection. + expect(loggerMock.error).toBeCalledWith(ERROR_EMPTY_ARRAY, ['test_method', 'feature flag names']); // Should log the error for the collection. expect(validateSplitMock).not.toBeCalled(); // Should not try to validate any inner value if there is no valid array. loggerMock.error.mockClear(); @@ -79,7 +79,7 @@ describe('INPUT VALIDATION for Split names', () => { expect(validateSplits(loggerMock, myArr, 'test_method')).toEqual(['valid_name', 'something_valid']); // It will return the array without the invalid values. for (let i = 0; i < myArr.length; i++) { - expect(validateSplitMock.mock.calls[i]).toEqual([loggerMock, myArr[i], 'test_method', 'split name']); // Should validate any inner value independently. + expect(validateSplitMock.mock.calls[i]).toEqual([loggerMock, myArr[i], 'test_method', 'feature flag name']); // Should validate any inner value independently. } expect(loggerMock.error).not.toBeCalled(); // Should not log any error for the collection. diff --git a/src/utils/inputValidation/apiKey.ts b/src/utils/inputValidation/apiKey.ts index cef0fa0f..d6ff3fea 100644 --- a/src/utils/inputValidation/apiKey.ts +++ b/src/utils/inputValidation/apiKey.ts @@ -1,51 +1,52 @@ -import { ERROR_NULL, ERROR_EMPTY, ERROR_INVALID, WARN_API_KEY, LOG_PREFIX_INSTANTIATION } from '../../logger/constants'; +import { ERROR_NULL, ERROR_EMPTY, ERROR_INVALID, WARN_SDK_KEY, LOG_PREFIX_INSTANTIATION } from '../../logger/constants'; import { ILogger } from '../../logger/types'; import { isString } from '../lang'; -const item = 'api_key'; +const item = 'sdk_key'; -/** validates the given api key */ -export function validateApiKey(log: ILogger, maybeApiKey: any): string | false { - let apiKey: string | false = false; - if (maybeApiKey == undefined) { // eslint-disable-line eqeqeq +// @TODO replace ApiKey with SdkKey in function names +/** validates the given SDK key */ +export function validateApiKey(log: ILogger, maybeSdkKey: any): string | false { + let sdkKey: string | false = false; + if (maybeSdkKey == undefined) { // eslint-disable-line eqeqeq log.error(ERROR_NULL, [LOG_PREFIX_INSTANTIATION, item]); - } else if (isString(maybeApiKey)) { - if (maybeApiKey.length > 0) - apiKey = maybeApiKey; + } else if (isString(maybeSdkKey)) { + if (maybeSdkKey.length > 0) + sdkKey = maybeSdkKey; else log.error(ERROR_EMPTY, [LOG_PREFIX_INSTANTIATION, item]); } else { log.error(ERROR_INVALID, [LOG_PREFIX_INSTANTIATION, item]); } - return apiKey; + return sdkKey; } // Exported for telemetry export const usedKeysMap: Record = {}; -/** validates the given api key and also warns if it is in use */ -export function validateAndTrackApiKey(log: ILogger, maybeApiKey: any): string | false { - const apiKey = validateApiKey(log, maybeApiKey); +/** validates the given SDK key and also warns if it is in use */ +export function validateAndTrackApiKey(log: ILogger, maybeSdkKey: any): string | false { + const sdkKey = validateApiKey(log, maybeSdkKey); - // If the apiKey is correct, we'll save it as the instance creation should work. - if (apiKey) { - if (!usedKeysMap[apiKey]) { + // If sdkKey is correct, we'll save it as the instance creation should work. + if (sdkKey) { + if (!usedKeysMap[sdkKey]) { // If this key is not present, only warning scenarios is that we have factories for other keys. - usedKeysMap[apiKey] = 1; + usedKeysMap[sdkKey] = 1; if (Object.keys(usedKeysMap).length > 1) { - log.warn(WARN_API_KEY, ['an instance of the Split factory']); + log.warn(WARN_SDK_KEY, ['an instance of the Split factory']); } } else { - log.warn(WARN_API_KEY, [`${usedKeysMap[apiKey]} factory/ies with this API Key`]); - usedKeysMap[apiKey]++; + log.warn(WARN_SDK_KEY, [`${usedKeysMap[sdkKey]} factory/ies with this SDK Key`]); + usedKeysMap[sdkKey]++; } } - return apiKey; + return sdkKey; } -export function releaseApiKey(apiKey: string) { - if (usedKeysMap[apiKey]) usedKeysMap[apiKey]--; - if (usedKeysMap[apiKey] === 0) delete usedKeysMap[apiKey]; +export function releaseApiKey(sdkKey: string) { + if (usedKeysMap[sdkKey]) usedKeysMap[sdkKey]--; + if (usedKeysMap[sdkKey] === 0) delete usedKeysMap[sdkKey]; } diff --git a/src/utils/inputValidation/preloadedData.ts b/src/utils/inputValidation/preloadedData.ts index 0b02c6c2..10531580 100644 --- a/src/utils/inputValidation/preloadedData.ts +++ b/src/utils/inputValidation/preloadedData.ts @@ -12,11 +12,11 @@ function validateTimestampData(log: ILogger, maybeTimestamp: any, method: string function validateSplitsData(log: ILogger, maybeSplitsData: any, method: string) { if (isObject(maybeSplitsData)) { const splitNames = Object.keys(maybeSplitsData); - if (splitNames.length === 0) log.warn(`${method}: preloadedData.splitsData doesn't contain split definitions.`); + if (splitNames.length === 0) log.warn(`${method}: preloadedData.splitsData doesn't contain feature flag definitions.`); // @TODO in the future, consider handling the possibility of having parsed definitions of splits if (splitNames.every(splitName => validateSplit(log, splitName, method) && isString(maybeSplitsData[splitName]))) return true; } - log.error(`${method}: preloadedData.splitsData must be a map of split names to their serialized definitions.`); + log.error(`${method}: preloadedData.splitsData must be a map of feature flag names to their stringified definitions.`); return false; } @@ -38,7 +38,7 @@ function validateSegmentsData(log: ILogger, maybeSegmentsData: any, method: stri const segmentNames = Object.keys(maybeSegmentsData); if (segmentNames.every(segmentName => isString(maybeSegmentsData[segmentName]))) return true; } - log.error(`${method}: preloadedData.segmentsData must be a map of segment names to their serialized definitions.`); + log.error(`${method}: preloadedData.segmentsData must be a map of segment names to their stringified definitions.`); return false; } diff --git a/src/utils/inputValidation/split.ts b/src/utils/inputValidation/split.ts index b7261604..d0e659e0 100644 --- a/src/utils/inputValidation/split.ts +++ b/src/utils/inputValidation/split.ts @@ -5,7 +5,7 @@ import { isString } from '../lang'; // include BOM and nbsp const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/; -export function validateSplit(log: ILogger, maybeSplit: any, method: string, item = 'split name'): string | false { +export function validateSplit(log: ILogger, maybeSplit: any, method: string, item = 'feature flag name'): string | false { if (maybeSplit == undefined) { // eslint-disable-line eqeqeq log.error(ERROR_NULL, [method, item]); } else if (!isString(maybeSplit)) { diff --git a/src/utils/inputValidation/splits.ts b/src/utils/inputValidation/splits.ts index ee381210..d23e7bd1 100644 --- a/src/utils/inputValidation/splits.ts +++ b/src/utils/inputValidation/splits.ts @@ -3,7 +3,7 @@ import { ILogger } from '../../logger/types'; import { uniq } from '../lang'; import { validateSplit } from './split'; -export function validateSplits(log: ILogger, maybeSplits: any, method: string, listName = 'split_names', item = 'split name'): string[] | false { +export function validateSplits(log: ILogger, maybeSplits: any, method: string, listName = 'feature flag names', item = 'feature flag name'): string[] | false { if (Array.isArray(maybeSplits) && maybeSplits.length > 0) { let validatedArray: string[] = []; // Remove invalid values diff --git a/src/utils/lang/__tests__/binarySearch.spec.ts b/src/utils/lang/__tests__/binarySearch.spec.ts index 9b7a5510..18494b0f 100644 --- a/src/utils/lang/__tests__/binarySearch.spec.ts +++ b/src/utils/lang/__tests__/binarySearch.spec.ts @@ -1,18 +1,3 @@ -/** -Copyright 2022 Split Software - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -**/ import { binarySearch } from '../binarySearch'; test('BINARY SEARCH / given [1,3,5,7,10] as dataset look for several elements', () => { diff --git a/src/utils/settingsValidation/__tests__/settings.mocks.ts b/src/utils/settingsValidation/__tests__/settings.mocks.ts index 8d213b03..ad8d812a 100644 --- a/src/utils/settingsValidation/__tests__/settings.mocks.ts +++ b/src/utils/settingsValidation/__tests__/settings.mocks.ts @@ -108,7 +108,7 @@ export const fullSettingsServerSide = { export const settingsSplitApi = { core: { - authorizationKey: 'api-key' + authorizationKey: 'sdk-key' }, version: 'jest', urls: { diff --git a/src/utils/settingsValidation/index.ts b/src/utils/settingsValidation/index.ts index 2a8cf167..aeb4e4ea 100644 --- a/src/utils/settingsValidation/index.ts +++ b/src/utils/settingsValidation/index.ts @@ -97,7 +97,7 @@ function fromSecondsToMillis(n: number) { /** * Validates the given config and use it to build a settings object. - * NOTE: it doesn't validate the Api Key. Call `validateApikey` or `validateAndTrackApiKey` for that after settings validation. + * NOTE: it doesn't validate the SDK Key. Call `validateApiKey` or `validateAndTrackApiKey` for that after settings validation. * * @param config user defined configuration * @param validationParams defaults and fields validators used to validate and creates a settings object from a given config diff --git a/src/utils/timeTracker/now/__tests__/now.spec.ts b/src/utils/timeTracker/now/__tests__/now.spec.ts index 5097d894..69f7587d 100644 --- a/src/utils/timeTracker/now/__tests__/now.spec.ts +++ b/src/utils/timeTracker/now/__tests__/now.spec.ts @@ -1,19 +1,3 @@ -/** -Copyright 2022 Split Software - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -**/ - import { now as nowBrowser } from '../browser'; import { now as nowNode } from '../node'; import { nearlyEqual } from '../../../../__tests__/testUtils/index';