├── .gitignore
├── .idea
└── vcs.xml
├── LICENSE
├── README.md
├── example
├── index.js
└── tasks
│ └── make-soup.js
├── index.js
├── lib
├── client-ip.js
└── json-rpc2-server.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE settings
2 | .idea
3 | .idea_modules
4 | *.sublime-project
5 | *.sublime-workspace
6 | *.cache
7 | *.settings
8 | *.classpath
9 | *.project
10 |
11 | #node modules
12 | node_modules
13 |
14 | # System
15 | .DS_Store
16 | ._*
17 |
18 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 archilogic.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Instant API
2 | Like instant soup but API. JSON-RPC2 flavor with Websockets and HTTP.
3 |
4 | **💾 Install**
5 | ```
6 | npm i -s instant-api
7 | ```
8 |
9 | **📡 Expose task 'makeSoup' at port 3000**
10 | ```javascript
11 | var tasks = {
12 | 'makeSoup': require('./tasks/make-soup')
13 | }
14 | require('instant-api')(tasks ,{ port: process.env.PORT || 3000 })
15 | ```
16 |
17 | **🤖 tasks/make-soup.js**
18 | ```javascript
19 | module.exports = function (rpc) {
20 |
21 | // use parameters
22 | console.log(rpc.params)
23 |
24 | // return result
25 | rpc.sendResult('Done. Enjoy!')
26 |
27 | // return param error
28 | //rpc.sendParamsError('Missing parameter ...')
29 |
30 | // return custom error
31 | //rpc.sendError('Splash')
32 |
33 | // use in promise chains
34 | // rawQuery(query).then(rpc.sendResult).catch(rpc.sendError)
35 |
36 | }
37 | ```
38 |
39 | **📣 Call task...**
40 | ```javascript
41 | var message = {
42 | method: 'makeSoup',
43 | params: { size: 'medium' },
44 | jsonrpc: '2.0',
45 | id: Math.round(Math.random()*1e20)
46 | }
47 |
48 | // ... from a browser using HTTP
49 | fetch('http://localhost:3000', {
50 | method: 'POST', body: JSON.stringify( message )
51 | }).then(function(response){
52 | return response.json()
53 | }).then(function(body){
54 | console.log(body.result)
55 | }).catch(console.error)
56 |
57 | // ... from a browser using Websockets
58 | var ws = new WebSocket('ws://localhost:3000')
59 | ws.onopen = function () {
60 | ws.send( JSON.stringify(message) )
61 | }
62 | ws.onmessage = function (event) {
63 | console.log(JSON.parse(event.data))
64 | }
65 |
66 | // ... from another server
67 | // npm install --save request
68 | require('request').post({
69 | url: 'http://localhost:3000',
70 | json: message
71 | }, function (error, response, body) {
72 | if (!error && response.statusCode === 200) {
73 | console.log(body.result)
74 | } else {
75 | console.error(error || body)
76 | }
77 | })
78 |
79 | ```
80 |
81 | [**🚨 Cross origin settings**](example/index.js)
82 |
83 | By default, [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) is enabled but does not permit transmitting credentials.
84 | You can specify allowed CORS domains which will also be able to send credentials:
85 |
86 | ```javascript
87 | var tasks = {
88 | 'makeSoup': require('./tasks/make-soup')
89 | }
90 | require('instant-api')(tasks ,{
91 | port: process.env.PORT || 3000,
92 | corsAllowedDomains: [ 'example.org', 'test.example.org' ]
93 | })
94 | ```
95 |
96 | [**🕹 Run example**](example/index.js)
97 | ```
98 | npm run example
99 | ```
100 |
101 |
102 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // _________________ start API server _________________
4 |
5 |
6 | var tasks = {
7 | 'makeSoup': require('./tasks/make-soup')
8 | }
9 | var api = require('../index')(tasks ,{ port: process.env.PORT || 3000 })
10 |
11 |
12 | // _____________________ call task _____________________
13 |
14 |
15 | var message = {
16 | method: 'makeSoup',
17 | params: { size: 'medium' },
18 | jsonrpc: '2.0',
19 | id: Math.round(Math.random()*1e20)
20 | }
21 |
22 | require('request').post({
23 | url: 'http://localhost:3000',
24 | json: message
25 | }, function (error, response, body) {
26 | // parse message
27 | if (!error && response.statusCode === 200) {
28 | console.log(body.result)
29 | } else {
30 | console.error(error || body)
31 | }
32 | // shut down API server because we don't need further
33 | api.server.close()
34 | })
--------------------------------------------------------------------------------
/example/tasks/make-soup.js:
--------------------------------------------------------------------------------
1 | module.exports = function (rpc) {
2 |
3 | // use parameters
4 | console.log(rpc.params)
5 |
6 | // return result
7 | rpc.sendResult('Done. Enjoy!')
8 |
9 | // return param error
10 | //rpc.sendParamsError('Missing parameter ...')
11 |
12 | // return custom error
13 | //rpc.sendError('Splash')
14 |
15 | // use in promise chains
16 | // rawQuery(query).then(rpc.sendResult).catch(rpc.sendError)
17 |
18 | }
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var http = require('http')
2 | var path = require('path')
3 | var fs = require('fs')
4 | var ws = require('ws')
5 | var express = require('express')
6 | var corsMiddleware = require('cors')
7 | var morgan = require('morgan')
8 | var bodyParser = require('body-parser')
9 | var handleWebsocketUpgrade = require('express-websocket')
10 | var getClientIp = require('./lib/client-ip')
11 | var JsonRpc2Server = require('./lib/json-rpc2-server')
12 |
13 | module.exports = function instantApi (exposedMethods, options) {
14 |
15 | // options
16 | options = options || {}
17 | var port = options.port ? options.port : ( process.env.PORT || 3000 )
18 | var staticDir = options.staticDir
19 | var wsKeepAlive = options.wsKeepAlive !== undefined ? options.wsKeepAlive : true
20 | var cors = options.cors !== undefined ? options.cors : corsMiddleware({
21 | // allow any origin
22 | credentials: !!options.corsAllowedDomains,
23 | origin: function (origin, callback) {
24 | if(!origin) return callback(null, '*')
25 | var originDomain = origin.replace(/^[^:]+:\/\//, '')
26 | var corsOrigin = origin
27 | if (options.corsAllowedDomains) {
28 | corsOrigin = options.corsAllowedDomains.includes(originDomain) ? origin : null
29 | }
30 | callback(corsOrigin ? null : `Invalid origin found: ${originDomain}`, corsOrigin)
31 | }
32 | })
33 |
34 | // check params
35 | if (!exposedMethods) throw new Error('Please provide a filename, directory or array with filenames.')
36 | if (typeof exposedMethods !== 'object') throw new Error('First argument must be of type string or array.')
37 |
38 | // create RPC server
39 | var rpcServer = new JsonRpc2Server()
40 | // expose methods
41 | Object.keys(exposedMethods).forEach(function (methodName) {
42 | rpcServer.exposeModule(methodName, exposedMethods[methodName])
43 | })
44 |
45 | // create express server
46 | var app = express()
47 | // enable debugging in non production environment
48 | if (process.env.NODE_ENV !== 'production') {
49 | app.set('showStackError', true)
50 | app.use(morgan('dev'))
51 | }
52 | // get client ip
53 | app.use(getClientIp)
54 | // configure CORS
55 | app.use(cors)
56 | // handle HTTP requests
57 | app.post('/',
58 | bodyParser.text({limit: '5000kb', type: '*/*'}),
59 | function (req, res) {
60 | rpcServer.handleRequest({
61 | message: req.body,
62 | request: req,
63 | response: res,
64 | user: req.user
65 | }, function (response) {
66 | // JSON RPC methods calls by specification have responses while notifications do not
67 | // http://www.jsonrpc.org/specification#notification
68 | response ? res.status(response.error ? 400 : 200).json(response) : res.status(200).end()
69 | }
70 | )
71 | })
72 | // handle websocket requests
73 | app.get('/', function (req, res, next) {
74 | if (!res.websocket) return next()
75 |
76 | res.websocket(function (ws) {
77 | // avoid timeouts by sending ping notifications. required on certain PaaS platforms like heroku
78 | // which close connections automatically after certain time (mostly 30s)
79 | var interval
80 | if (wsKeepAlive) {
81 | interval = setInterval(function () {
82 | ws.send(JSON.stringify({jsonrpc: '2.0', method: 'keepAlive...', params: {}}))
83 | }, 20 * 1000)
84 | }
85 | ws.on('close', function () {
86 | if (interval) clearInterval(interval)
87 | })
88 | ws.on('close', function () {
89 | if (interval) clearInterval(interval)
90 | })
91 | // handle messages
92 | ws.on('message', function (message) {
93 | rpcServer.handleRequest({
94 | message: message,
95 | request: req,
96 | response: res,
97 | user: req.user
98 | }, function (response) {
99 | if (response) ws.send(JSON.stringify(response))
100 | })
101 | })
102 |
103 | })
104 | })
105 |
106 | // static pages
107 | if (staticDir) {
108 | app.use(express.static(staticDir))
109 | console.log('Exposing statics files from directory "'+staticDir+'"')
110 | }
111 |
112 | // start server
113 | var httpServer = http.createServer(app)
114 | httpServer.listen(port, function () {
115 | // init websocket server
116 | var websocketServer = new ws.Server({noServer: true})
117 | httpServer.on('upgrade', handleWebsocketUpgrade(app, websocketServer))
118 | // ready to go ;)
119 | console.log('Server listening on http://localhost:' + port)
120 | })
121 |
122 | // expose server
123 | return {
124 | server: httpServer,
125 | expressApp: app
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/lib/client-ip.js:
--------------------------------------------------------------------------------
1 | module.exports = function (req, res, next) {
2 |
3 | var ipAddress;
4 | // Amazon EC2 / Heroku workaround to get real client IP
5 | var forwardedIpsStr = req.header('x-forwarded-for');
6 | if (forwardedIpsStr) {
7 | // 'x-forwarded-for' header may return multiple IP addresses in
8 | // the format: "client IP, proxy 1 IP, proxy 2 IP" so take the
9 | // the first one
10 | var forwardedIps = forwardedIpsStr.split(',');
11 | ipAddress = forwardedIps[ 0 ];
12 | }
13 | if (!ipAddress) {
14 | // Ensure getting client IP address still works in
15 | // development environment
16 | ipAddress = req.connection.remoteAddress;
17 | }
18 |
19 | // replace ip
20 | req.clientIP = ipAddress;
21 |
22 | next()
23 | };
--------------------------------------------------------------------------------
/lib/json-rpc2-server.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // TODO: Add batch request support
4 |
5 | var util = require('util')
6 |
7 | // helpers
8 |
9 | var errorCodes = {
10 | PARSE_ERROR: -32700,
11 | INVALID_REQUEST: -32600,
12 | METHOD_NOT_FOUND: -32601,
13 | INVALID_PARAMS: -32602,
14 | INTERNAL_ERROR: -32603,
15 | APPLICATION_ERROR: -32500,
16 | SYSTEM_ERROR: -32400,
17 | TRANSPORT_ERROR: -32300
18 | }
19 |
20 | var errorMessages = {
21 | PARSE_ERROR: 'Parse Error',
22 | INVALID_REQUEST: 'Invalid Request',
23 | METHOD_NOT_FOUND: 'Method Not Found',
24 | INVALID_PARAMS: 'Invalid Parameters',
25 | INTERNAL_ERROR: 'Internal Error',
26 | APPLICATION_ERROR: 'Application Error',
27 | SYSTEM_ERROR: 'System Error',
28 | TRANSPORT_ERROR: 'Transport Error'
29 | }
30 |
31 | var errorObjects = {
32 | PARSE_ERROR: {code: errorCodes.PARSE_ERROR, message: errorMessages.PARSE_ERROR},
33 | INVALID_REQUEST: {code: errorCodes.INVALID_REQUEST, message: errorMessages.INVALID_REQUEST},
34 | METHOD_NOT_FOUND: {code: errorCodes.METHOD_NOT_FOUND, message: errorMessages.METHOD_NOT_FOUND},
35 | INVALID_PARAMS: {code: errorCodes.INVALID_PARAMS, message: errorMessages.INVALID_PARAMS},
36 | INTERNAL_ERROR: {code: errorCodes.INTERNAL_ERROR, message: errorMessages.INTERNAL_ERROR},
37 | APPLICATION_ERROR: {code: errorCodes.APPLICATION_ERROR, message: errorMessages.APPLICATION_ERROR},
38 | SYSTEM_ERROR: {code: errorCodes.SYSTEM_ERROR, message: errorMessages.SYSTEM_ERROR},
39 | TRANSPORT_ERROR: {code: errorCodes.TRANSPORT_ERROR, message: errorMessages.TRANSPORT_ERROR}
40 | }
41 |
42 | function logNotificationResponseWarning (message, requestMessage) {
43 | console.warn(
44 | 'JSON-RPC2 request for method ' + requestMessage.methodName + ' was a notification call (no ID present) and should not send any response.\n'
45 | + 'Check JSON-RPC2 specification for details: "http://www.jsonrpc.org/specification#notification"\n'
46 | + 'Response has _not_ been sent:\n'
47 | + util.inspect(message) + '\n'
48 | + 'Original request:\n'
49 | + util.inspect(requestMessage)
50 | )
51 | }
52 |
53 | // class
54 |
55 | function JsonRpc2Server () {
56 |
57 | this._exposedMethods = {}
58 |
59 | }
60 |
61 | JsonRpc2Server.prototype = {
62 |
63 | exposeMethod: function (methodName, method) {
64 |
65 | console.log('Exposing API method "'+methodName+'"')
66 | this._exposedMethods[methodName.toLowerCase()] = method
67 |
68 | },
69 |
70 | exposeModule: function (methodPrefix, module) {
71 |
72 | if (typeof module === 'function') {
73 |
74 | this.exposeMethod(methodPrefix, module)
75 |
76 | } else if (typeof module === 'object') {
77 |
78 | for (var methodName in module) {
79 | if (module.hasOwnProperty(methodName) && methodName[0] !== '_') {
80 | this.exposeModule(methodPrefix + '.' + methodName, module[methodName])
81 | }
82 | }
83 | }
84 |
85 | },
86 |
87 | handleRequest: function (options, callback) {
88 |
89 | // API
90 | var requestMessage = options.message
91 | // to be forwarded to method
92 | var request = options.request
93 | var response = options.response
94 | var user = options.user
95 |
96 | // check if callback exists
97 | if (!callback) {
98 | console.error('callback param missing.')
99 | return
100 | }
101 |
102 | // parse message
103 | if (typeof requestMessage === 'string') {
104 | try {
105 | requestMessage = JSON.parse(requestMessage)
106 | } catch (e) {
107 | // non-valid JSON
108 | callback({
109 | jsonrpc: '2.0',
110 | error: {
111 | code: errorCodes.PARSE_ERROR,
112 | message: errorMessages.PARSE_ERROR + ': Non-valid JSON.'
113 | },
114 | id: null
115 | })
116 | return
117 | }
118 | }
119 |
120 | // validate message
121 | if (
122 | typeof requestMessage !== 'object'
123 | || requestMessage === null
124 | || requestMessage.jsonrpc !== '2.0'
125 | ) {
126 | // non-valid message
127 | console.error('Invalid JSON-RPC request: ', requestMessage)
128 | callback({
129 | jsonrpc: '2.0',
130 | error: {
131 | code: errorCodes.PARSE_ERROR,
132 | message: errorMessages.PARSE_ERROR + ': Non-valid JSON-RPC2 call.'
133 | },
134 | id: null
135 | })
136 | return
137 | }
138 |
139 | // internals
140 | var method
141 | var methodName = requestMessage.method
142 | var params = requestMessage.params || {}
143 | var id = requestMessage.id
144 | var isMethodCall = (id === null || typeof id === 'string' || typeof id === 'number') // method calls have valid id, notifications not (JSON-RPC2 specs)
145 | var responseHasBeenSent = false
146 |
147 | // validate method name
148 | if (typeof methodName !== 'string') {
149 | if (isMethodCall) {
150 | // send errors responses to method calls only (JSON-RPC2 specs http://www.jsonrpc.org/specification#notification)
151 | callback({
152 | jsonrpc: '2.0',
153 | error: {
154 | code: errorCodes.INVALID_REQUEST,
155 | message: 'Invalid request: Method name must be a string.'
156 | },
157 | id: id
158 | })
159 | } else {
160 | // at least log something to the console
161 | console.warn(
162 | 'JSON-RPC2 error: invalid request (method name must be a string).'
163 | + '\nOriginal request:\n'
164 | + util.inspect(requestMessage)
165 | )
166 | callback()
167 | }
168 | return
169 | }
170 |
171 | // check if method exists
172 | if (this._exposedMethods[methodName.toLowerCase()]) {
173 | method = this._exposedMethods[methodName.toLowerCase()]
174 | } else {
175 | if (isMethodCall) {
176 | // send errors responses to method calls only (JSON-RPC2 specs http://www.jsonrpc.org/specification#notification)
177 | callback({
178 | jsonrpc: '2.0',
179 | error: {
180 | code: errorCodes.METHOD_NOT_FOUND,
181 | message: 'Method "'+methodName+'" not found. Available methods are: '+Object.keys(this._exposedMethods).join(', ')
182 | },
183 | id: id
184 | })
185 | } else {
186 | // at least log something to the console
187 | console.warn(
188 | 'JSON-RPC2 method "' + methodName + '" not found.'
189 | + '\nOriginal request:\n'
190 | + util.inspect(requestMessage)
191 | )
192 | callback()
193 | }
194 | return
195 | }
196 |
197 | // create send result handler
198 | var sendResult = function (result) {
199 |
200 | // don't send a second response
201 | if (responseHasBeenSent) {
202 | console.warn('JSON-RPC2 response has already been sent.')
203 | return
204 | }
205 | // notifications should not send error responses (JSON-RPC2 specs)
206 | if (!isMethodCall) {
207 | logNotificationResponseWarning(result, requestMessage)
208 | callback()
209 | return
210 | }
211 |
212 | // result should not be undefined
213 | if (result === undefined) {
214 | console.warn('JSON-RPC2 response from method ' + methodName + ' should return a result. (JSON-RPC2 spec)')
215 | result = ''
216 | }
217 |
218 | var rpcMessage = {
219 | jsonrpc: '2.0',
220 | result: result,
221 | id: id
222 | }
223 |
224 | callback(rpcMessage)
225 | responseHasBeenSent = true
226 |
227 | }
228 |
229 | // create send message method
230 | var sendMessage = function (method, result) {
231 |
232 | // result should not be undefined
233 | if (result === undefined) {
234 | console.warn('JSON-RPC2 response from method ' + methodName + ' should return a result. (JSON-RPC2 spec)')
235 | result = ''
236 | }
237 |
238 | var rpcMessage = {
239 | jsonrpc: '2.0',
240 | result: result,
241 | id: id
242 | }
243 |
244 | callback(rpcMessage)
245 | responseHasBeenSent = true
246 |
247 | }
248 |
249 | // create send error handler
250 | var sendError = function (error, type) {
251 |
252 | // don't send a second response
253 | if (responseHasBeenSent) {
254 | console.warn('JSON-RPC2 response has already been sent.')
255 | return
256 | }
257 | // notifications should not send error responses (JSON-RPC2 specs)
258 | if (!isMethodCall) {
259 | var message = (error && error.toString) ? error.toString() : undefined
260 | logNotificationResponseWarning(message, requestMessage)
261 | callback()
262 | return
263 | }
264 |
265 | // internals
266 | var errorObject
267 |
268 | if (error instanceof Error) {
269 | // serious application error
270 | console.warn(
271 | 'Error in JSON-RPC2 method ' + methodName + ': ' + error.toString() + '\n'
272 | + error.stack
273 | )
274 | // not sending detailed error info to not expose details about server code
275 | errorObject = {
276 | code: errorCodes['APPLICATION_ERROR'],
277 | message: errorMessages['APPLICATION_ERROR'] + ' (Check server logs for details)'
278 | }
279 |
280 | } else if (typeof error === 'string') {
281 | // error message
282 | errorObject = {
283 | code: errorCodes[type] || errorCodes['INVALID_REQUEST'],
284 | message: error
285 | }
286 |
287 | } else if (typeof error === 'object') {
288 | if (error.message) {
289 | // error object
290 | errorObject = error
291 | if (error.code === undefined) {
292 | error.code = errorCodes['INVALID_REQUEST']
293 | }
294 |
295 | } else {
296 | // error data
297 | errorObject = {
298 | code: errorCodes[type] || errorCodes['INVALID_REQUEST'],
299 | message: errorMessages[type] || errorMessages['INVALID_REQUEST'],
300 | data: error
301 | }
302 | }
303 |
304 | } else {
305 | if (error !== undefined) {
306 | console.warn('Error parameter must be a string (error message) or object (error data)')
307 | }
308 | // fallback
309 | errorObject = errorObjects['INVALID_REQUEST']
310 |
311 | }
312 |
313 | var rpcMessage = {
314 | jsonrpc: '2.0',
315 | error: errorObject,
316 | id: id
317 | }
318 |
319 | callback(rpcMessage)
320 | responseHasBeenSent = true
321 |
322 | }
323 |
324 | // create send error handler for invalid params
325 | var sendParamsError = function (message) {
326 | sendError(errorMessages['INVALID_PARAMS'] + ': ' + message, 'INVALID_PARAMS')
327 | }
328 |
329 | // create handler for ending a notification
330 | var end = function () {
331 | responseHasBeenSent = true
332 | callback()
333 | }
334 |
335 | // create rpc object for methods
336 | var rpc = {
337 | // properties
338 | methodName: methodName,
339 | params: params,
340 | id: id,
341 | requestMessage: requestMessage,
342 | // methods
343 | send: sendResult,
344 | sendResult: sendResult,
345 | sendError: sendError,
346 | sendParamsError: sendParamsError,
347 | end: end
348 | }
349 |
350 | // run method
351 | try {
352 | method(rpc, user, request, response)
353 | } catch (error) {
354 | sendError(error)
355 | }
356 |
357 | }
358 |
359 | }
360 |
361 | // API
362 | module.exports = JsonRpc2Server
363 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "instant-api",
3 | "version": "1.0.5",
4 | "description": "Like instant soup but API. JSON-RPC2 flavor with Websockets and HTTP.",
5 | "homepage": "https://github.com/archilogic-com/instant-api",
6 | "repository": "archilogic-com/instant-api",
7 | "license": "MIT",
8 | "author": {
9 | "name": "archilogic",
10 | "email": "dev.rocks@archilogic.com",
11 | "url": "https://archilogic.com"
12 | },
13 | "main": "index.js",
14 | "scripts": {
15 | "example": "node example/index.js"
16 | },
17 | "dependencies": {
18 | "body-parser": "^1.17.2",
19 | "cors": "^2.8.3",
20 | "express": "^4.15.3",
21 | "express-websocket": "0.0.1",
22 | "morgan": "^1.8.2",
23 | "ws": "3.3.2"
24 | },
25 | "devDependencies": {
26 | "request": "^2.81.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------