FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

14
node_modules/jayson/lib/client/browser/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,14 @@
import * as jayson from '../../..';
type ClientBrowserCallServerFunctionCallback = (err?:Error | null, response?:string) => void;
type ClientBrowserCallServerFunction = (request:string, callback:ClientBrowserCallServerFunctionCallback) => void;
declare class ClientBrowser {
constructor(callServer:ClientBrowserCallServerFunction, options?:jayson.ClientOptions);
request(method: string, params: jayson.RequestParamsLike, id?: jayson.JSONRPCIDLike | null, callback?: jayson.JSONRPCCallbackType): jayson.JSONRPCRequest;
request(method: string, params: jayson.RequestParamsLike, callback?: jayson.JSONRPCCallbackType): jayson.JSONRPCRequest;
request(method: Array<jayson.JSONRPCRequestLike>, callback: jayson.JSONRPCCallbackTypeBatch): Array<jayson.JSONRPCRequest>;
}
export = ClientBrowser;

167
node_modules/jayson/lib/client/browser/index.js generated vendored Normal file
View File

@@ -0,0 +1,167 @@
'use strict';
const uuid = require('uuid').v4;
const generateRequest = require('../../generateRequest');
/**
* Constructor for a Jayson Browser Client that does not depend any node.js core libraries
* @class ClientBrowser
* @param {Function} callServer Method that calls the server, receives the stringified request and a regular node-style callback
* @param {Object} [options]
* @param {Function} [options.reviver] Reviver function for JSON
* @param {Function} [options.replacer] Replacer function for JSON
* @param {Number} [options.version=2] JSON-RPC version to use (1|2)
* @param {Function} [options.generator] Function to use for generating request IDs
* @param {Boolean} [options.notificationIdNull=false] When true, version 2 requests will set id to null instead of omitting it
* @return {ClientBrowser}
*/
const ClientBrowser = function(callServer, options) {
if(!(this instanceof ClientBrowser)) {
return new ClientBrowser(callServer, options);
}
if (!options) {
options = {};
}
this.options = {
reviver: typeof options.reviver !== 'undefined' ? options.reviver : null,
replacer: typeof options.replacer !== 'undefined' ? options.replacer : null,
generator: typeof options.generator !== 'undefined' ? options.generator : function() { return uuid(); },
version: typeof options.version !== 'undefined' ? options.version : 2,
notificationIdNull: typeof options.notificationIdNull === 'boolean' ? options.notificationIdNull : false,
};
this.callServer = callServer;
};
module.exports = ClientBrowser;
/**
* Creates a request and dispatches it if given a callback.
* @param {String|Array} method A batch request if passed an Array, or a method name if passed a String
* @param {Array|Object} [params] Parameters for the method
* @param {String|Number} [id] Optional id. If undefined an id will be generated. If null it creates a notification request
* @param {Function} [callback] Request callback. If specified, executes the request rather than only returning it.
* @throws {TypeError} Invalid parameters
* @return {Object} JSON-RPC 1.0 or 2.0 compatible request
*/
ClientBrowser.prototype.request = function(method, params, id, callback) {
const self = this;
let request = null;
// is this a batch request?
const isBatch = Array.isArray(method) && typeof params === 'function';
if (this.options.version === 1 && isBatch) {
throw new TypeError('JSON-RPC 1.0 does not support batching');
}
// is this a raw request?
const isRaw = !isBatch && method && typeof method === 'object' && typeof params === 'function';
if(isBatch || isRaw) {
callback = params;
request = method;
} else {
if(typeof id === 'function') {
callback = id;
// specifically undefined because "null" is a notification request
id = undefined;
}
const hasCallback = typeof callback === 'function';
try {
request = generateRequest(method, params, id, {
generator: this.options.generator,
version: this.options.version,
notificationIdNull: this.options.notificationIdNull,
});
} catch(err) {
if(hasCallback) {
callback(err);
return;
}
throw err;
}
// no callback means we should just return a raw request
if(!hasCallback) {
return request;
}
}
let message;
try {
message = JSON.stringify(request, this.options.replacer);
} catch(err) {
callback(err);
return;
}
this.callServer(message, function(err, response) {
self._parseResponse(err, response, callback);
});
// always return the raw request
return request;
};
/**
* Parses a response from a server
* @param {Object} err Error to pass on that is unrelated to the actual response
* @param {String} responseText JSON-RPC 1.0 or 2.0 response
* @param {Function} callback Callback that will receive different arguments depending on the amount of parameters
* @private
*/
ClientBrowser.prototype._parseResponse = function(err, responseText, callback) {
if(err) {
callback(err);
return;
}
if(!responseText) {
// empty response text, assume that is correct because it could be a
// notification which jayson does not give any body for
callback();
return;
}
let response;
try {
response = JSON.parse(responseText, this.options.reviver);
} catch(err) {
callback(err);
return;
}
if(callback.length === 3) {
// if callback length is 3, we split callback arguments on error and response
// is batch response?
if(Array.isArray(response)) {
// necessary to split strictly on validity according to spec here
const isError = function(res) {
return typeof res.error !== 'undefined';
};
const isNotError = function (res) {
return !isError(res);
};
callback(null, response.filter(isError), response.filter(isNotError));
return;
} else {
// split regardless of validity
callback(null, response.error, response.result);
return;
}
}
callback(null, response);
};

118
node_modules/jayson/lib/client/http.js generated vendored Normal file
View File

