├── .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 | Remix on Glitch 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 | --------------------------------------------------------------------------------