diff --git a/package-lock.json b/package-lock.json index 6b9ac957f..651876012 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1267,11 +1267,10 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.0.0.tgz", - "integrity": "sha512-hup5YVuf9eEd9FJix7siGhFgsUJndCfeMwTLNeO+mPL2gehjGZLS0GjOMofVAa+Y1cDrpo41CQ54w/QIbixYQg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-1.2.0.tgz", + "integrity": "sha512-UzfB/QNjbLkjSsxsOqb6S7qEp4DXMcVZ5gD1v6NKDuuSTF+m3B/xZPp9cXrzGDgZ9pwCgDAbScWRHvRBwLfA4g==", "requires": { - "object-assign": "^4.1.1", "tslib": "^2.1.0" } }, diff --git a/package.json b/package.json index 630da1423..2bb18cdea 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "dependencies": { "@babel/runtime": "^7.13.10", - "@splitsoftware/splitio-commons": "1.0.0", + "@splitsoftware/splitio-commons": "^1.2.0", "@types/google.analytics": "0.0.40", "events": "3.1.0", "ioredis": "^4.28.0", @@ -103,15 +103,15 @@ "test-browser-errors": "cross-env NODE_ENV=test karma start karma/errors.ci.karma.conf.js", "test-browser-gaintegration": "cross-env NODE_ENV=test karma start karma/gaintegration.ci.karma.conf.js", "test-browser-push": "cross-env NODE_ENV=test karma start karma/push.ci.karma.conf.js", - "test-node": "npm run test-node-unit && npm run test-node-online && npm run test-node-offline && npm run test-node-destroy && npm run test-node-errors && npm run test-node-push && npm run test-node-redis $$ npm run test-node-custom", + "test-node": "npm run test-node-unit && npm run test-node-online && npm run test-node-offline && npm run test-node-destroy && npm run test-node-errors && npm run test-node-push && npm run test-node-consumer-redis $$ npm run test-node-consumer-pluggable", "test-node-unit": "cross-env NODE_ENV=test tape -r @babel/register \"src/*/**/__tests__/**/!(browser).spec.js\" | tap-min", "test-node-online": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/node.spec.js | tap-min", "test-node-destroy": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/destroy/node.spec.js | tap-min", "test-node-errors": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/errorCatching/node.spec.js | tap-min", "test-node-offline": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/offline/node.spec.js | tap-min", "test-node-push": "cross-env NODE_ENV=test tape -r @babel/register src/__tests__/push/node.spec.js | tap-min", - "test-node-redis": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_redis.spec.js\" | tap-min", - "test-node-custom": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_custom.spec.js\" | tap-min", + "test-node-consumer-redis": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_redis.spec.js\" | tap-min", + "test-node-consumer-pluggable": "cross-env NODE_ENV=test tape -r @babel/register \"src/__tests__/consumerMode/node_pluggable.spec.js\" | tap-min", "pretest-ts-decls": "npm run build-es && npm run build-cjs && npm link", "test-ts-decls": "./scripts/ts-tests.sh", "posttest-ts-decls": "npm unlink && npm install", diff --git a/src/__tests__/browserSuites/ready-promise.spec.js b/src/__tests__/browserSuites/ready-promise.spec.js index c8f29c830..6a743b6d3 100644 --- a/src/__tests__/browserSuites/ready-promise.spec.js +++ b/src/__tests__/browserSuites/ready-promise.spec.js @@ -418,7 +418,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { // We also use the manager to get some of the promises const manager = splitio.manager(); - // promise1 is handled inmediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. + // promise1 is handled immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const promise1 = client.ready(); const tStart = Date.now(); @@ -434,7 +434,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { }); }, 0); - // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called inmediately (0 seconds aprox). + // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise2 = manager.ready(); const tStart = Date.now(); @@ -446,11 +446,11 @@ export default function readyPromiseAssertions(fetchMock, assert) { t.pass('### SDK TIMED OUT - time out is triggered before retry attempt finishes'); assertGetTreatmentControlNotReady(t, client); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "reject" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "reject" callback is expected to be called immediately (0 seconds aprox).'); }); }, fromSecondsToMillis(0.15)); - // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called inmediately (0 seconds aprox). + // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise3 = manager.ready(); const tStart = Date.now(); @@ -459,7 +459,7 @@ export default function readyPromiseAssertions(fetchMock, assert) { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called immediately (0 seconds aprox).'); return Promise.resolve(); }, () => { diff --git a/src/__tests__/consumerMode/ioredisWrapper.js b/src/__tests__/consumerMode/ioredisWrapper.js index 21bd84cbd..9ea4a1787 100644 --- a/src/__tests__/consumerMode/ioredisWrapper.js +++ b/src/__tests__/consumerMode/ioredisWrapper.js @@ -7,12 +7,21 @@ import { parseRedisOptions } from '../../utils/settings/storage/node'; * Operations fail until `connect` is resolved when the Redis 'ready' event is emitted. * * @param {Object} redisOptions redis options with the format expected at `settings.storage.options` - * @returns {import("@splitsoftware/splitio-commons/types/storages/types").ICustomStorageWrapper} wrapper for IORedis client + * @returns {import("@splitsoftware/splitio-commons/types/storages/types").IPluggableStorageWrapper} wrapper for IORedis client */ export function ioredisWrapper(redisOptions) { + const options = RedisAdapter._defineOptions(parseRedisOptions(redisOptions)); + /** @type ioredis.Redis */ - let redis; + const redis = new ioredis(...RedisAdapter._defineLibrarySettings(options)); + + let isConnected = false; + redis.on('ready', () => { isConnected = true; }); + // There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected. + // It is done to avoid getting the message `Unhandled error event: Error: connect ECONNREFUSED`. + // Also, we cannot reject the connect promise on an error, because the SDK will not get ready if the connection is established after the error. + redis.on('error', () => { }); return { get(key) { @@ -31,17 +40,14 @@ export function ioredisWrapper(redisOptions) { getKeysByPrefix(prefix) { return redis.keys(`${prefix}*`); }, - getByPrefix(prefix) { - return this.getKeysByPrefix(prefix).then(keys => redis.mget(...keys)); - }, getMany(keys) { return redis.mget(...keys); }, - incr(key) { - return redis.incr(key); + incr(key, increment = 1) { + return redis.incrby(key, increment); }, - decr(key) { - return redis.decr(key); + decr(key, decrement = 1) { + return redis.decrby(key, decrement); }, pushItems(key, items) { return redis.rpush(key, items); @@ -65,20 +71,13 @@ export function ioredisWrapper(redisOptions) { return redis.smembers(key); }, connect() { - const options = RedisAdapter._defineOptions(parseRedisOptions(redisOptions)); - redis = new ioredis(...RedisAdapter._defineLibrarySettings(options)); - return new Promise((res) => { - redis.on('ready', res); - - // There is no need to listen for redis 'error' event, because in that case ioredis calls will be rejected. - // But it is done to avoid getting the ioredis message `Unhandled error event: Error: connect ECONNREFUSED`. - // If we reject the promise, the SDK client will not get ready if the redis connection is established after an error. - redis.on('error', () => { }); + if (isConnected) res(); + else redis.on('ready', res); }); }, - close() { - return Promise.resolve(redis && redis.disconnect()); // close the connection + disconnect() { + return Promise.resolve(redis && redis.disconnect()); } }; } diff --git a/src/__tests__/consumerMode/node_custom.spec.js b/src/__tests__/consumerMode/node_pluggable.spec.js similarity index 84% rename from src/__tests__/consumerMode/node_custom.spec.js rename to src/__tests__/consumerMode/node_pluggable.spec.js index 49afe0631..5e943adf9 100644 --- a/src/__tests__/consumerMode/node_custom.spec.js +++ b/src/__tests__/consumerMode/node_pluggable.spec.js @@ -10,7 +10,6 @@ import { ioredisWrapper } from './ioredisWrapper'; import { exec } from 'child_process'; import { SplitFactory } from '../../index'; import { merge } from '../../utils/lang'; -import SettingsFactory from '../../utils/settings'; import { nearlyEqual } from '../testUtils'; const expectedSplitName = 'hierarchical_splits_testing_on'; @@ -30,15 +29,11 @@ const redisOptions = { /** @type SplitIO.INodeAsyncSettings */ const config = { core: { - authorizationKey: 'uoj4sb69bjv7d4d027f7ukkitd53ek6a9ai9' - }, - urls: { - sdk: 'https://sdk-aws-staging.split.io/api', - events: 'https://events-aws-staging.split.io/api' + authorizationKey: 'SOME API KEY' // in consumer mode, api key is only used to identify the sdk instance }, mode: 'consumer', storage: { - type: 'CUSTOM', + type: 'PLUGGABLE', prefix: redisPrefix, wrapper: ioredisWrapper(redisOptions) }, @@ -73,7 +68,7 @@ const initializeRedisServer = () => { return promise; }; -tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { +tape('NodeJS Pluggable Storage using a wrapper for Ioredis', function (t) { t.test('Regular usage', assert => { initializeRedisServer() @@ -100,11 +95,16 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { const splitsResult = manager.splits(); assert.equal(typeof getTreatmentResult.then, 'function', 'GetTreatment calls should always return a promise on Consumer mode.'); - // NOTE: unlike others, JS SDK attempts to retrieve splits from storage even if SDK_READY has not been emitted. - // This could change in a coming breaking change, in order to return a control treatment without calling the storage wrapper. - // ATM, we have to set `enableOfflineQueue: false` in our ioredisWrapper, to make wrapper calls fail immediately if Redis 'ready' event was not emitted. - // The label might be 'exception' instead of 'not ready', if the wrapper operation promise is settled after the SDK is ready. - assert.equal(await getTreatmentResult, 'control', 'Evaluations using custom storage should be control if initiated before SDK_READY.'); + // NOTE: unlike spec, JS SDK attempts to retrieve splits from storage even if SDK_READY has not been emitted, because it is flagged ready from cache. + // This could change to return control treatment without calling the storage wrapper (Breaking change). + // ATM, ioredisWrapper sets `enableOfflineQueue` to false (https://github.com/luin/ioredis#offline-queue), to make wrapper calls fail immediately until Redis 'ready' event is emitted. + // Therefore, the impression label is 'exception' instead of 'not ready'. + assert.equal(await getTreatmentResult, 'control', 'Evaluations using pluggable storage should be control if initiated before SDK_READY.'); + // @TODO SDK should not be operational, until wrapper connects + assert.deepEqual({ + isReadyFromCache: client.__context.get(client.__context.constants.READY_FROM_CACHE, true), + isReady: client.__context.get(client.__context.constants.READY, true) + }, { isReadyFromCache: true, isReady: undefined }, 'SDK in consumer mode is operational inmediatelly'); assert.equal(typeof trackResult.then, 'function', 'Track calls should always return a promise on Consumer mode.'); assert.false(await trackResult, 'If the event failed to be queued due to a wrapper operation failure, the promise will resolve to false'); @@ -118,43 +118,43 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { await client.ready(); - assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', '`getTreatment` evaluation using custom storage should be correct.'); - assert.equal((await client.getTreatmentWithConfig('other', 'UT_IN_SEGMENT')).treatment, 'off', '`getTreatmentWithConfig` evaluation using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', '`getTreatment` evaluation using pluggable storage should be correct.'); + assert.equal((await client.getTreatmentWithConfig('other', 'UT_IN_SEGMENT')).treatment, 'off', '`getTreatmentWithConfig` evaluation using pluggable storage should be correct.'); - assert.equal((await client.getTreatments('UT_Segment_member', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'], 'off', '`getTreatments` evaluation using custom storage should be correct.'); - assert.equal((await client.getTreatmentsWithConfig('other', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'].treatment, 'on', '`getTreatmentsWithConfig` evaluation using custom storage should be correct.'); + assert.equal((await client.getTreatments('UT_Segment_member', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'], 'off', '`getTreatments` evaluation using pluggable storage should be correct.'); + assert.equal((await client.getTreatmentsWithConfig('other', ['UT_NOT_IN_SEGMENT']))['UT_NOT_IN_SEGMENT'].treatment, 'on', '`getTreatmentsWithConfig` evaluation using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_SET_MATCHER', { permissions: ['admin'] - }), 'on', 'Evaluations using custom storage should be correct.'); + }), 'on', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_SET_MATCHER', { permissions: ['not_matching'] - }), 'off', 'Evaluations using custom storage should be correct.'); + }), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['create'] - }), 'off', 'Evaluations using custom storage should be correct.'); + }), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['not_matching'] - }), 'on', 'Evaluations using custom storage should be correct.'); + }), 'on', 'Evaluations using pluggable storage should be correct.'); assert.deepEqual(await client.getTreatmentWithConfig('UT_Segment_member', 'UT_NOT_SET_MATCHER', { permissions: ['not_matching'] }), { treatment: 'on', config: null - }, 'Evaluations using custom storage should be correct, including configs.'); + }, 'Evaluations using pluggable storage should be correct, including configs.'); assert.deepEqual(await client.getTreatmentWithConfig('UT_Segment_member', 'always-on-with-config'), { treatment: 'on', config: expectedConfig - }, 'Evaluations using custom storage should be correct, including configs.'); + }, 'Evaluations using pluggable storage should be correct, including configs.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'on', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'on', 'Evaluations using pluggable storage should be correct.'); // Below splits were added manually to the redis_mock.json file. // They are all_keys (always evaluate to on) which depend from always-on split. the _on/off is what treatment they are expecting there. - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_off'), 'off', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on_negated'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_off'), 'off', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'hierarchical_splits_testing_on_negated'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.equal(typeof client.track('nicolas@split.io', 'user', 'test.redis.event', 18).then, 'function', 'Track calls should always return a promise on Consumer mode.'); assert.equal(typeof client.track().then, 'function', 'Track calls should always return a promise on Consumer mode, even when parameters are incorrect.'); @@ -198,7 +198,7 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { // Unlike RedisAdapter, operations to ioredisWrapper fail if it is not ready. client.getTreatment('UT_Segment_member', 'always-on').then(treatment => { - assert.equal(treatment, 'control', 'Evaluations using custom storage should be control if Redis was not ready'); + assert.equal(treatment, 'control', 'Evaluations using pluggable storage should be control if Redis was not ready'); }); client.track('nicolas@split.io', 'user', 'test.redis.event', 18).then(result => { assert.false(result, 'If the event was not queued because Redis was not ready, the promise will resolve to false'); @@ -237,8 +237,8 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { assert.pass('Ready promise is resolved once SDK_READY is emitted'); // some asserts to test regular usage - assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.true(await client.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'If the event was succesfully queued the promise will resolve to true'); assert.false(await client.track(), 'If the event was NOT succesfully queued the promise will resolve to false'); @@ -265,8 +265,8 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { assert.pass('SDK_READY event must be emitted'); // some asserts to test regular usage - assert.equal(await client2.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using custom storage should be correct.'); - assert.equal(await client2.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using custom storage should be correct.'); + assert.equal(await client2.getTreatment('UT_Segment_member', 'UT_IN_SEGMENT'), 'on', 'Evaluations using pluggable storage should be correct.'); + assert.equal(await client2.getTreatment('other', 'UT_IN_SEGMENT'), 'off', 'Evaluations using pluggable storage should be correct.'); assert.true(await client2.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'If the event was succesfully queued the promise will resolve to true'); assert.false(await client2.track(), 'If the event was NOT succesfully queued the promise will resolve to false'); @@ -384,7 +384,6 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { for (let config of configs) { // Redis client and keys required to check Redis store. - const setting = SettingsFactory({ ...config, storage: { ...config.storage, wrapper: ioredisWrapper(redisOptions) } }); const connection = new RedisClient(redisOptions.url); const eventKey = `${redisPrefix}.SPLITIO.impressions`; @@ -409,15 +408,15 @@ tape('NodeJS Custom Storage using a wrapper for Ioredis', function (t) { let redisImpressions = await connection.lrange(impressionsKey, 0, -1); assert.equal(redisImpressions.length, 1, 'After getting a treatment, we should have one impression on Redis.'); const parsedImpression = JSON.parse(redisImpressions[0]); - assert.equal(parsedImpression.m.i, setting.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the impression object must be equal to the machine ip, or "${NA}" otherwise.`); - assert.equal(parsedImpression.m.n, setting.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the impression object must be equal to the machine hostname, or "${NA}" otherwise.`); + assert.equal(parsedImpression.m.i, sdk.settings.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the impression object must be equal to the machine ip, or "${NA}" otherwise.`); + assert.equal(parsedImpression.m.n, sdk.settings.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the impression object must be equal to the machine hostname, or "${NA}" otherwise.`); // Assert if the event object was stored properly let redisEvents = await connection.lrange(eventKey, 0, -1); assert.equal(redisEvents.length, 1, 'After tracking an event, we should have one event on Redis.'); const parsedEvent = JSON.parse(redisEvents[0]); - assert.equal(parsedEvent.m.i, setting.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the event object must be equal to the machine ip, or "${NA}" otherwise.`); - assert.equal(parsedEvent.m.n, setting.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the event object must be equal to the machine hostname, or "${NA}" otherwise.`); + assert.equal(parsedEvent.m.i, sdk.settings.core.IPAddressesEnabled ? IP_VALUE : NA, `If IPAddressesEnabled is true, the property .m.i of the event object must be equal to the machine ip, or "${NA}" otherwise.`); + assert.equal(parsedEvent.m.n, sdk.settings.core.IPAddressesEnabled ? HOSTNAME_VALUE : NA, `If IPAddressesEnabled is true, the property .m.n of the event object must be equal to the machine hostname, or "${NA}" otherwise.`); // Deallocate Split and Redis clients await client.destroy(); diff --git a/src/__tests__/consumerMode/node_redis.spec.js b/src/__tests__/consumerMode/node_redis.spec.js index 0cd01deb6..7626b2d2b 100644 --- a/src/__tests__/consumerMode/node_redis.spec.js +++ b/src/__tests__/consumerMode/node_redis.spec.js @@ -21,11 +21,7 @@ const redisPort = '6385'; const config = { core: { - authorizationKey: 'uoj4sb69bjv7d4d027f7ukkitd53ek6a9ai9' - }, - urls: { - sdk: 'https://sdk-aws-staging.split.io/api', - events: 'https://events-aws-staging.split.io/api' + authorizationKey: 'SOME API KEY' // in consumer mode, api key is only used to identify the sdk instance }, mode: 'consumer', storage: { diff --git a/src/__tests__/nodeSuites/ready-promise.spec.js b/src/__tests__/nodeSuites/ready-promise.spec.js index 5b745803b..26d76a88d 100644 --- a/src/__tests__/nodeSuites/ready-promise.spec.js +++ b/src/__tests__/nodeSuites/ready-promise.spec.js @@ -389,7 +389,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { // We also use the manager to get some of the promises const manager = splitio.manager(); - // promise1 is handled inmediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. + // promise1 is handled immediately. Thus, the 'reject' callback is expected to be called in 0.15 seconds aprox. setTimeout(() => { const promise1 = client.ready(); const tStart = Date.now(); @@ -405,7 +405,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { }); }, 0); - // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called inmediately (0 seconds aprox). + // promise2 is handled in 0.15 seconds, when the promise is just rejected. Thus, the 'reject' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise2 = manager.ready(); const tStart = Date.now(); @@ -417,11 +417,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { t.pass('### SDK TIMED OUT - time out is triggered before retry attempt finishes'); assertGetTreatmentControlNotReady(t, client, key); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "reject" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "reject" callback is expected to be called immediately (0 seconds aprox).'); }); }, fromSecondsToMillis(0.15)); - // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called inmediately (0 seconds aprox). + // promise3 is handled in 0.2 seconds, when the promise is just resolved. Thus, the 'resolve' callback is expected to be called immediately (0 seconds aprox). setTimeout(() => { const promise3 = manager.ready(); const tStart = Date.now(); @@ -430,7 +430,7 @@ export default function readyPromiseAssertions(key, fetchMock, assert) { t.pass('### SDK IS READY - retry attempt finishes before the requestTimeoutBeforeReady limit'); assertGetTreatmentWhenReady(t, client, key); const tDelta = Date.now() - tStart; - assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called inmediately (0 seconds aprox).'); + assert.ok(tDelta < 20, 'The "resolve" callback is expected to be called immediately (0 seconds aprox).'); return Promise.resolve(); }, () => { diff --git a/src/client/inputValidation.js b/src/client/inputValidation.js index 86d19bd77..021c2e26a 100644 --- a/src/client/inputValidation.js +++ b/src/client/inputValidation.js @@ -13,7 +13,7 @@ import { validateIfReady } from '../utils/inputValidation'; import { startsWith } from '../utils/lang'; -import { STORAGE_REDIS, STORAGE_CUSTOM, CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants'; +import { STORAGE_REDIS, STORAGE_PLUGGABLE, CONTROL, CONTROL_WITH_CONFIG } from '../utils/constants'; /** * We will validate the input before actually executing the client methods. We should "guard" the client here, @@ -21,7 +21,7 @@ import { STORAGE_REDIS, STORAGE_CUSTOM, CONTROL, CONTROL_WITH_CONFIG } from '../ */ function ClientInputValidationLayer(context, isKeyBinded, isTTBinded) { const settings = context.get(context.constants.SETTINGS); - const isStorageSync = settings.storage.type !== STORAGE_REDIS && settings.storage.type !== STORAGE_CUSTOM; + const isStorageSync = settings.storage.type !== STORAGE_REDIS && settings.storage.type !== STORAGE_PLUGGABLE; // instantiate the client const client = ClientFactory(context); // Keep a reference to the original methods diff --git a/src/factory/online.js b/src/factory/online.js index a0863434b..7aee1aea4 100644 --- a/src/factory/online.js +++ b/src/factory/online.js @@ -51,7 +51,7 @@ function SplitFactoryOnline(context, readyTrackers, mainClientMetricCollectors) break; } case CONSUMER_MODE: { - context.put(context.constants.READY_FROM_CACHE, true); // For SDK inner workings it's supposed to be ready from cache. + context.put(context.constants.READY_FROM_CACHE, true); // For SDK inner workings it's supposed to be ready from cache, to be operational for evaluations immediately break; } } diff --git a/src/listeners/browser.js b/src/listeners/browser.js index 1cc0534ce..20ec5e685 100644 --- a/src/listeners/browser.js +++ b/src/listeners/browser.js @@ -60,13 +60,15 @@ export default class BrowserSignalListener { * using beacon API if possible, or falling back to regular post transport. */ flushData() { + if (!this.syncManager) return; + this._flushImpressions(); this._flushEvents(); if (this.impressionsCounter) { this._flushImpressionsCount(); } // Close streaming - if (this.syncManager && this.syncManager.pushManager) this.syncManager.pushManager.stop(); + if (this.syncManager.pushManager) this.syncManager.pushManager.stop(); } _flushImpressions() { diff --git a/src/producer/updater/MySegments.js b/src/producer/updater/MySegments.js index f2a528f98..4a17f3dca 100644 --- a/src/producer/updater/MySegments.js +++ b/src/producer/updater/MySegments.js @@ -32,7 +32,7 @@ export default function MySegmentsUpdaterFactory(context) { let readyOnAlreadyExistentState = true; let startingUp = true; - // @TODO if allowing custom storages, handle async execution and wrap errors as SplitErrors to distinguish from user callback errors + // @TODO if allowing pluggable storages, handle async execution and wrap errors as SplitErrors to distinguish from user callback errors function updateSegments(segmentsData) { const mySegmentsCache = storage.segments; diff --git a/src/producer/updater/SplitChanges.js b/src/producer/updater/SplitChanges.js index 4551ec3c5..8dd64ed73 100644 --- a/src/producer/updater/SplitChanges.js +++ b/src/producer/updater/SplitChanges.js @@ -85,7 +85,7 @@ export default function SplitChangesUpdaterFactory(context, isNode = false) { log.debug(`Segment names collected ${mutation.segments}`); // Write into storage - // @TODO if allowing custom storages, wrap errors as SplitErrors to distinguish from user callback errors + // @TODO wrap errors as SplitErrors or migrate error handling as in JS-commons return Promise.all([ // calling first `setChangenumber` method, to perform cache flush if split filter queryString changed storage.splits.setChangeNumber(splitChanges.till), diff --git a/src/storage/node.js b/src/storage/node.js index aa306c8db..3f3a9287a 100644 --- a/src/storage/node.js +++ b/src/storage/node.js @@ -13,7 +13,7 @@ import EventsCacheInMemory from './EventsCache/InMemory'; import EventsCacheInRedis from './EventsCache/InRedis'; import KeyBuilder from './Keys'; import MetaBuilder from './Meta'; -import { STORAGE_MEMORY, STORAGE_REDIS, STORAGE_CUSTOM } from '../utils/constants'; +import { STORAGE_MEMORY, STORAGE_REDIS, STORAGE_PLUGGABLE } from '../utils/constants'; import { PluggableStorage } from '@splitsoftware/splitio-commons'; import LogFactory from '../utils/logger'; @@ -60,7 +60,7 @@ const NodeStorageFactory = context => { }; } - case STORAGE_CUSTOM: { + case STORAGE_PLUGGABLE: { const storageFactory = PluggableStorage(storage); diff --git a/src/sync/SplitUpdateWorker/index.js b/src/sync/SplitUpdateWorker/index.js index 3e9560435..27722b9ef 100644 --- a/src/sync/SplitUpdateWorker/index.js +++ b/src/sync/SplitUpdateWorker/index.js @@ -67,7 +67,6 @@ export default class SplitUpdateWorker { * @param {string} defaultTreatment default treatment value */ killSplit(changeNumber, splitName, defaultTreatment) { - // @TODO handle retry due to errors in storage, once we allow the definition of custom async storages this.splitStorage.killLocally(splitName, defaultTreatment, changeNumber).then((updated) => { // trigger an SDK_UPDATE if Split was killed locally if (updated) this.splitsEventEmitter.emit(this.splitsEventEmitter.SDK_SPLITS_ARRIVED, true); diff --git a/src/utils/constants/index.js b/src/utils/constants/index.js index 7a8dca494..98e1527b5 100644 --- a/src/utils/constants/index.js +++ b/src/utils/constants/index.js @@ -7,7 +7,7 @@ export const CONSUMER_MODE = 'consumer'; export const STORAGE_MEMORY = 'MEMORY'; export const STORAGE_REDIS = 'REDIS'; export const STORAGE_LOCALSTORAGE = 'LOCALSTORAGE'; -export const STORAGE_CUSTOM = 'CUSTOM'; +export const STORAGE_PLUGGABLE = 'PLUGGABLE'; // Special treatments export const CONTROL = 'control'; export const CONTROL_WITH_CONFIG = { diff --git a/ts-tests/index.ts b/ts-tests/index.ts index 36832bc3c..3f038cf67 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -186,7 +186,7 @@ asyncSettings = { authorizationKey: 'key' }, storage: { - type: 'CUSTOM', + type: 'PLUGGABLE', wrapper: new MyWrapper() } }; diff --git a/types/index.d.ts b/types/index.d.ts index eb10a0471..21a3db407 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,4 +1,4 @@ -// Declaration file for Javascript and Node Split Software SDK v8.1.0 +// Declaration file for Javascript and Node Split Software SDK // Project: http://www.split.io/ // Definitions by: Nico Zelaya diff --git a/types/splitio.d.ts b/types/splitio.d.ts index d15b797ae..baa175214 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Javascript and Node Split Software SDK v8.1.0 +// Type definitions for Javascript and Node Split Software SDK // Project: http://www.split.io/ // Definitions by: Nico Zelaya @@ -51,7 +51,7 @@ type SDKMode = 'standalone' | 'consumer'; * Storage types. * @typedef {string} StorageType */ -type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'CUSTOM'; +type StorageType = 'MEMORY' | 'LOCALSTORAGE' | 'REDIS' | 'PLUGGABLE'; /** * Settings interface. This is a representation of the settings the SDK expose, that's why * most of it's props are readonly. Only features should be rewritten when localhost mode is active. @@ -329,7 +329,7 @@ interface INodeBasicSettings extends ISharedSettings { */ options?: Object, /** - * Custom storage wrapper. Use it with type: 'CUSTOM' + * Storage wrapper. Use it with type: 'PLUGGABLE' * @property {Object} wrapper */ wrapper?: Object, @@ -624,7 +624,7 @@ declare namespace SplitIO { * Asynchronous storages valid types for NodeJS. * @typedef {string} NodeAsyncStorage */ - type NodeAsyncStorage = 'REDIS' | 'CUSTOM'; + type NodeAsyncStorage = 'REDIS' | 'PLUGGABLE'; /** * Storage valid types for the browser. * @typedef {string} BrowserStorage @@ -1037,7 +1037,7 @@ declare namespace SplitIO { interface INodeAsyncSettings extends INodeBasicSettings { storage: { /** - * Async storage type (Redis or Custom) to be instantiated by the SDK. + * Async storage type ('REDIS' or 'PLUGGABLE') to be instantiated by the SDK. * @property {NodeAsyncStorage} type */ type: NodeAsyncStorage, @@ -1047,7 +1047,7 @@ declare namespace SplitIO { */ options?: Object, /** - * Custom storage wrapper. Use it with type: 'CUSTOM' + * Storage wrapper. Use it with type: 'PLUGGABLE' * @property {Object} wrapper */ wrapper?: Object,