@@ -0,0 +1,118 @@
'use strict';
const http = require('http');
const url = require('url');
const utils = require('../utils');
const Client = require('../client');
const { version } = require('../../package.json');
/**
* Constructor for a Jayson HTTP Client
* @class ClientHttp
* @constructor
* @extends Client
* @param {Object|String} [options] String interpreted as a URL
* @param {String} [options.encoding="utf8"] Encoding to use
* @return {ClientHttp}
*/
const ClientHttp = function(options) {
// accept first parameter as a url string
if(typeof(options) === 'string') {
options = url.parse(options);
}
if(!(this instanceof ClientHttp)) {
return new ClientHttp(options);
}
Client.call(this, options);
const defaults = utils.merge(this.options, {
encoding: 'utf8'
});
this.options = utils.merge(defaults, options || {});
};
require('util').inherits(ClientHttp, Client);
module.exports = ClientHttp;
ClientHttp.prototype._request = function(request, callback) {
const self = this;
// copies options so object can be modified in this context
const options = utils.merge({}, this.options);
utils.JSON.stringify(request, options, function(err, body) {
if(err) {
callback(err);
return;
}
options.method = options.method || 'POST';
const headers = {
'Content-Length': Buffer.byteLength(body, options.encoding),
'Content-Type': 'application/json; charset=utf-8',
Accept: 'application/json',
'User-Agent': `jayson-${version}`,
};
// let user override the headers
options.headers = utils.merge(headers, options.headers || {});
const req = self._getRequestStream(options);
self.emit('http request', req);
req.on('response', function(res) {
self.emit('http response', res, req);
res.setEncoding(options.encoding);
let data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
// assume we have an error
if(res.statusCode < 200 || res.statusCode >= 300) {
// assume the server gave the reason in the body
const err = new Error(data);
err.code = res.statusCode;
callback(err);
} else {
// empty reply
if(!data || typeof(data) !== 'string') {
callback();
return;
}
utils.JSON.parse(data, options, callback);
}
});
});
// abort on timeout
req.on('timeout', function() {
req.abort(); // req.abort causes "error" event
});
// abort on error
req.on('error', function(err) {
self.emit('http error', err);
callback(err);
req.abort();
});
req.end(body);
});
};
/**
* Gets a stream interface to a http server
* @param {Object} options An options object
* @return {require('http').ClientRequest}
* @private
*/
ClientHttp.prototype._getRequestStream = function(options) {
return http.request(options || {});
};

34
node_modules/jayson/lib/client/https.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
const https = require('https');
const ClientHttp = require('./http');
/**
* Constructor for a Jayson HTTPS Client
* @class ClientHttps
* @constructor
* @extends ClientHttp
* @param {Object|String} [options] String interpreted as a URL
* @param {String} [options.encoding="utf8"] Encoding to use
* @return {ClientHttps}
*/
const ClientHttps = function(options) {
if(!(this instanceof ClientHttps)) {
return new ClientHttps(options);
}
// just proxy to constructor for ClientHttp
ClientHttp.call(this, options);
};
require('util').inherits(ClientHttps, ClientHttp);
module.exports = ClientHttps;
/**
* Gets a stream interface to a https server
* @param {Object} options An options object
* @return {require('https').ClientRequest}
* @private
*/
ClientHttps.prototype._getRequestStream = function(options) {
return https.request(options || {});
};

221
node_modules/jayson/lib/client/index.js generated vendored Normal file
View File

@@ -0,0 +1,221 @@
'use strict';
const events = require('events');
const utils = require('../utils');
/**
* Constructor for a Jayson Client
* @class Client
* @extends require('events').EventEmitter
* @param {Server} [server] An instance of Server (a object with a "call" method")
* @param {Object} [options]
* @param {Function} [options.reviver] Reviver function for JSON
* @param {Function} [options.replacer] Replacer function for JSON
* @param {Number} [options.version=2] JSON-RPC version to use (1|2)
* @param {Boolean} [options.notificationIdNull=false] When true, version 2 requests will set id to null instead of omitting it
* @param {Function} [options.generator] Function to use for generating request IDs
* @return {Client}
*/
const Client = function(server, options) {
if(arguments.length === 1 && utils.isPlainObject(server)) {
options = server;
server = null;
}
if(!(this instanceof Client)) {
return new Client(server, options);
}
const defaults = {
reviver: null,
replacer: null,
generator: utils.generateId,
version: 2,
notificationIdNull: false,
};
this.options = utils.merge(defaults, options || {});
if(server) {
this.server = server;
}
};
require('util').inherits(Client, events.EventEmitter);
module.exports = Client;
/**
* HTTP client constructor
* @type ClientHttp
* @static
*/
Client.http = require('./http');
/**
* HTTPS client constructor
* @type ClientHttps
* @static
*/
Client.https = require('./https');
/**
* TCP client constructor
* @type ClientTcp
* @static
*/
Client.tcp = require('./tcp');
/**
* TLS client constructor
* @type ClientTls
* @static
*/
Client.tls = require('./tls');
/**
* Browser client constructor
* @type ClientBrowser
* @static
*/
Client.browser = require('./browser');
/**
* Websocket client constructor
* @type ClientWebsocket
* @static
*/
Client.websocket = require('./websocket');
/**
* Creates a request and dispatches it if given a callback.
* @param {String|Array} method A batch request if passed an Array, or a method name if passed a String
* @param {Array|Object} params Parameters for the method
* @param {String|Number} [id] Optional id. If undefined an id will be generated. If null it creates a notification request
* @param {Function} [callback] Request callback. If specified, executes the request rather than only returning it.
* @throws {TypeError} Invalid parameters
* @return {Object} JSON-RPC 1.0 or 2.0 compatible request
*/
Client.prototype.request = function(method, params, id, callback) {
const self = this;
let request = null;
// is this a batch request?
const isBatch = Array.isArray(method) && typeof(params) === 'function';
if (this.options.version === 1 && isBatch) {
throw new TypeError('JSON-RPC 1.0 does not support batching');
}
// is this a raw request?
const isRaw = !isBatch && method && typeof(method) === 'object' && typeof(params) === 'function';
if(isBatch || isRaw) {
callback = params;
request = method;
} else {
if(typeof(id) === 'function') {
callback = id;
// specifically undefined because "null" is a notification request
id = undefined;
}
const hasCallback = typeof(callback) === 'function';
try {
request = utils.request(method, params, id, {
generator: this.options.generator,
version: this.options.version,
notificationIdNull: this.options.notificationIdNull,
});
} catch(err) {
if(hasCallback) {
callback(err);
return;
}
throw err;
}
// no callback means we should just return a raw request before sending
if(!hasCallback) {
return request;
}
}
this.emit('request', request);
this._request(request, function(err, response) {
self.emit('response', request, response);
self._parseResponse(err, response, callback);
});
// always return the raw request
return request;
};
/**
* Executes a request on a directly bound server
* @param {Object} request A JSON-RPC 1.0 or 2.0 request
* @param {Function} callback Request callback that will receive the server response as the second argument
* @private
*/
Client.prototype._request = function(request, callback) {
const self = this;
// serializes the request as a JSON string so that we get a copy and can run the replacer as intended
utils.JSON.stringify(request, this.options, function(err, message) {
if(err) {
callback(err);
return;
}
self.server.call(message, function(error, success) {
const response = error || success;
callback(null, response);
});
});
};
/**
* Parses a response from a server, taking care of sugaring
* @param {Object} err Error to pass on that is unrelated to the actual response
* @param {Object} response JSON-RPC 1.0 or 2.0 response
* @param {Function} callback Callback that will receive different arguments depending on the amount of parameters
* @private
*/
Client.prototype._parseResponse = function(err, response, callback) {
if(err) {
callback(err);
return;
}
if(!response || typeof(response) !== 'object') {
callback();
return;
}
if(callback.length === 3) {
// if callback length is 3, we split callback arguments on error and response
// is batch response?
if(Array.isArray(response)) {
// necessary to split strictly on validity according to spec here
const isError = function(res) { return typeof(res.error) !== 'undefined'; };
const isNotError = function(res) { return !isError(res); };
callback(null, response.filter(isError), response.filter(isNotError));
return;
} else {
// split regardless of validity
callback(null, response.error, response.result);
return;
}
}
callback(null, response);
};

