Initial commit
This commit is contained in:
511
node_modules/jayson/lib/utils.js
generated
vendored
Normal file
511
node_modules/jayson/lib/utils.js
generated
vendored
Normal file
@@ -0,0 +1,511 @@
|
||||
'use strict';
|
||||
|
||||
const JSONStream = require('JSONStream');
|
||||
const JSONstringify = require('json-stringify-safe');
|
||||
const uuid = require('uuid').v4;
|
||||
|
||||
const generateRequest = require('./generateRequest');
|
||||
|
||||
/** * @namespace */
|
||||
const Utils = module.exports;
|
||||
|
||||
// same reference as other files use, for tidyness
|
||||
const utils = Utils;
|
||||
|
||||
Utils.request = generateRequest;
|
||||
|
||||
/**
|
||||
* Generates a JSON-RPC 1.0 or 2.0 response
|
||||
* @param {Object} error Error member
|
||||
* @param {Object} result Result member
|
||||
* @param {String|Number|null} id Id of request
|
||||
* @param {Number} version JSON-RPC version to use
|
||||
* @return {Object} A JSON-RPC 1.0 or 2.0 response
|
||||
*/
|
||||
Utils.response = function(error, result, id, version) {
|
||||
id = typeof(id) === 'undefined' || id === null ? null : id;
|
||||
error = typeof(error) === 'undefined' || error === null ? null : error;
|
||||
version = typeof(version) === 'undefined' || version === null ? 2 : version;
|
||||
result = typeof(result) === 'undefined' || result === null ? null : result;
|
||||
const response = (version === 2) ? { jsonrpc: "2.0", id: id } : { id: id };
|
||||
|
||||
// errors are always included in version 1
|
||||
if(version === 1) {
|
||||
response.error = error;
|
||||
}
|
||||
|
||||
// one or the other with precedence for errors
|
||||
if(error) {
|
||||
response.error = error;
|
||||
} else {
|
||||
response.result = result;
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a random UUID
|
||||
* @return {String}
|
||||
*/
|
||||
Utils.generateId = function() {
|
||||
return uuid();
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges properties of object b into object a
|
||||
* @param {...Object} args Objects to be merged
|
||||
* @return {Object}
|
||||
* @private
|
||||
*/
|
||||
Utils.merge = function(...args) {
|
||||
return args.reduce(function (out, obj) {
|
||||
return {...out, ...obj};
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses an incoming stream for requests using JSONStream
|
||||
* @param {Stream} stream
|
||||
* @param {Object} options
|
||||
* @param {Function} onRequest Called once for stream errors and an unlimited amount of times for valid requests
|
||||
*/
|
||||
Utils.parseStream = function(stream, options, onRequest) {
|
||||
|
||||
const onError = Utils.once(onRequest);
|
||||
const onSuccess = (...args) => onRequest(null, ...args);
|
||||
|
||||
const result = JSONStream.parse();
|
||||
|
||||
result.on('data', function(data) {
|
||||
|
||||
// apply reviver walk function to prevent stringify/parse again
|
||||
if(typeof options.reviver === 'function') {
|
||||
data = Utils.walk({'': data}, '', options.reviver);
|
||||
}
|
||||
|
||||
onSuccess(data);
|
||||
});
|
||||
|
||||
result.on('error', onError);
|
||||
stream.on('error', onError);
|
||||
|
||||
stream.pipe(result);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a function that can only be called once
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
*/
|
||||
Utils.once = function (fn) {
|
||||
let called = false;
|
||||
let lastRetval;
|
||||
return function (...args) {
|
||||
if (called) return lastRetval;
|
||||
called = true;
|
||||
lastRetval = fn.call(this, ...args);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if obj is a plain object (not null)
|
||||
* @param {*} obj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.isPlainObject = function (obj) {
|
||||
return typeof obj === 'object' && obj !== null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an object to an array
|
||||
* @param {*} obj
|
||||
* @return {Array}
|
||||
*/
|
||||
Utils.toArray = function (obj) {
|
||||
if (Array.isArray(obj)) return obj;
|
||||
if (Utils.isPlainObject(obj)) return Object.keys(obj).map(function (key) {
|
||||
return obj[key];
|
||||
});
|
||||
if (!obj) return [];
|
||||
return Array.prototype.slice.call(obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an object to a plain object
|
||||
* @param {*} obj
|
||||
* @return {Object}
|
||||
*/
|
||||
Utils.toPlainObject = function (value) {
|
||||
value = Object(value);
|
||||
const result = {};
|
||||
for (const key in value) {
|
||||
result[key] = value[key];
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Picks keys from obj
|
||||
* @param {Object} obj
|
||||
* @param {String[]} keys
|
||||
* @return {Object}
|
||||
*/
|
||||
Utils.pick = function (obj, keys) {
|
||||
return keys.reduce(function (out, key) {
|
||||
out[key] = obj[key];
|
||||
return out;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to parse a stream and interpret it as JSON
|
||||
* @param {Stream} stream Stream instance
|
||||
* @param {Function} [options] Optional options for JSON.parse
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Utils.parseBody = function(stream, options, callback) {
|
||||
|
||||
callback = Utils.once(callback);
|
||||
let data = '';
|
||||
|
||||
stream.setEncoding('utf8');
|
||||
|
||||
stream.on('data', function(str) {
|
||||
data += str;
|
||||
});
|
||||
|
||||
stream.on('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
stream.on('end', function() {
|
||||
utils.JSON.parse(data, options, function(err, request) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, request);
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a HTTP request listener bound to the server in the argument.
|
||||
* @param {http.Server} self Instance of a HTTP server
|
||||
* @param {JaysonServer} server Instance of JaysonServer (typically jayson.Server)
|
||||
* @return {Function}
|
||||
* @private
|
||||
*/
|
||||
Utils.getHttpListener = function(self, server) {
|
||||
return function(req, res) {
|
||||
const options = self.options || {};
|
||||
|
||||
server.emit('http request', req);
|
||||
|
||||
// 405 method not allowed if not POST
|
||||
if(!Utils.isMethod(req, 'POST')) {
|
||||
return respond('Method Not Allowed', 405, {'allow': 'POST'});
|
||||
}
|
||||
|
||||
// 415 unsupported media type if Content-Type is not correct
|
||||
if(!Utils.isContentType(req, 'application/json')) {
|
||||
return respond('Unsupported Media Type', 415);
|
||||
}
|
||||
|
||||
Utils.parseBody(req, options, function(err, request) {
|
||||
if(err) {
|
||||
return respond(err, 400);
|
||||
}
|
||||
|
||||
server.call(request, function(error, success) {
|
||||
const response = error || success;
|
||||
if(!response) {
|
||||
// no response received at all, must be a notification
|
||||
return respond('', 204);
|
||||
}
|
||||
|
||||
utils.JSON.stringify(response, options, function(err, body) {
|
||||
if(err) {
|
||||
return respond(err, 500);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'content-length': Buffer.byteLength(body, options.encoding),
|
||||
'content-type': 'application/json; charset=utf-8'
|
||||
};
|
||||
|
||||
respond(body, 200, headers);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function respond(response, code, headers) {
|
||||
const body = response instanceof Error ? response.toString() : response;
|
||||
server.emit('http response', res, req);
|
||||
res.writeHead(code, headers || {});
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a HTTP Request comes with a specific Content-Type
|
||||
* @param {ServerRequest} request
|
||||
* @param {String} type
|
||||
* @return {Boolean}
|
||||
* @private
|
||||
*/
|
||||
Utils.isContentType = function(request, type) {
|
||||
request = request || {headers: {}};
|
||||
const contentType = request.headers['content-type'] || '';
|
||||
return RegExp(type, 'i').test(contentType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a HTTP Request is of a specific method
|
||||
* @param {ServerRequest} request
|
||||
* @param {String} method
|
||||
* @return {Boolean}
|
||||
* @private
|
||||
*/
|
||||
Utils.isMethod = function(request, method) {
|
||||
method = (method || '').toUpperCase();
|
||||
return (request.method || '') === method;
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively walk an object and apply a function on its members
|
||||
* @param {Object} holder The object to walk
|
||||
* @param {String} key The key to look at
|
||||
* @param {Function} fn The function to apply to members
|
||||
* @return {Object}
|
||||
*/
|
||||
Utils.walk = function(holder, key, fn) {
|
||||
let k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = Utils.walk(value, k, fn);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fn.call(holder, key, value);
|
||||
};
|
||||
|
||||
/** * @namespace */
|
||||
Utils.JSON = {};
|
||||
|
||||
/**
|
||||
* Parses a JSON string and then invokes the given callback
|
||||
* @param {String} str The string to parse
|
||||
* @param {Object} options Object with options, possibly holding a "reviver" function
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Utils.JSON.parse = function(str, options, callback) {
|
||||
let reviver = null;
|
||||
let obj = null;
|
||||
options = options || {};
|
||||
|
||||
if(typeof options.reviver === 'function') {
|
||||
reviver = options.reviver;
|
||||
}
|
||||
|
||||
try {
|
||||
obj = JSON.parse.apply(JSON, [str, reviver].filter(v => v));
|
||||
} catch(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, obj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Stringifies JSON and then invokes the given callback
|
||||
* @param {Object} obj The object to stringify
|
||||
* @param {Object} options Object with options, possibly holding a "replacer" function
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Utils.JSON.stringify = function(obj, options, callback) {
|
||||
let replacer = null;
|
||||
let str = null;
|
||||
options = options || {};
|
||||
|
||||
if(typeof options.replacer === 'function') {
|
||||
replacer = options.replacer;
|
||||
}
|
||||
|
||||
try {
|
||||
str = JSONstringify.apply(JSON, [obj, replacer].filter(v => v));
|
||||
} catch(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, str);
|
||||
};
|
||||
|
||||
/** * @namespace */
|
||||
Utils.Request = {};
|
||||
|
||||
/**
|
||||
* Determines if the passed request is a batch request
|
||||
* @param {Object} request The request
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Request.isBatch = function(request) {
|
||||
return Array.isArray(request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the passed request is a notification request
|
||||
* @param {Object} request The request
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Request.isNotification = function(request) {
|
||||
return Boolean(
|
||||
request
|
||||
&& !Utils.Request.isBatch(request)
|
||||
&& (typeof(request.id) === 'undefined'
|
||||
|| request.id === null)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the passed request is a valid JSON-RPC 2.0 Request
|
||||
* @param {Object} request The request
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Request.isValidVersionTwoRequest = function(request) {
|
||||
return Boolean(
|
||||
request
|
||||
&& typeof(request) === 'object'
|
||||
&& request.jsonrpc === '2.0'
|
||||
&& typeof(request.method) === 'string'
|
||||
&& (
|
||||
typeof(request.params) === 'undefined'
|
||||
|| Array.isArray(request.params)
|
||||
|| (request.params && typeof(request.params) === 'object')
|
||||
)
|
||||
&& (
|
||||
typeof(request.id) === 'undefined'
|
||||
|| typeof(request.id) === 'string'
|
||||
|| typeof(request.id) === 'number'
|
||||
|| request.id === null
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the passed request is a valid JSON-RPC 1.0 Request
|
||||
* @param {Object} request The request
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Request.isValidVersionOneRequest = function(request) {
|
||||
return Boolean(
|
||||
request
|
||||
&& typeof(request) === 'object'
|
||||
&& typeof(request.method) === 'string'
|
||||
&& Array.isArray(request.params)
|
||||
&& typeof(request.id) !== 'undefined'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the passed request is a valid JSON-RPC Request
|
||||
* @param {Object} request The request
|
||||
* @param {Number} [version=2] JSON-RPC version 1 or 2
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Request.isValidRequest = function(request, version) {
|
||||
version = version === 1 ? 1 : 2;
|
||||
return Boolean(
|
||||
request
|
||||
&& (
|
||||
(version === 1 && Utils.Request.isValidVersionOneRequest(request)) ||
|
||||
(version === 2 && Utils.Request.isValidVersionTwoRequest(request))
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/** * @namespace */
|
||||
Utils.Response = {};
|
||||
|
||||
/**
|
||||
* Determines if the passed error is a valid JSON-RPC error response
|
||||
* @param {Object} error The error
|
||||
* @param {Number} [version=2] JSON-RPC version 1 or 2
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Response.isValidError = function(error, version) {
|
||||
version = version === 1 ? 1 : 2;
|
||||
return Boolean(
|
||||
version === 1 && (
|
||||
typeof(error) !== 'undefined'
|
||||
&& error !== null
|
||||
)
|
||||
|| version === 2 && (
|
||||
error
|
||||
&& typeof(error.code) === 'number'
|
||||
&& parseInt(error.code, 10) === error.code
|
||||
&& typeof(error.message) === 'string'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the passed object is a valid JSON-RPC response
|
||||
* @param {Object} response The response
|
||||
* @param {Number} [version=2] JSON-RPC version 1 or 2
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Utils.Response.isValidResponse = function(response, version) {
|
||||
version = version === 1 ? 1 : 2;
|
||||
return Boolean(
|
||||
response !== null
|
||||
&& typeof response === 'object'
|
||||
&& (version === 2 && (
|
||||
// check version
|
||||
response.jsonrpc === '2.0'
|
||||
&& (
|
||||
// check id
|
||||
response.id === null
|
||||
|| typeof response.id === 'string'
|
||||
|| typeof response.id === 'number'
|
||||
)
|
||||
&& (
|
||||
// result and error do not exist at the same time
|
||||
(typeof response.result === 'undefined' && typeof response.error !== 'undefined')
|
||||
|| (typeof response.result !== 'undefined' && typeof response.error === 'undefined')
|
||||
)
|
||||
&& (
|
||||
// check result
|
||||
(typeof response.result !== 'undefined')
|
||||
// check error object
|
||||
|| (
|
||||
response.error !== null
|
||||
&& typeof response.error === 'object'
|
||||
&& typeof response.error.code === 'number'
|
||||
// check error.code is integer
|
||||
&& ((response.error.code | 0) === response.error.code)
|
||||
&& typeof response.error.message === 'string'
|
||||
)
|
||||
)
|
||||
)
|
||||
|| version === 1 && (
|
||||
typeof response.id !== 'undefined'
|
||||
&& (
|
||||
// result and error relation (the other null if one is not)
|
||||
(typeof response.result !== 'undefined' && response.error === null)
|
||||
|| (typeof response.error !== 'undefined' && response.result === null)
|
||||
)
|
||||
))
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user