Comparing version 0.4.7 to 0.4.8
@@ -5,3 +5,7 @@ { | ||
"standard" | ||
] | ||
], | ||
"env": { | ||
"mocha": true, | ||
"es6": true | ||
} | ||
} |
@@ -14,8 +14,8 @@ var child_process = require('child_process') | ||
// read package.json | ||
const package = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')) | ||
const packageObject = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8')) | ||
const versionInfo = { | ||
version: package.version, | ||
version: packageObject.version, | ||
git: { | ||
//tag: git('describe --always --tag --abbrev=0'), | ||
// tag: git('describe --always --tag --abbrev=0'), | ||
short: git('rev-parse --short HEAD'), | ||
@@ -28,6 +28,6 @@ long: git('rev-parse HEAD'), | ||
// convert to JSON | ||
const json = JSON.stringify(versionInfo,null,2) | ||
const json = JSON.stringify(versionInfo, null, 2) | ||
// write file | ||
console.log('writing '+target) | ||
fs.writeFileSync(target,'export default '+json) | ||
console.log('writing ' + target) | ||
fs.writeFileSync(target, 'export default ' + json) |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -28,7 +28,9 @@ Object.defineProperty(exports, "__esModule", { | ||
reject({ | ||
code: "AJAX_ERROR", | ||
title: "Request to endpoint failed", | ||
description: "The request failed. Check that the endpoint is valid?", | ||
code: 'AJAX_ERROR', | ||
title: 'Request to endpoint failed', | ||
description: 'The request failed. Check that the endpoint is valid?', | ||
details: { | ||
endpoint: endpoint | ||
endpoint: endpoint, | ||
url: url, | ||
status: req.status | ||
} | ||
@@ -38,2 +40,15 @@ }); | ||
req.ontimeout = function (event) { | ||
reject({ | ||
code: 'AJAX_ERROR', | ||
title: 'Request to endpoint timed out', | ||
description: 'The request timed out. Check that the endpoint is valid?', | ||
details: { | ||
endpoint: endpoint, | ||
url: url, | ||
status: req.status | ||
} | ||
}); | ||
}; | ||
req.onload = function () { | ||
@@ -58,3 +73,3 @@ // Content-Type | ||
reject({ | ||
code: "INVALID_RESPONSE", | ||
code: 'INVALID_RESPONSE', | ||
title: "Response isn't valid JSON", | ||
@@ -64,3 +79,5 @@ description: "The response couldn't be parsed into an Javascript object. Check that the endpoint is valid?", | ||
endpoint: endpoint, | ||
responseText: req.responseText | ||
url: url, | ||
responseText: req.responseText, | ||
status: req.status | ||
} | ||
@@ -72,9 +89,9 @@ }); | ||
if (contentType.startsWith('text/plain')) { | ||
resolve(req.responseText); | ||
reject(req.responseText); | ||
} else { | ||
resolve(JSON.parse(req.responseText)); | ||
reject(JSON.parse(req.responseText)); | ||
} | ||
} catch (e) { | ||
reject({ | ||
code: "INVALID_RESPONSE", | ||
code: 'INVALID_RESPONSE', | ||
title: "Response isn't valid JSON", | ||
@@ -84,3 +101,5 @@ description: "The response couldn't be parsed into an Javascript object. Check that the endpoint is valid?", | ||
endpoint: endpoint, | ||
responseText: req.responseText | ||
url: url, | ||
responseText: req.responseText, | ||
status: req.status | ||
} | ||
@@ -110,2 +129,13 @@ }); | ||
}); | ||
}; | ||
var createFetch = exports.createFetch = function createFetch(token, url, endpoint, context) { | ||
var _ref2 = arguments.length <= 4 || arguments[4] === undefined ? { method: 'GET', body: null } : arguments[4]; | ||
var method = _ref2.method; | ||
var body = _ref2.body; | ||
return function savedFetch() { | ||
return fetch(token, url, endpoint, context, { method: method, body: body }); | ||
}; | ||
}; |
@@ -7,8 +7,8 @@ "use strict"; | ||
exports.default = { | ||
"version": "0.4.7", | ||
"version": "0.4.8", | ||
"git": { | ||
"short": "e14aa53", | ||
"long": "e14aa53eeba47c54c0d9396143a859070899c1ae", | ||
"short": "6eebc9f", | ||
"long": "6eebc9f8fc84b9fb2cbabf0b9dbad377d25891c9", | ||
"branch": "master" | ||
} | ||
}; |
@@ -12,3 +12,4 @@ 'use strict'; | ||
exports.default = function (token, endpoint) { | ||
//TODO: can we use Map? | ||
var currentToken = token; | ||
// TODO: can we use Map? | ||
var subscriptions = {}; | ||
@@ -29,3 +30,3 @@ var resourceToSubscription = {}; | ||
resourceToSubscription[subscription.resource] = resourceToSubscription[subscription.resource].filter(function (s) { | ||
return !(s.id == id); | ||
return !(s.id === id); | ||
}); | ||
@@ -108,4 +109,4 @@ } | ||
// special handling for "naked" orderbooks | ||
if (obj.url && obj.url.endsWith("/orderbook")) { | ||
var entityUrl = obj.url.substring(0, obj.url.length - "/orderbook".length); | ||
if (obj.url && obj.url.endsWith('/orderbook')) { | ||
var entityUrl = obj.url.substring(0, obj.url.length - '/orderbook'.length); | ||
listingWithOrderbook = entityCache[entityUrl]; | ||
@@ -132,2 +133,3 @@ | ||
_internal: { | ||
_endpoint: endpoint, | ||
_subscriptions: subscriptions, | ||
@@ -140,15 +142,52 @@ _resourceToSubscription: resourceToSubscription, | ||
getToken: function getToken() { | ||
return currentToken; | ||
}, | ||
publishError: function publishError(resource, data, err) { | ||
// Access denied | ||
if (err.details.status === 401) { | ||
var sessionExpiredSubscriptions = resourceToSubscription['session-expired']; | ||
if (sessionExpiredSubscriptions) { | ||
(function () { | ||
var called = Object.create(null); | ||
sessionExpiredSubscriptions.forEach(function (s) { | ||
if (!called[s.id]) { | ||
s.callback(err, data, s.unsubscribeFn); | ||
called[s.id] = true; | ||
} | ||
}); | ||
})(); | ||
} | ||
} | ||
var errorSubscriptions = resourceToSubscription['error']; | ||
if (errorSubscriptions) { | ||
(function () { | ||
var called = Object.create(null); | ||
errorSubscriptions.forEach(function (s) { | ||
if (!called[s.id]) { | ||
setTimeout(function () { | ||
s.callback(err, data, s.unsubscribeFn); | ||
}); | ||
called[s.id] = true; | ||
} | ||
}); | ||
})(); | ||
} | ||
var subscriptions = resourceToSubscription[resource]; | ||
if (subscriptions) { | ||
subscriptions.forEach(function (s) { | ||
return s.callback(err, data, s.unsubscribeFn); | ||
}); | ||
} | ||
}, | ||
publish: function publish(resource, data, err) { | ||
// on errors, notify all subscriptions *only* for original resource | ||
if (err) { | ||
var _subscriptions = resourceToSubscription[resource]; | ||
if (_subscriptions) { | ||
_subscriptions.forEach(function (s) { | ||
return s.callback(err, data, s.unsubscribeFn); | ||
}); | ||
} | ||
this.publishError(resource, data, err); | ||
return; | ||
} | ||
//merge data into cache | ||
// merge data into cache | ||
data = merge(resource, data); | ||
@@ -256,4 +295,11 @@ | ||
fetch: function fetch(resource, callback) { | ||
var _this = this; | ||
this.debug && console.log('fetch', resource); | ||
return (0, _fetch2.fetch)(token, resource, endpoint, this._context); | ||
var errFunc = function errFunc(err) { | ||
_this._internal.publishError(resource, null, err); | ||
}; | ||
var promise = retry((0, _fetch.createFetch)(currentToken, resource, endpoint, this._context), errFunc); | ||
promise.catch(errFunc); | ||
return promise; | ||
} | ||
@@ -263,3 +309,3 @@ }, | ||
subscribe: function subscribe(resource, callback) { | ||
this.debug && console.log('subscribe', token, resource, endpoint); | ||
this.debug && console.log('subscribe', currentToken, resource, endpoint); | ||
var sub = { id: nextId(), resource: resource, callback: callback }; | ||
@@ -278,7 +324,7 @@ | ||
resourceToSubscription[resource].push(sub); | ||
//console.log('resourceToSubscription',resourceToSubscription) | ||
// console.log('resourceToSubscription',resourceToSubscription) | ||
// check cache for this resource | ||
if (resourceCache[resource]) { | ||
//console.log("resource found in cache, notify direct") | ||
// console.log("resource found in cache, notify direct") | ||
callback(null, resourceCache[resource], sub.unsubscribeFn); | ||
@@ -290,3 +336,3 @@ return sub.unsubscribeFn; | ||
// (only for API resources, i.e starting with a /) | ||
if (resource[0] == '/') { | ||
if (resource[0] === '/') { | ||
this.refresh(resource); | ||
@@ -300,49 +346,59 @@ } | ||
refresh: function refresh(resource) { | ||
var _this = this; | ||
var _this2 = this; | ||
this.debug && console.log('refresh', resource); | ||
(0, _fetch2.fetch)(token, resource, endpoint, this._internal._context).then(function (response) { | ||
var errFunc = function errFunc(err) { | ||
_this2._internal.publishError(resource, null, err); | ||
}; | ||
retry((0, _fetch.createFetch)(currentToken, resource, endpoint, this._internal._context), errFunc).then(function (response) { | ||
return setTimeout(function () { | ||
return _this._internal.publish(resource, response, null); | ||
return _this2._internal.publish(resource, response, null); | ||
}, 0); | ||
}).catch(function (err) { | ||
return setTimeout(function () { | ||
return _this._internal.publish(resource, null, err); | ||
}, 0); | ||
}); | ||
}).catch(errFunc); | ||
}, | ||
create: function refresh(resource, content) { | ||
var _this2 = this; | ||
create: function create(resource, content) { | ||
var _this3 = this; | ||
this.debug && console.log('refresh', resource, content); | ||
var promise = (0, _fetch2.fetch)(token, resource, endpoint, this._internal._context, { method: 'POST', body: content }); | ||
var errFunc = function errFunc(err) { | ||
_this3._internal.publishError(resource, null, err); | ||
}; | ||
var promise = retry((0, _fetch.createFetch)(currentToken, resource, endpoint, this._internal._context, { method: 'POST', body: content }), errFunc); | ||
promise.then(function (response) { | ||
return setTimeout(function () { | ||
if (response && response.url) { | ||
_this2._internal.publish(response.url, response, null); | ||
_this3._internal.publish(response.url, response, null); | ||
} | ||
}, 0); | ||
}); | ||
promise.catch(errFunc); | ||
return promise; | ||
}, | ||
update: function refresh(resource, content) { | ||
var _this3 = this; | ||
update: function update(resource, content) { | ||
var _this4 = this; | ||
this.debug && console.log('update', resource, content); | ||
var promise = (0, _fetch2.fetch)(token, resource, endpoint, this._internal._context, { method: 'PUT', body: content }); | ||
var errFunc = function errFunc(err) { | ||
_this4._internal.publishError(resource, null, err); | ||
}; | ||
var promise = retry((0, _fetch.createFetch)(currentToken, resource, endpoint, this._internal._context, { method: 'PUT', body: content }), errFunc); | ||
promise.then(function (response) { | ||
return setTimeout(function () { | ||
return _this3._internal.publish(resource, response, null); | ||
return _this4._internal.publish(resource, response, null); | ||
}, 0); | ||
}); | ||
promise.catch(errFunc); | ||
return promise; | ||
}, | ||
remove: function refresh(resource) { | ||
var _this4 = this; | ||
remove: function remove(resource) { | ||
var _this5 = this; | ||
this.debug && console.log('remove', resource); | ||
var promise = (0, _fetch2.fetch)(token, resource, endpoint, this._internal._context, { method: 'DELETE', body: null }); | ||
var errFunc = function errFunc(err) { | ||
_this5._internal.publishError(resource, null, err); | ||
}; | ||
var promise = retry((0, _fetch.createFetch)(currentToken, resource, endpoint, this._internal._context, { method: 'DELETE', body: null }), errFunc); | ||
@@ -352,5 +408,6 @@ promise.then(function (response) { | ||
delete resourceCache[resource]; | ||
_this4._internal.publish(resource, null, null); | ||
_this5._internal.publish(resource, null, null); | ||
}, 0); | ||
}); | ||
promise.catch(errFunc); | ||
@@ -361,2 +418,3 @@ return promise; | ||
clearCache: function clearCache() { | ||
/* eslint no-multi-spaces: 0 */ | ||
for (var prop in entityCache) { | ||
@@ -367,10 +425,10 @@ if (entityCache.hasOwnProperty(prop)) { | ||
} | ||
for (var prop in resourceCache) { | ||
if (resourceCache.hasOwnProperty(prop)) { | ||
delete resourceCache[prop]; | ||
for (var _prop in resourceCache) { | ||
if (resourceCache.hasOwnProperty(_prop)) { | ||
delete resourceCache[_prop]; | ||
} | ||
} | ||
for (var prop in entityToResource) { | ||
if (entityToResource.hasOwnProperty(prop)) { | ||
delete entityToResource[prop]; | ||
for (var _prop2 in entityToResource) { | ||
if (entityToResource.hasOwnProperty(_prop2)) { | ||
delete entityToResource[_prop2]; | ||
} | ||
@@ -385,2 +443,6 @@ } | ||
return newSession; | ||
}, | ||
setToken: function setToken(newToken) { | ||
currentToken = newToken; | ||
} | ||
@@ -390,3 +452,3 @@ }; | ||
var _fetch2 = require('./fetch'); | ||
var _fetch = require('./fetch'); | ||
@@ -399,2 +461,31 @@ var _metaVersion = require('./meta-version'); | ||
var MAX_RETRIES = 100; | ||
var MAX_RETRY_TIMEOUT = 900000; // 15 minutes | ||
var RETRY_START_TIMEOUT = 5000; | ||
var RETRY_TIMEOUT_INCREMENT = 25000; | ||
var retry = function retry(fetchFunc, failFunc) { | ||
var retryTimeout = global.RETRY_START_TIMEOUT || RETRY_START_TIMEOUT; | ||
var retries = 0; | ||
return new Promise(function (resolve, reject) { | ||
function doRetry(err) { | ||
var status = err.details.status; | ||
// status 0 should be transport errors and 5xx server errors | ||
if ((status === 0 || status >= 500 && status < 600) && retries < (global.MAX_RETRIES || MAX_RETRIES)) { | ||
failFunc(err); | ||
setTimeout(function () { | ||
retries++; | ||
if (retryTimeout < (global.MAX_RETRY_TIMEOUT || MAX_RETRY_TIMEOUT)) { | ||
retryTimeout = retryTimeout + (global.RETRY_TIMEOUT_INCREMENT || RETRY_TIMEOUT_INCREMENT); | ||
} | ||
fetchFunc().then(resolve).catch(doRetry); | ||
}, retryTimeout); | ||
} else { | ||
reject(err); | ||
} | ||
} | ||
fetchFunc().then(resolve).catch(doRetry); | ||
}); | ||
}; | ||
var deepMerge = function deepMerge(target, source) { | ||
@@ -421,9 +512,9 @@ if (!target) return source; | ||
var i = arr.indexOf(item); | ||
if (i == -1) arr.push(item); | ||
if (i === -1) arr.push(item); | ||
return arr; | ||
}; | ||
var nextId = function () { | ||
var nextId = function generateNextId() { | ||
var id = 0; | ||
return function () { | ||
return function incrementId() { | ||
id += 1; | ||
@@ -430,0 +521,0 @@ return id; |
{ | ||
"name": "six-sdk", | ||
"version": "0.4.7", | ||
"version": "0.4.8", | ||
"description": "SIX Javascript SDK", | ||
@@ -48,3 +48,3 @@ "main": "lib", | ||
"eslint-plugin-react": "^3.14.0", | ||
"eslint-plugin-standard": "^1.3.1", | ||
"eslint-plugin-standard": "^1.3.2", | ||
"jsdom": "^8.0.2", | ||
@@ -51,0 +51,0 @@ "mocha": "^2.4.5", |
@@ -80,2 +80,3 @@ # About | ||
- clearCache() | ||
- setToken() | ||
@@ -139,2 +140,34 @@ | ||
#### error | ||
All errors are sent to a 'error' subscription. | ||
```javascript | ||
// register an error handler with the session | ||
session.subscribe('error', function logErrors (error) { | ||
// this callback can be called more then once | ||
}) | ||
``` | ||
#### session-expired and setToken | ||
To listen to when the session has expired the 'session-expired' resource can be subscribed to. | ||
The resource will be called when the api responds with a access denied. | ||
It is then possible to set a new token with the setToken method | ||
**Subscribe to session-expired and set a new token** | ||
```javascript | ||
session.subscribe('session-expired', function refreshToken(error, data) { | ||
// This callback will be called for every request that receives session expired | ||
session.setToken(getNewTokenFromMyBackend()) | ||
}) | ||
``` | ||
**Refresh token every hour** | ||
```javascript | ||
// You can set a new token any time | ||
var ONE_HOUR = 1000 * 60 * 60 | ||
setInterval(function updateToken() { | ||
session.setToken(fetchNewToken()) | ||
}, ONE_HOUR) | ||
``` | ||
### Caching | ||
@@ -141,0 +174,0 @@ |
@@ -16,2 +16,3 @@ import { expect } from 'chai'; | ||
let session = null | ||
let clock | ||
@@ -66,2 +67,18 @@ // restore xhr | ||
it('should propagate errors to session error handlers',(done) => { | ||
const errorSpy = sinon.spy((error) => { | ||
expect(error).to.exist | ||
expect(error.code).to.equal('AJAX_ERROR') | ||
done() | ||
}) | ||
session.subscribe('error', errorSpy) | ||
session.subscribe('/listings/848',(err,data) => { | ||
expect(data).to.not.exist | ||
expect(err).to.exist | ||
expect(err.code).to.equal('AJAX_ERROR') | ||
}) | ||
XMLHttpRequest.respondWithError({}) | ||
}) | ||
it('refresh', (done) => { | ||
@@ -97,2 +114,103 @@ let calls = 0 | ||
describe('retries', () => { | ||
it('should retry on failure',(done) => { | ||
global.MAX_RETRIES = 1 | ||
global.RETRY_START_TIMEOUT = 1 | ||
global.RETRY_TIMEOUT_INCREMENT = 1 | ||
const errorSpy = sinon.spy((error) => { | ||
expect(error).to.exist | ||
expect(error.code).to.equal('AJAX_ERROR') | ||
}) | ||
session.subscribe('error', errorSpy) | ||
const callback = sinon.spy() | ||
session.subscribe('/listings/848', callback) | ||
XMLHttpRequest.respondWithError({}) | ||
XMLHttpRequest.respondWith(response) | ||
setTimeout(() => { | ||
// request successful | ||
expect(callback.called).to.be.true | ||
expect(errorSpy.calledOnce).to.be.true | ||
expect(callback.calledTwice).to.be.true | ||
done() | ||
}, 15) | ||
}) | ||
it('should not retry on user error',(done) => { | ||
global.MAX_RETRIES = 1 | ||
global.RETRY_START_TIMEOUT = 1 | ||
global.RETRY_TIMEOUT_INCREMENT = 1 | ||
const errorSpy = sinon.spy((error) => { | ||
expect(error).to.exist | ||
expect(error.code).to.equal('AJAX_ERROR') | ||
}) | ||
session.subscribe('error', errorSpy) | ||
const callback = sinon.spy() | ||
session.subscribe('/listings/848', callback) | ||
// bad request | ||
XMLHttpRequest.respondWithError({}, 400) | ||
// this request should not be executed | ||
XMLHttpRequest.respondWith(response) | ||
setTimeout(() => { | ||
// request failed | ||
expect(callback.calledOnce).to.be.true | ||
}, 2) | ||
setTimeout(() => { | ||
// no new request | ||
expect(callback.calledTwice).to.be.false | ||
expect(errorSpy.calledOnce).to.be.true | ||
done() | ||
}, 10) | ||
}) | ||
}) | ||
describe('onSessionExpired', () => { | ||
it('should call onSessionExpired on 401 errors',(done) => { | ||
session.subscribe('session-expired', (err, data) => { | ||
session.setToken('new-token') | ||
expect(data).to.not.exist | ||
expect(err).to.exist | ||
expect(err.code).to.equal('AJAX_ERROR') | ||
}) | ||
session.subscribe('/listings/848',(err,data) => { | ||
expect(data).to.not.exist | ||
expect(err).to.exist | ||
expect(err.code).to.equal('AJAX_ERROR') | ||
}) | ||
XMLHttpRequest.respondWithError({}, 401) | ||
setTimeout(() => { | ||
expect(session._internal.getToken()).to.equal('new-token') | ||
done() | ||
}, 0) | ||
}) | ||
it('should set the token in all instances',(done) => { | ||
const newContext = session.withContext({}) | ||
session.subscribe('session-expired', (err, data) => { | ||
session.setToken('new-token') | ||
expect(data).to.not.exist | ||
expect(err).to.exist | ||
expect(err.code).to.equal('AJAX_ERROR') | ||
}) | ||
session.subscribe('/listings/848',(err,data) => { | ||
expect(data).to.not.exist | ||
expect(err).to.exist | ||
expect(err.code).to.equal('AJAX_ERROR') | ||
}) | ||
XMLHttpRequest.respondWithError({}, 401) | ||
setTimeout(() => { | ||
expect(session._internal.getToken()).to.equal('new-token') | ||
expect(newContext._internal.getToken()).to.equal('new-token') | ||
done() | ||
}, 0) | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
63141
1423
212
26