92
node_modules/jayson/lib/client/tcp.js generated vendored Normal file
View File

@@ -0,0 +1,92 @@
'use strict';
const net = require('net');
const utils = require('../utils');
const Client = require('../client');
/**
* Constructor for a Jayson TCP Client
* @class ClientTcp
* @constructor
* @extends Client
* @param {Object|String} [options] Object goes into options for net.connect, String goes into options.path. String option argument is NOT recommended.
* @return {ClientTcp}
*/
const ClientTcp = function(options) {
if(typeof(options) === 'string') {
options = {path: options};
}
if(!(this instanceof ClientTcp)) {
return new ClientTcp(options);
}
Client.call(this, options);
const defaults = utils.merge(this.options, {
encoding: 'utf8'
});
this.options = utils.merge(defaults, options || {});
};
require('util').inherits(ClientTcp, Client);
module.exports = ClientTcp;
ClientTcp.prototype._request = function(request, callback) {
const self = this;
// copies options so object can be modified in this context
const options = utils.merge({}, this.options);
const delimiter = options.delimiter || '\n';
utils.JSON.stringify(request, options, function(err, body) {
if(err) {
callback(err);
return;
}
let handled = false;
const conn = net.connect(options, function() {
conn.setEncoding(options.encoding);
// wont get anything for notifications, just end here
if(utils.Request.isNotification(request)) {
handled = true;
conn.end(body + delimiter);
callback();
} else {
utils.parseStream(conn, options, function(err, response) {
handled = true;
conn.end();
if(err) {
callback(err);
return;
}
callback(null, response);
});
conn.write(body + delimiter);
}
});
self.emit('tcp socket', conn);
conn.on('error', function(err) {
self.emit('tcp error', err);
callback(err);
});
conn.on('end', function() {
if(!handled) {
callback();
}
});
});
};

91
node_modules/jayson/lib/client/tls.js generated vendored Normal file
View File

@@ -0,0 +1,91 @@
'use strict';
const tls = require('tls');
const utils = require('../utils');
const Client = require('../client');
/**
* Constructor for a Jayson TLS-encrypted TCP Client
* @class ClientTls
* @constructor
* @extends Client
* @param {Object|String} [options] Object goes into options for tls.connect, String goes into options.path. String option argument is NOT recommended.
* @return {ClientTls}
*/
const ClientTls = function(options) {
if(typeof(options) === 'string') {
options = {path: options};
}
if(!(this instanceof ClientTls)) {
return new ClientTls(options);
}
Client.call(this, options);
const defaults = utils.merge(this.options, {
encoding: 'utf8'
});
this.options = utils.merge(defaults, options || {});
};
require('util').inherits(ClientTls, Client);
module.exports = ClientTls;
ClientTls.prototype._request = function(request, callback) {
const self = this;
// copies options so object can be modified in this context
const options = utils.merge({}, this.options);
utils.JSON.stringify(request, options, function(err, body) {
if(err) {
callback(err);
return;
}
let handled = false;
const conn = tls.connect(options, function() {
conn.setEncoding(options.encoding);
// wont get anything for notifications, just end here
if(utils.Request.isNotification(request)) {
handled = true;
conn.end(body + '\n');
callback();
} else {
utils.parseStream(conn, options, function(err, response) {
handled = true;
conn.end();
if(err) {
callback(err);
return;
}
callback(null, response);
});
conn.write(body + '\n');
}
});
self.emit('tcp socket', conn);
conn.on('error', function(err) {
self.emit('tcp error', err);
callback(err);
});
conn.on('end', function() {
if(!handled) {
callback();
}
});
});
};

135
node_modules/jayson/lib/client/websocket.js generated vendored Normal file
View File

