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

Filter by extension

Filter by extension


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

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio",
"version": "10.15.10-rc.0",
"version": "10.15.10-rc.1",
"description": "Split SDK",
"files": [
"README.md",
Expand Down
26 changes: 17 additions & 9 deletions src/__tests__/browserSuites/ready-from-cache.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,20 @@ export default function (fetchMock, assert) {
t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud');
t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud');

client.once(client.Event.SDK_READY_TIMED_OUT, () => {
client.on(client.Event.SDK_READY_TIMED_OUT, () => {
t.fail('It should not timeout in this scenario.');
t.end();
});

client.once(client.Event.SDK_READY_FROM_CACHE, () => {
client.on(client.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');
});
client2.once(client2.Event.SDK_READY_FROM_CACHE, () => {
client2.on(client2.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client2.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');
});
client3.once(client3.Event.SDK_READY_FROM_CACHE, () => {
client3.on(client3.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client3.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');
});
Expand Down Expand Up @@ -248,7 +248,7 @@ export default function (fetchMock, assert) {
events: 'https://events.baseurl/readyFromCacheWithData3'
};
localStorage.clear();
t.plan(12 * 2 + 4);
t.plan(12 * 2 + 5);

fetchMock.get(testUrls.sdk + '/splitChanges?since=25', function () {
t.equal(localStorage.getItem('readyFromCache_3.SPLITIO.split.always_on'), alwaysOnSplitInverted, 'splits must not be cleaned from cache');
Expand All @@ -264,6 +264,7 @@ export default function (fetchMock, assert) {
fetchMock.get(testUrls.sdk + '/mySegments/nicolas3%40split.io', function () {
return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'mySegments': [] }, headers: {} }), 1000); }); // Third client mySegments will come after 1s
});
fetchMock.get(testUrls.sdk + '/mySegments/nicolas4%40split.io', { 'mySegments': [] });
fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200);
fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200);

Expand Down Expand Up @@ -292,20 +293,27 @@ export default function (fetchMock, assert) {
t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud');
t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud');

client.once(client.Event.SDK_READY_TIMED_OUT, () => {
client.on(client.Event.SDK_READY_TIMED_OUT, () => {
t.fail('It should not timeout in this scenario.');
t.end();
});

client.once(client.Event.SDK_READY_FROM_CACHE, () => {
client.on(client.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');

const client4 = splitio.client('nicolas4@split.io');
t.equal(client4.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control');

client4.on(client4.Event.SDK_READY_FROM_CACHE, () => {
t.fail('It should not emit SDK_READY_FROM_CACHE if already done.');
});
});
client2.once(client2.Event.SDK_READY_FROM_CACHE, () => {
client2.on(client2.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client2.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');
});
client3.once(client3.Event.SDK_READY_FROM_CACHE, () => {
client3.on(client3.Event.SDK_READY_FROM_CACHE, () => {
t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.');
t.equal(client3.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation');
});
Expand Down
42 changes: 33 additions & 9 deletions src/__tests__/offline/browser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ tape('Browser offline mode', function (assert) {
assert.false(client.track({}, [], 'invalid_stuff'));
assert.true(sharedClient.track('a_tt', 'another_ev_id', 10));

assert.equal(client.getTreatment('testing_split'), 'control');
assert.equal(sharedClient.getTreatment('testing_split'), 'control');
assert.equal(client.getTreatment('testing_split'), 'control', 'control due to not ready');
assert.equal(sharedClient.getTreatment('testing_split'), 'control', 'control due to not ready');
assert.equal(manager.splits().length, 0);

// SDK events on shared client
Expand All @@ -90,23 +90,46 @@ tape('Browser offline mode', function (assert) {
const factories = [
SplitFactory(config),
SplitFactory({ ...config }),
SplitFactory({ ...config, features: { ...config.features } })
SplitFactory({ ...config, features: { ...config.features } }),
SplitFactory({ ...config, storage: { type: 'LOCALSTORAGE' } })
];
let readyCount = 0, updateCount = 0;
let readyCount = 0, updateCount = 0, readyFromCacheCount = 0;

for (let i = 0; i < factories.length; i++) {
let client = factories[i].client(), manager = factories[i].manager();
const factory = factories[i], client = factory.client(), manager = factory.manager(), client2 = factory.client('other');

client.on(client.Event.SDK_READY, () => {
assert.deepEqual(manager.names(), ['testing_split', 'testing_split_with_config']);
assert.equal(client.getTreatment('testing_split_with_config'), 'off');
readyCount++;
});
client.on(client.Event.SDK_UPDATE, () => {
assert.equal(client.getTreatment('testing_split_with_config'), 'nope');
assert.deepEqual(manager.names(), ['testing_split', 'testing_split_2', 'testing_split_3', 'testing_split_with_config']);
assert.equal(client.getTreatment('testing_split_with_config'), 'nope');
updateCount++;
});

const sdkReadyFromCache = (client) => () => {
assert.equal(client.__context.get(client.__context.constants.READY_FROM_CACHE, true), true, 'If ready from cache, READY_FROM_CACHE status must be true');
assert.equal(client.__context.get(client.__context.constants.READY, true), undefined, 'READY status must not be set before READY_FROM_CACHE');

assert.deepEqual(manager.names(), ['testing_split', 'testing_split_with_config']);
assert.equal(client.getTreatment('testing_split_with_config'), 'off');
readyFromCacheCount++;

client.on(client.Event.SDK_READY_FROM_CACHE, () => {
assert.fail('It should not emit SDK_READY_FROM_CACHE again');
});

const newClient = factory.client('another');
assert.equal(newClient.getTreatment('testing_split_with_config'), 'off', 'It should evaluate treatments with data from cache instead of control');
newClient.on(newClient.Event.SDK_READY_FROM_CACHE, () => {
assert.fail('It should not emit SDK_READY_FROM_CACHE if already done.');
});
};

client.on(client.Event.SDK_READY_FROM_CACHE, sdkReadyFromCache(client));
client2.on(client2.Event.SDK_READY_FROM_CACHE, sdkReadyFromCache(client2));
}

client.once(client.Event.SDK_READY, function () {
Expand Down Expand Up @@ -140,10 +163,10 @@ tape('Browser offline mode', function (assert) {

// Manager tests
const expectedSplitView1 = {
name: 'testing_split', trafficType: null, killed: false, changeNumber: 0, treatments: ['on'], configs: {}
name: 'testing_split', trafficType: 'localhost', killed: false, changeNumber: 0, treatments: ['on'], configs: {}
};
const expectedSplitView2 = {
name: 'testing_split_with_config', trafficType: null, killed: false, changeNumber: 0, treatments: ['off'], configs: { off: '{ "color": "blue" }' }
name: 'testing_split_with_config', trafficType: 'localhost', killed: false, changeNumber: 0, treatments: ['off'], configs: { off: '{ "color": "blue" }' }
};
assert.deepEqual(manager.names(), ['testing_split', 'testing_split_with_config']);
assert.deepEqual(manager.split('testing_split'), expectedSplitView1);
Expand Down Expand Up @@ -241,7 +264,7 @@ tape('Browser offline mode', function (assert) {

// Manager tests
const expectedSplitView3 = {
name: 'testing_split_with_config', trafficType: null, killed: false, changeNumber: 0, treatments: ['nope'], configs: {}
name: 'testing_split_with_config', trafficType: 'localhost', killed: false, changeNumber: 0, treatments: ['nope'], configs: {}
};
assert.deepEqual(manager.names(), ['testing_split', 'testing_split_2', 'testing_split_3', 'testing_split_with_config']);
assert.deepEqual(manager.split('testing_split'), expectedSplitView1);
Expand Down Expand Up @@ -310,6 +333,7 @@ tape('Browser offline mode', function (assert) {
// SDK events on other factory clients
assert.equal(readyCount, factories.length, 'Each factory client should have emitted SDK_READY event once');
assert.equal(updateCount, factories.length - 1, 'Each factory client except one should have emitted SDK_UPDATE event once');
assert.equal(readyFromCacheCount, 2, 'The main and shared client of the factory with LOCALSTORAGE should have emitted SDK_READY_FROM_CACHE event');

assert.end();
});
Expand Down
12 changes: 6 additions & 6 deletions src/__tests__/offline/node.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,15 @@ function ManagerDotSplitTests(assert) {
assert.deepEqual(manager.names(), ['testing_split', 'testing_split2', 'testing_split3']);

const expectedView1 = {
name: 'testing_split', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['on'], configs: {}
};
const expectedView2 = {
name: 'testing_split2', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split2', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['off'], configs: {}
};
const expectedView3 = {
name: 'testing_split3', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split3', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['custom_treatment'], configs: {}
};

Expand Down Expand Up @@ -397,15 +397,15 @@ function MultipleInstancesTests(assert) {
assert.deepEqual(manager.names(), ['testing_split', 'testing_split2', 'testing_split3']);

const expectedView1 = {
name: 'testing_split', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['on'], configs: {}
};
const expectedView2 = {
name: 'testing_split2', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split2', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['off'], configs: {}
};
const expectedView3 = {
name: 'testing_split3', changeNumber: 0, killed: false, trafficType: null,
name: 'testing_split3', changeNumber: 0, killed: false, trafficType: 'localhost',
treatments: ['custom_treatment'], configs: {}
};

Expand Down
17 changes: 17 additions & 0 deletions src/impressions/hasher/__tests__/buildKey.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import tape from 'tape-catch';
import { buildKey } from '../buildKey';

tape('buildKey', assert => {
assert.equal(buildKey({}), 'undefined:undefined:undefined:undefined:undefined');

const imp = {
feature: 'feature_0',
keyName: 'key_0',
changeNumber: 0,
label: 'in segment all',
treatment: 'someTreatment',
};
assert.equal(buildKey(imp), 'key_0:feature_0:someTreatment:in segment all:0');

assert.end();
});
2 changes: 1 addition & 1 deletion src/impressions/hasher/hashImpression32.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import murmur from '../../engine/engine/murmur3/murmur3';
import { buildKey } from './buildKey';

export function hashImpression32(impression) {
return murmur.hash(buildKey(impression)).toString();
return murmur.hash(buildKey(impression));
}
2 changes: 1 addition & 1 deletion src/metrics/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const MetricsFactory = context => {
const pushMetrics = () => {
if (storage.metrics.isEmpty() && storage.count.isEmpty()) return Promise.resolve();

log.info('Pushing metrics');
log.debug('Pushing metrics');
const latencyTrackerStop = tracker.start(tracker.TaskNames.METRICS_PUSH);

// POST latencies
Expand Down
7 changes: 5 additions & 2 deletions src/producer/updater/SplitChanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,11 @@ export default function SplitChangesUpdaterFactory(context, isNode = false) {
return false;
});

// After triggering the requests, if we have cached splits information let's notify that.
if (startingUp && storage.splits.checkCache()) splitsEventEmitter.emit(splitsEventEmitter.SDK_SPLITS_CACHE_LOADED);
// After triggering the requests, if we have cached splits information let's notify
// that asynchronously, to let attach a listener for SDK_READY_FROM_CACHE
if (startingUp && storage.splits.checkCache()) {
setTimeout(splitsEventEmitter.emit(splitsEventEmitter.SDK_SPLITS_CACHE_LOADED), 0);
}

return fetcherPromise;
}
Expand Down
14 changes: 9 additions & 5 deletions src/producer/updater/SplitChangesFromObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function FromObjectUpdaterFactory(Fetcher, context) {
[context.constants.STORAGE]: storage
} = context.getAll();

let firstTime = true;
let startingUp = true;

return function ObjectUpdater() {
const splits = [];
Expand Down Expand Up @@ -58,13 +58,17 @@ function FromObjectUpdaterFactory(Fetcher, context) {
});

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

if (startingUp) {
startingUp = false;
// Emits SDK_READY_FROM_CACHE
if (storage.splits.checkCache()) readiness.splits.emit(readiness.splits.SDK_SPLITS_CACHE_LOADED);
// Only emits SDK_SEGMENTS_ARRIVED the first time for SDK_READY
readiness.segments.emit(readiness.segments.SDK_SEGMENTS_ARRIVED);
}
});
Expand Down
3 changes: 1 addition & 2 deletions src/readiness/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ function GateContext() {
});

splits.once(Events.SDK_SPLITS_CACHE_LOADED, () => {
// Make it async
setTimeout(() => gate.emit(Events.SDK_READY_FROM_CACHE), 0);
gate.emit(Events.SDK_READY_FROM_CACHE);
});

segments.on(Events.SDK_SEGMENTS_ARRIVED, () => {
Expand Down
1 change: 1 addition & 0 deletions src/services/splitChanges/offline/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export default function createGetConfigurationFromSettings() {
if (config !== null) configurations[treatment] = config;

splitObjects[splitName] = {
trafficTypeName: 'localhost',
conditions: [parseCondition({ treatment })],
configurations
};
Expand Down
2 changes: 1 addition & 1 deletion src/services/splitChanges/offline/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default function createGetSplitConfigForFile() {
} else {
const splitName = tuple[SPLIT_POSITION];
const condition = parseCondition({ treatment: tuple[TREATMENT_POSITION] });
accum[splitName] = { conditions: [condition], configurations: {} };
accum[splitName] = { conditions: [condition], configurations: {}, trafficTypeName: 'localhost' };
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/sync/PushManager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export default function PushManagerFactory(context, clientContexts /* undefined
pushEmitter.on(SPLIT_UPDATE, splitUpdateWorker.put);

if (clientContexts) { // browser
// @TODO remove
pushEmitter.on(MY_SEGMENTS_UPDATE, function handleMySegmentsUpdate(parsedData, channel) {
const userKeyHash = channel.split('_')[2];
const userKey = userKeyHashes[userKeyHash];
Expand All @@ -231,8 +230,7 @@ export default function PushManagerFactory(context, clientContexts /* undefined

forOwn(clients, ({ hash64, worker }) => {
if (isInBitmap(bitmap, hash64.hex)) {
// fetch mySegments
worker.put(parsedData.changeNumber);
worker.put(parsedData.changeNumber); // fetch mySegments
}
});
return;
Expand Down
1 change: 0 additions & 1 deletion src/sync/SSEHandler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export default function SSEHandlerFactory(pushEmitter) {
parsedData.changeNumber,
parsedData.segmentName);
break;
// @TODO remove
case MY_SEGMENTS_UPDATE:
pushEmitter.emit(MY_SEGMENTS_UPDATE,
parsedData,
Expand Down
1 change: 1 addition & 0 deletions src/utils/__tests__/settings/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ tape('SETTINGS / check defaults', assert => {
});
assert.equal(settings.sync.impressionsMode, OPTIMIZED);
assert.equal(settings.version, `${language}-${packageJson.version}`);
assert.true(packageJson.version.length <= 16, 'SDK version must not exceed 16 chars length');
assert.end();
});

Expand Down
2 changes: 1 addition & 1 deletion src/utils/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { API } from '../../utils/logger';
import { STANDALONE_MODE, STORAGE_MEMORY, CONSUMER_MODE, OPTIMIZED } from '../../utils/constants';
import validImpressionsMode from './impressionsMode';

const version = '10.15.10-rc.0';
const version = '10.15.10-rc.1';
const eventsEndpointMatcher = /^\/(testImpressions|metrics|events)/;
const authEndpointMatcher = /^\/v2\/auth/;
const streamingEndpointMatcher = /^\/(sse|event-stream)/;
Expand Down
7 changes: 0 additions & 7 deletions src/utils/settings/storage/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import logFactory from '../../../utils/logger';
const log = logFactory('splitio-settings');
import isLocalStorageAvailable from '../../../utils/localstorage/isAvailable';
import {
LOCALHOST_MODE,
STORAGE_MEMORY,
STORAGE_LOCALSTORAGE
} from '../../../utils/constants';

const ParseStorageSettings = settings => {
let {
mode,
storage: {
type = STORAGE_MEMORY,
options = {},
Expand All @@ -39,11 +37,6 @@ const ParseStorageSettings = settings => {
prefix = 'SPLITIO';
}

if (mode === LOCALHOST_MODE) return {
type: STORAGE_MEMORY,
prefix
};

// If an invalid storage type is provided OR we want to use LOCALSTORAGE and
// it's not available, fallback into MEMORY
if (type !== STORAGE_MEMORY && type !== STORAGE_LOCALSTORAGE ||
Expand Down