box-view-api
Version:
Box View API, node.js client library
264 lines (231 loc) • 8.14 kB
JavaScript
// Defines the details for the RESTful Box View API,
// as well as providing some helper methods for creating URIs to resources
// and submitting the request.
//
// Core node module dependencies
//
var inspect = require('util').inspect;
var querystring = require('querystring');
var url = require('url');
var path = require('path');
// 3rd party library dependencies
//
var request = require('request');
var FormData = require('form-data');
// Internal dependencies
//
var BoxViewError = require('./error');
var ErrorTypes = BoxViewError.errors; // aliasing for brevity
// private helpers
//
function normaliseQuery(query) {
var nq;
switch (typeof query) {
case 'string':
// parse out the querystring into a hash of the query params
nq = querystring.parse(query);
break;
case 'object':
// shallow clone the query hash, so we don't end up mutating the original
nq = (function(o){
var c = {};
for (var k in o) {
c[k] = o[k];
}
return c;
})(query);
break;
default:
nq = {};
}
return nq;
}
function wrappedCallback(cb) {
cb = (typeof cb === 'function') ? cb : function(){};
return function(e, r, b) {
var body;
if (typeof b === 'object') {
body = b;
}
else if (typeof b === 'string') {
try {
body = JSON.parse(b);
}
catch (x) {
body = {body: b};
}
}
else {
body = {body: b};
}
cb(e, r, body);
};
}
function validateCommonArgs(resource, options, cb) {
if (typeof resource !== 'string') {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied resource is not a string: ' + (typeof resource) + ' [' + inspect(resource) + ']');
}
if (resource.length <= 0) {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied resource "' + resource + '" is too short');
}
if (typeof options !== 'object') {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied options is not an object: ' + (typeof options) + ' [' + inspect(options) + ']');
}
if (!cb) {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'No callback supplied');
}
if (typeof cb !== 'function') {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied callback is not a function: '+(typeof cb)+' ['+inspect(cb)+']');
}
}
function stringFromStream(stream, callback) {
var chunks = [];
var bodyLen = 0;
stream.on('data', function (chunk) {
chunks.push(chunk);
bodyLen += chunk.length;
});
stream.on('end', function () {
var body;
if (chunks.length && Buffer.isBuffer(chunks[0])) {
body = new Buffer(bodyLen);
var i = 0;
chunks.forEach(function (chunk) {
chunk.copy(body, i, 0, chunk.length);
i += chunk.length;
});
body = body.toString('utf8');
}
else if (chunks.length) {
body = chunks.join('');
}
callback(body);
});
}
function normaliseAcceptsHeader(original, mime) {
original = original || '';
mime = mime || 'application/json';
var split = original.split(',');
if (split.indexOf(mime) < 0 ) {
split.push(mime);
}
return split.join(', ');
}
// Exposed interface
//
var API = module.exports = {
// Base definitions of the RESTful API
//
base: {
protocol: 'https',
host: 'view-api.box.com',
pathname: '/1'
},
constructor: function(apiToken) {
if (typeof apiToken !== 'string') {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied API Token is not a string: ' + (typeof apiToken) + ' [' + inspect(apiToken) + ']');
}
if (apiToken.length <= 0) {
throw new BoxViewError(ErrorTypes.INVALID_API_TOKEN, 'Supplied API Token "' + apiToken + '" is too short');
}
this.apiToken = apiToken;
},
uriString: function(resource, query) {
if (typeof resource !== 'string') {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied resource is not a string: '+(typeof resource)+' ['+inspect(resource)+']');
}
if (resource.length <= 0) {
throw new BoxViewError(ErrorTypes.INVALID_TYPE, 'Supplied resource "'+resource+'" is too short');
}
return url.format({
protocol: API.base.protocol,
host: API.base.host,
pathname: path.join(API.base.pathname, resource),
query: normaliseQuery(query)
});
},
// get(resource, [{query: query, successStream: writeStream},] callback)
get: function(resource) {
if (arguments.length < 2 || arguments.length > 3) {
throw new BoxViewError(ErrorTypes.INVALID_USE, 'Incorrect number of arguments passed to '+__filename+'.get(): '+arguments.length);
}
var cb = arguments[arguments.length - 1];
var options = (arguments.length === 3) ? arguments[1] : {};
validateCommonArgs(resource, options, cb);
var successStream = options.successStream;
options.qs = normaliseQuery(options.query);
options.headers = options.headers || {};
options.uri = API.uriString(resource);
options.headers.Authorization = 'Token ' + this.apiToken;
options.headers.accept = normaliseAcceptsHeader(options.headers.accept);
var _callback = wrappedCallback(cb);
var req;
if (successStream) {
req = request.get(options);
req.on('error', function(err) {
_callback(err);
});
req.on('response', function(response) {
if (response.statusCode === 200) {
response.pipe(successStream);
successStream.on('close', function() {
_callback(null, response, {});
});
}
else {
stringFromStream(response, function(body) {
_callback(null, response, body);
});
}
});
}
else {
req = request.get(options, _callback);
}
return req;
},
// put(resource, [{query: query, successStream: writeStream},] callback)
put: function (resource) {
if (arguments.length < 2 || arguments.length > 3) {
throw new BoxViewError(ErrorTypes.INVALID_USE, 'Incorrect number of arguments passed to ' + __filename + '.get(): ' + arguments.length);
}
var cb = arguments[arguments.length - 1];
var options = (arguments.length === 3) ? arguments[1] : {};
validateCommonArgs(resource, options, cb);
options.qs = normaliseQuery(options.query);
options.headers = options.headers || {};
options.uri = API.uriString(resource);
options.headers.Authorization = 'Token ' + this.apiToken;
options.headers.accept = normaliseAcceptsHeader(options.headers.accept);
return request.put(options, wrappedCallback(cb));
},
// post(apiToken, resource, [{query: query, encodingOptions: encodingOptions},] callback)
post: function(resource) {
if (arguments.length < 2 || arguments.length > 3) {
throw new BoxViewError(ErrorTypes.INVALID_USE, 'Incorrect number of arguments passed to '+__filename+'.post(): '+arguments.length);
}
var cb = arguments[arguments.length - 1];
var options = (arguments.length === 3) ? arguments[1] : {};
validateCommonArgs(resource, options, cb);
options.qs = normaliseQuery(options.query);
options.headers = options.headers || {};
options.uri = API.uriString(resource);
options.headers.Authorization = 'Token '+this.apiToken;
options.headers.accept = normaliseAcceptsHeader(options.headers.accept);
return request.post(options, wrappedCallback(cb));
},
del: function (resource) {
if (arguments.length < 2 || arguments.length > 3) {
throw new BoxViewError(ErrorTypes.INVALID_USE, 'Incorrect number of arguments passed to ' + __filename + '.get(): ' + arguments.length);
}
var cb = arguments[arguments.length - 1];
var options = (arguments.length === 3) ? arguments[1] : {};
validateCommonArgs(resource, options, cb);
options.qs = normaliseQuery(options.query);
options.headers = options.headers || {};
options.uri = API.uriString(resource);
options.headers.Authorization = 'Token ' + this.apiToken;
options.headers.accept = normaliseAcceptsHeader(options.headers.accept);
return request.del(options, wrappedCallback(cb));
}
};