@@ -0,0 +1,135 @@
'use strict';
const WebSocket = require('isomorphic-ws');
const utils = require('../utils');
const delay = require('delay');
const Client = require('../client');
/**
* Constructor for a Jayson Websocket Client
* @class ClientWebsocket
* @constructor
* @extends Client
* @param {Object} [options]
* @param {String} [options.url] When options.ws not provided this will be the URL to open the websocket to
* @param {ws.WebSocket} [options.ws] When not provided will create a WebSocket instance with options.url
* @param {Number} [options.timeout] Will wait this long in ms until callbacking with an error
* @return {ClientWebsocket}
*/
const ClientWebsocket = function(options) {
if(!(this instanceof ClientWebsocket)) {
return new ClientWebsocket(options);
}
Client.call(this, options);
const defaults = utils.merge(this.options, {});
this.options = utils.merge(defaults, options || {});
const self = this;
this.ws = this.options.ws || new WebSocket(this.options.url);
this.outstandingRequests = [];
this.handlers = {};
this.handlers.message = function (str) {
utils.JSON.parse(str, self.options, function(err, response) {
if (err) {
// invalid JSON is ignored
return;
}
if (Array.isArray(response)) {
// we have a batch reply
const matchingRequest = self.outstandingRequests.find(function ([request]) {
if (Array.isArray(request)) {
// a batch is considered matching if at least one response id matches one request id
return response.some(function (resp) {
if (utils.Response.isValidResponse(resp)) {
return request.some(function (req) {
return req.id === resp.id;
});
}
return false;
});
}
});
if (matchingRequest) {
const [ , resolve ] = matchingRequest;
return resolve(response);
}
} else if (utils.Response.isValidResponse(response)) {
const matchingRequest = self.outstandingRequests.find(function ([request]) {
return !Array.isArray(request) && request.id === response.id;
});
if (matchingRequest) {
const [ , resolve ] = matchingRequest;
return resolve(response);
}
}
});
};
this.ws.on('message', this.handlers.message);
};
require('util').inherits(ClientWebsocket, Client);
module.exports = ClientWebsocket;
/**
* @desc Removes all event listeners from Websocket instance which cancels all outstanding requests too
*/
ClientWebsocket.prototype.unlisten = function () {
for (const eventName in this.handlers) {
this.ws.off(eventName, this.handlers[eventName]);
}
};
ClientWebsocket.prototype._request = function(request, callback) {
const self = this;
const { ws, options } = this;
// we have to remove the object representing this request when the promise resolves/rejects
let outstandingItem;
Promise.race([
options.timeout > 0 ? delay(options.timeout).then(function () {
throw new Error('timeout reached after ' + options.timeout + ' ms');
}) : null,
new Promise(function (resolve, reject) {
utils.JSON.stringify(request, options, function(err, body) {
if (err) {
return resolve(err);
}
ws.send(body);
if (utils.Request.isNotification(request)) {
// notifications callback immediately since they don't have a reply
return resolve();
}
outstandingItem = [request, resolve, reject];
self.outstandingRequests.push(outstandingItem);
});
}),
].filter(v => v !== null)).then(function (result) {
removeOutstandingRequest();
callback(null, result);
}).catch(function (err) {
removeOutstandingRequest();
callback(err);
});
function removeOutstandingRequest () {
if (!outstandingItem) {
return;
}
self.outstandingRequests = self.outstandingRequests.filter(v => v !== outstandingItem);
}
};

63
node_modules/jayson/lib/generateRequest.js generated vendored Normal file
View File

@@ -0,0 +1,63 @@
'use strict';
const uuid = require('uuid').v4;
/**
* Generates a JSON-RPC 1.0 or 2.0 request
* @param {String} method Name of method to call
* @param {Array|Object} params Array of parameters passed to the method as specified, or an object of parameter names and corresponding value
* @param {String|Number|null} [id] Request ID can be a string, number, null for explicit notification or left out for automatic generation
* @param {Object} [options]
* @param {Number} [options.version=2] JSON-RPC version to use (1 or 2)
* @param {Boolean} [options.notificationIdNull=false] When true, version 2 requests will set id to null instead of omitting it
* @param {Function} [options.generator] Passed the request, and the options object and is expected to return a request ID
* @throws {TypeError} If any of the parameters are invalid
* @return {Object} A JSON-RPC 1.0 or 2.0 request
* @memberOf Utils
*/
const generateRequest = function(method, params, id, options) {
if(typeof method !== 'string') {
throw new TypeError(method + ' must be a string');
}
options = options || {};
// check valid version provided
const version = typeof options.version === 'number' ? options.version : 2;
if (version !== 1 && version !== 2) {
throw new TypeError(version + ' must be 1 or 2');
}
const request = {
method: method
};
if(version === 2) {
request.jsonrpc = '2.0';
}
if(params) {
// params given, but invalid?
if(typeof params !== 'object' && !Array.isArray(params)) {
throw new TypeError(params + ' must be an object, array or omitted');
}
request.params = params;
}
// if id was left out, generate one (null means explicit notification)
if(typeof(id) === 'undefined') {
const generator = typeof options.generator === 'function' ? options.generator : function() { return uuid(); };
request.id = generator(request, options);
} else if (version === 2 && id === null) {
// we have a version 2 notification
if (options.notificationIdNull) {
request.id = null; // id will not be set at all unless option provided
}
} else {
request.id = id;
}
return request;
};
module.exports = generateRequest;

31
node_modules/jayson/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
/**
* Namespace available as require('jayson')
* @namespace Jayson
*/
const Jayson = module.exports;
/**
* @static
* @type Client
*/
Jayson.Client = Jayson.client = require('./client');
/**
* @static
* @type Server
*/
Jayson.Server = Jayson.server = require('./server');
/**
* @static
* @type Utils
*/
Jayson.Utils = Jayson.utils = require('./utils');
/**
* @static
* @type Method
*/
Jayson.Method = Jayson.method = require('./method');

120
node_modules/jayson/lib/method.js generated vendored Normal file
View File

