1 | 'use strict';
|
2 |
|
3 | const compact = require('lodash/compact');
|
4 | const extend = require('lodash/extend');
|
5 | const isFunction = require('lodash/isFunction');
|
6 | const once = require('lodash/once');
|
7 | const partial = require('lodash/partial');
|
8 | const JSONStream = require('JSONStream');
|
9 | const JSONstringify = require('json-stringify-safe');
|
10 | const uuid = require('uuid/v4');
|
11 |
|
12 | const generateRequest = require('./generateRequest');
|
13 |
|
14 |
|
15 | const Utils = module.exports;
|
16 |
|
17 |
|
18 | const utils = Utils;
|
19 |
|
20 | Utils.request = generateRequest;
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | Utils.response = function(error, result, id, version) {
|
31 | id = typeof(id) === 'undefined' || id === null ? null : id;
|
32 | error = typeof(error) === 'undefined' || error === null ? null : error;
|
33 | version = typeof(version) === 'undefined' || version === null ? 2 : version;
|
34 | const response = (version === 2) ? { jsonrpc: "2.0", id: id } : { id: id };
|
35 |
|
36 |
|
37 | if(version === 1) {
|
38 | response.error = error;
|
39 | }
|
40 |
|
41 |
|
42 | if(error) {
|
43 | response.error = error;
|
44 | } else {
|
45 | response.result = result;
|
46 | }
|
47 | return response;
|
48 | };
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | Utils.generateId = function() {
|
55 | return uuid();
|
56 | };
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | Utils.merge = function() {
|
65 | return extend.apply(null, arguments);
|
66 | };
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | Utils.parseStream = function(stream, options, onRequest) {
|
75 |
|
76 | const onError = once(onRequest);
|
77 | const onSuccess = partial(onRequest, null);
|
78 |
|
79 | const result = JSONStream.parse();
|
80 |
|
81 | result.on('data', function(data) {
|
82 |
|
83 |
|
84 | if(isFunction(options.reviver)) {
|
85 | data = Utils.walk({'': data}, '', options.reviver);
|
86 | }
|
87 |
|
88 | onSuccess(data);
|
89 | });
|
90 |
|
91 | result.on('error', onError);
|
92 | stream.on('error', onError);
|
93 |
|
94 | stream.pipe(result);
|
95 |
|
96 | };
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | Utils.parseBody = function(stream, options, callback) {
|
105 |
|
106 | callback = once(callback);
|
107 | let data = '';
|
108 |
|
109 | stream.setEncoding('utf8');
|
110 |
|
111 | stream.on('data', function(str) {
|
112 | data += str;
|
113 | });
|
114 |
|
115 | stream.on('error', function(err) {
|
116 | callback(err);
|
117 | });
|
118 |
|
119 | stream.on('end', function() {
|
120 | utils.JSON.parse(data, options, function(err, request) {
|
121 | if(err) {
|
122 | return callback(err);
|
123 | }
|
124 | callback(null, request);
|
125 | });
|
126 | });
|
127 |
|
128 | };
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | Utils.getHttpListener = function(self, server) {
|
138 | return function(req, res) {
|
139 | const options = self.options || {};
|
140 |
|
141 | server.emit('http request', req);
|
142 |
|
143 |
|
144 | if(!Utils.isMethod(req, 'POST')) {
|
145 | return respond('Method Not Allowed', 405, {'allow': 'POST'});
|
146 | }
|
147 |
|
148 |
|
149 | if(!Utils.isContentType(req, 'application/json')) {
|
150 | return respond('Unsupported Media Type', 415);
|
151 | }
|
152 |
|
153 | Utils.parseBody(req, options, function(err, request) {
|
154 | if(err) {
|
155 | return respond(err, 400);
|
156 | }
|
157 |
|
158 | server.call(request, function(error, success) {
|
159 | const response = error || success;
|
160 | if(!response) {
|
161 |
|
162 | return respond('', 204);
|
163 | }
|
164 |
|
165 | utils.JSON.stringify(response, options, function(err, body) {
|
166 | if(err) {
|
167 | return respond(err, 500);
|
168 | }
|
169 |
|
170 | const headers = {
|
171 | 'content-length': Buffer.byteLength(body, options.encoding),
|
172 | 'content-type': 'application/json; charset=utf-8'
|
173 | };
|
174 |
|
175 | respond(body, 200, headers);
|
176 | });
|
177 |
|
178 | });
|
179 |
|
180 | });
|
181 |
|
182 | function respond(response, code, headers) {
|
183 | const body = response instanceof Error ? response.toString() : response;
|
184 | server.emit('http response', res, req);
|
185 | res.writeHead(code, headers || {});
|
186 | res.end(body);
|
187 | }
|
188 |
|
189 | };
|
190 | };
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 |
|
199 | Utils.isContentType = function(request, type) {
|
200 | request = request || {headers: {}};
|
201 | const contentType = request.headers['content-type'] || '';
|
202 | return RegExp(type, 'i').test(contentType);
|
203 | };
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 | Utils.isMethod = function(request, method) {
|
213 | method = (method || '').toUpperCase();
|
214 | return (request.method || '') === method;
|
215 | };
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 | Utils.walk = function(holder, key, fn) {
|
225 | let k, v, value = holder[key];
|
226 | if (value && typeof value === 'object') {
|
227 | for (k in value) {
|
228 | if (Object.prototype.hasOwnProperty.call(value, k)) {
|
229 | v = Utils.walk(value, k, fn);
|
230 | if (v !== undefined) {
|
231 | value[k] = v;
|
232 | } else {
|
233 | delete value[k];
|
234 | }
|
235 | }
|
236 | }
|
237 | }
|
238 | return fn.call(holder, key, value);
|
239 | };
|
240 |
|
241 |
|
242 | Utils.JSON = {};
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | Utils.JSON.parse = function(str, options, callback) {
|
251 | let reviver = null;
|
252 | let obj = null;
|
253 | options = options || {};
|
254 |
|
255 | if(isFunction(options.reviver)) {
|
256 | reviver = options.reviver;
|
257 | }
|
258 |
|
259 | try {
|
260 | obj = JSON.parse.apply(JSON, compact([str, reviver]));
|
261 | } catch(err) {
|
262 | return callback(err);
|
263 | }
|
264 |
|
265 | callback(null, obj);
|
266 | };
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | Utils.JSON.stringify = function(obj, options, callback) {
|
275 | let replacer = null;
|
276 | let str = null;
|
277 | options = options || {};
|
278 |
|
279 | if(isFunction(options.replacer)) {
|
280 | replacer = options.replacer;
|
281 | }
|
282 |
|
283 | try {
|
284 | str = JSONstringify.apply(JSON, compact([obj, replacer]));
|
285 | } catch(err) {
|
286 | return callback(err);
|
287 | }
|
288 |
|
289 | callback(null, str);
|
290 | };
|
291 |
|
292 |
|
293 | Utils.Request = {};
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 | Utils.Request.isBatch = function(request) {
|
301 | return Array.isArray(request);
|
302 | };
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | Utils.Request.isNotification = function(request) {
|
310 | return Boolean(
|
311 | request
|
312 | && !Utils.Request.isBatch(request)
|
313 | && (typeof(request.id) === 'undefined'
|
314 | || request.id === null)
|
315 | );
|
316 | };
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 | Utils.Request.isValidVersionTwoRequest = function(request) {
|
324 | return Boolean(
|
325 | request
|
326 | && typeof(request) === 'object'
|
327 | && request.jsonrpc === '2.0'
|
328 | && typeof(request.method) === 'string'
|
329 | && (
|
330 | typeof(request.params) === 'undefined'
|
331 | || Array.isArray(request.params)
|
332 | || (request.params && typeof(request.params) === 'object')
|
333 | )
|
334 | && (
|
335 | typeof(request.id) === 'undefined'
|
336 | || typeof(request.id) === 'string'
|
337 | || typeof(request.id) === 'number'
|
338 | || request.id === null
|
339 | )
|
340 | );
|
341 | };
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | Utils.Request.isValidVersionOneRequest = function(request) {
|
349 | return Boolean(
|
350 | request
|
351 | && typeof(request) === 'object'
|
352 | && typeof(request.method) === 'string'
|
353 | && Array.isArray(request.params)
|
354 | && typeof(request.id) !== 'undefined'
|
355 | );
|
356 | };
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 | Utils.Request.isValidRequest = function(request, version) {
|
365 | version = version === 1 ? 1 : 2;
|
366 | return Boolean(
|
367 | request
|
368 | && (
|
369 | (version === 1 && Utils.Request.isValidVersionOneRequest(request)) ||
|
370 | (version === 2 && Utils.Request.isValidVersionTwoRequest(request))
|
371 | )
|
372 | );
|
373 | };
|
374 |
|
375 |
|
376 | Utils.Response = {};
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 | Utils.Response.isValidError = function(error, version) {
|
385 | version = version === 1 ? 1 : 2;
|
386 | return Boolean(
|
387 | version === 1 && (
|
388 | typeof(error) !== 'undefined'
|
389 | && error !== null
|
390 | )
|
391 | || version === 2 && (
|
392 | error
|
393 | && typeof(error.code) === 'number'
|
394 | && parseInt(error.code, 10) === error.code
|
395 | && typeof(error.message) === 'string'
|
396 | )
|
397 | );
|
398 | };
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 | Utils.Response.isValidResponse = function(response, version) {
|
407 | version = version === 1 ? 1 : 2;
|
408 | return Boolean(
|
409 | response !== null
|
410 | && typeof response === 'object'
|
411 | && (version === 2 && (
|
412 |
|
413 | response.jsonrpc === '2.0'
|
414 | && (
|
415 |
|
416 | response.id === null
|
417 | || typeof response.id === 'string'
|
418 | || typeof response.id === 'number'
|
419 | )
|
420 | && (
|
421 |
|
422 | (typeof response.result === 'undefined' && typeof response.error !== 'undefined')
|
423 | || (typeof response.result !== 'undefined' && typeof response.error === 'undefined')
|
424 | )
|
425 | && (
|
426 |
|
427 | (typeof response.result !== 'undefined')
|
428 |
|
429 | || (
|
430 | response.error !== null
|
431 | && typeof response.error === 'object'
|
432 | && typeof response.error.code === 'number'
|
433 |
|
434 | && ((response.error.code | 0) === response.error.code)
|
435 | && typeof response.error.message === 'string'
|
436 | )
|
437 | )
|
438 | )
|
439 | || version === 1 && (
|
440 | typeof response.id !== 'undefined'
|
441 | && (
|
442 |
|
443 | (typeof response.result !== 'undefined' && response.error === null)
|
444 | || (typeof response.error !== 'undefined' && response.result === null)
|
445 | )
|
446 | ))
|
447 | );
|
448 | };
|