Comparing version 0.0.5 to 2.0.0
{ | ||
"name": "halfpenny", | ||
"version": "0.0.5", | ||
"description": "Official javascript COINS API client. Nodejs and Browser compatible!", | ||
"main": "build/halfpenny.bundle.js", | ||
"version": "2.0.0", | ||
"description": "Official javascript COINS API client. Not browser compatible!", | ||
"main": "src/index.js", | ||
"scripts": { | ||
"test": "echo 'run integration tests in nodeapi'", | ||
"test": "node src/test.js", | ||
"preversion": "git checkout master && git pull && npm ls", | ||
@@ -12,6 +12,5 @@ "publish-patch": "npm run preversion && npm version patch && git push origin master --tags && npm publish", | ||
"publish-major": "npm run preversion && npm version major && git push origin master --tags && npm publish", | ||
"lint": "grunt lint", | ||
"validate": "npm ls", | ||
"build": "grunt build", | ||
"add-build-to-commit": "git add build/*" | ||
"lint": "jscs", | ||
"postlint": "jshint", | ||
"validate": "npm ls" | ||
}, | ||
@@ -29,25 +28,6 @@ "repository": { | ||
"devDependencies": { | ||
"babel-core": "^6.3.21", | ||
"babel-loader": "^6.2.0", | ||
"babel-plugin-transform-runtime": "^6.3.13", | ||
"babel-preset-es2015": "^6.3.13", | ||
"coins-grunt-env": "^1.0.2", | ||
"grunt": "^0.4.5", | ||
"grunt-contrib-jshint": "^0.11.3", | ||
"grunt-env": "^0.4.4", | ||
"grunt-exec": "^0.4.6", | ||
"grunt-jscs": "^2.3.0", | ||
"load-grunt-config": "^0.19.0", | ||
"load-grunt-tasks": "^3.3.0", | ||
"precommit-hook": "^3.0.0", | ||
"time-grunt": "^1.2.2", | ||
"webpack": "^1.12.4" | ||
"axios": "0.9.1", | ||
"dom-storage": "2.0.2" | ||
}, | ||
"dependencies": { | ||
"btoa": "^1.1.2", | ||
"dom-storage": "^2.0.2", | ||
"es6-object-assign": "^1.0.1", | ||
"hawk": "^4.0.0", | ||
"rename-keys": "^1.1.2" | ||
}, | ||
"dependencies": {}, | ||
"pre-commit": [ | ||
@@ -57,5 +37,4 @@ "lint", | ||
"test", | ||
"build", | ||
"add-build-to-commit" | ||
] | ||
} |
# halfpenny // COINS API Javascript client | ||
What is a [halfpenny](https://en.wikipedia.org/wiki/Halfpenny_(British_pre-decimal_coin)), anyway? Beyond an old-timey coin, halfpenny is the official javascript API client for the COINS platform. | ||
What is a [halfpenny](https://en.wikipedia.org/wiki/Halfpenny_(British_pre-decimal_coin)), anyway? Beyond an old-timey coin (AKA a pence), halfpenny is the official javascript API client for the COINS platform. | ||
## alpha warning | ||
halfpenny is still in active development! it is not ready for public usage | ||
halfpenny is still in active development! it is not ready for public usage! | ||
### TODO: | ||
* support UMD and browser compatibility. | ||
* Auto-generate documentation from API client | ||
## peer dependencies | ||
### request | ||
In order to keep the use of this client as flexible as possible, the client | ||
needs to be initialized with a function that can be used to make XHR requests. | ||
The client was designed to work with the [request](https://www.npmjs.com/package/request) | ||
package, but it can be adapted to work with others (like browser-request, or xhr). | ||
### axios | ||
A HTTP request agent is required to retrieve the Client source code and to make | ||
requests. Axios was chosen as that client in order to support both server and | ||
browser environments. | ||
## configuration | ||
### localstorage | ||
In order to persist data to disk, a localstorage interface is required. In the | ||
browser, this means supplying `window.localStorage` in the browser, and using | ||
`node-localstorage` or `dom-storage` on the server. | ||
### basic configuration: | ||
``` | ||
const request = require('request'); | ||
const Promise = require('bluebird'); | ||
const apiClientOptions = { | ||
requestFn: Promise.promisify(request), | ||
baseUrl: 'http://localhost:3000' | ||
};j | ||
const client = require('../sdk/index.js')(apiClientOptions); | ||
``` | ||
## basic configuration: | ||
*See examples* | ||
The above configuration parameters are *required*. | ||
**Note that the 'requestFn' must be promisified** | ||
### advanced configuration | ||
## How it works: | ||
This client can use multiple request engines to make requests to the API. This | ||
allows the COINS team to use the client for its integration tests as well as | ||
for its browser application. Most of the configuration is centered around mapping | ||
the parameters that the client will feed to the request engine. If you are using | ||
*request* as your request engine, then there is no configuration needed. See | ||
`nodeapi/test/utils/init-api-client` for an example of how to configure the | ||
client to use *hapi server.injectThen*. | ||
The source code for the client is auto-generated during the build process of the | ||
API itself. The API then bundles and serves the source code from `/client/client.js`. | ||
## usage | ||
This package retrieves the bundled source code and evaluates it. The result is an | ||
object with properties corresponding to each method exposed by an API endpoint | ||
(e.g. `ScansApi.get()`). | ||
Once you have a configured client, you can use the following methods to interact | ||
with the COINS API: | ||
## examples | ||
### *{Promise}* = .auth.login(username, password) | ||
```js | ||
const agent = require('axios'); | ||
const DomStorage = require('dom-storage'); | ||
const store = new DomStorage('/path/to/file', {strict: true}); | ||
const apiClientOptions = { | ||
xhrAgent: agent, | ||
baseUrl: 'http://localhost:8800', // hostname:port of nodeapi service | ||
store: store | ||
}; | ||
const clientReady = require('halfpenny')(apiClientOptions) | ||
.catch((err) => { | ||
//whoops | ||
}); | ||
Sends POST request to /auth/keys, and stores resulting credentials. | ||
// login | ||
clientReady.then((client) => { | ||
return client.auth.login('username', 'password'); | ||
}); | ||
### *{Promise}* = .auth.logout(username, password) | ||
// get scans | ||
clientReady.then((client) => { | ||
return client.ScansApi.get(null, 'M87100000') | ||
}); | ||
``` | ||
Sends DELETE request to /auth/keys, and removes credentials. | ||
### *{Promise}* = .getAuthCredentials() | ||
Get the credentials currently stored. | ||
### *{Promise}* = .setAuthCredentials(val) | ||
Set the credentials stored: will overwrite if already set. | ||
### *{Promise}* = .makeRequest(options, sign) | ||
Takes a set of request options formatted for the *request* library, and | ||
modifies the options according to the requestObjectMap before signing the | ||
request (*if `sign !== false`*) and sending it. | ||
# examples | ||
See `nodeapi/test/integration/keys.js` | ||
See `nodeapi/test/integration` for more |
185
src/index.js
@@ -1,145 +0,72 @@ | ||
/* global Promise */ | ||
/** | ||
* @module halfpenny | ||
*/ | ||
var defaultBaseUrl = 'https://coins-api.mrn.org'; | ||
var defaultClientPath = '/api/client/client.js'; | ||
var assign = require('es6-object-assign').assign; | ||
var hawk = require('hawk/lib/browser.js'); | ||
var renameKeys = require('rename-keys'); | ||
var storage = require('./utils/get-storage-engine.js'); | ||
var routes = require('./routes/index.js'); | ||
function evalClientSrc (src) { | ||
// @TODO: verify hmac of src for security | ||
return eval(src); | ||
} | ||
var hawkClientHeader = hawk.client.header; | ||
var authKeyId = 'COINS_AUTH_CREDENTIALS'; | ||
var defaultConfig = require('./config-default.js'); | ||
var me = {}; | ||
function extractClientSrc(response) { | ||
if (!response.data) { | ||
throw new Error('Client source code invalid'); | ||
} | ||
return response.data; | ||
} | ||
/** | ||
* get the currently stored auth credentials | ||
* @return {Promise} resolves to the credentials object | ||
*/ | ||
var getAuthCredentials = function() { | ||
return new Promise(function(resolve) { | ||
resolve(JSON.parse(storage.getItem(authKeyId))); | ||
}); | ||
}; | ||
function assertOptions(options) { | ||
// validate options | ||
if (!options.xhrAgent) { | ||
throw new Error('Options must include an xhrAgent (e.g. axios)'); | ||
} | ||
/** | ||
* set auth credentials | ||
* @param {object} val the credentials to be saved | ||
* @return {Promise} resolves to an object containing the id and rev | ||
*/ | ||
var setAuthCredentials = function(credentials) { | ||
return new Promise(function(resolve) { | ||
storage.setItem(authKeyId, JSON.stringify(credentials)); | ||
resolve(getAuthCredentials()); | ||
}); | ||
}; | ||
if (!options.store || !options.store.setItem || !options.store.getItem) { | ||
throw new Error('Options must include a `store` (e.g. localstorage)'); | ||
} | ||
/** | ||
* generate the header expected by Hawk. | ||
* Note that the url must include a protocol and hostname | ||
* @param {string} url | ||
* @param {string} method e.g. 'GET' | ||
* @return {string} The hawk auth signature | ||
*/ | ||
var generateHawkHeader = function(url, method) { | ||
return me.getAuthCredentials() | ||
.then(function(credentials) { | ||
if (!credentials) { | ||
throw new Error('No credentials found to sign with'); | ||
} | ||
if (!options.baseUrl || options.baseUrl.indexOf('http') !== 0) { | ||
throw new Error('Options must include a valid baseUrl'); | ||
} | ||
return me.getHawkHeader( | ||
url, | ||
method, | ||
{ credentials: credentials } | ||
); | ||
}); | ||
}; | ||
/** | ||
* format requestObj for use by requestFn | ||
* @param {object} requestObj the object to be passed to the requestFn | ||
* @return {object} the object after mapping keys to new ones | ||
*/ | ||
var formatRequestOptions = function(requestOptions) { | ||
if (me.config.onPreFormatRequestOptions instanceof Function) { | ||
requestOptions = me.config.onPreFormatRequestOptions( | ||
requestOptions | ||
); | ||
if ( | ||
!options.clientUrl || | ||
options.clientUrl.indexOf(options.baseUrl) !== 0 | ||
) { | ||
throw new Error('Options must include a valid clientUrl'); | ||
} | ||
requestOptions.uri = [ | ||
me.config.baseUrl, | ||
'/v' + me.config.version, | ||
requestOptions.uri | ||
].join(''); | ||
return options; | ||
} | ||
return renameKeys(requestOptions, function(key) { | ||
return me.config.requestObjectMap[key] || key; | ||
}); | ||
}; | ||
function getDefaults(_options) { | ||
/** | ||
* make a request using config.requestFn | ||
* @param {object} options request options to be passed to requestFn | ||
* @return {Promise} promise that resolves with the value of the response | ||
*/ | ||
var makeRequest = function(options, sign) { | ||
var formattedOptions = formatRequestOptions(options); | ||
//shallow copy options | ||
var options = Object.create(_options); | ||
options.baseUrl = options.baseUrl || defaultBaseUrl; | ||
options.clientUrl = options.baseUrl + defaultClientPath; | ||
return options; | ||
} | ||
/** | ||
* convenience function for sending the formatted request | ||
* @return {Promise} resolves to the formatted response value | ||
*/ | ||
var sendRequest = function() { | ||
return me.config.requestFn(formattedOptions) | ||
.then(me.config.formatResponseCallback); | ||
function constructClient (options, clientFactory) { | ||
var config = { | ||
xhrAgent: options.xhrAgent, | ||
store: options.store, | ||
apiUrl: options.baseUrl | ||
}; | ||
return clientFactory(config); | ||
} | ||
/** | ||
* convenience function for adding a header property to the | ||
* formattedOptions | ||
* @param {object} header output from hawk.client.header(); | ||
* @return {null} nothing | ||
*/ | ||
var addFormattedHeader = function(header) { | ||
var headers = formattedOptions.headers || []; | ||
headers.push({ | ||
name: 'Authorization', | ||
value: header.field | ||
}); | ||
formattedOptions.headers = me.config.formatRequestHeaders(headers); | ||
return; | ||
}; | ||
module.exports = function(_options) { | ||
var options = getDefaults(_options); | ||
assertOptions(options); | ||
var masterPromise; | ||
if (sign !== false) { | ||
masterPromise = generateHawkHeader( | ||
formattedOptions.url, | ||
formattedOptions.method | ||
) | ||
.then(addFormattedHeader); | ||
} else { | ||
masterPromise = Promise.resolve(); | ||
} | ||
return masterPromise.then(sendRequest); | ||
return options.xhrAgent.get(options.clientUrl) | ||
.then(extractClientSrc) | ||
.then(evalClientSrc) | ||
.then(constructClient.bind(null, options)) | ||
}; | ||
module.exports = function init(config) { | ||
me.config = assign({}, defaultConfig, config); | ||
me.getHawkHeader = hawkClientHeader; | ||
me.generateHawkHeader = generateHawkHeader; | ||
me.makeRequest = makeRequest; | ||
me.setAuthCredentials = setAuthCredentials; | ||
me.getAuthCredentials = getAuthCredentials; | ||
me.auth = routes.authentication(me); | ||
me.scans = routes.scans(me); | ||
me.users = routes.users(me); | ||
me.coinstac = { | ||
consortia: routes.coinstac.consortia(me) | ||
}; | ||
return me; | ||
}; | ||
module.exports.getDefaults = getDefaults; | ||
module.exports.assertOptions = assertOptions; | ||
module.exports.evalClientSrc = evalClientSrc; | ||
module.exports.extractClientSrc = extractClientSrc; | ||
module.exports.constructClient = constructClient; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
2
0
12110
10
246
66
- Removedbtoa@^1.1.2
- Removeddom-storage@^2.0.2
- Removedes6-object-assign@^1.0.1
- Removedhawk@^4.0.0
- Removedrename-keys@^1.1.2
- Removedboom@3.2.25.3.3(transitive)
- Removedbtoa@1.2.1(transitive)
- Removedcryptiles@3.2.1(transitive)
- Removeddom-storage@2.1.0(transitive)
- Removedes6-object-assign@1.1.0(transitive)
- Removedhawk@4.1.2(transitive)
- Removedhoek@3.0.44.3.1(transitive)
- Removedrename-keys@1.2.0(transitive)
- Removedsntp@2.1.0(transitive)