@@ -0,0 +1,120 @@
'use strict';
const utils = require('./utils');
/**
* @summary Constructor for a Jayson Method
* @class Method
* @param {Function} [handler] Function to set as handler
* @param {Object} [options]
* @param {Function} [options.handler] Same as separate handler
* @param {Boolean} [options.useContext=false] When true, the handler expects a context object
* @param {Array|Object} [options.params] Defines params that the handler accepts
*/
const Method = function(handler, options) {
if(!(this instanceof Method)) {
return new Method(handler, options);
}
// only got passed options
if(utils.isPlainObject(handler)) {
options = handler;
handler = null;
}
const defaults = {
useContext: false,
};
options = options || {};
this.options = utils.merge(defaults, options);
this.handler = handler || options.handler;
};
module.exports = Method;
/**
* @summary Returns the handler function associated with this method
* @return {Function}
*/
Method.prototype.getHandler = function() {
return this.handler;
};
/**
* @summary Sets the handler function associated with this method
* @param {Function} handler
*/
Method.prototype.setHandler = function(handler) {
this.handler = handler;
};
/**
* @summary Prepare parameters for the method handler
* @private
*/
Method.prototype._getHandlerParams = function(params) {
const options = this.options;
const isObjectParams = !Array.isArray(params) && utils.isPlainObject(params) && params;
const isArrayParams = Array.isArray(params);
switch(true) {
// handler always gets an array
case options.params === Array:
return isArrayParams ? params : utils.toArray(params);
// handler always gets an object
case options.params === Object:
return isObjectParams ? params : utils.toPlainObject(params);
// handler gets a list of defined properties that should always be set
case Array.isArray(options.params): {
const undefinedParams = Object.keys(options.params).reduce(function (out, index) {
const key = options.params[index];
out[key] = undefined;
return out;
}, {});
return {...undefinedParams, ...utils.pick(params, Object.keys(params))};
}
// handler gets a map of defined properties and their default values
case utils.isPlainObject(options.params):
return {...options.params, ...utils.pick(params, Object.keys(params))};
// give params as is
default:
return params;
}
};
/**
* @summary Executes this method in the context of a server
* @param {Server} server
* @param {Array|Object} requestParams
* @param {Object} [context]
* @param {Function} callback
*/
Method.prototype.execute = function(server, requestParams, context, callback) {
if(typeof(context) === 'function') {
callback = context;
context = {};
}
if(!context) {
context = {};
}
// when useContext is true, the handler gets a context object every time
const useContext = Boolean(this.options.useContext);
const handler = this.getHandler();
const params = this._getHandlerParams(requestParams);
const args = useContext ? [params, context, callback] : [params, callback];
return handler.call(server, ...args);
};

26
node_modules/jayson/lib/server/http.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const http = require('http');
const utils = require('../utils');
/**
* Constructor for a Jayson HTTP server
* @class ServerHttp
* @extends require('http').Server
* @param {Server} server Server instance
* @param {Object} [options] Options for this instance
* @return {ServerHttp}
*/
const ServerHttp = function(server, options) {
if(!(this instanceof ServerHttp)) {
return new ServerHttp(server, options);
}
this.options = utils.merge(server.options, options || {});
const listener = utils.getHttpListener(this, server);
http.Server.call(this, listener);
};
require('util').inherits(ServerHttp, http.Server);
module.exports = ServerHttp;

26
node_modules/jayson/lib/server/https.js generated vendored Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const https = require('https');
const utils = require('../utils');
/**
* Constructor for a Jayson HTTPS server
* @class ServerHttps
* @extends require('https').Server
* @param {Server} server Server instance
* @param {Object} [options] Options for this instance
* @return {ServerHttps}
*/
const ServerHttps = function(server, options) {
if(!(this instanceof ServerHttps)) {
return new ServerHttps(server, options);
}
this.options = utils.merge(server.options, options || {});
const listener = utils.getHttpListener(this, server);
https.Server.call(this, this.options, listener);
};
require('util').inherits(ServerHttps, https.Server);
module.exports = ServerHttps;

476
node_modules/jayson/lib/server/index.js generated vendored Normal file
View File

