diff --git a/CHANGES.txt b/CHANGES.txt index 74ab03f90..f4fbaccb5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,43 @@ +## 6.0.0 (June 24, 2016) + +* In the browser land we allow quick retries before start using the refresh + rates defined for segments and splits, plus the possibility of receive an + event when the SDK is taking to much time to startup. + + ```html + + + ``` + ## 5.1.1 (June 13, 2016) * None API changes. Bug fixing release. diff --git a/NEWS.txt b/NEWS.txt index 5d5138126..2febb426e 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -1,3 +1,12 @@ +## 6.0.0 (June 24, 2016) + +* In the browser land, it's important to have quick retries before after a + stretch timeouts. We added new configurations to handle this. +* Added the concept of 'ready timeout', so you could take a controlled action if + the SDK is taking too much time doing the startup. +* Fixed few bugs, specially for the browser release. +* `.ready()` method is deprecated in favor of `sdk.on(sdk.Event.SDK_READY, function onReady() { do something; })` + ## 5.1.1 (June 13, 2016) * Added missing support for events in offline mode (NodeJS and Browser). diff --git a/crazy-cdn/.babelrc b/crazy-cdn/.babelrc new file mode 100644 index 000000000..e7b199803 --- /dev/null +++ b/crazy-cdn/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015-node4"] +} diff --git a/crazy-cdn/.gitignore b/crazy-cdn/.gitignore new file mode 100644 index 000000000..502167fa0 --- /dev/null +++ b/crazy-cdn/.gitignore @@ -0,0 +1 @@ +/lib diff --git a/crazy-cdn/README.md b/crazy-cdn/README.md new file mode 100644 index 000000000..4e60b02e1 --- /dev/null +++ b/crazy-cdn/README.md @@ -0,0 +1,28 @@ +# Crazy CDN + +The concept is a proxy which add delays and eventually errors in the services +using express middlewares. + +## How to run + +1. `nvm install v4` +2. `nvm use v4` +3. `npm install` +4. `npm run b` +5. `npm start` +6. `The proxy server is running in localhost:3000` + +By default the target is hardcoded to be staging servers, but you could change +that quickly. + +> HTTPS is disabled using a "un-secure" scheme because we are a man in the + middle. + +## How to development + +1. `nvm install v4` +2. `nvm use v4` +3. `npm install` +4. `npm run w` => live recompilation on changes +5. `npm run m` => live nodejs reload after recompilation changes +6. `The proxy server is running in localhost:3000` diff --git a/crazy-cdn/package.json b/crazy-cdn/package.json new file mode 100644 index 000000000..0de2cff58 --- /dev/null +++ b/crazy-cdn/package.json @@ -0,0 +1,30 @@ +{ + "name": "@splitsoftware/crazy-cdn", + "version": "1.0.0", + "description": "Crazy CDN", + "readme": "README.md", + "author": "Facundo Cabrera ", + "homepage": "https://github.com/splitio/javascript-client", + "license": "Apache-2.0", + "repository": "https://github.com/splitio/javascript-client/tree/master/crazy-cdn", + "main": "lib/index.js", + "scripts": { + "start": "node lib/index.js", + "m": "nodemon lib/index.js", + "b": "babel src --out-dir lib", + "w": "babel src --out-dir lib -w" + }, + "engines": { + "node": "4.4.3", + "npm": "3.9.3" + }, + "dependencies": { + "express": "^4.14.0", + "http-proxy": "^1.14.0" + }, + "devDependencies": { + "babel-cli": "^6.10.1", + "babel-preset-es2015-node4": "^2.1.0", + "nodemon": "^1.9.2" + } +} diff --git a/crazy-cdn/src/index.js b/crazy-cdn/src/index.js new file mode 100644 index 000000000..d999eff0a --- /dev/null +++ b/crazy-cdn/src/index.js @@ -0,0 +1,38 @@ +const express = require('express'); +const app = express(); + +const httpProxy = require('http-proxy'); +const proxy = httpProxy.createProxyServer({}); + +app.use(function(err, req, res, next) { + res.status(503).send(JSON.stringigy(err)); +}); + +app.use(function delay(req, res, next) { + setTimeout(next, Math.random() * 3000); +}); + +app.use(function internalError(req, res, next) { + // if (Math.random() > 0.8) { + if (false) { + res.status(500).send({ + status: 500, + message: 'internal error', + type:'internal' + }); + } else { + next(); + } +}); + +app.all('/*', function(req, res) { + proxy.web(req, res, { + target: 'https://sdk-aws-staging.split.io', + secure: false, + changeOrigin: true + }); +}); + +app.listen(3000, function () { + console.log('Crazy proxy at 3000 port'); +}); diff --git a/demos/browser-split/offline/app.js b/demos/browser-split/offline/app.js index e7648c459..99f22f4fb 100644 --- a/demos/browser-split/offline/app.js +++ b/demos/browser-split/offline/app.js @@ -31,7 +31,7 @@ console.info( // The following code will be evaluated once the engine finalice the // initialization // -sdk.ready().then(function () { +sdk.on(sdk.Event.SDK_READY, function onSDKReady() { // // Some simple cases for my defined features // @@ -48,3 +48,6 @@ sdk.ready().then(function () { "<= The expected answer based on the definition before is 'delta'" ); }); + +// just to show up the deprecated message +sdk.ready().then(function() {}); diff --git a/demos/browser-split/offline/index.html b/demos/browser-split/offline/index.html index fda2b5db3..e95a0a26d 100644 --- a/demos/browser-split/offline/index.html +++ b/demos/browser-split/offline/index.html @@ -10,7 +10,7 @@
Hello SPLIT! => Please use the devTools to start testing the engine!
- + diff --git a/demos/browser-split/online/app.js b/demos/browser-split/online/app.js index 9e8b3e713..0e2c2f949 100644 --- a/demos/browser-split/online/app.js +++ b/demos/browser-split/online/app.js @@ -1,7 +1,5 @@ 'use strict'; -console.log('SPLIT DEMO!'); - // // Bellow you will see how you could define features and the defaults treatments // for each one. @@ -9,22 +7,45 @@ console.log('SPLIT DEMO!'); // NOTICE: there is NONE asyncronous initialization in offline mode, because you // are providing the default feedback of the engine. // - var sdk = splitio({ core: { - authorizationKey: '29lsbc79peklpksdto0a90s2e3u1agv8vqm2', // change this with your api token - key: '4a2c4490-ced1-11e5-9b97-d8a25e8b1578' // change this with your user key - }/*, + // change this with your api token + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib', + // change this with your user key + key: '1f84e5ddb06a3e66145ccfc1aac247' + }, scheduler: { - featuresRefreshRate: 1, // fetch feature updates each 1 sec - segmentsRefreshRate: 1, // fetch segment updates each 1 sec - metricsRefreshRate: 30, // publish metrics each 30 sec - impressionsRefreshRate: 30 // publish evaluations each 30 sec - }*/ + // fetch feature updates each 15 sec + featuresRefreshRate: 15, + // fetch segment updates each 15 sec + segmentsRefreshRate: 15, + // publish metrics each 15 sec + metricsRefreshRate: 15, + // publish evaluations each 15 sec + impressionsRefreshRate: 15 + }, + urls: { + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' + } }); -console.info( sdk.getTreatment('early_evaluation') , '<= We are asking for a feature before the engine is ready'); +console.assert( + sdk.getTreatment('in_five_keys') === 'control' +); + +sdk.on(sdk.Event.SDK_READY_TIMED_OUT, function onTimeout() { + console.log('SDK ready timeout'); +}); -sdk.ready().then(function () { - console.info( sdk.getTreatment('js_sdk'), '<= This answer depends on split configurations' ); +sdk.on(sdk.Event.SDK_READY, function onSDKReady() { + console.assert(sdk.getTreatment('in_five_keys') === 'activated'); }); + +sdk.on(sdk.Event.SDK_UPDATE, function onSDKUpdate() { + console.log(sdk.getTreatment('in_five_keys')); + console.log(sdk.getTreatment('in_ten_keys')); +}); + +// just to show up the deprecated message +sdk.ready().then(function () {}); diff --git a/demos/browser-split/online/index.html b/demos/browser-split/online/index.html index 8e4f012a9..0a2dfed3f 100644 --- a/demos/browser-split/online/index.html +++ b/demos/browser-split/online/index.html @@ -10,7 +10,7 @@
Hello SPLIT! => Please use the devTools to start testing the engine!
- + diff --git a/demos/browser-split/package.json b/demos/browser-split/package.json index ba8e096d1..0ec1952de 100644 --- a/demos/browser-split/package.json +++ b/demos/browser-split/package.json @@ -15,6 +15,6 @@ "http-server": "^0.9.0" }, "devDependencies": { - "@splitsoftware/splitio-browser": "5.1.1" + "@splitsoftware/splitio-browser": "6.0.0" } } diff --git a/demos/express-split/index.js b/demos/express-split/index.js index c39628d86..a659c2a46 100644 --- a/demos/express-split/index.js +++ b/demos/express-split/index.js @@ -14,17 +14,17 @@ if (process.env.SPLIT_SDK_MODE === 'offline') { } else { sdk = splitio({ core: { - authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' // nodejs environment + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { sdk: 'https://sdk-aws-staging.split.io/api', events: 'https://events-aws-staging.split.io/api' }, scheduler: { - featuresRefreshRate: 5, // fetch feature updates each 1 sec - segmentsRefreshRate: 5, // fetch segment updates each 1 sec - metricsRefreshRate: 10, // publish metrics each 30 sec - impressionsRefreshRate: 10 // publish evaluations each 30 sec + featuresRefreshRate: 15, + segmentsRefreshRate: 15, + metricsRefreshRate: 15, + impressionsRefreshRate: 15 } }); } diff --git a/demos/express-split/package.json b/demos/express-split/package.json index bb950faca..129075878 100644 --- a/demos/express-split/package.json +++ b/demos/express-split/package.json @@ -9,14 +9,15 @@ "repository": "https://github.com/splitio/javascript-client/tree/master/demo/express-split", "main": "index.js", "scripts": { - "start": "DEBUG=splitio* node index.js" + "start": "node index.js", + "debug": "DEBUG=splitio* node index.js" }, "engines": { "node": ">=0.12", "npm": "3.x" }, "dependencies": { - "@splitsoftware/splitio": "5.1.1", + "@splitsoftware/splitio": "6.0.0", "express": "^4.13.3" }, "devDependencies": { diff --git a/e2e/package.json b/e2e/package.json index 59c34ed4f..1049b27ee 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -14,8 +14,8 @@ "watch": "babel es6 --out-dir lib --watch" }, "dependencies": { - "@splitsoftware/splitio": "5.1.1", - "@splitsoftware/splitio-utils": "5.1.1", + "@splitsoftware/splitio": "6.0.0", + "@splitsoftware/splitio-utils": "6.0.0", "babel-runtime": "^5.8.35", "core-js": "^1.2.6" }, diff --git a/package.json b/package.json index 99fd1651e..48a21f16c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "license": "Apache-2", - "version": "5.1.1", + "version": "6.0.0", "scripts": { "lint": "./scripts/lint.sh", "install-all": "./scripts/install-all.sh", diff --git a/packages/splitio-browser/bin/bundler.js b/packages/splitio-browser/bin/bundler.js index 462b4e8ae..24a19ed6c 100755 --- a/packages/splitio-browser/bin/bundler.js +++ b/packages/splitio-browser/bin/bundler.js @@ -86,7 +86,7 @@ const debug = browserify({ debug: true }); debug.add(splitSource); debug.transform(envify({ _: 'purge', - NODE_ENV: process.env.NODE_ENV || 'production' + NODE_ENV: 'development' }), { global: true }) .bundle() .pipe(fs.createWriteStream(debugBundlePath)); @@ -95,7 +95,7 @@ const prod = browserify(); prod.add(splitSource); prod.transform(envify({ _: 'purge', - NODE_ENV: process.env.NODE_ENV || 'production' + NODE_ENV: 'production' }), { global: true }) .bundle() .pipe(fs.createWriteStream(productionBundlePath)) diff --git a/packages/splitio-browser/package.json b/packages/splitio-browser/package.json index f0841745a..e63bd5e87 100644 --- a/packages/splitio-browser/package.json +++ b/packages/splitio-browser/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-browser", - "version": "5.1.1", + "version": "6.0.0", "description": "Split SDK tools for the browser", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -25,7 +25,7 @@ "sdk" ], "dependencies": { - "@splitsoftware/splitio": "5.1.1", + "@splitsoftware/splitio": "6.0.0", "browserify": "^13.0.0", "browserify-derequire": "^0.9.4", "bundle-collapser": "^1.2.1", diff --git a/packages/splitio-cache/es6/ds/mySegments.js b/packages/splitio-cache/es6/ds/mySegments.js index e0c041e65..57dda98f6 100644 --- a/packages/splitio-cache/es6/ds/mySegments.js +++ b/packages/splitio-cache/es6/ds/mySegments.js @@ -13,16 +13,23 @@ 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. **/ +const timeout = require('@splitsoftware/splitio-utils/lib/promise/timeout'); + const mySegmentsService = require('@splitsoftware/splitio-services/lib/mySegments'); const mySegmentsRequest = require('@splitsoftware/splitio-services/lib/mySegments/get'); const mySegmentMutationsFactory = require('../mutators/mySegments'); -function mySegmentsDataSource(settings) { - return mySegmentsService(mySegmentsRequest(settings)) - .then(resp => resp.json()) - .then(json => mySegmentMutationsFactory(json.mySegments.map(segment => segment.name))) - .catch(() => false); +function mySegmentsDataSource(settings, shouldApplyTimeout = false) { + let requestPromise = mySegmentsService(mySegmentsRequest(settings)); + + if (shouldApplyTimeout) { + requestPromise = timeout(settings.startup.requestTimeoutBeforeReady, requestPromise); + } + + return requestPromise.then(resp => resp.json()).then( + json => mySegmentMutationsFactory(json.mySegments.map(segment => segment.name)) + ); } module.exports = mySegmentsDataSource; diff --git a/packages/splitio-cache/es6/ds/segmentChanges.js b/packages/splitio-cache/es6/ds/segmentChanges.js index df1dcf0a0..fdfac18a8 100644 --- a/packages/splitio-cache/es6/ds/segmentChanges.js +++ b/packages/splitio-cache/es6/ds/segmentChanges.js @@ -13,7 +13,6 @@ 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. **/ - const segmentChangesService = require('@splitsoftware/splitio-services/lib/segmentChanges'); const segmentChangesRequest = require('@splitsoftware/splitio-services/lib/segmentChanges/get'); @@ -46,15 +45,30 @@ function greedyFetch(settings, lastSinceValue, segmentName) { function segmentChangesDataSource(settings, segmentName, sinceValuesCache) { const sinceValue = sinceValuesCache.get(segmentName) || -1; - return greedyFetch(settings, sinceValue, segmentName).then((changes) => { + return greedyFetch(settings, sinceValue, segmentName).then(changes => { const len = changes.length; + const lastChange = len > 0 ? changes[len - 1] : false; + + // doesn't matter if we fully download the information, say if we need to + // update the data or not. const shouldUpdate = !(len === 0 || len === 1 && changes[0].since === changes[0].till); + // do we fully download the segment's changes from the server? + const isFullUpdate = lastChange && lastChange.since === lastChange.till; + + // fn which actually applies the changes + let mutator = () => false; + if (shouldUpdate) { sinceValuesCache.set(segmentName, changes[len - 1].till); + mutator = segmentMutatorFactory(changes); } - return segmentMutatorFactory(shouldUpdate, changes); + return { + shouldUpdate, // did an update? + isFullUpdate, // did it was partial or full? + mutator + }; }); } diff --git a/packages/splitio-cache/es6/ds/splitChanges.js b/packages/splitio-cache/es6/ds/splitChanges.js index a11c02dad..fc6a2925b 100644 --- a/packages/splitio-cache/es6/ds/splitChanges.js +++ b/packages/splitio-cache/es6/ds/splitChanges.js @@ -13,24 +13,28 @@ 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. **/ +const timeout = require('@splitsoftware/splitio-utils/lib/promise/timeout'); const splitChangesService = require('@splitsoftware/splitio-services/lib/splitChanges'); const splitChangesRequest = require('@splitsoftware/splitio-services/lib/splitChanges/get'); const splitMutatorFactory = require('../mutators/splitChanges'); -function splitChangesDataSource(settings, sinceValueCache) { - return splitChangesService(splitChangesRequest(settings, sinceValueCache)) - .then(resp => resp.json()) - .then(json => { +function splitChangesDataSource(settings, sinceValueCache, shouldApplyTimeout = false) { + let requestPromise = splitChangesService(splitChangesRequest(settings, sinceValueCache)); + + if (shouldApplyTimeout) { + requestPromise = timeout(settings.startup.requestTimeoutBeforeReady, requestPromise); + } + + return requestPromise.then(resp => resp.json()).then(json => { const {till, splits} = json; const shouldUpdate = sinceValueCache.since !== till; sinceValueCache.since = till; return splitMutatorFactory(shouldUpdate, splits); - }) - .catch(() => false); + }); } module.exports = splitChangesDataSource; diff --git a/packages/splitio-cache/es6/index.js b/packages/splitio-cache/es6/index.js index 23b9dbf1e..389988056 100644 --- a/packages/splitio-cache/es6/index.js +++ b/packages/splitio-cache/es6/index.js @@ -13,38 +13,31 @@ 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. **/ -const SchedulerFactory = require('@splitsoftware/splitio-utils/lib/scheduler'); const Storage = require('./storage'); -const Updaters = require('./updaters'); - -const log = require('debug')('splitio-cache'); -const sync = require('./sync'); +const { + SplitsUpdater, + SegmentsUpdater, + Updater +} = require('./updaters'); class Cache { constructor(settings, hub) { - this.settings = settings; - this.hub = hub; - - this.splitRefreshScheduler = SchedulerFactory(); - this.segmentsRefreshScheduler = SchedulerFactory(); - this.storage = Storage.createStorage(); - this.splitsUpdater = Updaters.SplitsUpdater(this.settings, this.hub, this.storage); - this.segmentsUpdater = Updaters.SegmentsUpdater(this.settings, this.hub, this.storage); + this.updater = new Updater( + SplitsUpdater(settings, hub, this.storage), + SegmentsUpdater(settings, hub, this.storage), + settings.scheduler.featuresRefreshRate, + settings.scheduler.segmentsRefreshRate + ); } start() { - log('sync started'); - - return sync.call(this); + this.updater.start(); } stop() { - log('stopped syncing'); - - this.splitRefreshScheduler.kill(); - this.segmentsRefreshScheduler.kill(); + this.updater.stop(); } } diff --git a/packages/splitio-cache/es6/mutators/mySegments.js b/packages/splitio-cache/es6/mutators/mySegments.js index b01dae532..344e551c7 100644 --- a/packages/splitio-cache/es6/mutators/mySegments.js +++ b/packages/splitio-cache/es6/mutators/mySegments.js @@ -23,15 +23,15 @@ module.exports = function MySegmentMutationsFactory( return function segmentMutations(storage /*: Object */) /*: void */ { const nextSegments = new Set(mySegments); + const sameAmountOfElements = storage.segments.size === nextSegments.size; let isEqual = true; - let shouldUpdate; + let shouldUpdate = false; - // weak logic for performance for (let i = 0; i < mySegments.length && isEqual; i++) { isEqual = storage.segments.has(mySegments[i]); } - shouldUpdate = !isEqual; + shouldUpdate = !isEqual || !sameAmountOfElements; if (shouldUpdate) { storage.segments.update(nextSegments); diff --git a/packages/splitio-cache/es6/mutators/segmentChanges.js b/packages/splitio-cache/es6/mutators/segmentChanges.js index 3a6ce0b8d..6a3ba9cee 100644 --- a/packages/splitio-cache/es6/mutators/segmentChanges.js +++ b/packages/splitio-cache/es6/mutators/segmentChanges.js @@ -25,11 +25,9 @@ type SegmentChangesDTOCollection = Array; */ const log = require('debug')('splitio-cache:mutators'); -module.exports = function SegmentMutationsFactory( - shouldUpdate /*: bool */, changes /*: SegmentChangesDTOCollection */ -) /*: Function */ { +function SegmentMutationsFactory(changes /*: SegmentChangesDTOCollection */) /*: Function */ { return function segmentMutations(storage /*: Object */) /*: void */ { - shouldUpdate && changes.forEach(({name, added, removed}) => { + changes.forEach(({name, added, removed}) => { const segment = storage.segments.get(name); log(`Adding ${added.length} new keys to the segment ${name}`); @@ -42,7 +40,7 @@ module.exports = function SegmentMutationsFactory( storage.segments.update(name, segment); }); - - return shouldUpdate; }; -}; +} + +module.exports = SegmentMutationsFactory; diff --git a/packages/splitio-cache/es6/storage/segments/browser.js b/packages/splitio-cache/es6/storage/segments/browser.js index c89b0823e..a0515afe4 100644 --- a/packages/splitio-cache/es6/storage/segments/browser.js +++ b/packages/splitio-cache/es6/storage/segments/browser.js @@ -29,6 +29,10 @@ class SegmentsStorage { toJSON() { return this.storage.toJSON(); } + + get size() { + return this.storage.size; + } } module.exports = SegmentsStorage; diff --git a/packages/splitio-cache/es6/storage/segments/node.js b/packages/splitio-cache/es6/storage/segments/node.js index 7493f6908..a118bb73a 100644 --- a/packages/splitio-cache/es6/storage/segments/node.js +++ b/packages/splitio-cache/es6/storage/segments/node.js @@ -33,6 +33,10 @@ class SegmentsStorage { segmentNames() { return this.storage.keys(); } + + get size() { + return this.storage.size; + } } module.exports = SegmentsStorage; diff --git a/packages/splitio-cache/es6/storage/splits/index.js b/packages/splitio-cache/es6/storage/splits/index.js index 5f50a2a52..87efb270b 100644 --- a/packages/splitio-cache/es6/storage/splits/index.js +++ b/packages/splitio-cache/es6/storage/splits/index.js @@ -47,6 +47,10 @@ class SplitsStorage { toJSON() /*: string */ { return this.storage.toJSON(); } + + get size() { + return this.storage.size; + } } module.exports = SplitsStorage; diff --git a/packages/splitio-cache/es6/sync/node.js b/packages/splitio-cache/es6/sync/node.js deleted file mode 100644 index d07ac75e0..000000000 --- a/packages/splitio-cache/es6/sync/node.js +++ /dev/null @@ -1,32 +0,0 @@ -/** -Copyright 2016 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. -**/ - -module.exports = function parallel() { - return Promise.all([ - this.splitRefreshScheduler.forever( - this.splitsUpdater, - this.settings.get('featuresRefreshRate'), - this.settings.get('core') - ), - this.segmentsRefreshScheduler.forever( - this.segmentsUpdater, - this.settings.get('segmentsRefreshRate'), - this.settings.get('core') - ) - ]).then(() => { - return this.storage; - }); -}; diff --git a/packages/splitio-cache/es6/updater/mySegments.js b/packages/splitio-cache/es6/updater/mySegments.js index 45c4407fe..2e5a4c05b 100644 --- a/packages/splitio-cache/es6/updater/mySegments.js +++ b/packages/splitio-cache/es6/updater/mySegments.js @@ -16,13 +16,37 @@ limitations under the License. const log = require('debug')('splitio-cache:updater'); const mySegmentsDataSource = require('../ds/mySegments'); -module.exports = function MySegmentsUpdater(settings, hub, storage) { - return function updateMySegments() { - log('Updating mySegments'); +function MySegmentsUpdater(settings, hub, storage) { + // only enable retries first load + let startingUp = true; - return mySegmentsDataSource(settings) + return function updateMySegments(retry = 0) { + return mySegmentsDataSource(settings, startingUp) .then(segmentsMutator => segmentsMutator(storage)) - .then(shouldUpdate => shouldUpdate && hub.emit(hub.Event.SDK_UPDATE, storage)) - .catch(error => hub.emit(hub.Event.SDK_UPDATE_ERROR, error)); + .then(shouldUpdate => { + if (startingUp) { + startingUp = false; + } + + if (shouldUpdate) { + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + + return shouldUpdate; + }) + .catch(error => { + if (startingUp && settings.startup.retriesOnFailureBeforeReady > retry) { + retry += 1; + log('retrying download of segments #%s reason %s', retry, error); + return updateMySegments(retry); + } else { + startingUp = false; + } + + return false; // shouldUpdate = false + }); }; -}; + +} + +module.exports = MySegmentsUpdater; diff --git a/packages/splitio-cache/es6/updater/segmentChanges.js b/packages/splitio-cache/es6/updater/segmentChanges.js index 0fa2fec41..7e1ca32df 100644 --- a/packages/splitio-cache/es6/updater/segmentChanges.js +++ b/packages/splitio-cache/es6/updater/segmentChanges.js @@ -18,21 +18,62 @@ const segmentChangesDataSource = require('../ds/segmentChanges'); module.exports = function SegmentChangesUpdater(settings, hub, storage) { const sinceValuesCache = new Map(); + let segmentsAreReady = new Map(); + let startingUp = true; return function updateSegments() { log('Updating segmentChanges'); const downloads = [...storage.splits.getSegments()].map(segmentName => { - return segmentChangesDataSource(settings, segmentName, sinceValuesCache).then(mutator => { + // register segments for future check if they are ready or not + if (startingUp) { + if (segmentsAreReady.get(segmentName) === undefined) { + segmentsAreReady.set(segmentName, false); + } + } + + return segmentChangesDataSource(settings, segmentName, sinceValuesCache).then(({ + shouldUpdate, isFullUpdate, mutator + }) => { log(`completed download of ${segmentName}`); - return mutator(storage); + // apply mutations + mutator(storage); + + // register segment data as ready if required + if (startingUp && segmentsAreReady.get(segmentName) === false && isFullUpdate) { + segmentsAreReady.set(segmentName, true); + } + + // did we apply an update? + return shouldUpdate; }); }); - return Promise.all(downloads) - .then(shouldUpdates => - (shouldUpdates.indexOf(true) !== -1) && hub.emit(hub.Event.SDK_UPDATE, storage) - ).catch((error) => hub.emit(hub.Event.SDK_UPDATE_ERROR, error)); + return Promise.all(downloads).then(shouldUpdates => { + // if at least one segment was updated + const shouldUpdate = shouldUpdates.indexOf(true) !== -1; + + // check if everything was correctly downloaded only required on start up + if (startingUp) { + let ready = true; + + for (const v of segmentsAreReady.values()) { + ready = ready && v; + } + + if (ready) { + startingUp = false; + segmentsAreReady = null; + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + } + // should we notificate an update? + else { + shouldUpdate && hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + + return shouldUpdate; + }); }; }; diff --git a/packages/splitio-cache/es6/updater/splitChanges.js b/packages/splitio-cache/es6/updater/splitChanges.js index 2944bc2e7..0db91bb39 100644 --- a/packages/splitio-cache/es6/updater/splitChanges.js +++ b/packages/splitio-cache/es6/updater/splitChanges.js @@ -16,15 +16,37 @@ limitations under the License. const log = require('debug')('splitio-cache:updater'); const splitChangesDataSource = require('../ds/splitChanges'); -module.exports = function SplitChangesUpdater(settings, hub, storage) { +function SplitChangesUpdater(settings, hub, storage) { const sinceValueCache = {since: -1}; + // only enable retries first load + let startingUp = true; - return function updateSplits() { - log('Updating splitChanges'); - - return splitChangesDataSource(settings, sinceValueCache) + return function updateSplits(retry = 0) { + return splitChangesDataSource(settings, sinceValueCache, startingUp) .then(splitsMutator => splitsMutator(storage)) - .then(shouldUpdate => shouldUpdate && hub.emit(hub.Event.SDK_UPDATE, storage)) - .catch(error => hub.emit(hub.Event.SDK_UPDATE_ERROR, error)); + .then(shouldUpdate => { + if (startingUp) { + startingUp = false; + } + + if (shouldUpdate) { + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + } + + return shouldUpdate; + }) + .catch(error => { + if (startingUp && settings.startup.retriesOnFailureBeforeReady > retry) { + retry += 1; + log('retrying download of splits #%s reason %s', retry, error); + return updateSplits(retry); + } else { + startingUp = false; + } + + return false; // shouldUpdate = false + }); }; -}; +} + +module.exports = SplitChangesUpdater; diff --git a/packages/splitio-cache/es6/updaters/browser.js b/packages/splitio-cache/es6/updaters/browser.js index 8008f9feb..d36ce28fc 100644 --- a/packages/splitio-cache/es6/updaters/browser.js +++ b/packages/splitio-cache/es6/updaters/browser.js @@ -13,6 +13,41 @@ 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. **/ +const repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); -exports.SplitsUpdater = require('../updater/splitChanges'); -exports.SegmentsUpdater = require('../updater/mySegments'); +class Updater { + constructor( + splitsUpdater, + segmentsUpdater, + splitsUpdaterRefreshRate, + segmentsUpdaterRefreshRate + ) { + this.splitsUpdater = splitsUpdater; + this.segmentsUpdater = segmentsUpdater; + this.splitsUpdaterRefreshRate = splitsUpdaterRefreshRate; + this.segmentsUpdaterRefreshRate = segmentsUpdaterRefreshRate; + } + + start() { + this.stopSplitsUpdate = repeat( + scheduleSplitsUpdate => this.splitsUpdater().then(() => scheduleSplitsUpdate()), + this.splitsUpdaterRefreshRate + ); + + this.stopSegmentsUpdate = repeat( + scheduleSegmentsUpdate => this.segmentsUpdater().then(() => scheduleSegmentsUpdate()), + this.segmentsUpdaterRefreshRate + ); + } + + stop() { + this.stopSplitsUpdate && this.stopSplitsUpdate(); + this.stopSegmentsUpdate && this.stopSegmentsUpdate(); + } +} + +module.exports = { + SplitsUpdater: require('../updater/splitChanges'), + SegmentsUpdater: require('../updater/mySegments'), + Updater +}; diff --git a/packages/splitio-cache/es6/updaters/node.js b/packages/splitio-cache/es6/updaters/node.js index 3c0551d86..e83ce4847 100644 --- a/packages/splitio-cache/es6/updaters/node.js +++ b/packages/splitio-cache/es6/updaters/node.js @@ -13,6 +13,59 @@ 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. **/ +const repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); -exports.SplitsUpdater = require('../updater/splitChanges'); -exports.SegmentsUpdater = require('../updater/segmentChanges'); +class Updater { + constructor( + splitsUpdater, + segmentsUpdater, + splitsUpdaterRefreshRate, + segmentsUpdaterRefreshRate + ) { + this.splitsUpdater = splitsUpdater; + this.segmentsUpdater = segmentsUpdater; + this.splitsUpdaterRefreshRate = splitsUpdaterRefreshRate; + this.segmentsUpdaterRefreshRate = segmentsUpdaterRefreshRate; + + this.preventSchedulingOfSegmentsUpdates = false; + } + + start() { + let isSegmentsUpdaterRunning = false; + + this.stopSplitsUpdate = repeat(scheduleSplitsUpdate => { + this.splitsUpdater().then(splitsHasBeenUpdated => { + if (!isSegmentsUpdaterRunning && splitsHasBeenUpdated && !this.preventSchedulingOfSegmentsUpdates) { + isSegmentsUpdaterRunning = true; + + this.stopSegmentsUpdate = repeat(scheduleSegmentsUpdate => { + return this.segmentsUpdater().then(() => { + scheduleSegmentsUpdate(); + }); + }, + this.segmentsUpdaterRefreshRate + ); + } + + scheduleSplitsUpdate(); + }); + }, + this.splitsUpdaterRefreshRate + ); + } + + stop() { + this.stopSplitsUpdate && this.stopSplitsUpdate(); + if (this.stopSegmentsUpdate) { + this.stopSegmentsUpdate(); + } else { + this.preventSchedulingOfSegmentsUpdates = true; + } + } +} + +module.exports = { + SplitsUpdater: require('../updater/splitChanges'), + SegmentsUpdater: require('../updater/segmentChanges'), + Updater +}; diff --git a/packages/splitio-cache/lib/ds/mySegments.js b/packages/splitio-cache/lib/ds/mySegments.js index 5fb076c7a..6b596df7c 100644 --- a/packages/splitio-cache/lib/ds/mySegments.js +++ b/packages/splitio-cache/lib/ds/mySegments.js @@ -15,21 +15,29 @@ 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. **/ +var timeout = require('@splitsoftware/splitio-utils/lib/promise/timeout'); + var mySegmentsService = require('@splitsoftware/splitio-services/lib/mySegments'); var mySegmentsRequest = require('@splitsoftware/splitio-services/lib/mySegments/get'); var mySegmentMutationsFactory = require('../mutators/mySegments'); function mySegmentsDataSource(settings) { - return mySegmentsService(mySegmentsRequest(settings)).then(function (resp) { - return resp.json(); - }).then(function (json) { - return mySegmentMutationsFactory(json.mySegments.map(function (segment) { - return segment.name; - })); - }).catch(function () { - return false; - }); + var shouldApplyTimeout = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; + + var requestPromise = mySegmentsService(mySegmentsRequest(settings)); + + if (shouldApplyTimeout) { + requestPromise = timeout(settings.startup.requestTimeoutBeforeReady, requestPromise); + } + + return requestPromise.then(function (resp) { + return resp.json(); + }).then(function (json) { + return mySegmentMutationsFactory(json.mySegments.map(function (segment) { + return segment.name; + })); + }); } module.exports = mySegmentsDataSource; \ No newline at end of file diff --git a/packages/splitio-cache/lib/ds/segmentChanges.js b/packages/splitio-cache/lib/ds/segmentChanges.js index c14e3566b..2e30be1eb 100644 --- a/packages/splitio-cache/lib/ds/segmentChanges.js +++ b/packages/splitio-cache/lib/ds/segmentChanges.js @@ -25,7 +25,6 @@ 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. **/ - var segmentChangesService = require('@splitsoftware/splitio-services/lib/segmentChanges'); var segmentChangesRequest = require('@splitsoftware/splitio-services/lib/segmentChanges/get'); @@ -61,13 +60,30 @@ function segmentChangesDataSource(settings, segmentName, sinceValuesCache) { return greedyFetch(settings, sinceValue, segmentName).then(function (changes) { var len = changes.length; + var lastChange = len > 0 ? changes[len - 1] : false; + + // doesn't matter if we fully download the information, say if we need to + // update the data or not. var shouldUpdate = !(len === 0 || len === 1 && changes[0].since === changes[0].till); + // do we fully download the segment's changes from the server? + var isFullUpdate = lastChange && lastChange.since === lastChange.till; + + // fn which actually applies the changes + var mutator = function mutator() { + return false; + }; + if (shouldUpdate) { sinceValuesCache.set(segmentName, changes[len - 1].till); + mutator = segmentMutatorFactory(changes); } - return segmentMutatorFactory(shouldUpdate, changes); + return { + shouldUpdate: shouldUpdate, // did an update? + isFullUpdate: isFullUpdate, // did it was partial or full? + mutator: mutator + }; }); } diff --git a/packages/splitio-cache/lib/ds/splitChanges.js b/packages/splitio-cache/lib/ds/splitChanges.js index e4a1a54a2..95dbd2301 100644 --- a/packages/splitio-cache/lib/ds/splitChanges.js +++ b/packages/splitio-cache/lib/ds/splitChanges.js @@ -15,6 +15,7 @@ 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. **/ +var timeout = require('@splitsoftware/splitio-utils/lib/promise/timeout'); var splitChangesService = require('@splitsoftware/splitio-services/lib/splitChanges'); var splitChangesRequest = require('@splitsoftware/splitio-services/lib/splitChanges/get'); @@ -22,7 +23,15 @@ var splitChangesRequest = require('@splitsoftware/splitio-services/lib/splitChan var splitMutatorFactory = require('../mutators/splitChanges'); function splitChangesDataSource(settings, sinceValueCache) { - return splitChangesService(splitChangesRequest(settings, sinceValueCache)).then(function (resp) { + var shouldApplyTimeout = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; + + var requestPromise = splitChangesService(splitChangesRequest(settings, sinceValueCache)); + + if (shouldApplyTimeout) { + requestPromise = timeout(settings.startup.requestTimeoutBeforeReady, requestPromise); + } + + return requestPromise.then(function (resp) { return resp.json(); }).then(function (json) { var till = json.till; @@ -33,8 +42,6 @@ function splitChangesDataSource(settings, sinceValueCache) { sinceValueCache.since = till; return splitMutatorFactory(shouldUpdate, splits); - }).catch(function () { - return false; }); } diff --git a/packages/splitio-cache/lib/index.js b/packages/splitio-cache/lib/index.js index c759493bf..9ef6ebd70 100644 --- a/packages/splitio-cache/lib/index.js +++ b/packages/splitio-cache/lib/index.js @@ -25,43 +25,32 @@ 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. **/ -var SchedulerFactory = require('@splitsoftware/splitio-utils/lib/scheduler'); var Storage = require('./storage'); -var Updaters = require('./updaters'); -var log = require('debug')('splitio-cache'); -var sync = require('./sync'); +var _require = require('./updaters'); + +var SplitsUpdater = _require.SplitsUpdater; +var SegmentsUpdater = _require.SegmentsUpdater; +var Updater = _require.Updater; var Cache = function () { function Cache(settings, hub) { (0, _classCallCheck3.default)(this, Cache); - this.settings = settings; - this.hub = hub; - - this.splitRefreshScheduler = SchedulerFactory(); - this.segmentsRefreshScheduler = SchedulerFactory(); - this.storage = Storage.createStorage(); - this.splitsUpdater = Updaters.SplitsUpdater(this.settings, this.hub, this.storage); - this.segmentsUpdater = Updaters.SegmentsUpdater(this.settings, this.hub, this.storage); + this.updater = new Updater(SplitsUpdater(settings, hub, this.storage), SegmentsUpdater(settings, hub, this.storage), settings.scheduler.featuresRefreshRate, settings.scheduler.segmentsRefreshRate); } (0, _createClass3.default)(Cache, [{ key: 'start', value: function start() { - log('sync started'); - - return sync.call(this); + this.updater.start(); } }, { key: 'stop', value: function stop() { - log('stopped syncing'); - - this.splitRefreshScheduler.kill(); - this.segmentsRefreshScheduler.kill(); + this.updater.stop(); } }]); return Cache; diff --git a/packages/splitio-cache/lib/mutators/mySegments.js b/packages/splitio-cache/lib/mutators/mySegments.js index 6724ce8fd..20b024b16 100644 --- a/packages/splitio-cache/lib/mutators/mySegments.js +++ b/packages/splitio-cache/lib/mutators/mySegments.js @@ -30,15 +30,15 @@ module.exports = function MySegmentMutationsFactory(mySegments /*: MySegmentsDTO return function segmentMutations(storage /*: Object */) /*: void */{ var nextSegments = new _set2.default(mySegments); + var sameAmountOfElements = storage.segments.size === nextSegments.size; var isEqual = true; - var shouldUpdate = void 0; + var shouldUpdate = false; - // weak logic for performance for (var i = 0; i < mySegments.length && isEqual; i++) { isEqual = storage.segments.has(mySegments[i]); } - shouldUpdate = !isEqual; + shouldUpdate = !isEqual || !sameAmountOfElements; if (shouldUpdate) { storage.segments.update(nextSegments); diff --git a/packages/splitio-cache/lib/mutators/segmentChanges.js b/packages/splitio-cache/lib/mutators/segmentChanges.js index ae4289fdd..4471c101d 100644 --- a/packages/splitio-cache/lib/mutators/segmentChanges.js +++ b/packages/splitio-cache/lib/mutators/segmentChanges.js @@ -27,10 +27,9 @@ type SegmentChangesDTOCollection = Array; */ var log = require('debug')('splitio-cache:mutators'); -module.exports = function SegmentMutationsFactory(shouldUpdate /*: bool */, changes /*: SegmentChangesDTOCollection */ -) /*: Function */{ +function SegmentMutationsFactory(changes /*: SegmentChangesDTOCollection */) /*: Function */{ return function segmentMutations(storage /*: Object */) /*: void */{ - shouldUpdate && changes.forEach(function (_ref) { + changes.forEach(function (_ref) { var name = _ref.name; var added = _ref.added; var removed = _ref.removed; @@ -51,7 +50,7 @@ module.exports = function SegmentMutationsFactory(shouldUpdate /*: bool */, chan storage.segments.update(name, segment); }); - - return shouldUpdate; }; -}; \ No newline at end of file +} + +module.exports = SegmentMutationsFactory; \ No newline at end of file diff --git a/packages/splitio-cache/lib/storage/segments/browser.js b/packages/splitio-cache/lib/storage/segments/browser.js index 7394338b9..a1179d366 100644 --- a/packages/splitio-cache/lib/storage/segments/browser.js +++ b/packages/splitio-cache/lib/storage/segments/browser.js @@ -52,6 +52,11 @@ var SegmentsStorage = function () { value: function toJSON() { return this.storage.toJSON(); } + }, { + key: "size", + get: function get() { + return this.storage.size; + } }]); return SegmentsStorage; }(); diff --git a/packages/splitio-cache/lib/storage/segments/node.js b/packages/splitio-cache/lib/storage/segments/node.js index fdd1b3477..b08a887a6 100644 --- a/packages/splitio-cache/lib/storage/segments/node.js +++ b/packages/splitio-cache/lib/storage/segments/node.js @@ -61,6 +61,11 @@ var SegmentsStorage = function () { value: function segmentNames() { return this.storage.keys(); } + }, { + key: "size", + get: function get() { + return this.storage.size; + } }]); return SegmentsStorage; }(); diff --git a/packages/splitio-cache/lib/storage/splits/index.js b/packages/splitio-cache/lib/storage/splits/index.js index 2828e2b12..f3d985d4a 100644 --- a/packages/splitio-cache/lib/storage/splits/index.js +++ b/packages/splitio-cache/lib/storage/splits/index.js @@ -125,6 +125,11 @@ var SplitsStorage = function () { value: function toJSON() /*: string */{ return this.storage.toJSON(); } + }, { + key: "size", + get: function get() { + return this.storage.size; + } }]); return SplitsStorage; }(); diff --git a/packages/splitio-cache/lib/updater/mySegments.js b/packages/splitio-cache/lib/updater/mySegments.js index c480e2655..c06d45002 100644 --- a/packages/splitio-cache/lib/updater/mySegments.js +++ b/packages/splitio-cache/lib/updater/mySegments.js @@ -18,16 +18,37 @@ limitations under the License. var log = require('debug')('splitio-cache:updater'); var mySegmentsDataSource = require('../ds/mySegments'); -module.exports = function MySegmentsUpdater(settings, hub, storage) { +function MySegmentsUpdater(settings, hub, storage) { + // only enable retries first load + var startingUp = true; + return function updateMySegments() { - log('Updating mySegments'); + var retry = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0]; - return mySegmentsDataSource(settings).then(function (segmentsMutator) { + return mySegmentsDataSource(settings, startingUp).then(function (segmentsMutator) { return segmentsMutator(storage); }).then(function (shouldUpdate) { - return shouldUpdate && hub.emit(hub.Event.SDK_UPDATE, storage); + if (startingUp) { + startingUp = false; + } + + if (shouldUpdate) { + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + + return shouldUpdate; }).catch(function (error) { - return hub.emit(hub.Event.SDK_UPDATE_ERROR, error); + if (startingUp && settings.startup.retriesOnFailureBeforeReady > retry) { + retry += 1; + log('retrying download of segments #%s reason %s', retry, error); + return updateMySegments(retry); + } else { + startingUp = false; + } + + return false; // shouldUpdate = false }); }; -}; \ No newline at end of file +} + +module.exports = MySegmentsUpdater; \ No newline at end of file diff --git a/packages/splitio-cache/lib/updater/segmentChanges.js b/packages/splitio-cache/lib/updater/segmentChanges.js index 3000c3b88..c4afa80d8 100644 --- a/packages/splitio-cache/lib/updater/segmentChanges.js +++ b/packages/splitio-cache/lib/updater/segmentChanges.js @@ -1,5 +1,9 @@ 'use strict'; +var _getIterator2 = require('babel-runtime/core-js/get-iterator'); + +var _getIterator3 = _interopRequireDefault(_getIterator2); + var _promise = require('babel-runtime/core-js/promise'); var _promise2 = _interopRequireDefault(_promise); @@ -34,22 +38,85 @@ var segmentChangesDataSource = require('../ds/segmentChanges'); module.exports = function SegmentChangesUpdater(settings, hub, storage) { var sinceValuesCache = new _map2.default(); + var segmentsAreReady = new _map2.default(); + var startingUp = true; return function updateSegments() { log('Updating segmentChanges'); var downloads = [].concat((0, _toConsumableArray3.default)(storage.splits.getSegments())).map(function (segmentName) { - return segmentChangesDataSource(settings, segmentName, sinceValuesCache).then(function (mutator) { + // register segments for future check if they are ready or not + if (startingUp) { + if (segmentsAreReady.get(segmentName) === undefined) { + segmentsAreReady.set(segmentName, false); + } + } + + return segmentChangesDataSource(settings, segmentName, sinceValuesCache).then(function (_ref) { + var shouldUpdate = _ref.shouldUpdate; + var isFullUpdate = _ref.isFullUpdate; + var mutator = _ref.mutator; + log('completed download of ' + segmentName); - return mutator(storage); + // apply mutations + mutator(storage); + + // register segment data as ready if required + if (startingUp && segmentsAreReady.get(segmentName) === false && isFullUpdate) { + segmentsAreReady.set(segmentName, true); + } + + // did we apply an update? + return shouldUpdate; }); }); return _promise2.default.all(downloads).then(function (shouldUpdates) { - return shouldUpdates.indexOf(true) !== -1 && hub.emit(hub.Event.SDK_UPDATE, storage); - }).catch(function (error) { - return hub.emit(hub.Event.SDK_UPDATE_ERROR, error); + // if at least one segment was updated + var shouldUpdate = shouldUpdates.indexOf(true) !== -1; + + // check if everything was correctly downloaded only required on start up + if (startingUp) { + var ready = true; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = (0, _getIterator3.default)(segmentsAreReady.values()), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var v = _step.value; + + ready = ready && v; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (ready) { + startingUp = false; + segmentsAreReady = null; + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + } + // should we notificate an update? + else { + shouldUpdate && hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + } + + return shouldUpdate; }); }; }; \ No newline at end of file diff --git a/packages/splitio-cache/lib/updater/splitChanges.js b/packages/splitio-cache/lib/updater/splitChanges.js index d997f069c..5ee4a9836 100644 --- a/packages/splitio-cache/lib/updater/splitChanges.js +++ b/packages/splitio-cache/lib/updater/splitChanges.js @@ -18,18 +18,38 @@ limitations under the License. var log = require('debug')('splitio-cache:updater'); var splitChangesDataSource = require('../ds/splitChanges'); -module.exports = function SplitChangesUpdater(settings, hub, storage) { +function SplitChangesUpdater(settings, hub, storage) { var sinceValueCache = { since: -1 }; + // only enable retries first load + var startingUp = true; return function updateSplits() { - log('Updating splitChanges'); + var retry = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0]; - return splitChangesDataSource(settings, sinceValueCache).then(function (splitsMutator) { + return splitChangesDataSource(settings, sinceValueCache, startingUp).then(function (splitsMutator) { return splitsMutator(storage); }).then(function (shouldUpdate) { - return shouldUpdate && hub.emit(hub.Event.SDK_UPDATE, storage); + if (startingUp) { + startingUp = false; + } + + if (shouldUpdate) { + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + } + + return shouldUpdate; }).catch(function (error) { - return hub.emit(hub.Event.SDK_UPDATE_ERROR, error); + if (startingUp && settings.startup.retriesOnFailureBeforeReady > retry) { + retry += 1; + log('retrying download of splits #%s reason %s', retry, error); + return updateSplits(retry); + } else { + startingUp = false; + } + + return false; // shouldUpdate = false }); }; -}; \ No newline at end of file +} + +module.exports = SplitChangesUpdater; \ No newline at end of file diff --git a/packages/splitio-cache/lib/updaters/browser.js b/packages/splitio-cache/lib/updaters/browser.js index a886d9696..151e56f17 100644 --- a/packages/splitio-cache/lib/updaters/browser.js +++ b/packages/splitio-cache/lib/updaters/browser.js @@ -1,5 +1,15 @@ 'use strict'; +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + /** Copyright 2016 Split Software @@ -15,6 +25,47 @@ 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. **/ +var repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); + +var Updater = function () { + function Updater(splitsUpdater, segmentsUpdater, splitsUpdaterRefreshRate, segmentsUpdaterRefreshRate) { + (0, _classCallCheck3.default)(this, Updater); + + this.splitsUpdater = splitsUpdater; + this.segmentsUpdater = segmentsUpdater; + this.splitsUpdaterRefreshRate = splitsUpdaterRefreshRate; + this.segmentsUpdaterRefreshRate = segmentsUpdaterRefreshRate; + } + + (0, _createClass3.default)(Updater, [{ + key: 'start', + value: function start() { + var _this = this; + + this.stopSplitsUpdate = repeat(function (scheduleSplitsUpdate) { + return _this.splitsUpdater().then(function () { + return scheduleSplitsUpdate(); + }); + }, this.splitsUpdaterRefreshRate); + + this.stopSegmentsUpdate = repeat(function (scheduleSegmentsUpdate) { + return _this.segmentsUpdater().then(function () { + return scheduleSegmentsUpdate(); + }); + }, this.segmentsUpdaterRefreshRate); + } + }, { + key: 'stop', + value: function stop() { + this.stopSplitsUpdate && this.stopSplitsUpdate(); + this.stopSegmentsUpdate && this.stopSegmentsUpdate(); + } + }]); + return Updater; +}(); -exports.SplitsUpdater = require('../updater/splitChanges'); -exports.SegmentsUpdater = require('../updater/mySegments'); \ No newline at end of file +module.exports = { + SplitsUpdater: require('../updater/splitChanges'), + SegmentsUpdater: require('../updater/mySegments'), + Updater: Updater +}; \ No newline at end of file diff --git a/packages/splitio-cache/lib/updaters/node.js b/packages/splitio-cache/lib/updaters/node.js index c1b0a6558..a72f3c737 100644 --- a/packages/splitio-cache/lib/updaters/node.js +++ b/packages/splitio-cache/lib/updaters/node.js @@ -1,5 +1,15 @@ 'use strict'; +var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); + +var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); + +var _createClass2 = require('babel-runtime/helpers/createClass'); + +var _createClass3 = _interopRequireDefault(_createClass2); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + /** Copyright 2016 Split Software @@ -15,6 +25,59 @@ 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. **/ +var repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); + +var Updater = function () { + function Updater(splitsUpdater, segmentsUpdater, splitsUpdaterRefreshRate, segmentsUpdaterRefreshRate) { + (0, _classCallCheck3.default)(this, Updater); + + this.splitsUpdater = splitsUpdater; + this.segmentsUpdater = segmentsUpdater; + this.splitsUpdaterRefreshRate = splitsUpdaterRefreshRate; + this.segmentsUpdaterRefreshRate = segmentsUpdaterRefreshRate; + + this.preventSchedulingOfSegmentsUpdates = false; + } + + (0, _createClass3.default)(Updater, [{ + key: 'start', + value: function start() { + var _this = this; + + var isSegmentsUpdaterRunning = false; + + this.stopSplitsUpdate = repeat(function (scheduleSplitsUpdate) { + _this.splitsUpdater().then(function (splitsHasBeenUpdated) { + if (!isSegmentsUpdaterRunning && splitsHasBeenUpdated && !_this.preventSchedulingOfSegmentsUpdates) { + isSegmentsUpdaterRunning = true; + + _this.stopSegmentsUpdate = repeat(function (scheduleSegmentsUpdate) { + return _this.segmentsUpdater().then(function () { + scheduleSegmentsUpdate(); + }); + }, _this.segmentsUpdaterRefreshRate); + } + + scheduleSplitsUpdate(); + }); + }, this.splitsUpdaterRefreshRate); + } + }, { + key: 'stop', + value: function stop() { + this.stopSplitsUpdate && this.stopSplitsUpdate(); + if (this.stopSegmentsUpdate) { + this.stopSegmentsUpdate(); + } else { + this.preventSchedulingOfSegmentsUpdates = true; + } + } + }]); + return Updater; +}(); -exports.SplitsUpdater = require('../updater/splitChanges'); -exports.SegmentsUpdater = require('../updater/segmentChanges'); \ No newline at end of file +module.exports = { + SplitsUpdater: require('../updater/splitChanges'), + SegmentsUpdater: require('../updater/segmentChanges'), + Updater: Updater +}; \ No newline at end of file diff --git a/packages/splitio-cache/package.json b/packages/splitio-cache/package.json index 2e181dc98..d4048d9b3 100644 --- a/packages/splitio-cache/package.json +++ b/packages/splitio-cache/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-cache", - "version": "5.1.1", + "version": "6.0.0", "description": "Split Cache", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -50,9 +50,9 @@ "sdk" ], "dependencies": { - "@splitsoftware/splitio-utils": "5.1.1", - "@splitsoftware/splitio-engine": "5.1.1", - "@splitsoftware/splitio-services": "5.1.1", + "@splitsoftware/splitio-utils": "6.0.0", + "@splitsoftware/splitio-engine": "6.0.0", + "@splitsoftware/splitio-services": "6.0.0", "babel-runtime": "^6.9.0", "core-js": "^2.4.0", "debug": "^2.2.0" diff --git a/packages/splitio-cache/test/es6/ds/segmentChanges/node.spec.js b/packages/splitio-cache/test/es6/ds/segmentChanges/node.spec.js index 1a57a7123..7ec855f83 100644 --- a/packages/splitio-cache/test/es6/ds/segmentChanges/node.spec.js +++ b/packages/splitio-cache/test/es6/ds/segmentChanges/node.spec.js @@ -78,55 +78,17 @@ tape('DS SEGMENT CHANGES / greedy fetch should download while since != till', as }, JSON.stringify(response4)); greedyFetch('segment_1').then(function (responses) { - assert.equal(responses.length, 4); + const len = responses.length; + const last = len - 1; - assert.deepEqual(responses[0], response1); - assert.deepEqual(responses[1], response2); - assert.deepEqual(responses[2], response3); - assert.deepEqual(responses[3], response4); + assert.equal(len, 4); - fetchMock.restore(); - assert.end(); - }); -}); + assert.deepEqual(responses[0], response1, 'response #1 should be at position 0'); + assert.deepEqual(responses[1], response2, 'response #2 should be at position 1'); + assert.deepEqual(responses[2], response3, 'response #3 should be at position 2'); + assert.deepEqual(responses[3], response4, 'response #4 should be at position 3'); -tape('DS SEGMENT CHANGES / ', assert => { - const response1 = { - name: 'segment_1', - added: [ - 1, 2 - ], - removed: [], - since: 1, - till: 2 - }; - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=-1'); - }, JSON.stringify(response1)); - - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=2'); - }, { - status: 500 - }); - - const response3 = { - name: 'segment_1', - added: [ - 5, 6 - ], - removed: [], - since: 3, - till: 4 - }; - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=3'); - }, JSON.stringify(response3)); - - greedyFetch('segment_1').then(function (responses) { - assert.equal(responses.length, 1); - - assert.deepEqual(responses[0], response1); + assert.equal(responses[last].since, responses[last].till, 'response #4 should have since === till'); fetchMock.restore(); assert.end(); diff --git a/packages/splitio-cache/test/es6/mutators/segmentChanges.spec.js b/packages/splitio-cache/test/es6/mutators/segmentChanges.spec.js index c280cbab6..ad4d4a2a7 100644 --- a/packages/splitio-cache/test/es6/mutators/segmentChanges.spec.js +++ b/packages/splitio-cache/test/es6/mutators/segmentChanges.spec.js @@ -27,9 +27,7 @@ tape('Segment Changes', assert => { const segments = new SegmentsStorage; segments.update('test-segment', new Set(['d', 'e', 'f'])); - const shouldUpdate = true; - - const mutator = MutatorFactory(shouldUpdate, [segmentChanges]); + const mutator = MutatorFactory([segmentChanges]); mutator({segments}); assert.deepEqual([...segments.get('test-segment')], segmentChanges.added, diff --git a/packages/splitio-cache/test/es6/storage/segments/browser.spec.js b/packages/splitio-cache/test/es6/storage/segments/browser.spec.js index c202126d9..45fbe2720 100644 --- a/packages/splitio-cache/test/es6/storage/segments/browser.spec.js +++ b/packages/splitio-cache/test/es6/storage/segments/browser.spec.js @@ -17,13 +17,23 @@ limitations under the License. const tape = require('tape'); const SegmentsStorage = require('../../../../lib/storage/segments'); -tape('SEGMENTS STORAGE', assert => { +tape('SEGMENTS STORAGE / has(string) should answer true / false if the elements if present or not', assert => { const storage = new SegmentsStorage; const segments = new Set(['a', 'b', 'c']); storage.update(segments); assert.true(storage.has('b'), 'b is present in the list of segment names'); - assert.false(storage.has('s'), 's is present in the list of segment names'); + assert.false(storage.has('s'), 's is not present in the list of segment names'); + assert.end(); +}); + +tape('SEGMENTS STORAGE / .size property should represent the amount of segments stored', assert => { + const storage = new SegmentsStorage; + const segment = new Set(['a', 'b', 'c', 'd', 'e']); + + storage.update(segment); + + assert.equal(storage.size, 5, 'we should have 5 keys stored in the current segment'); assert.end(); }); diff --git a/packages/splitio-cache/test/es6/storage/segments/node.spec.js b/packages/splitio-cache/test/es6/storage/segments/node.spec.js index abc3c0281..02f22ab82 100644 --- a/packages/splitio-cache/test/es6/storage/segments/node.spec.js +++ b/packages/splitio-cache/test/es6/storage/segments/node.spec.js @@ -17,7 +17,7 @@ limitations under the License. const tape = require('tape'); const SegmentsStorage = require('../../../../lib/storage/segments'); -tape('SEGMENTS STORAGE', assert => { +tape('SEGMENTS STORAGE / get(string) should retrieve the Set which represents the segment requested', assert => { const storage = new SegmentsStorage; const segmentName = 's'; @@ -25,6 +25,17 @@ tape('SEGMENTS STORAGE', assert => { storage.update(segmentName, segmentSet); - assert.equal(storage.get(segmentName), segmentSet, 'should use the same instance'); + assert.equal(storage.get(segmentName), segmentSet, 'should be the same Set instance'); + assert.end(); +}); + +tape('SEGMENTS STORAGE / .size property should represent the amount of segments stored', assert => { + const storage = new SegmentsStorage; + const segmentName = 'mock'; + const segment = new Set(['a', 'b', 'c', 'd', 'e']); + + storage.update(segmentName, segment); + + assert.equal(storage.size, 1, 'we should have 1 segment stored'); assert.end(); }); diff --git a/packages/splitio-cache/test/es6/storage/splits/index.spec.js b/packages/splitio-cache/test/es6/storage/splits/index.spec.js index 0d5734dc1..8842bbc40 100644 --- a/packages/splitio-cache/test/es6/storage/splits/index.spec.js +++ b/packages/splitio-cache/test/es6/storage/splits/index.spec.js @@ -38,10 +38,8 @@ tape('SPLITS STORAGE / should return a list of unique segment names', assert => allMustBePresent = allMustBePresent && mergedSegments.has(segment); } - // RangeError: Maximum call stack size exceeded. - // assert.deepEqual(storage.getSegments(), mergedSegments, 'all the segment names should be included'); - assert.true(allMustBePresent, 'all the segment names should be included'); + assert.equal(storage.size, 3, 'we should have 3 splits stored'); assert.end(); }); @@ -53,5 +51,6 @@ tape('SPLITS STORAGE / get by split name', assert => { assert.equal(storage.get('sample_01'), s1, 'should be the same object'); assert.equal(storage.get('sample_02'), s2, 'should be the same object'); assert.equal(storage.get('sample_03'), s3, 'should be the same object'); + assert.equal(storage.size, 3, 'we should have 3 splits stored'); assert.end(); }); diff --git a/packages/splitio-cache/test/lib/ds/segmentChanges/node.spec.js b/packages/splitio-cache/test/lib/ds/segmentChanges/node.spec.js index 2f03ce529..1543f0d86 100644 --- a/packages/splitio-cache/test/lib/ds/segmentChanges/node.spec.js +++ b/packages/splitio-cache/test/lib/ds/segmentChanges/node.spec.js @@ -80,51 +80,17 @@ tape('DS SEGMENT CHANGES / greedy fetch should download while since != till', fu }, (0, _stringify2.default)(response4)); greedyFetch('segment_1').then(function (responses) { - assert.equal(responses.length, 4); + var len = responses.length; + var last = len - 1; - assert.deepEqual(responses[0], response1); - assert.deepEqual(responses[1], response2); - assert.deepEqual(responses[2], response3); - assert.deepEqual(responses[3], response4); + assert.equal(len, 4); - fetchMock.restore(); - assert.end(); - }); -}); + assert.deepEqual(responses[0], response1, 'response #1 should be at position 0'); + assert.deepEqual(responses[1], response2, 'response #2 should be at position 1'); + assert.deepEqual(responses[2], response3, 'response #3 should be at position 2'); + assert.deepEqual(responses[3], response4, 'response #4 should be at position 3'); -tape('DS SEGMENT CHANGES / ', function (assert) { - var response1 = { - name: 'segment_1', - added: [1, 2], - removed: [], - since: 1, - till: 2 - }; - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=-1'); - }, (0, _stringify2.default)(response1)); - - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=2'); - }, { - status: 500 - }); - - var response3 = { - name: 'segment_1', - added: [5, 6], - removed: [], - since: 3, - till: 4 - }; - fetchMock.mock(function (request) { - return request.url === url('/segmentChanges/segment_1?since=3'); - }, (0, _stringify2.default)(response3)); - - greedyFetch('segment_1').then(function (responses) { - assert.equal(responses.length, 1); - - assert.deepEqual(responses[0], response1); + assert.equal(responses[last].since, responses[last].till, 'response #4 should have since === till'); fetchMock.restore(); assert.end(); diff --git a/packages/splitio-cache/test/lib/mutators/segmentChanges.spec.js b/packages/splitio-cache/test/lib/mutators/segmentChanges.spec.js index 68e456f3e..58c5b2942 100644 --- a/packages/splitio-cache/test/lib/mutators/segmentChanges.spec.js +++ b/packages/splitio-cache/test/lib/mutators/segmentChanges.spec.js @@ -39,9 +39,7 @@ tape('Segment Changes', function (assert) { var segments = new SegmentsStorage(); segments.update('test-segment', new _set2.default(['d', 'e', 'f'])); - var shouldUpdate = true; - - var mutator = MutatorFactory(shouldUpdate, [segmentChanges]); + var mutator = MutatorFactory([segmentChanges]); mutator({ segments: segments }); assert.deepEqual([].concat((0, _toConsumableArray3.default)(segments.get('test-segment'))), segmentChanges.added, 'We should only have [a, b, c]'); diff --git a/packages/splitio-cache/test/lib/storage/segments/browser.spec.js b/packages/splitio-cache/test/lib/storage/segments/browser.spec.js index 481c78b74..c9e21ec1c 100644 --- a/packages/splitio-cache/test/lib/storage/segments/browser.spec.js +++ b/packages/splitio-cache/test/lib/storage/segments/browser.spec.js @@ -25,13 +25,23 @@ limitations under the License. var tape = require('tape'); var SegmentsStorage = require('../../../../lib/storage/segments'); -tape('SEGMENTS STORAGE', function (assert) { +tape('SEGMENTS STORAGE / has(string) should answer true / false if the elements if present or not', function (assert) { var storage = new SegmentsStorage(); var segments = new _set2.default(['a', 'b', 'c']); storage.update(segments); assert.true(storage.has('b'), 'b is present in the list of segment names'); - assert.false(storage.has('s'), 's is present in the list of segment names'); + assert.false(storage.has('s'), 's is not present in the list of segment names'); + assert.end(); +}); + +tape('SEGMENTS STORAGE / .size property should represent the amount of segments stored', function (assert) { + var storage = new SegmentsStorage(); + var segment = new _set2.default(['a', 'b', 'c', 'd', 'e']); + + storage.update(segment); + + assert.equal(storage.size, 5, 'we should have 5 keys stored in the current segment'); assert.end(); }); \ No newline at end of file diff --git a/packages/splitio-cache/test/lib/storage/segments/node.spec.js b/packages/splitio-cache/test/lib/storage/segments/node.spec.js index bab695e37..3ddb316f7 100644 --- a/packages/splitio-cache/test/lib/storage/segments/node.spec.js +++ b/packages/splitio-cache/test/lib/storage/segments/node.spec.js @@ -25,7 +25,7 @@ limitations under the License. var tape = require('tape'); var SegmentsStorage = require('../../../../lib/storage/segments'); -tape('SEGMENTS STORAGE', function (assert) { +tape('SEGMENTS STORAGE / get(string) should retrieve the Set which represents the segment requested', function (assert) { var storage = new SegmentsStorage(); var segmentName = 's'; @@ -33,6 +33,17 @@ tape('SEGMENTS STORAGE', function (assert) { storage.update(segmentName, segmentSet); - assert.equal(storage.get(segmentName), segmentSet, 'should use the same instance'); + assert.equal(storage.get(segmentName), segmentSet, 'should be the same Set instance'); + assert.end(); +}); + +tape('SEGMENTS STORAGE / .size property should represent the amount of segments stored', function (assert) { + var storage = new SegmentsStorage(); + var segmentName = 'mock'; + var segment = new _set2.default(['a', 'b', 'c', 'd', 'e']); + + storage.update(segmentName, segment); + + assert.equal(storage.size, 1, 'we should have 1 segment stored'); assert.end(); }); \ No newline at end of file diff --git a/packages/splitio-cache/test/lib/storage/splits/index.spec.js b/packages/splitio-cache/test/lib/storage/splits/index.spec.js index 46d25b283..a62779c4d 100644 --- a/packages/splitio-cache/test/lib/storage/splits/index.spec.js +++ b/packages/splitio-cache/test/lib/storage/splits/index.spec.js @@ -56,9 +56,6 @@ tape('SPLITS STORAGE / should return a list of unique segment names', function ( allMustBePresent = allMustBePresent && mergedSegments.has(segment); } - - // RangeError: Maximum call stack size exceeded. - // assert.deepEqual(storage.getSegments(), mergedSegments, 'all the segment names should be included'); } catch (err) { _didIteratorError = true; _iteratorError = err; @@ -75,6 +72,7 @@ tape('SPLITS STORAGE / should return a list of unique segment names', function ( } assert.true(allMustBePresent, 'all the segment names should be included'); + assert.equal(storage.size, 3, 'we should have 3 splits stored'); assert.end(); }); @@ -86,5 +84,6 @@ tape('SPLITS STORAGE / get by split name', function (assert) { assert.equal(storage.get('sample_01'), s1, 'should be the same object'); assert.equal(storage.get('sample_02'), s2, 'should be the same object'); assert.equal(storage.get('sample_03'), s3, 'should be the same object'); + assert.equal(storage.size, 3, 'we should have 3 splits stored'); assert.end(); }); \ No newline at end of file diff --git a/packages/splitio-engine/es6/transforms/matchers.js b/packages/splitio-engine/es6/transforms/matchers.js index 4a2dd141e..22b2ad3c8 100644 --- a/packages/splitio-engine/es6/transforms/matchers.js +++ b/packages/splitio-engine/es6/transforms/matchers.js @@ -13,11 +13,10 @@ 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. **/ - +const findIndex = require('core-js/library/fn/array/find-index'); const matcherTypes = require('../matchers/types'); const segmentTransform = require('./segment'); const whitelistTransform = require('./whitelist'); -const findIndex = require('lodash.findindex'); /*:: type dataTypes = null | 'NUMBER' | 'DATETIME'; diff --git a/packages/splitio-engine/lib/transforms/matchers.js b/packages/splitio-engine/lib/transforms/matchers.js index 910964a26..b434efcfc 100644 --- a/packages/splitio-engine/lib/transforms/matchers.js +++ b/packages/splitio-engine/lib/transforms/matchers.js @@ -15,11 +15,10 @@ 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. **/ - +var findIndex = require('core-js/library/fn/array/find-index'); var matcherTypes = require('../matchers/types'); var segmentTransform = require('./segment'); var whitelistTransform = require('./whitelist'); -var findIndex = require('lodash.findindex'); /*:: type dataTypes = null | 'NUMBER' | 'DATETIME'; diff --git a/packages/splitio-engine/package.json b/packages/splitio-engine/package.json index 0e74426a2..40ed2ad1c 100644 --- a/packages/splitio-engine/package.json +++ b/packages/splitio-engine/package.json @@ -6,8 +6,7 @@ "dependencies": { "babel-runtime": "^6.9.0", "core-js": "^2.4.0", - "debug": "^2.2.0", - "lodash.findindex": "4.3.0" + "debug": "^2.2.0" }, "description": "Split Engine", "devDependencies": { @@ -56,5 +55,5 @@ "watch": "babel es6 --out-dir lib --watch", "watch-test": "babel test/es6 --out-dir test/lib --watch" }, - "version": "5.1.1" + "version": "6.0.0" } diff --git a/packages/splitio-engine/test/lib/parser/index.spec.js b/packages/splitio-engine/test/lib/parser/index.spec.js index f859d8590..f4fc22471 100644 --- a/packages/splitio-engine/test/lib/parser/index.spec.js +++ b/packages/splitio-engine/test/lib/parser/index.spec.js @@ -391,7 +391,6 @@ tape('PARSER / if user is in segment all then split 20%:A,20%:B,60%:A', function }]); var evaluator = _parser10.evaluator; - var segments = _parser10.segments; assert.equal(evaluator('aaaaa', 31), 'A', '20%:A'); // bucket 15 diff --git a/packages/splitio-metrics/es6/index.js b/packages/splitio-metrics/es6/index.js index 954629743..15ebf9f3c 100644 --- a/packages/splitio-metrics/es6/index.js +++ b/packages/splitio-metrics/es6/index.js @@ -13,7 +13,7 @@ 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. **/ -const SchedulerFactory = require('@splitsoftware/splitio-utils/lib/scheduler'); +const repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); const metricsService = require('@splitsoftware/splitio-services/lib/metrics'); const metricsServiceRequest = require('@splitsoftware/splitio-services/lib/metrics/post'); @@ -30,53 +30,77 @@ const SequentialCollector = require('./collector/sequential'); const FibonacciCollector = require('./collector/fibonacci'); class Metrics { - constructor() { + constructor(settings) { + this.settings = settings; + this.impressionsCollector = SequentialCollector(); this.getTreatmentCollector = FibonacciCollector(); - this.performanceScheduler = SchedulerFactory(); - this.impressionsScheduler = SchedulerFactory(); - this.impressions = PassThroughFactory(this.impressionsCollector); this.getTreatment = TimerFactory(this.getTreatmentCollector); } - publishToTime(settings) { - if (!this.getTreatmentCollector.isEmpty()) { - metricsService(metricsServiceRequest(settings, { - body: JSON.stringify(metricsDTO.fromGetTreatmentCollector(this.getTreatmentCollector)) - })).then(resp => { + publishToTime() { + return new Promise(resolve => { + if (this.getTreatmentCollector.isEmpty()) { + return resolve(); + } + + resolve(metricsService(metricsServiceRequest(this.settings, { + body: JSON.stringify( + metricsDTO.fromGetTreatmentCollector(this.getTreatmentCollector) + ) + })) + .then(resp => { this.getTreatmentCollector.clear(); + return resp; - }).catch(() => { + }) + .catch(() => { this.getTreatmentCollector.clear(); - }); - } + })); + }); } - publishToImpressions(settings) { - if (!this.impressionsCollector.isEmpty()) { - impressionsService(impressionsBulkRequest(settings, { - body: JSON.stringify(impressionsDTO.fromImpressionsCollector(this.impressionsCollector)) - })).then(resp => { + publishToImpressions() { + return new Promise(resolve => { + if (this.impressionsCollector.isEmpty()) { + return resolve(); + } + + resolve(impressionsService(impressionsBulkRequest(this.settings, { + body: JSON.stringify( + impressionsDTO.fromImpressionsCollector(this.impressionsCollector) + ) + })) + .then(resp => { this.impressionsCollector.clear(); + return resp; - }).catch(() => { + }) + .catch(() => { this.impressionsCollector.clear(); - }); - } + })); + }); } - start(settings) { - this.performanceScheduler.forever(this.publishToTime.bind(this, settings), - settings.get('metricsRefreshRate')); - this.impressionsScheduler.forever(this.publishToImpressions.bind(this, settings), - settings.get('impressionsRefreshRate')); + start() { + this.stopImpressionsPublisher = repeat(schedulePublisher => { + this.publishToImpressions().then(() => { + schedulePublisher(); + }); + }, this.settings.scheduler.impressionsRefreshRate); + + this.stopPerformancePublisher = repeat(schedulePublisher => { + this.publishToTime().then(() => { + schedulePublisher(); + }); + }, this.settings.scheduler.metricsRefreshRate); } stop() { - this.performanceScheduler.kill(); - this.impressionsScheduler.kill(); + this.stopImpressionsPublisher && this.stopImpressionsPublisher(); + this.stopPerformancePublisher && this.stopPerformancePublisher(); } } diff --git a/packages/splitio-metrics/lib/index.js b/packages/splitio-metrics/lib/index.js index 7185ceb96..1fb5aa6d7 100644 --- a/packages/splitio-metrics/lib/index.js +++ b/packages/splitio-metrics/lib/index.js @@ -4,6 +4,10 @@ var _stringify = require('babel-runtime/core-js/json/stringify'); var _stringify2 = _interopRequireDefault(_stringify); +var _promise = require('babel-runtime/core-js/promise'); + +var _promise2 = _interopRequireDefault(_promise); + var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); @@ -29,7 +33,7 @@ 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. **/ -var SchedulerFactory = require('@splitsoftware/splitio-utils/lib/scheduler'); +var repeat = require('@splitsoftware/splitio-utils/lib/fn/repeat'); var metricsService = require('@splitsoftware/splitio-services/lib/metrics'); var metricsServiceRequest = require('@splitsoftware/splitio-services/lib/metrics/post'); @@ -46,62 +50,82 @@ var SequentialCollector = require('./collector/sequential'); var FibonacciCollector = require('./collector/fibonacci'); var Metrics = function () { - function Metrics() { + function Metrics(settings) { (0, _classCallCheck3.default)(this, Metrics); + this.settings = settings; + this.impressionsCollector = SequentialCollector(); this.getTreatmentCollector = FibonacciCollector(); - this.performanceScheduler = SchedulerFactory(); - this.impressionsScheduler = SchedulerFactory(); - this.impressions = PassThroughFactory(this.impressionsCollector); this.getTreatment = TimerFactory(this.getTreatmentCollector); } (0, _createClass3.default)(Metrics, [{ key: 'publishToTime', - value: function publishToTime(settings) { + value: function publishToTime() { var _this = this; - if (!this.getTreatmentCollector.isEmpty()) { - metricsService(metricsServiceRequest(settings, { - body: (0, _stringify2.default)(metricsDTO.fromGetTreatmentCollector(this.getTreatmentCollector)) + return new _promise2.default(function (resolve) { + if (_this.getTreatmentCollector.isEmpty()) { + return resolve(); + } + + resolve(metricsService(metricsServiceRequest(_this.settings, { + body: (0, _stringify2.default)(metricsDTO.fromGetTreatmentCollector(_this.getTreatmentCollector)) })).then(function (resp) { _this.getTreatmentCollector.clear(); + return resp; }).catch(function () { _this.getTreatmentCollector.clear(); - }); - } + })); + }); } }, { key: 'publishToImpressions', - value: function publishToImpressions(settings) { + value: function publishToImpressions() { var _this2 = this; - if (!this.impressionsCollector.isEmpty()) { - impressionsService(impressionsBulkRequest(settings, { - body: (0, _stringify2.default)(impressionsDTO.fromImpressionsCollector(this.impressionsCollector)) + return new _promise2.default(function (resolve) { + if (_this2.impressionsCollector.isEmpty()) { + return resolve(); + } + + resolve(impressionsService(impressionsBulkRequest(_this2.settings, { + body: (0, _stringify2.default)(impressionsDTO.fromImpressionsCollector(_this2.impressionsCollector)) })).then(function (resp) { _this2.impressionsCollector.clear(); + return resp; }).catch(function () { _this2.impressionsCollector.clear(); - }); - } + })); + }); } }, { key: 'start', - value: function start(settings) { - this.performanceScheduler.forever(this.publishToTime.bind(this, settings), settings.get('metricsRefreshRate')); - this.impressionsScheduler.forever(this.publishToImpressions.bind(this, settings), settings.get('impressionsRefreshRate')); + value: function start() { + var _this3 = this; + + this.stopImpressionsPublisher = repeat(function (schedulePublisher) { + _this3.publishToImpressions().then(function () { + schedulePublisher(); + }); + }, this.settings.scheduler.impressionsRefreshRate); + + this.stopPerformancePublisher = repeat(function (schedulePublisher) { + _this3.publishToTime().then(function () { + schedulePublisher(); + }); + }, this.settings.scheduler.metricsRefreshRate); } }, { key: 'stop', value: function stop() { - this.performanceScheduler.kill(); - this.impressionsScheduler.kill(); + this.stopImpressionsPublisher && this.stopImpressionsPublisher(); + this.stopPerformancePublisher && this.stopPerformancePublisher(); } }]); return Metrics; diff --git a/packages/splitio-metrics/package.json b/packages/splitio-metrics/package.json index dbeb55a17..cf0badbc2 100644 --- a/packages/splitio-metrics/package.json +++ b/packages/splitio-metrics/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-metrics", - "version": "5.1.1", + "version": "6.0.0", "description": "Split Metrics", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -49,8 +49,8 @@ "sdk" ], "dependencies": { - "@splitsoftware/splitio-utils": "5.1.1", - "@splitsoftware/splitio-services": "5.1.1", + "@splitsoftware/splitio-utils": "6.0.0", + "@splitsoftware/splitio-services": "6.0.0", "babel-runtime": "^6.9.0", "core-js": "^2.4.0", "debug": "^2.2.0" diff --git a/packages/splitio-services/es6/impressions/dto.js b/packages/splitio-services/es6/impressions/dto.js index 2086e131a..871f5aabd 100644 --- a/packages/splitio-services/es6/impressions/dto.js +++ b/packages/splitio-services/es6/impressions/dto.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. **/ -const groupBy = require('lodash.groupby'); +const groupBy = require('lodash/groupBy'); module.exports = { fromImpressionsCollector(collector) { diff --git a/packages/splitio-services/es6/request/index.js b/packages/splitio-services/es6/request/index.js index 251451987..b04e4031f 100644 --- a/packages/splitio-services/es6/request/index.js +++ b/packages/splitio-services/es6/request/index.js @@ -27,7 +27,8 @@ function RequestFactory(settings, relativeUrl, params) { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, 'SplitSDKVersion': `${version}`, - 'Connection': 'keep-alive' // node-fetch requires this to correctly support keep-alive connections + // node-fetch requires this to correctly support keep-alive connections + 'Connection': 'keep-alive' }, compress: true }, baseline, params)); diff --git a/packages/splitio-services/lib/impressions/dto.js b/packages/splitio-services/lib/impressions/dto.js index 2dd1fb653..2f2d3b33c 100644 --- a/packages/splitio-services/lib/impressions/dto.js +++ b/packages/splitio-services/lib/impressions/dto.js @@ -16,7 +16,7 @@ See the License for the specific language governing permissions and limitations under the License. **/ -var groupBy = require('lodash.groupby'); +var groupBy = require('lodash/groupBy'); module.exports = { fromImpressionsCollector: function fromImpressionsCollector(collector) { diff --git a/packages/splitio-services/lib/request/index.js b/packages/splitio-services/lib/request/index.js index 062e036ee..af35a9c8b 100644 --- a/packages/splitio-services/lib/request/index.js +++ b/packages/splitio-services/lib/request/index.js @@ -35,7 +35,8 @@ function RequestFactory(settings, relativeUrl, params) { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token, 'SplitSDKVersion': '' + version, - 'Connection': 'keep-alive' // node-fetch requires this to correctly support keep-alive connections + // node-fetch requires this to correctly support keep-alive connections + 'Connection': 'keep-alive' }, compress: true }, baseline, params)); diff --git a/packages/splitio-services/package.json b/packages/splitio-services/package.json index 41fd26892..750105377 100644 --- a/packages/splitio-services/package.json +++ b/packages/splitio-services/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-services", - "version": "5.1.1", + "version": "6.0.0", "description": "Split services layer for Node and the Browser", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -14,12 +14,12 @@ "watch": "babel es6 --out-dir lib --watch" }, "dependencies": { - "@splitsoftware/splitio-utils": "5.1.1", + "@splitsoftware/splitio-utils": "6.0.0", "babel-runtime": "^6.9.0", "core-js": "^2.4.0", "debug": "^2.2.0", "isomorphic-fetch": "^2.2.1", - "lodash.groupby": "4.3.0" + "lodash": "^4.13.1" }, "devDependencies": { "babel-cli": "^6.9.0", diff --git a/packages/splitio-utils/es6/events/index.js b/packages/splitio-utils/es6/events/index.js index 70df75021..c5b971df3 100644 --- a/packages/splitio-utils/es6/events/index.js +++ b/packages/splitio-utils/es6/events/index.js @@ -17,43 +17,82 @@ const log = require('debug')('splitio-utils:events'); const EventEmitter = require('events').EventEmitter; const Event = { - SDK_READY: 'state::ready', + SDK_READY_TIMED_OUT: 'init::timeout', + SDK_READY: 'init::ready', + SDK_SPLITS_ARRIVED: 'state::splits-arrived', + SDK_SEGMENTS_ARRIVED: 'state::segments-arrived', SDK_UPDATE: 'state::update', SDK_UPDATE_ERROR: 'state::update-error' }; +function throwOnInvalidEvent(eventName) { + switch (eventName) { + case Event.SDK_READY: + case Event.SDK_UPDATE: + throw new Error('Reserved event name.'); + } +} + module.exports = function EventFactory() { const proto = new EventEmitter(); const hub = Object.create(proto); - let _isReady = false; + + let isReady = false; + let isReadyEventEmitted = false; + let areSplitsReady = false; + let areSegmentsReady = false; return Object.assign(hub, { - emit(eventName, ...listeners) { - // simulating once event just for simplicity - if (eventName === Event.SDK_READY) { - if (!_isReady) { - log(`Emitting event ${Event.SDK_READY}`); - _isReady = true; - return proto.emit(eventName, ...listeners); - } else { - log(`Discarding event ${Event.SDK_READY}`); - return false; - } + emit(eventName, ...rest) { + throwOnInvalidEvent(eventName); + + const isDataUpdateEvent = eventName === Event.SDK_SPLITS_ARRIVED || + eventName === Event.SDK_SEGMENTS_ARRIVED; + + if (!areSplitsReady && eventName === Event.SDK_SPLITS_ARRIVED) { + log('splits are ready'); + + areSplitsReady = true; + isReady = areSplitsReady && areSegmentsReady; + } + + if (!areSegmentsReady && eventName === Event.SDK_SEGMENTS_ARRIVED) { + log('segments are ready'); + + areSegmentsReady = true; + isReady = areSplitsReady && areSegmentsReady; } - // updates should be fired only after ready state - if (eventName === Event.SDK_UPDATE) { - if (_isReady) { - log(`Emitting event ${eventName}`); - return proto.emit(eventName, ...listeners); + if (eventName === Event.SDK_READY_TIMED_OUT) { + if (!isReadyEventEmitted) { + log(`Emitting event ${Event.SDK_READY_TIMED_OUT}`); + + return proto.emit(eventName, ...rest); } else { - log(`Discarding event ${Event.SDK_UPDATE}`); return false; } } - // for future events just fire them - return proto.emit(eventName, ...listeners); + if (!isReadyEventEmitted && isReady && isDataUpdateEvent) { + log(`Emitting event ${Event.SDK_READY}`); + + isReadyEventEmitted = true; + return proto.emit(Event.SDK_READY, ...rest); + } + + if (isReady && isDataUpdateEvent) { + log(`Emitting event ${Event.SDK_UPDATE}`); + + return proto.emit(Event.SDK_UPDATE, ...rest); + } + + if (!isDataUpdateEvent) { + log(`Emitting custom event ${eventName}`); + + return proto.emit(eventName, ...rest); + } + + return false; }, Event }); diff --git a/packages/splitio-cache/lib/sync/browser.js b/packages/splitio-utils/es6/fn/repeat.js similarity index 53% rename from packages/splitio-cache/lib/sync/browser.js rename to packages/splitio-utils/es6/fn/repeat.js index 72f7f261a..bd5303a66 100644 --- a/packages/splitio-cache/lib/sync/browser.js +++ b/packages/splitio-utils/es6/fn/repeat.js @@ -1,5 +1,3 @@ -'use strict'; - /** Copyright 2016 Split Software @@ -16,12 +14,28 @@ See the License for the specific language governing permissions and limitations under the License. **/ -module.exports = function serial() { - var _this = this; +function repeat(fn, delay, ...rest) { + let tid; + let stopped = false; + + function next(_delay = delay, ...rest) { + if (!stopped) { + // IE 9 doesn't support function arguments through setTimeout call. + // https://msdn.microsoft.com/en-us/library/ms536753(v=vs.85).aspx + tid = setTimeout(() => { + fn(...rest, next); + }, _delay); + } + } + + function till() { + clearTimeout(tid); + stopped = true; + } + + fn(...rest, next); + + return till; +} - return this.splitRefreshScheduler.forever(this.splitsUpdater, this.settings.get('featuresRefreshRate'), this.settings.get('core')).then(function () { - return _this.segmentsRefreshScheduler.forever(_this.segmentsUpdater, _this.settings.get('segmentsRefreshRate'), _this.settings.get('core')); - }).then(function () { - return _this.storage; - }); -}; \ No newline at end of file +module.exports = repeat; diff --git a/packages/splitio-cache/es6/sync/browser.js b/packages/splitio-utils/es6/promise/timeout.js similarity index 58% rename from packages/splitio-cache/es6/sync/browser.js rename to packages/splitio-utils/es6/promise/timeout.js index ffdfb4133..1c8182f57 100644 --- a/packages/splitio-cache/es6/sync/browser.js +++ b/packages/splitio-utils/es6/promise/timeout.js @@ -14,18 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. **/ -module.exports = function serial() { - return this.splitRefreshScheduler.forever( - this.splitsUpdater, - this.settings.get('featuresRefreshRate'), - this.settings.get('core') - ).then(() => { - return this.segmentsRefreshScheduler.forever( - this.segmentsUpdater, - this.settings.get('segmentsRefreshRate'), - this.settings.get('core') - ); - }).then(() => { - return this.storage; +function timeout(ms, promise) { + return new Promise((resolve, reject) => { + const tid = setTimeout(() => { + reject('timeout'); + }, ms); + + promise.then((res) => { + clearTimeout(tid); + resolve(res); + }, + (err) => { + clearTimeout(tid); + reject(err); + }); }); -}; +} + +module.exports = timeout; diff --git a/packages/splitio-utils/es6/scheduler/index.js b/packages/splitio-utils/es6/scheduler/index.js index af638dbf3..2d0a44b60 100644 --- a/packages/splitio-utils/es6/scheduler/index.js +++ b/packages/splitio-utils/es6/scheduler/index.js @@ -23,7 +23,7 @@ function SchedulerFactory() { timeoutID = setTimeout(() => { this.forever(fn, delay, ...fnArgs); - }, Math.floor(delay * 1000)); + }, delay); return firstRunReturnPromise; }, diff --git a/packages/splitio-utils/es6/settings/defaults/browser.js b/packages/splitio-utils/es6/settings/defaults/browser.js new file mode 100644 index 000000000..3510b827d --- /dev/null +++ b/packages/splitio-utils/es6/settings/defaults/browser.js @@ -0,0 +1,10 @@ +module.exports = { + startup: { + // stress the request time used while starting up the SDK. + requestTimeoutBeforeReady: 0.8, + // how many quick retries we will do while starting up the SDK. + retriesOnFailureBeforeReady: 1, + // maximun amount of time used before notifies me a timeout. + readyTimeout: 1.5 + } +}; diff --git a/packages/splitio-utils/es6/settings/defaults/node.js b/packages/splitio-utils/es6/settings/defaults/node.js new file mode 100644 index 000000000..fcbd3ab76 --- /dev/null +++ b/packages/splitio-utils/es6/settings/defaults/node.js @@ -0,0 +1,10 @@ +module.exports = { + startup: { + // stress the request time used while starting up the SDK. + requestTimeoutBeforeReady: 15, + // how many quick retries we will do while starting up the SDK. + retriesOnFailureBeforeReady: 0, + // maximun amount of time used before notifies me a timeout. + readyTimeout: 0 + } +}; diff --git a/packages/splitio-cache/es6/sync/package.json b/packages/splitio-utils/es6/settings/defaults/package.json similarity index 100% rename from packages/splitio-cache/es6/sync/package.json rename to packages/splitio-utils/es6/settings/defaults/package.json diff --git a/packages/splitio-utils/es6/settings/index.js b/packages/splitio-utils/es6/settings/index.js index ffa65ec55..40c8a835c 100644 --- a/packages/splitio-utils/es6/settings/index.js +++ b/packages/splitio-utils/es6/settings/index.js @@ -29,65 +29,66 @@ type Settings = { urls: { sdk: string, events: string + }, + startup: { + requestTimeoutBeforeReady: number, + retriesOnFailureBeforeReady: number, + readyTimeout: number } }; */ +const merge = require('lodash/merge'); +const defaultsPerPlatform = require('./defaults'); + const eventsEndpointMatcher = /\/(testImpressions|metrics)/; +function fromSecondsToMillis(n) { + return Math.round(n * 1000); +} + function defaults(custom /*: Settings */) /*: Settings */ { let init = { core: { - authorizationKey: undefined, // API token (tight to an environment) - key: undefined // user key in your system (only required for browser version). + // API token (tight to an environment) + authorizationKey: undefined, + // key used in your system (only required for browser version) + key: undefined }, scheduler: { - featuresRefreshRate: 30, // 30 sec - segmentsRefreshRate: 60, // 60 sec - metricsRefreshRate: 60, // 60 sec - impressionsRefreshRate: 60 // 60 sec + // fetch feature updates each 30 sec + featuresRefreshRate: 30, + // fetch segments updates each 60 sec + segmentsRefreshRate: 60, + // publish metrics each 60 sec + metricsRefreshRate: 60, + // publish evaluations each 60 sec + impressionsRefreshRate: 60 }, urls: { + // CDN having all the information for your environment sdk: 'https://sdk.split.io/api', + // Storage for your SDK events events: 'https://events.split.io/api' } }; - let final = Object.assign({}, init, custom); - - // we can't start the engine without the authorization token. - - if (typeof final.core.authorizationKey !== 'string') { - throw Error('Please provide an authorization token to startup the engine'); - } - - // override invalid values with default ones - - if (typeof final.scheduler.featuresRefreshRate !== 'number') { - final.scheduler.featuresRefreshRate = init.scheduler.featuresRefreshRate; - } - - if (typeof final.scheduler.segmentsRefreshRate !== 'number') { - final.scheduler.segmentsRefreshRate = init.scheduler.segmentsRefreshRate; - } - - if (typeof final.scheduler.metricsRefreshRate !== 'number') { - final.scheduler.metricsRefreshRate = init.scheduler.metricsRefreshRate; - } - - if (typeof final.scheduler.impressionsRefreshRate !== 'number') { - final.scheduler.impressionsRefreshRate = init.scheduler.impressionsRefreshRate; - } + const withDefaults = merge(init, defaultsPerPlatform, custom); - // return an object with all the magic on it + withDefaults.scheduler.featuresRefreshRate = fromSecondsToMillis(withDefaults.scheduler.featuresRefreshRate); + withDefaults.scheduler.segmentsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.segmentsRefreshRate); + withDefaults.scheduler.metricsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.metricsRefreshRate); + withDefaults.scheduler.impressionsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.impressionsRefreshRate); + withDefaults.startup.requestTimeoutBeforeReady = fromSecondsToMillis(withDefaults.startup.requestTimeoutBeforeReady); + withDefaults.startup.readyTimeout = fromSecondsToMillis(withDefaults.startup.readyTimeout); - return final; + return withDefaults; } const proto = { get(name) { switch (name) { case 'version': - return 'javascript-5.1.1'; + return 'javascript-6.0.0'; case 'authorizationKey': return this.core.authorizationKey; case 'key': diff --git a/packages/splitio-utils/lib/events/index.js b/packages/splitio-utils/lib/events/index.js index 74ecbcd3f..21cc22527 100644 --- a/packages/splitio-utils/lib/events/index.js +++ b/packages/splitio-utils/lib/events/index.js @@ -29,47 +29,85 @@ var log = require('debug')('splitio-utils:events'); var EventEmitter = require('events').EventEmitter; var Event = { - SDK_READY: 'state::ready', + SDK_READY_TIMED_OUT: 'init::timeout', + SDK_READY: 'init::ready', + SDK_SPLITS_ARRIVED: 'state::splits-arrived', + SDK_SEGMENTS_ARRIVED: 'state::segments-arrived', SDK_UPDATE: 'state::update', SDK_UPDATE_ERROR: 'state::update-error' }; +function throwOnInvalidEvent(eventName) { + switch (eventName) { + case Event.SDK_READY: + case Event.SDK_UPDATE: + throw new Error('Reserved event name.'); + } +} + module.exports = function EventFactory() { var proto = new EventEmitter(); var hub = (0, _create2.default)(proto); - var _isReady = false; + + var isReady = false; + var isReadyEventEmitted = false; + var areSplitsReady = false; + var areSegmentsReady = false; return (0, _assign2.default)(hub, { emit: function emit(eventName) { - for (var _len = arguments.length, listeners = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - listeners[_key - 1] = arguments[_key]; + throwOnInvalidEvent(eventName); + + var isDataUpdateEvent = eventName === Event.SDK_SPLITS_ARRIVED || eventName === Event.SDK_SEGMENTS_ARRIVED; + + if (!areSplitsReady && eventName === Event.SDK_SPLITS_ARRIVED) { + log('splits are ready'); + + areSplitsReady = true; + isReady = areSplitsReady && areSegmentsReady; } - // simulating once event just for simplicity - if (eventName === Event.SDK_READY) { - if (!_isReady) { - log('Emitting event ' + Event.SDK_READY); - _isReady = true; - return proto.emit.apply(proto, [eventName].concat(listeners)); - } else { - log('Discarding event ' + Event.SDK_READY); - return false; - } + if (!areSegmentsReady && eventName === Event.SDK_SEGMENTS_ARRIVED) { + log('segments are ready'); + + areSegmentsReady = true; + isReady = areSplitsReady && areSegmentsReady; + } + + for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + rest[_key - 1] = arguments[_key]; } - // updates should be fired only after ready state - if (eventName === Event.SDK_UPDATE) { - if (_isReady) { - log('Emitting event ' + eventName); - return proto.emit.apply(proto, [eventName].concat(listeners)); + if (eventName === Event.SDK_READY_TIMED_OUT) { + if (!isReadyEventEmitted) { + log('Emitting event ' + Event.SDK_READY_TIMED_OUT); + + return proto.emit.apply(proto, [eventName].concat(rest)); } else { - log('Discarding event ' + Event.SDK_UPDATE); return false; } } - // for future events just fire them - return proto.emit.apply(proto, [eventName].concat(listeners)); + if (!isReadyEventEmitted && isReady && isDataUpdateEvent) { + log('Emitting event ' + Event.SDK_READY); + + isReadyEventEmitted = true; + return proto.emit.apply(proto, [Event.SDK_READY].concat(rest)); + } + + if (isReady && isDataUpdateEvent) { + log('Emitting event ' + Event.SDK_UPDATE); + + return proto.emit.apply(proto, [Event.SDK_UPDATE].concat(rest)); + } + + if (!isDataUpdateEvent) { + log('Emitting custom event ' + eventName); + + return proto.emit.apply(proto, [eventName].concat(rest)); + } + + return false; }, Event: Event diff --git a/packages/splitio-utils/lib/fn/repeat.js b/packages/splitio-utils/lib/fn/repeat.js new file mode 100644 index 000000000..6148ed02f --- /dev/null +++ b/packages/splitio-utils/lib/fn/repeat.js @@ -0,0 +1,53 @@ +"use strict"; + +/** +Copyright 2016 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. +**/ + +function repeat(fn, delay) { + for (var _len = arguments.length, rest = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + rest[_key - 2] = arguments[_key]; + } + + var tid = void 0; + var stopped = false; + + function next() { + for (var _len2 = arguments.length, rest = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { + rest[_key2 - 1] = arguments[_key2]; + } + + var _delay = arguments.length <= 0 || arguments[0] === undefined ? delay : arguments[0]; + + if (!stopped) { + // IE 9 doesn't support function arguments through setTimeout call. + // https://msdn.microsoft.com/en-us/library/ms536753(v=vs.85).aspx + tid = setTimeout(function () { + fn.apply(undefined, rest.concat([next])); + }, _delay); + } + } + + function till() { + clearTimeout(tid); + stopped = true; + } + + fn.apply(undefined, rest.concat([next])); + + return till; +} + +module.exports = repeat; \ No newline at end of file diff --git a/packages/splitio-cache/lib/sync/node.js b/packages/splitio-utils/lib/promise/timeout.js similarity index 66% rename from packages/splitio-cache/lib/sync/node.js rename to packages/splitio-utils/lib/promise/timeout.js index a7bef558a..9fa5ef925 100644 --- a/packages/splitio-cache/lib/sync/node.js +++ b/packages/splitio-utils/lib/promise/timeout.js @@ -22,10 +22,20 @@ See the License for the specific language governing permissions and limitations under the License. **/ -module.exports = function parallel() { - var _this = this; - - return _promise2.default.all([this.splitRefreshScheduler.forever(this.splitsUpdater, this.settings.get('featuresRefreshRate'), this.settings.get('core')), this.segmentsRefreshScheduler.forever(this.segmentsUpdater, this.settings.get('segmentsRefreshRate'), this.settings.get('core'))]).then(function () { - return _this.storage; +function timeout(ms, promise) { + return new _promise2.default(function (resolve, reject) { + var tid = setTimeout(function () { + reject('timeout'); + }, ms); + + promise.then(function (res) { + clearTimeout(tid); + resolve(res); + }, function (err) { + clearTimeout(tid); + reject(err); + }); }); -}; \ No newline at end of file +} + +module.exports = timeout; \ No newline at end of file diff --git a/packages/splitio-utils/lib/scheduler/index.js b/packages/splitio-utils/lib/scheduler/index.js index 8a30b7b7c..160f9c2a7 100644 --- a/packages/splitio-utils/lib/scheduler/index.js +++ b/packages/splitio-utils/lib/scheduler/index.js @@ -31,7 +31,7 @@ function SchedulerFactory() { timeoutID = setTimeout(function () { _this.forever.apply(_this, [fn, delay].concat(fnArgs)); - }, Math.floor(delay * 1000)); + }, delay); return firstRunReturnPromise; }, diff --git a/packages/splitio-utils/lib/settings/defaults/browser.js b/packages/splitio-utils/lib/settings/defaults/browser.js new file mode 100644 index 000000000..dfafa1177 --- /dev/null +++ b/packages/splitio-utils/lib/settings/defaults/browser.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + startup: { + // stress the request time used while starting up the SDK. + requestTimeoutBeforeReady: 0.8, + // how many quick retries we will do while starting up the SDK. + retriesOnFailureBeforeReady: 1, + // maximun amount of time used before notifies me a timeout. + readyTimeout: 1.5 + } +}; \ No newline at end of file diff --git a/packages/splitio-utils/lib/settings/defaults/node.js b/packages/splitio-utils/lib/settings/defaults/node.js new file mode 100644 index 000000000..0772324a3 --- /dev/null +++ b/packages/splitio-utils/lib/settings/defaults/node.js @@ -0,0 +1,12 @@ +"use strict"; + +module.exports = { + startup: { + // stress the request time used while starting up the SDK. + requestTimeoutBeforeReady: 15, + // how many quick retries we will do while starting up the SDK. + retriesOnFailureBeforeReady: 0, + // maximun amount of time used before notifies me a timeout. + readyTimeout: 0 + } +}; \ No newline at end of file diff --git a/packages/splitio-cache/lib/sync/package.json b/packages/splitio-utils/lib/settings/defaults/package.json similarity index 100% rename from packages/splitio-cache/lib/sync/package.json rename to packages/splitio-utils/lib/settings/defaults/package.json diff --git a/packages/splitio-utils/lib/settings/index.js b/packages/splitio-utils/lib/settings/index.js index 3303df6ac..d53de71ae 100644 --- a/packages/splitio-utils/lib/settings/index.js +++ b/packages/splitio-utils/lib/settings/index.js @@ -41,65 +41,66 @@ type Settings = { urls: { sdk: string, events: string + }, + startup: { + requestTimeoutBeforeReady: number, + retriesOnFailureBeforeReady: number, + readyTimeout: number } }; */ +var merge = require('lodash/merge'); +var defaultsPerPlatform = require('./defaults'); + var eventsEndpointMatcher = /\/(testImpressions|metrics)/; +function fromSecondsToMillis(n) { + return Math.round(n * 1000); +} + function defaults(custom /*: Settings */) /*: Settings */{ var init = { core: { - authorizationKey: undefined, // API token (tight to an environment) - key: undefined // user key in your system (only required for browser version). + // API token (tight to an environment) + authorizationKey: undefined, + // key used in your system (only required for browser version) + key: undefined }, scheduler: { - featuresRefreshRate: 30, // 30 sec - segmentsRefreshRate: 60, // 60 sec - metricsRefreshRate: 60, // 60 sec - impressionsRefreshRate: 60 // 60 sec + // fetch feature updates each 30 sec + featuresRefreshRate: 30, + // fetch segments updates each 60 sec + segmentsRefreshRate: 60, + // publish metrics each 60 sec + metricsRefreshRate: 60, + // publish evaluations each 60 sec + impressionsRefreshRate: 60 }, urls: { + // CDN having all the information for your environment sdk: 'https://sdk.split.io/api', + // Storage for your SDK events events: 'https://events.split.io/api' } }; - var final = (0, _assign2.default)({}, init, custom); - - // we can't start the engine without the authorization token. + var withDefaults = merge(init, defaultsPerPlatform, custom); - if (typeof final.core.authorizationKey !== 'string') { - throw Error('Please provide an authorization token to startup the engine'); - } - - // override invalid values with default ones - - if (typeof final.scheduler.featuresRefreshRate !== 'number') { - final.scheduler.featuresRefreshRate = init.scheduler.featuresRefreshRate; - } + withDefaults.scheduler.featuresRefreshRate = fromSecondsToMillis(withDefaults.scheduler.featuresRefreshRate); + withDefaults.scheduler.segmentsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.segmentsRefreshRate); + withDefaults.scheduler.metricsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.metricsRefreshRate); + withDefaults.scheduler.impressionsRefreshRate = fromSecondsToMillis(withDefaults.scheduler.impressionsRefreshRate); + withDefaults.startup.requestTimeoutBeforeReady = fromSecondsToMillis(withDefaults.startup.requestTimeoutBeforeReady); + withDefaults.startup.readyTimeout = fromSecondsToMillis(withDefaults.startup.readyTimeout); - if (typeof final.scheduler.segmentsRefreshRate !== 'number') { - final.scheduler.segmentsRefreshRate = init.scheduler.segmentsRefreshRate; - } - - if (typeof final.scheduler.metricsRefreshRate !== 'number') { - final.scheduler.metricsRefreshRate = init.scheduler.metricsRefreshRate; - } - - if (typeof final.scheduler.impressionsRefreshRate !== 'number') { - final.scheduler.impressionsRefreshRate = init.scheduler.impressionsRefreshRate; - } - - // return an object with all the magic on it - - return final; + return withDefaults; } var proto = { get: function get(name) { switch (name) { case 'version': - return 'javascript-5.1.1'; + return 'javascript-6.0.0'; case 'authorizationKey': return this.core.authorizationKey; case 'key': @@ -127,4 +128,4 @@ var proto = { module.exports = function CreateSettings(settings) { return (0, _assign2.default)((0, _create2.default)(proto), defaults(settings)); -}; +}; \ No newline at end of file diff --git a/packages/splitio-utils/package.json b/packages/splitio-utils/package.json index 67c438d5c..5c5e2c852 100644 --- a/packages/splitio-utils/package.json +++ b/packages/splitio-utils/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-utils", - "version": "5.1.1", + "version": "6.0.0", "description": "Split Utils", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -50,6 +50,7 @@ "dependencies": { "babel-runtime": "^6.9.0", "core-js": "^2.4.0", - "debug": "^2.2.0" + "debug": "^2.2.0", + "lodash": "^4.13.1" } } diff --git a/packages/splitio-utils/test/es6/events/index.spec.js b/packages/splitio-utils/test/es6/events/index.spec.js index 69dfaf62c..51e59d0d1 100644 --- a/packages/splitio-utils/test/es6/events/index.spec.js +++ b/packages/splitio-utils/test/es6/events/index.spec.js @@ -24,21 +24,40 @@ tape(`EVENTS / ${Event.SDK_READY} should be emitted once`, assert => { hub.on(hub.Event.SDK_READY, () => { counter++; }); - hub.emit(hub.Event.SDK_READY); - hub.emit(hub.Event.SDK_READY); + + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); assert.equal(counter, 1, 'called once'); assert.end(); }); -tape(`EVENTS / should not emit ${Event.SDK_UPDATE} if ${Event.SDK_READY} is not emitted yet`, assert => { +tape(`EVENTS / should emit ${Event.SDK_UPDATE} after ${Event.SDK_READY}`, assert => { const hub = EventsFactory(); + let isReady = false; + let counter = 0; + + hub.on(hub.Event.SDK_READY, () => { + counter++; + isReady = true; + }); hub.on(hub.Event.SDK_UPDATE, () => { - assert.fail('should not be called yet'); + isReady && (counter++); }); - hub.emit(hub.Event.SDK_UPDATE); - assert.pass('looks good'); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 1 + + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); // counter = 2 + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 3 + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); // counter = 4 + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 5 + + assert.equal(counter, 5, 'counter should have a 5'); assert.end(); }); diff --git a/packages/splitio-utils/test/es6/settings/index.spec.js b/packages/splitio-utils/test/es6/settings/index.spec.js index d0f8c394b..77cfc48df 100644 --- a/packages/splitio-utils/test/es6/settings/index.spec.js +++ b/packages/splitio-utils/test/es6/settings/index.spec.js @@ -13,6 +13,7 @@ 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. **/ +const _ = require('lodash'); const tape = require('tape'); const SettingsFactory = require('../../../lib/settings'); @@ -46,3 +47,56 @@ tape('SETTINGS / urls should be configurable', assert => { assert.deepEqual(settings.get('urls'), urls); assert.end(); }); + +tape('SETTINGS / required properties should be always present', assert => { + const locatorAuthorizationKey = _.property('core.authorizationKey'); + + const locatorSchedulerFeaturesRefreshRate = _.property('scheduler.featuresRefreshRate'); + const locatorSchedulerSegmentsRefreshRate = _.property('scheduler.segmentsRefreshRate'); + const locatorSchedulerMetricsRefreshRate = _.property('scheduler.metricsRefreshRate'); + const locatorSchedulerImpressionsRefreshRate = _.property('scheduler.impressionsRefreshRate'); + + const locatorUrlsSDK = _.property('urls.sdk'); + const locatorUrlsEvents = _.property('urls.events'); + + const locatorStartupRequestTimeoutBeforeReady = _.property('startup.requestTimeoutBeforeReady'); + const locatorStartupRetriesOnFailureBeforeReady = _.property('startup.retriesOnFailureBeforeReady'); + const locatorStartupReadyTimeout = _.property('startup.readyTimeout'); + + const settings = SettingsFactory({ + core: { + authorizationKey: 'dummy token' + }, + scheduler: { + featuresRefreshRate: undefined, + segmentsRefreshRate: undefined, + metricsRefreshRate: undefined, + impressionsRefreshRate: undefined + }, + urls: { + sdk: undefined, + events: undefined + }, + startup: { + requestTimeoutBeforeReady: undefined, + retriesOnFailureBeforeReady: undefined, + readyTimeout: undefined + } + }); + + assert.ok(locatorAuthorizationKey(settings) !== undefined, 'authorizationKey should be present'); + + assert.ok(locatorSchedulerFeaturesRefreshRate(settings) !== undefined, 'scheduler.featuresRefreshRate should be present'); + assert.ok(locatorSchedulerSegmentsRefreshRate(settings) !== undefined, 'scheduler.segmentsRefreshRate should be present'); + assert.ok(locatorSchedulerMetricsRefreshRate(settings) !== undefined, 'scheduler.metricsRefreshRate should be present'); + assert.ok(locatorSchedulerImpressionsRefreshRate(settings) !== undefined, 'scheduler.impressionsRefreshRate should be present'); + + assert.ok(locatorUrlsSDK(settings) !== undefined, 'urls.sdk should be present'); + assert.ok(locatorUrlsEvents(settings) !== undefined, 'urls.events should be present'); + + assert.ok(locatorStartupRequestTimeoutBeforeReady(settings) !== undefined, 'startup.requestTimeoutBeforeReady should be present'); + assert.ok(locatorStartupRetriesOnFailureBeforeReady(settings) !== undefined, 'startup.retriesOnFailureBeforeReady should be present'); + assert.ok(locatorStartupReadyTimeout(settings) !== undefined, 'startup.readyTimeout should be present'); + + assert.end(); +}); diff --git a/packages/splitio-utils/test/lib/events/index.spec.js b/packages/splitio-utils/test/lib/events/index.spec.js index 6230fe8a5..a3651ee82 100644 --- a/packages/splitio-utils/test/lib/events/index.spec.js +++ b/packages/splitio-utils/test/lib/events/index.spec.js @@ -26,21 +26,40 @@ tape('EVENTS / ' + Event.SDK_READY + ' should be emitted once', function (assert hub.on(hub.Event.SDK_READY, function () { counter++; }); - hub.emit(hub.Event.SDK_READY); - hub.emit(hub.Event.SDK_READY); + + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); assert.equal(counter, 1, 'called once'); assert.end(); }); -tape('EVENTS / should not emit ' + Event.SDK_UPDATE + ' if ' + Event.SDK_READY + ' is not emitted yet', function (assert) { +tape('EVENTS / should emit ' + Event.SDK_UPDATE + ' after ' + Event.SDK_READY, function (assert) { var hub = EventsFactory(); + var isReady = false; + var counter = 0; + + hub.on(hub.Event.SDK_READY, function () { + counter++; + isReady = true; + }); hub.on(hub.Event.SDK_UPDATE, function () { - assert.fail('should not be called yet'); + isReady && counter++; }); - hub.emit(hub.Event.SDK_UPDATE); - assert.pass('looks good'); + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 1 + + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); // counter = 2 + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 3 + hub.emit(hub.Event.SDK_SPLITS_ARRIVED); // counter = 4 + hub.emit(hub.Event.SDK_SEGMENTS_ARRIVED); // counter = 5 + + assert.equal(counter, 5, 'counter should have a 5'); assert.end(); }); \ No newline at end of file diff --git a/packages/splitio-utils/test/lib/settings/index.spec.js b/packages/splitio-utils/test/lib/settings/index.spec.js index cd27fce0d..f1559ffc6 100644 --- a/packages/splitio-utils/test/lib/settings/index.spec.js +++ b/packages/splitio-utils/test/lib/settings/index.spec.js @@ -15,6 +15,7 @@ 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. **/ +var _ = require('lodash'); var tape = require('tape'); var SettingsFactory = require('../../../lib/settings'); @@ -46,5 +47,58 @@ tape('SETTINGS / urls should be configurable', function (assert) { }); assert.deepEqual(settings.get('urls'), urls); + assert.end(); +}); + +tape('SETTINGS / required properties should be always present', function (assert) { + var locatorAuthorizationKey = _.property('core.authorizationKey'); + + var locatorSchedulerFeaturesRefreshRate = _.property('scheduler.featuresRefreshRate'); + var locatorSchedulerSegmentsRefreshRate = _.property('scheduler.segmentsRefreshRate'); + var locatorSchedulerMetricsRefreshRate = _.property('scheduler.metricsRefreshRate'); + var locatorSchedulerImpressionsRefreshRate = _.property('scheduler.impressionsRefreshRate'); + + var locatorUrlsSDK = _.property('urls.sdk'); + var locatorUrlsEvents = _.property('urls.events'); + + var locatorStartupRequestTimeoutBeforeReady = _.property('startup.requestTimeoutBeforeReady'); + var locatorStartupRetriesOnFailureBeforeReady = _.property('startup.retriesOnFailureBeforeReady'); + var locatorStartupReadyTimeout = _.property('startup.readyTimeout'); + + var settings = SettingsFactory({ + core: { + authorizationKey: 'dummy token' + }, + scheduler: { + featuresRefreshRate: undefined, + segmentsRefreshRate: undefined, + metricsRefreshRate: undefined, + impressionsRefreshRate: undefined + }, + urls: { + sdk: undefined, + events: undefined + }, + startup: { + requestTimeoutBeforeReady: undefined, + retriesOnFailureBeforeReady: undefined, + readyTimeout: undefined + } + }); + + assert.ok(locatorAuthorizationKey(settings) !== undefined, 'authorizationKey should be present'); + + assert.ok(locatorSchedulerFeaturesRefreshRate(settings) !== undefined, 'scheduler.featuresRefreshRate should be present'); + assert.ok(locatorSchedulerSegmentsRefreshRate(settings) !== undefined, 'scheduler.segmentsRefreshRate should be present'); + assert.ok(locatorSchedulerMetricsRefreshRate(settings) !== undefined, 'scheduler.metricsRefreshRate should be present'); + assert.ok(locatorSchedulerImpressionsRefreshRate(settings) !== undefined, 'scheduler.impressionsRefreshRate should be present'); + + assert.ok(locatorUrlsSDK(settings) !== undefined, 'urls.sdk should be present'); + assert.ok(locatorUrlsEvents(settings) !== undefined, 'urls.events should be present'); + + assert.ok(locatorStartupRequestTimeoutBeforeReady(settings) !== undefined, 'startup.requestTimeoutBeforeReady should be present'); + assert.ok(locatorStartupRetriesOnFailureBeforeReady(settings) !== undefined, 'startup.retriesOnFailureBeforeReady should be present'); + assert.ok(locatorStartupReadyTimeout(settings) !== undefined, 'startup.readyTimeout should be present'); + assert.end(); }); \ No newline at end of file diff --git a/packages/splitio/es6/sdk/offline/base.js b/packages/splitio/es6/sdk/offline/base.js index 75240c8c1..8608bb615 100644 --- a/packages/splitio/es6/sdk/offline/base.js +++ b/packages/splitio/es6/sdk/offline/base.js @@ -13,7 +13,7 @@ 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. **/ - +const warning = require('warning'); const log = require('debug')('splitio:offline'); const EventsFactory = require('@splitsoftware/splitio-utils/lib/events'); @@ -25,12 +25,12 @@ function isIdentifierInvalid(str) { } function offlineFactory(settings) { + const hub = EventsFactory(); + let { features } = Object.assign({ features: {} }, settings); - const hub = EventsFactory(); - log('Running Split in Off-the-grid mode!!!!'); for (let [name, treatment] of Object.entries(features)) { @@ -56,8 +56,14 @@ function offlineFactory(settings) { } } - const alwaysReadyPromise = Promise.resolve(undefined).then(() => { - hub.emit(Event.SDK_READY); + // simulates data has been arrived asyncronously + setTimeout(function simulateDataArrived() { + hub.emit(Event.SDK_SPLITS_ARRIVED); + hub.emit(Event.SDK_SEGMENTS_ARRIVED); + }, 10); + + const readyPromise = new Promise(function onReady(resolve) { + hub.on(hub.Event.SDK_READY, resolve); }); return Object.assign(hub, { @@ -68,7 +74,8 @@ function offlineFactory(settings) { return typeof treatment !== 'string' ? 'control' : treatment; }, ready() { - return alwaysReadyPromise; + warning(false, '`.ready()` is deprecated. Please use `sdk.on(sdk.Event.SDK_READY, callback)`'); + return readyPromise; }, destroy() { hub.removeAllListeners(); diff --git a/packages/splitio/es6/sdk/online.js b/packages/splitio/es6/sdk/online.js index c2c868f47..790a1d249 100644 --- a/packages/splitio/es6/sdk/online.js +++ b/packages/splitio/es6/sdk/online.js @@ -18,11 +18,11 @@ limitations under the License. // babel-runtime before remove this line of code. require('core-js/es6/promise'); +const warning = require('warning'); const log = require('debug')('splitio'); const SettingsFactory = require('@splitsoftware/splitio-utils/lib/settings'); const EventsFactory = require('@splitsoftware/splitio-utils/lib/events'); -const Event = EventsFactory.Event; const Metrics = require('@splitsoftware/splitio-metrics'); const Cache = require('@splitsoftware/splitio-cache'); @@ -34,41 +34,29 @@ function onlineFactory(params /*: object */) /*: object */ { const getTreatmentTracker = metrics.getTreatment; const cache = new Cache(settings, hub); - let storage; - let storageReadyPromise; + log(settings); - storageReadyPromise = cache.start().then((_storage) => { - return storage = _storage; - }) - .catch(() => { - return storage = undefined; - }) - .then(() => { - hub.emit(Event.SDK_READY, storage); + cache.start(); + metrics.start(); - return storage; - }); + // start the race vs the SDK startup! + if (settings.startup.readyTimeout > 0) { + setTimeout(() => { + hub.emit(hub.Event.SDK_READY_TIMED_OUT); + }, settings.startup.readyTimeout); + } - metrics.start(settings); + const readyPromise = new Promise(function onReady(resolve) { + hub.on(hub.Event.SDK_READY, resolve); + }); return Object.assign(hub, { getTreatment(key /*: string */, featureName /*: string */, attributes /*: object */) /*: string */ { let treatment = 'control'; - if (storage === undefined) { - impressionsTracker({ - feature: featureName, - key, - treatment, - when: Date.now() - }); - - return treatment; - } - let stopGetTreatmentTracker = getTreatmentTracker(); // start engine perf monitoring - let split = storage.splits.get(featureName); + let split = cache.storage.splits.get(featureName); if (split) { treatment = split.getTreatment(key, attributes); @@ -89,11 +77,14 @@ function onlineFactory(params /*: object */) /*: object */ { return treatment; }, - ready() /*: Promise */ { - return storageReadyPromise; + ready() { + warning(false, '`.ready()` is deprecated. Please use `sdk.on(sdk.Event.SDK_READY, callback)`'); + return readyPromise; }, destroy() { + log('destroying sdk instance'); + hub.removeAllListeners(); metrics.stop(); cache.stop(); diff --git a/packages/splitio/lib/sdk/offline/base.js b/packages/splitio/lib/sdk/offline/base.js index 3a95875d7..69c9d3171 100644 --- a/packages/splitio/lib/sdk/offline/base.js +++ b/packages/splitio/lib/sdk/offline/base.js @@ -37,7 +37,7 @@ 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. **/ - +var warning = require('warning'); var log = require('debug')('splitio:offline'); var EventsFactory = require('@splitsoftware/splitio-utils/lib/events'); @@ -49,6 +49,8 @@ function isIdentifierInvalid(str) { } function offlineFactory(settings) { + var hub = EventsFactory(); + var _Object$assign = (0, _assign2.default)({ features: {} }, settings); @@ -56,8 +58,6 @@ function offlineFactory(settings) { var features = _Object$assign.features; - var hub = EventsFactory(); - log('Running Split in Off-the-grid mode!!!!'); var _iteratorNormalCompletion = true; @@ -81,6 +81,8 @@ function offlineFactory(settings) { delete features[name]; } } + + // simulates data has been arrived asyncronously } catch (err) { _didIteratorError = true; _iteratorError = err; @@ -96,8 +98,13 @@ function offlineFactory(settings) { } } - var alwaysReadyPromise = _promise2.default.resolve(undefined).then(function () { - hub.emit(Event.SDK_READY); + setTimeout(function simulateDataArrived() { + hub.emit(Event.SDK_SPLITS_ARRIVED); + hub.emit(Event.SDK_SEGMENTS_ARRIVED); + }, 10); + + var readyPromise = new _promise2.default(function onReady(resolve) { + hub.on(hub.Event.SDK_READY, resolve); }); return (0, _assign2.default)(hub, { @@ -108,7 +115,8 @@ function offlineFactory(settings) { return typeof treatment !== 'string' ? 'control' : treatment; }, ready: function ready() { - return alwaysReadyPromise; + warning(false, '`.ready()` is deprecated. Please use `sdk.on(sdk.Event.SDK_READY, callback)`'); + return readyPromise; }, destroy: function destroy() { hub.removeAllListeners(); diff --git a/packages/splitio/lib/sdk/online.js b/packages/splitio/lib/sdk/online.js index 893c908db..21e264d5d 100644 --- a/packages/splitio/lib/sdk/online.js +++ b/packages/splitio/lib/sdk/online.js @@ -4,6 +4,10 @@ var _assign = require('babel-runtime/core-js/object/assign'); var _assign2 = _interopRequireDefault(_assign); +var _promise = require('babel-runtime/core-js/promise'); + +var _promise2 = _interopRequireDefault(_promise); + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** @@ -26,11 +30,11 @@ limitations under the License. // babel-runtime before remove this line of code. require('core-js/es6/promise'); +var warning = require('warning'); var log = require('debug')('splitio'); var SettingsFactory = require('@splitsoftware/splitio-utils/lib/settings'); var EventsFactory = require('@splitsoftware/splitio-utils/lib/events'); -var Event = EventsFactory.Event; var Metrics = require('@splitsoftware/splitio-metrics'); var Cache = require('@splitsoftware/splitio-cache'); @@ -42,39 +46,29 @@ function onlineFactory(params /*: object */) /*: object */{ var getTreatmentTracker = metrics.getTreatment; var cache = new Cache(settings, hub); - var storage = void 0; - var storageReadyPromise = void 0; + log(settings); - storageReadyPromise = cache.start().then(function (_storage) { - return storage = _storage; - }).catch(function () { - return storage = undefined; - }).then(function () { - hub.emit(Event.SDK_READY, storage); + cache.start(); + metrics.start(); - return storage; - }); + // start the race vs the SDK startup! + if (settings.startup.readyTimeout > 0) { + setTimeout(function () { + hub.emit(hub.Event.SDK_READY_TIMED_OUT); + }, settings.startup.readyTimeout); + } - metrics.start(settings); + var readyPromise = new _promise2.default(function onReady(resolve) { + hub.on(hub.Event.SDK_READY, resolve); + }); return (0, _assign2.default)(hub, { getTreatment: function getTreatment(key /*: string */, featureName /*: string */, attributes /*: object */) /*: string */{ var treatment = 'control'; - if (storage === undefined) { - impressionsTracker({ - feature: featureName, - key: key, - treatment: treatment, - when: Date.now() - }); - - return treatment; - } - var stopGetTreatmentTracker = getTreatmentTracker(); // start engine perf monitoring - var split = storage.splits.get(featureName); + var split = cache.storage.splits.get(featureName); if (split) { treatment = split.getTreatment(key, attributes); @@ -94,10 +88,13 @@ function onlineFactory(params /*: object */) /*: object */{ return treatment; }, - ready: function ready() /*: Promise */{ - return storageReadyPromise; + ready: function ready() { + warning(false, '`.ready()` is deprecated. Please use `sdk.on(sdk.Event.SDK_READY, callback)`'); + return readyPromise; }, destroy: function destroy() { + log('destroying sdk instance'); + hub.removeAllListeners(); metrics.stop(); cache.stop(); diff --git a/packages/splitio/package.json b/packages/splitio/package.json index eccc126e8..9d59a8357 100644 --- a/packages/splitio/package.json +++ b/packages/splitio/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "5.1.1", + "version": "6.0.0", "description": "Split SDK", "author": "Facundo Cabrera ", "homepage": "https://github.com/splitio/javascript-client", @@ -20,12 +20,13 @@ "karma-sauce": "karma start sauce.karma.conf.js" }, "dependencies": { - "@splitsoftware/splitio-cache": "5.1.1", - "@splitsoftware/splitio-metrics": "5.1.1", - "@splitsoftware/splitio-utils": "5.1.1", + "@splitsoftware/splitio-cache": "6.0.0", + "@splitsoftware/splitio-metrics": "6.0.0", + "@splitsoftware/splitio-utils": "6.0.0", "babel-runtime": "^6.9.0", "core-js": "^2.4.0", - "debug": "^2.2.0" + "debug": "^2.2.0", + "warning": "^3.0.0" }, "devDependencies": { "babel-cli": "^6.9.0", diff --git a/packages/splitio/test/es6/multiple.instances.spec.js b/packages/splitio/test/es6/multiple.instances.spec.js index 30c82f5ad..ea5b2e40e 100644 --- a/packages/splitio/test/es6/multiple.instances.spec.js +++ b/packages/splitio/test/es6/multiple.instances.spec.js @@ -18,21 +18,21 @@ const tape = require('tape'); const prod = splitio({ core: { - authorizationKey: 'kn4j3ctq14ipifmjvbbqu8dgt6' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' } }); const stage = splitio({ core: { - authorizationKey: 'kn6d9bgjqdme3u2n795u850537' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' } }); diff --git a/packages/splitio/test/es6/sdk.events.spec.js b/packages/splitio/test/es6/sdk.events.spec.js index ed35d38ea..463949a7e 100644 --- a/packages/splitio/test/es6/sdk.events.spec.js +++ b/packages/splitio/test/es6/sdk.events.spec.js @@ -16,16 +16,32 @@ limitations under the License. const splitio = require('../../'); const tape = require('tape'); -tape('SDK / check the event SDK_READY is fired', { - timeout: 5000 -}, assert => { +tape('SDK / check the event SDK_READY is fired', assert => { const prod = splitio({ core: { - authorizationKey: 'kn4j3ctq14ipifmjvbbqu8dgt6' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' + }, + scheduler: { + // fetch feature updates each 15 sec + featuresRefreshRate: 15, + // fetch segments updates each 30 sec + segmentsRefreshRate: 30, + // publish metrics each 600 sec + metricsRefreshRate: 600, + // publish evaluations each 600 sec + impressionsRefreshRate: 600 + }, + startup: { + // initial requests will have a stretch timeout + requestTimeoutBeforeReady: 10, + // if something fails because a timeout or a network error, retry at least + retriesOnFailureBeforeReady: 0, + // fires SDK_READY_TIMEOUT after this amount of seconds + readyTimeout: 0 } }); diff --git a/packages/splitio/test/lib/localhost.spec.js b/packages/splitio/test/lib/localhost.spec.js index dbe177636..78de2d3f2 100644 --- a/packages/splitio/test/lib/localhost.spec.js +++ b/packages/splitio/test/lib/localhost.spec.js @@ -24,7 +24,7 @@ limitations under the License. var splitio = require('../../'); var tape = require('tape'); -tape('SDK / evaluates a feature in prod sdk instance', function (assert) { +tape('SDK / evaluates a feature in offline mode', function (assert) { // Look for configurations into $HOME/.split file var sdk = splitio({ core: { diff --git a/packages/splitio/test/lib/multiple.instances.spec.js b/packages/splitio/test/lib/multiple.instances.spec.js index 15d49f242..4806d7ff4 100644 --- a/packages/splitio/test/lib/multiple.instances.spec.js +++ b/packages/splitio/test/lib/multiple.instances.spec.js @@ -26,21 +26,21 @@ var tape = require('tape'); var prod = splitio({ core: { - authorizationKey: 'kn4j3ctq14ipifmjvbbqu8dgt6' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' } }); var stage = splitio({ core: { - authorizationKey: 'kn6d9bgjqdme3u2n795u850537' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' } }); diff --git a/packages/splitio/test/lib/sdk.events.spec.js b/packages/splitio/test/lib/sdk.events.spec.js index ac606ff74..1947ae491 100644 --- a/packages/splitio/test/lib/sdk.events.spec.js +++ b/packages/splitio/test/lib/sdk.events.spec.js @@ -18,16 +18,32 @@ limitations under the License. var splitio = require('../../'); var tape = require('tape'); -tape('SDK / check the event SDK_READY is fired', { - timeout: 5000 -}, function (assert) { +tape('SDK / check the event SDK_READY is fired', function (assert) { var prod = splitio({ core: { - authorizationKey: 'kn4j3ctq14ipifmjvbbqu8dgt6' + authorizationKey: '5p2c0r4so20ill66lm35i45h6pkvrd2skmib' }, urls: { - sdk: 'https://sdk-staging.split.io/api', - events: 'https://events-staging.split.io/api' + sdk: 'https://sdk-aws-staging.split.io/api', + events: 'https://events-aws-staging.split.io/api' + }, + scheduler: { + // fetch feature updates each 15 sec + featuresRefreshRate: 15, + // fetch segments updates each 30 sec + segmentsRefreshRate: 30, + // publish metrics each 600 sec + metricsRefreshRate: 600, + // publish evaluations each 600 sec + impressionsRefreshRate: 600 + }, + startup: { + // initial requests will have a stretch timeout + requestTimeoutBeforeReady: 10, + // if something fails because a timeout or a network error, retry at least + retriesOnFailureBeforeReady: 0, + // fires SDK_READY_TIMEOUT after this amount of seconds + readyTimeout: 0 } });