@@ -0,0 +1,476 @@
'use strict';
const events = require('events');
const jayson = require('../');
const utils = require('../utils');
/**
* Constructor for a Jayson Server
* @class Server
* @extends require('events').EventEmitter
* @param {Object<String,Function>} [methods] Methods to add
* @param {Object} [options]
* @param {Array|Object} [options.params] Passed to Jayson.Method as an option when created
* @param {Boolean} [options.useContext=false] Passed to Jayson.Method as an option when created
* @param {Function} [options.reviver] Reviver function for JSON
* @param {Function} [options.replacer] Replacer function for JSON
* @param {Function} [options.methodConstructor] Methods will be made instances of this class
* @param {String} [options.encoding="utf8"] Encoding to use
* @param {Number} [options.version=2] JSON-RPC version to use (1|2)
* @param {Number} [options.maxBatchLength=Infinity] Maximum requests allowed in a batch
* @param {Function} [options.router] Function to use for routing methods
* @property {Object} options A reference to the internal options object that can be modified directly
* @property {Object} errorMessages Map of error code to error message pairs that will be used in server responses
* @property {ServerHttp} http HTTP interface constructor
* @property {ServerHttps} https HTTPS interface constructor
* @property {ServerTcp} tcp TCP interface constructor
* @property {ServerTls} tls TLS interface constructor
* @property {Middleware} middleware Middleware generator function
* @return {Server}
*/
const Server = function(methods, options) {
if(!(this instanceof Server)) {
return new Server(methods, options);
}
const defaults = {
reviver: null,
replacer: null,
encoding: 'utf8',
version: 2,
useContext: false,
methodConstructor: jayson.Method,
maxBatchLength: Infinity,
router: function(method) {
return this.getMethod(method);
}
};
this.options = utils.merge(defaults, options || {});
// bind router to the server
this.options.router = this.options.router.bind(this);
this._methods = {};
// adds methods passed to constructor
this.methods(methods || {});
// assigns interfaces to this instance
const interfaces = Server.interfaces;
for(let name in interfaces) {
this[name] = interfaces[name].bind(interfaces[name], this);
}
// copies error messages for defined codes into this instance
this.errorMessages = {};
for(let handle in Server.errors) {
const code = Server.errors[handle];
this.errorMessages[code] = Server.errorMessages[code];
}
};
require('util').inherits(Server, events.EventEmitter);
module.exports = Server;
/**
* Interfaces that will be automatically bound as properties of a Server instance
* @enum {Function}
* @static
*/
Server.interfaces = {
http: require('./http'),
https: require('./https'),
tcp: require('./tcp'),
tls: require('./tls'),
websocket: require('./websocket'),
middleware: require('./middleware')
};
/**
* JSON-RPC specification errors that map to an integer code
* @enum {Number}
* @static
*/
Server.errors = {
PARSE_ERROR: -32700,
INVALID_REQUEST: -32600,
METHOD_NOT_FOUND: -32601,
INVALID_PARAMS: -32602,
INTERNAL_ERROR: -32603,
INVALID_REQUEST_MAX_BATCH_LENGTH_EXCEEDED: -32099,
};
/*
* Error codes that map to an error message
* @enum {String}
* @static
*/
Server.errorMessages = {};
Server.errorMessages[Server.errors.PARSE_ERROR] = 'Parse Error';
Server.errorMessages[Server.errors.INVALID_REQUEST] = 'Invalid request';
Server.errorMessages[Server.errors.METHOD_NOT_FOUND] = 'Method not found';
Server.errorMessages[Server.errors.INVALID_PARAMS] = 'Invalid method parameter(s)';
Server.errorMessages[Server.errors.INTERNAL_ERROR] = 'Internal error';
Server.errorMessages[Server.errors.INVALID_REQUEST_MAX_BATCH_LENGTH_EXCEEDED] = 'Invalid request: Maximum batch length exceeded';
/**
* Adds a single method to the server
* @param {String} name Name of method to add
* @param {Function|Client} definition Function or Client for a relayed method
* @throws {TypeError} Invalid parameters
*/
Server.prototype.method = function(name, definition) {
const Method = this.options.methodConstructor;
const isRelay = definition instanceof jayson.Client;
const isMethod = definition instanceof Method;
const isDefinitionFunction = typeof definition === 'function';
// a valid method is either a function or a client (relayed method)
if(!isRelay && !isMethod && !isDefinitionFunction) {
throw new TypeError('method definition must be either a function, an instance of jayson.Client or an instance of jayson.Method');
}
if(!name || typeof(name) !== 'string') {
throw new TypeError('"' + name + '" must be a non-zero length string');
}
if(/^rpc\./.test(name)) {
throw new TypeError('"' + name + '" is a reserved method name');
}
// make instance of jayson.Method
if(!isRelay && !isMethod) {
definition = new Method(definition, {
params: this.options.params,
useContext: this.options.useContext
});
}
this._methods[name] = definition;
};
/**
* Adds a batch of methods to the server
* @param {Object} methods Methods to add
*/
Server.prototype.methods = function(methods) {
methods = methods || {};
for(let name in methods) {
this.method(name, methods[name]);
}
};
/**
* Checks if a method is registered with the server
* @param {String} name Name of method
* @return {Boolean}
*/
Server.prototype.hasMethod = function(name) {
return name in this._methods;
};
/**
* Removes a method from the server
* @param {String} name
*/
Server.prototype.removeMethod = function(name) {
if(this.hasMethod(name)) {
delete this._methods[name];
}
};
/**
* Gets a method from the server
* @param {String} name
* @return {Method}
*/
Server.prototype.getMethod = function(name) {
if (Object.prototype.hasOwnProperty.call(this._methods, name)) {
return this._methods[name];
}
};
/**
* Returns a JSON-RPC compatible error property
* @param {Number} [code=-32603] Error code
* @param {String} [message="Internal error"] Error message
* @param {Object} [data] Additional data that should be provided
* @return {Object}
*/
Server.prototype.error = function(code, message, data) {
if(typeof(code) !== 'number') {
code = Server.errors.INTERNAL_ERROR;
}
if(typeof(message) !== 'string') {
message = this.errorMessages[code] || '';
}
const error = { code: code, message: message };
if(typeof(data) !== 'undefined') {
error.data = data;
}
return error;
};
/**
* Calls a method on the server
* @param {Object|Array|String} request A JSON-RPC request object. Object for single request, Array for batches and String for automatic parsing (using the reviver option)
* @param {Object} [context] Optional context object passed to methods
* @param {Function} [originalCallback] Callback that receives one of two arguments: first is an error and the second a response
*/
Server.prototype.call = function(request, context, originalCallback) {
const self = this;
if(typeof(context) === 'function') {
originalCallback = context;
context = {};
}
if(typeof(context) === 'undefined') {
context = {};
}
if(typeof(originalCallback) !== 'function') {
originalCallback = function() {};
}
// compose the callback so that we may emit an event on every response
const callback = function(error, response) {
self.emit('response', request, response || error);
originalCallback.apply(null, arguments);
};
maybeParse(request, this.options, function(err, request) {
let error = null; // JSON-RPC error
if(err) {
error = self.error(Server.errors.PARSE_ERROR, null, err);
callback(utils.response(error, undefined, undefined, self.options.version));
return;
}
// is this a batch request?
if(utils.Request.isBatch(request)) {
// batch requests not allowed for version 1
if(self.options.version === 1) {
error = self.error(Server.errors.INVALID_REQUEST);
callback(utils.response(error, undefined, undefined, self.options.version));
return;
}
// special case if empty batch request
if(!request.length) {
error = self.error(Server.errors.INVALID_REQUEST);
callback(utils.response(error, undefined, undefined, self.options.version));
return;
}
// verify number of batch requests does not exceed maximum allowed length
if (self.options.maxBatchLength >= 0 && request.length > self.options.maxBatchLength) {
error = self.error(Server.errors.INVALID_REQUEST_MAX_BATCH_LENGTH_EXCEEDED);
callback(utils.response(error, undefined, undefined, self.options.version));
return;
}
self._batch(request, context, callback);
return;
}
self.emit('request', request);
// is the request valid?
if(!utils.Request.isValidRequest(request, self.options.version)) {
error = self.error(Server.errors.INVALID_REQUEST);
callback(utils.response(error, undefined, undefined, self.options.version));
return;
}
// from now on we are "notification-aware" and can deliberately ignore errors for such requests
const respond = function(error, result) {
if(utils.Request.isNotification(request)) {
callback();
return;
}
const response = utils.response(error, result, request.id, self.options.version);
if(response.error) {
callback(response);
} else {
callback(null, response);
}
};
const method = self._resolveRouter(request.method, request.params);
// are we attempting to invoke a relayed method?
if(method instanceof jayson.Client) {
return method.request(request.method, request.params, request.id, function(error, response) {
if(utils.Request.isNotification(request)) {
callback();
return;
}
callback(error, response);
});
}
// does the method exist?
if(!(method instanceof jayson.Method)) {
respond(self.error(Server.errors.METHOD_NOT_FOUND));
return;
}
// execute jayson.Method instance
method.execute(self, request.params, context, function(error, result) {
if(utils.Response.isValidError(error, self.options.version)) {
respond(error);
return;
}
// got an invalid error
if(error) {
respond(self.error(Server.errors.INTERNAL_ERROR));
return;
}
respond(null, result);
});
});
};
/**
* Calls a method on the server returning a promise
* @param {Object|Array|String} request A JSON-RPC request object. Object for single request, Array for batches and String for automatic parsing (using the reviver option)
* @param {Object} [context] Optional context object passed to methods
* @return {Promise<Object>}
*/
Server.prototype.callp = function (...args) {
const self = this;
return new Promise(function (resolve, reject) {
return self.call(...args, function (err, response) {
if (err) {
reject(err);
return;
}
resolve(response);
});
});
};
/**
* Invoke the router
* @param {String} method Method to resolve
* @param {Array|Object} params Request params
* @return {Method}
*/
Server.prototype._resolveRouter = function(method, params) {
let router = this.options.router;
if(typeof router !== 'function') {
router = function(method) {
return this.getMethod(method);
};
}
const resolved = router.call(this, method, params);
// got a jayson.Method or a jayson.Client, return it
if((resolved instanceof jayson.Method) || (resolved instanceof jayson.Client)) {
return resolved;
}
// got a regular function, make it an instance of jayson.Method
if(typeof resolved === 'function') {
return new jayson.Method(resolved);
}
};
/**
* Evaluates a batch request
* @private
*/
Server.prototype._batch = function(requests, context, callback) {
const self = this;
const responses = [];
this.emit('batch', requests);
/**
* @ignore
*/
const maybeRespond = function() {
// done when we have filled up all the responses with a truthy value
const isDone = responses.every(function(response) { return response !== null; });
if(isDone) {
// filters away notifications
const filtered = responses.filter(function(res) {
return res !== true;
});
// only notifications in request means empty response
if(!filtered.length) {
callback();
return;
}
callback(null, filtered);
}
};
/**
* @ignore
*/
const wrapper = function(request, index) {
responses[index] = null;
return function() {
if(utils.Request.isValidRequest(request, self.options.version)) {
self.call(request, context, function(error, response) {
responses[index] = error || response || true;
maybeRespond();
});
} else {
const error = self.error(Server.errors.INVALID_REQUEST);
responses[index] = utils.response(error, undefined, undefined, self.options.version);
maybeRespond();
}
};
};
const stack = requests.map(function(request, index) {
// ignore possibly nested requests
if(utils.Request.isBatch(request)) {
return null;
}
return wrapper(request, index);
});
stack.forEach(function(method) {
if(typeof(method) === 'function') {
method();
}
});
};
/**
* Parse "request" if it is a string, else just invoke callback
* @ignore
*/
function maybeParse(request, options, callback) {
if(typeof(request) === 'string') {
utils.JSON.parse(request, options, callback);
} else {
callback(null, request);
}
}

75
node_modules/jayson/lib/server/middleware.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
'use strict';
const utils = require('../utils');
/**
* Creates a Connect/Express compatible middleware bound to a Server
* @class ServerMiddleware
* @param {Server} server Server instance
* @param {Object} [outerOptions] Specific options for the middleware
* @return {Function}
*/
const Middleware = function(server, outerOptions) {
return function(req, res, next) {
const options = utils.merge(server.options, outerOptions || {});
// default options.end to true
if(typeof(options.end) !== 'boolean') {
options.end = true;
}
// 405 method not allowed if not POST
if(!utils.isMethod(req, 'POST')) {
return error(405, { 'Allow': 'POST' });
}
// 415 unsupported media type if Content-Type is not correct
if(!utils.isContentType(req, 'application/json')) {
return error(415);
}
// body does not appear to be parsed, 500 server error
if(!req.body || typeof(req.body) !== 'object') {
return next(new Error('Request body must be parsed'));
}
server.call(req.body, function(error, success) {
const response = error || success;
utils.JSON.stringify(response, options, function(err, body) {
if(err) {
return next(err);
}
// empty response?
if(body) {
const headers = {
'content-length': Buffer.byteLength(body, options.encoding),
'content-type': 'application/json; charset=utf-8'
};
res.writeHead(200, headers);
res.write(body);
} else {
res.writeHead(204);
}
// if end is false, next request instead of ending it
if(options.end) {
res.end();
} else {
next();
}
});
});
// ends the request with an error code
function error(code, headers) {
res.writeHead(code, headers || {});
res.end();
}
};
};
module.exports = Middleware;

72
node_modules/jayson/lib/server/tcp.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
'use strict';
const net = require('net');
const utils = require('../utils');
/**
* Constructor for a Jayson TCP server
* @class ServerTcp
* @extends require('net').Server
* @param {Server} server Server instance
* @param {Object} [options] Options for this instance
* @return {ServerTcp}
*/
const ServerTcp = function(server, options) {
if(!(this instanceof ServerTcp)) {
return new ServerTcp(server, options);
}
this.options = utils.merge(server.options, options || {});
net.Server.call(this, getTcpListener(this, server));
};
require('util').inherits(ServerTcp, net.Server);
module.exports = ServerTcp;
/**
* Returns a TCP connection listener bound to the server in the argument.
* @param {Server} server Instance of JaysonServer
* @param {net.Server} self Instance of net.Server
* @return {Function}
* @private
* @ignore
*/
function getTcpListener(self, server) {
return function(conn) {
const options = self.options || {};
utils.parseStream(conn, options, function(err, request) {
if(err) {
return respondError(err);
}
server.call(request, function(error, success) {
const response = error || success;
if(response) {
utils.JSON.stringify(response, options, function(err, body) {
if(err) {
return respondError(err);
}
conn.write(body);
});
} else {
// no response received at all, must be a notification
}
});
});
// ends the request with an error code
function respondError(err) {
const error = server.error(-32700, null, String(err));
const response = utils.response(error, undefined, undefined, self.options.version);
utils.JSON.stringify(response, options, function(err, body) {
if(err) {
body = ''; // we tried our best.
}
conn.end(body);
});
}
};
}

72
node_modules/jayson/lib/server/tls.js generated vendored Normal file
View File

@@ -0,0 +1,72 @@
'use strict';
const tls = require('tls');
const utils = require('../utils');
/**
* Constructor for a Jayson TLS-encrypted TCP server
* @class ServerTls
* @extends require('tls').Server
* @param {Server} server Server instance
* @param {Object} [options] Options for this instance
* @return {ServerTls}
*/
const ServerTls = function(server, options) {
if(!(this instanceof ServerTls)) {
return new ServerTls(server, options);
}
this.options = utils.merge(server.options, options || {});
tls.Server.call(this, this.options, getTlsListener(this, server));
};
require('util').inherits(ServerTls, tls.Server);
module.exports = ServerTls;
/**
* Returns a TLS-encrypted TCP connection listener bound to the server in the argument.
* @param {Server} server Instance of JaysonServer
* @param {tls.Server} self Instance of tls.Server
* @return {Function}
* @private
* @ignore
*/
function getTlsListener(self, server) {
return function(conn) {
const options = self.options || {};
utils.parseStream(conn, options, function(err, request) {
if(err) {
return respondError(err);
}
server.call(request, function(error, success) {
const response = error || success;
if(response) {
utils.JSON.stringify(response, options, function(err, body) {
if(err) {
return respondError(err);
}
conn.write(body);
});
} else {
// no response received at all, must be a notification
}
});
});
// ends the request with an error code
function respondError(err) {
const error = server.error(-32700, null, String(err));
const response = utils.response(error, undefined, undefined, self.options.version);
utils.JSON.stringify(response, options, function(err, body) {
if(err) {
body = ''; // we tried our best.
}
conn.end(body);
});
}
};
}

62
node_modules/jayson/lib/server/websocket.js generated vendored Normal file
View File

@@ -0,0 +1,62 @@
'use strict';
const WebSocket = require('isomorphic-ws');
const utils = require('../utils');
/**
* Constructor for a Jayson Websocket Server
* @name ServerWebsocket
* @param {Server} server Server instance
* @param {Object} [options] Options for this instance
* @param {ws.Websocket.Server} [options.wss] When provided will not create a new ws.WebSocket.Server but use this one
* @return {ws.WebSocket.Server}
*/
const ServerWebsocket = function(server, options) {
const jaysonOptions = utils.merge(server.options, options || {});
const wss = options.wss || new WebSocket.Server(options);
wss.on('connection', onConnection);
function onConnection (ws) {
// every message received on the socket is handled as a JSON-RPC message
ws.on('message', function (buf) {
const str = Buffer.isBuffer(buf) ? buf.toString('utf8') : buf;
utils.JSON.parse(str, jaysonOptions, function(err, request) {
if (err) {
return respondError(err);
}
server.call(request, function(error, success) {
const response = error || success;
if (response) {
utils.JSON.stringify(response, jaysonOptions, function (err, str) {
if (err) {
return respondError(err);
}
ws.send(str);
});
} else {
// no response received at all, must be a notification which we do nothing about
}
});
});
});
// writes an error message to the client
function respondError (err) {
const error = server.error(-32700, null, String(err));
const response = utils.response(error, undefined, undefined, jaysonOptions.version);
utils.JSON.stringify(response, jaysonOptions, function(err, str) {
if(err) {
// not much to do here, we couldn't even respond with an error
throw err;
}
ws.send(str);
});
}
}
return wss;
};
module.exports = ServerWebsocket;

517
node_modules/jayson/lib/utils.js generated vendored Normal file
View File

@@ -0,0 +1,517 @@
'use strict';
const StreamValues = require('stream-json/streamers/StreamValues');
const Verifier = require('stream-json/utils/Verifier');
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 stream-json
* @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 verifier = new Verifier({jsonStreaming: true});
const parser = StreamValues.withParser();
parser.on('data', function(obj) {
let data = obj.value;
// apply reviver walk function to prevent stringify/parse again
if(typeof options.reviver === 'function') {
data = Utils.walk({'': data}, '', options.reviver);
}
onSuccess(data);
});
parser.on('error', onError);
verifier.on('error', onError);
stream.on('error', onError);
stream.pipe(verifier);
stream.pipe(parser);
};
/**
* 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) {
callback(err);
return;
}
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) {
callback(err);
return;
}
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) {
callback(err);
return;
}
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)
)
))
);
};