├── .gitignore ├── paddle ├── paddle.conf.example ├── sharedoff.json ├── package.json ├── paddle └── package-lock.json ├── package.json ├── README.md ├── addmqttuser └── canoed /.gitignore: -------------------------------------------------------------------------------- 1 | canoed.conf 2 | node_modules 3 | -------------------------------------------------------------------------------- /paddle/paddle.conf.example: -------------------------------------------------------------------------------- 1 | { 2 | "logging": { 3 | "level": "debug" 4 | }, 5 | "debug": true 6 | } 7 | -------------------------------------------------------------------------------- /paddle/sharedoff.json: -------------------------------------------------------------------------------- 1 | { 2 | "servermessage": null, 3 | "stateblocks": { 4 | "enable": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /paddle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paddle", 3 | "description": "Canoe backend CLI", 4 | "author": "Göran Krampe ", 5 | "dependencies": { 6 | "body-parser": "^1.18.2", 7 | "express": "^4.16.3", 8 | "fs": "0.0.1-security", 9 | "mqtt": "^2.17.0", 10 | "mqtt-regex": "^1.0.5", 11 | "neodoc": "^2.0.0", 12 | "node-schedule": "^1.3.0", 13 | "pg": "^7.4.1", 14 | "redis": "^2.8.0", 15 | "request": "^2.85.0", 16 | "util": "^0.10.3", 17 | "winston": "^2.4.2", 18 | "xhr2": "^0.1.4" 19 | }, 20 | "main": "paddle", 21 | "version": "0.0.1" 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "canoed2", 3 | "description": "Canoe backend server", 4 | "author": "Göran Krampe ", 5 | "dependencies": { 6 | "big-integer": "^1.6.32", 7 | "body-parser": "^1.18.2", 8 | "compare-versions": "^3.3.0", 9 | "express": "^4.16.3", 10 | "fs": "0.0.1-security", 11 | "mqtt": "^2.17.0", 12 | "mqtt-regex": "^1.0.5", 13 | "neodoc": "^2.0.0", 14 | "node-schedule": "^1.3.0", 15 | "pg": "^7.4.1", 16 | "redis": "^2.8.0", 17 | "request": "^2.85.0", 18 | "util": "^0.10.3", 19 | "winston": "^2.4.2", 20 | "xhr2": "^0.1.4" 21 | }, 22 | "main": "canoed2.js", 23 | "version": "0.0.1" 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOT MAINTAINED ANYMORE 2 | Canoe is not maintained, the Canoe website with the binary downloads **is not live anymore** and this wallet backend is not running. The domain "getcanoe.io" will not be renewed at end of 2021. 3 | This means Canoe **can no longer be used** but if you used Canoe earlier you can easily import your seed to another wallet, no funds are lost. Canoe is fully Open Source and anyone could revive it, rebrand and start their own server. If anyone is interested in doing that, feel free to contact me. 4 | 5 | regards, Göran and the rest of the developers of Canoe 6 | _____ 7 | # Canoed 8 | Canoed is a backend for the Canoe Nano wallet. It uses a rai_node and creates a middle layer for mediating RPC calls, holding external state, forwarding blocks over MQTT and various other things Canoe needs to be done on the server. It uses a runing rai_node, Redis, PostgreSQL and VerneMQ. 9 | 10 | ## Nodejs 11 | Canoed was first written in Nim, a modern high performance language that produces small and fast binaries by compiling via C. I love Nim, but we switched to Nodejs because there is no properly working MQTT library in Nim. The code style is fairly plain vanilla. 12 | 13 | ## Running Canoed 14 | It's the standard: 15 | 16 | ``` 17 | npm install 18 | ./canoed 19 | ``` 20 | 21 | See source code to find the default `canoed.conf` JSON configuration. 22 | 23 | ### Adding a systemd service 24 | This presumes you already have a rai_node.service defined according to [the wiki page](https://github.com/clemahieu/raiblocks/wiki/Running-rai_node-as-a-service). 25 | 26 | Create `/etc/systemd/system/canoed.service`: 27 | 28 | [Unit] 29 | Description=Canoed 30 | Documentation=https://github.com/gokr/canoed 31 | After=network.target httpd.service rai_node.service 32 | 33 | [Service] 34 | User=canoed 35 | WorkingDirectory=/home/canoed 36 | ExecStart=/home/canoed/canoed/canoed 37 | KillMode=mixed 38 | KillSignal=SIGTERM 39 | Restart=always 40 | RestartSec=2s 41 | NoNewPrivileges=yes 42 | StandardOutput=syslog+console 43 | StandardError=syslog+console 44 | 45 | [Install] 46 | WantedBy=multi-user.target 47 | 48 | 49 | Then enable it: 50 | 51 | systemctl daemon-reload 52 | systemctl enable canoed 53 | systemctl start canoed 54 | 55 | -------------------------------------------------------------------------------- /addmqttuser: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs') 4 | const extend = require('extend') // To merge objects 5 | const winston = require('winston') // Solid logging lib 6 | const { Pool } = require('pg') // For proper database stuff 7 | const neodoc = require('neodoc') // For nice command line opts 8 | 9 | // Parse out command line 10 | const args = neodoc.run(` 11 | Usage: 12 | addmqttuser [--config=] 13 | addmqttuser -h | --help | --version 14 | `, { optionsFirst: true, smartOptions: true }) 15 | 16 | // Default config that is extended (merged) with CONFIG_FILE 17 | var CONFIG_FILE = 'canoed.conf' 18 | if (args['--config']) { 19 | CONFIG_FILE = args['--config'] 20 | } 21 | 22 | var config = { 23 | logging: { 24 | level: 'info' 25 | }, 26 | debug: false, 27 | postgres: { 28 | user: 'canoe', 29 | host: 'localhost', 30 | database: 'canoe', 31 | password: 'secretpassword', 32 | port: 5432 33 | } 34 | } 35 | 36 | var username = args[''] 37 | var password = args[''] 38 | var clientId = args[''] 39 | 40 | // console.log( "Username: " + username) 41 | // process.exit(0) 42 | // Postgres pool client 43 | var pool = null 44 | 45 | // Read configuration 46 | function configure () { 47 | // Read config file if exists 48 | if (fs.existsSync(CONFIG_FILE)) { 49 | try { 50 | var fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) 51 | extend(true, config, fileConfig) 52 | } catch (e) { 53 | winston.error('Failed to parse config file: ' + CONFIG_FILE + e.message) 54 | process.exit(1) 55 | } 56 | } 57 | winston.level = config.logging.level 58 | } 59 | 60 | // Connect Postgres 61 | function connectPostgres () { 62 | pool = new Pool(config.postgres) 63 | winston.info('Connected to Postgres') 64 | } 65 | 66 | // Create an account given a walletId, token and a tokenPass 67 | async function createAccount (spec) { 68 | var values = [spec.wallet, spec.token, spec.tokenPass, 69 | spec.pubacl || config.mqtt.pubacl, 70 | spec.subacl || config.mqtt.subacl] 71 | var sql = `WITH x AS ( 72 | SELECT 73 | ''::text AS mountpoint, 74 | $1::text AS client_id, 75 | $2::text AS username, 76 | $3::text AS password, 77 | gen_salt('bf')::text AS salt, 78 | $4::json AS publish_acl, 79 | $5::json AS subscribe_acl 80 | ) 81 | INSERT INTO vmq_auth_acl (mountpoint, client_id, username, password, publish_acl, subscribe_acl) 82 | SELECT 83 | x.mountpoint, 84 | x.client_id, 85 | x.username, 86 | crypt(x.password, x.salt), 87 | publish_acl, 88 | subscribe_acl 89 | FROM x;` 90 | const client = await pool.connect() 91 | try { 92 | await client.query(sql, values) 93 | } catch (e) { 94 | winston.error('Error creating account: ' + e) 95 | } finally { 96 | client.release() 97 | } 98 | } 99 | 100 | // Let's start doing something 101 | configure() 102 | connectPostgres() 103 | 104 | createAccount({ 105 | wallet: clientId, // This maps to clientId 106 | token: username, 107 | tokenPass: password, 108 | pubacl: '[{"pattern":"#"}]', 109 | subacl: '[{"pattern":"#"}]' 110 | }).then( 111 | text => { 112 | console.log('Added ' + username) 113 | }, 114 | err => { 115 | console.log('Error: ' + err) 116 | } 117 | ) 118 | -------------------------------------------------------------------------------- /paddle/paddle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * This is a CLI tool for Canoed servers. 4 | */ 5 | 6 | const fs = require('fs') 7 | const mqtt = require('mqtt') 8 | // const mqttRegex = require('mqtt-regex') // Used to parse out parameters from wildcard MQTT topics 9 | // const request = require('request') 10 | const extend = require('extend') // To merge objects 11 | const winston = require('winston') // Solid logging lib 12 | const redis = require('redis') // For maintaining session state 13 | const { Pool } = require('pg') // For proper database stuff 14 | const neodoc = require('neodoc') // For nice command line opts 15 | const {promisify} = require('util') // Promises for redisClient 16 | 17 | // Parse out command line 18 | const args = neodoc.run(` 19 | Usage: 20 | paddle [--config=] 21 | paddle log [] 22 | paddle show 23 | paddle pubshared 24 | paddle pub 25 | paddle set 26 | paddle get 27 | paddle -h | --help | --version 28 | `, { optionsFirst: true, smartOptions: true }) 29 | 30 | // Default config that is extended (merged) with CONFIG_FILE 31 | var CONFIG_FILE = 'paddle.conf' 32 | if (args['--config']) { 33 | CONFIG_FILE = args['--config'] 34 | } 35 | 36 | var config = { 37 | logging: { 38 | level: 'info' 39 | }, 40 | debug: false, 41 | server: { 42 | port: 8080 43 | }, 44 | rainode: { 45 | host: '[::1]', 46 | port: 7076 47 | }, 48 | postgres: { 49 | user: 'canoe', 50 | host: 'localhost', 51 | database: 'canoe', 52 | password: 'secretpassword', 53 | port: 5432 54 | }, 55 | redis: { 56 | host: 'localhost', 57 | port: 6379 58 | }, 59 | mqtt: { 60 | url: 'tcp://localhost', 61 | options: { 62 | clientId: 'canoed', 63 | username: 'canoed', 64 | password: '1234' 65 | }, 66 | subacl: '[{"pattern": "#"}]', 67 | pubacl: '[{"pattern": "#"}]', 68 | block: { 69 | opts: { 70 | qos: 2, 71 | retain: false 72 | } 73 | }, 74 | sharedconfig: { 75 | opts: { 76 | qos: 2, 77 | retain: true 78 | } 79 | } 80 | } 81 | } 82 | 83 | // Published as retained message and shared by all wallets connecting to canoed 84 | var sharedConfig 85 | 86 | // MQTT Client 87 | var mqttClient = null 88 | 89 | // Postgres pool client 90 | var pool = null 91 | 92 | // Redis Client 93 | var redisClient = null 94 | var asyncSet 95 | var asyncGet 96 | 97 | // Are we logging all MQTT messages 98 | var logging = false 99 | 100 | // Read configuration 101 | function configure () { 102 | // Read config file if exists 103 | if (fs.existsSync(CONFIG_FILE)) { 104 | try { 105 | var fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) 106 | extend(true, config, fileConfig) 107 | } catch (e) { 108 | winston.error('Failed to parse config file: ' + CONFIG_FILE + e.message) 109 | process.exit(1) 110 | } 111 | } 112 | winston.level = config.logging.level 113 | } 114 | 115 | // Connect Postgres 116 | function connectPostgres () { 117 | pool = new Pool(config.postgres) 118 | winston.info('Connected to Postgres') 119 | } 120 | 121 | // Initialize database for VerneMQ auth 122 | // We also need: CREATE EXTENSION pgcrypto; 123 | // But that needs superuser privilege to do. 124 | async function initializeDb () { 125 | var sql = ` 126 | CREATE TABLE IF NOT EXISTS block_timestamp ( 127 | hash varchar(64) NOT NULL, 128 | timestamp timestamp default current_timestamp, 129 | PRIMARY KEY (hash) 130 | ); 131 | CREATE TABLE IF NOT EXISTS vmq_auth_acl 132 | ( 133 | mountpoint character varying(10) NOT NULL, 134 | client_id character varying(128) NOT NULL, 135 | username character varying(128) NOT NULL, 136 | password character varying(128), 137 | publish_acl json, 138 | subscribe_acl json, 139 | CONSTRAINT vmq_auth_acl_primary_key PRIMARY KEY (mountpoint, client_id, username) 140 | );` 141 | const client = await pool.connect() 142 | try { 143 | await client.query(sql) 144 | } catch (e) { 145 | winston.error('Error initializing db: ' + e) 146 | } finally { 147 | client.release() 148 | } 149 | await createAccount({ 150 | wallet: config.mqtt.options.username, // This maps to clientId 151 | token: config.mqtt.options.username, 152 | tokenPass: config.mqtt.options.password, 153 | pubacl: '[{"pattern":"#"}]', 154 | subacl: '[{"pattern":"#"}]' 155 | }) 156 | } 157 | 158 | // Connect Redis 159 | function connectRedis () { 160 | winston.info('Connecting to Redis ...') 161 | redisClient = redis.createClient(config.redis.port, config.redis.host, {no_ready_check: true}) 162 | asyncSet = promisify(redisClient.set).bind(redisClient) 163 | asyncGet = promisify(redisClient.get).bind(redisClient) 164 | redisClient.on('connect', function () { 165 | winston.info('Connected to Redis') 166 | }) 167 | } 168 | 169 | function handleLogging (topic, message) { 170 | winston.debug(topic + ': ' + JSON.stringify(JSON.parse(message))) 171 | } 172 | 173 | // Connect to MQTT 174 | function connectMQTT (cb) { 175 | mqttClient = mqtt.connect(config.mqtt.url, config.mqtt.options) 176 | mqttClient.on('connect', function () { 177 | winston.info('Connected to MQTT server') 178 | cb() 179 | }) 180 | 181 | // Where all subscribed messages come in 182 | mqttClient.on('message', function (topic, message) { 183 | switch (topic) { 184 | case 'sharedconfig': 185 | return handleSharedConfig(message) 186 | } 187 | if (logging) { 188 | return handleLogging(topic, message) 189 | } 190 | winston.error('No handler for topic %s', topic) 191 | }) 192 | } 193 | 194 | function publishSharedConfig (payload, cb) { 195 | winston.debug('Publish shared config: ' + JSON.stringify(payload)) 196 | mqttClient.publish('sharedconfig', JSON.stringify(payload), config.mqtt.sharedconfig.opts, cb) 197 | } 198 | 199 | // Subscribe to control 200 | function subscribe (topic) { 201 | mqttClient.subscribe(topic) 202 | winston.debug('Subscribed to ' + topic) 203 | // /+/ for wildcards 204 | } 205 | 206 | function handleSharedConfig (message) { 207 | sharedConfig = JSON.parse(message) 208 | winston.debug('Saved new sharedconfig: ' + JSON.stringify(sharedConfig)) 209 | } 210 | 211 | // Create an account given a walletId, token and a tokenPass 212 | async function createAccount (spec) { 213 | var values = [spec.wallet, spec.token, spec.tokenPass, 214 | spec.pubacl || config.mqtt.pubacl, 215 | spec.subacl || config.mqtt.subacl] 216 | var sql = `WITH x AS ( 217 | SELECT 218 | ''::text AS mountpoint, 219 | $1::text AS client_id, 220 | $2::text AS username, 221 | $3::text AS password, 222 | gen_salt('bf')::text AS salt, 223 | $4::json AS publish_acl, 224 | $5::json AS subscribe_acl 225 | ) 226 | INSERT INTO vmq_auth_acl (mountpoint, client_id, username, password, publish_acl, subscribe_acl) 227 | SELECT 228 | x.mountpoint, 229 | x.client_id, 230 | x.username, 231 | crypt(x.password, x.salt), 232 | publish_acl, 233 | subscribe_acl 234 | FROM x ON CONFLICT DO NOTHING;` 235 | const client = await pool.connect() 236 | try { 237 | winston.debug('Creating account: ' + JSON.stringify(values)) 238 | await client.query(sql, values) 239 | winston.debug('Created account: ' + JSON.stringify(values)) 240 | return {status: 'ok'} 241 | } catch (e) { 242 | winston.error('Error creating account: ' + e) 243 | return {error: '' + e} 244 | } finally { 245 | client.release() 246 | } 247 | } 248 | 249 | function isObject (o) { 250 | return o !== null && typeof o === 'object' 251 | } 252 | 253 | function setWalletForAccounts (accounts, value) { 254 | var promises = [] 255 | for (let acc of accounts) { 256 | promises.push(asyncSet('accounts:' + acc, value)) 257 | } 258 | return Promise.all(promises) 259 | } 260 | 261 | function getWalletForAccount (account, cb) { 262 | winston.debug('Get wallet for account: ' + account) 263 | redisClient.get('accounts:' + account, cb) 264 | } 265 | 266 | // Want to notify before shutting down 267 | function handleAppExit (options, err) { 268 | if (err) { 269 | winston.error(err.stack) 270 | } 271 | if (options.cleanup) { 272 | winston.info('Cleaning up...') 273 | if (mqttClient) { 274 | mqttClient.end(true) 275 | } 276 | } 277 | if (options.exit) { 278 | winston.info('Calling exit...') 279 | process.exit() 280 | } 281 | } 282 | 283 | function configureSignals () { 284 | // Handle the different ways an application can shutdown 285 | process.on('exit', handleAppExit.bind(null, { 286 | cleanup: true 287 | })) 288 | process.on('SIGINT', handleAppExit.bind(null, { 289 | exit: true 290 | })) 291 | process.on('uncaughtException', handleAppExit.bind(null, { 292 | exit: true 293 | })) 294 | } 295 | 296 | function publishConfig (fn, cb) { 297 | if (!fs.existsSync(fn)) { 298 | winston.error('No config file found named ' + fn) 299 | process.exit(0) 300 | } 301 | var contents = fs.readFileSync(fn).toString() 302 | var json = JSON.parse(contents) 303 | var topic = json.topic 304 | var config = json.config 305 | if (!topic || !config) { 306 | return cb(new Error('Malformed config JSON, needs topic and config: ' + fn)) 307 | } 308 | winston.info('Publishing: ' + fn + ' on topic: ' + topic) 309 | mqttClient.publish(topic, JSON.stringify(config), config.mqtt.config.opts, cb) 310 | } 311 | 312 | // Let's start doing something 313 | configure() 314 | configureSignals() 315 | connectPostgres() 316 | 317 | if (args['--initialize']) { 318 | winston.debug('Initializing database ...') 319 | initializeDb().then( 320 | () => { 321 | winston.debug('Initialization done.') 322 | process.exit(0) 323 | }, 324 | err => { 325 | winston.debug('Initialization failed: ' + err) 326 | process.exit(0) 327 | } 328 | ) 329 | } else { 330 | connectRedis() 331 | if (args['pubshared']) { 332 | connectMQTT(function () { 333 | var fn = args[''] 334 | if (!fs.existsSync(fn)) { 335 | winston.error('No config file found named ' + fn) 336 | process.exit(0) 337 | } 338 | var contents = fs.readFileSync(fn).toString() 339 | var json = JSON.parse(contents) 340 | winston.debug('Sending: ' + JSON.stringify(json)) 341 | publishSharedConfig(json, function (err) { 342 | if (err) { 343 | winston.error(err) 344 | } 345 | process.exit(0) 346 | }) 347 | }) 348 | } else if (args['pub']) { 349 | connectMQTT(function () { 350 | var fn = args[''] 351 | publishConfig(fn, function (err) { 352 | if (err) { 353 | winston.error(err) 354 | } 355 | process.exit(0) 356 | }) 357 | }) 358 | } else { 359 | var acc = args[''] 360 | if (args['set'] && acc) { 361 | setWalletForAccounts([acc], JSON.stringify({wallet: 'xyz'})).then((res) => { 362 | winston.debug('Set wallet xyz for accounts ' + [acc]) 363 | }, (err) => { 364 | winston.error('Error setting wallet for accounts: ' + err) 365 | }) 366 | } 367 | if (args['get'] && acc) { 368 | getWalletForAccount(acc, function (err, value) { 369 | if (err) throw err 370 | var o = JSON.parse(value) 371 | winston.debug('IsOject: ' + isObject(o)) 372 | winston.debug('Obj: ' + JSON.stringify(o)) 373 | }) 374 | } 375 | } 376 | if (args['show']) { 377 | connectMQTT(function () { 378 | subscribe('sharedconfig') 379 | }) 380 | } 381 | if (args['log']) { 382 | connectMQTT(function () { 383 | logging = true 384 | pattern = args[''] || '#' 385 | subscribe(pattern) 386 | }) 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /canoed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * This is a service that has an express HTTP server exposing a subset of the rai_node 4 | * RPC protocol. It also connects to an MQTT server 5 | * for pub/sub operations and uses Redis and PostgreSQL for state management. 6 | */ 7 | 8 | const fs = require('fs') 9 | const express = require('express') 10 | const bodyParser = require('body-parser') 11 | const mqtt = require('mqtt') 12 | const mqttRegex = require('mqtt-regex') // Used to parse out parameters from wildcard MQTT topics 13 | const request = require('request') 14 | const extend = require('extend') // To merge objects 15 | const winston = require('winston') // Solid logging lib 16 | const redis = require('redis') // For maintaining session state 17 | const { Pool } = require('pg') // For proper database stuff 18 | const neodoc = require('neodoc') // For nice command line opts 19 | const {promisify} = require('util') // Promises for redisClient 20 | const compareVersions = require('compare-versions') // To use semver version matching 21 | const bigInt = require("big-integer") 22 | 23 | // Parse out command line 24 | const args = neodoc.run(` 25 | Usage: 26 | canoed [--initialize] [--config=] 27 | canoed -h | --help | --version 28 | `, { optionsFirst: true, smartOptions: true }) 29 | 30 | // Default config that is extended (merged) with CONFIG_FILE 31 | var CONFIG_FILE = 'canoed.conf' 32 | if (args['--config']) { 33 | CONFIG_FILE = args['--config'] 34 | } 35 | 36 | var config = { 37 | logging: { 38 | level: 'info' 39 | }, 40 | debug: false, 41 | server: { 42 | port: 8080, 43 | host: '' 44 | }, 45 | rainode: { 46 | host: '[::1]', 47 | port: 7076 48 | }, 49 | postgres: { 50 | user: 'canoe', 51 | host: 'localhost', 52 | database: 'canoe', 53 | password: 'secretpassword1', 54 | port: 5432 55 | }, 56 | redis: { 57 | host: 'localhost', 58 | port: 6379 59 | }, 60 | wallets: { 61 | accepted: [ 62 | {name: 'canoe', versions: ['>=0.9.12']} 63 | ], 64 | rejected: [ 65 | {name: 'canoe', versions: ['<0.9.12'], message: 'Canoe older than 0.9.12 is not allowed anymore since it has a bug that can in a specific scenario cause loss of funds, see https://github.com/getcanoe/canoe/issues/286'} 66 | ] 67 | }, 68 | mqtt: { 69 | url: 'tcp://localhost', 70 | options: { 71 | clientId: 'canoed', 72 | username: 'canoed', 73 | password: '1234' 74 | }, 75 | subacl: '[{"pattern": "#"}]', 76 | pubacl: '[{"pattern": "#"}]', 77 | block: { 78 | opts: { 79 | qos: 2, 80 | retain: false 81 | } 82 | }, 83 | sharedconfig: { 84 | opts: { 85 | qos: 2, 86 | retain: true 87 | } 88 | } 89 | } 90 | } 91 | 92 | // Published as retained message and shared by all wallets connecting to canoed 93 | var sharedConfig 94 | 95 | // MQTT Client 96 | var mqttClient = null 97 | 98 | // Postgres pool client 99 | var pool = null 100 | 101 | // Redis Client 102 | var redisClient = null 103 | // var asyncSet 104 | // var asyncGet 105 | var asyncSetAdd 106 | var asyncSetMembers 107 | 108 | // An Express server to handle REST calls, either from Canoe or from rai_node callback 109 | var restServer = null 110 | 111 | // Patterns for topics 112 | var walletMqttRegex = mqttRegex('wallet/+id/+cmd').exec // 113 | var broadcastMqttRegex = mqttRegex('broadcast/+account').exec 114 | var workerMqttRegex = mqttRegex('worker/+id/+cmd').exec 115 | 116 | // Flag to indicate we have already subscribed to topics 117 | var subscribed = false 118 | 119 | // We maintain a map of registered workers 120 | var workers = new Map() 121 | 122 | // And a small queue of PoW requests 123 | var powRequests = [] 124 | 125 | // Read configuration 126 | function configure () { 127 | // Read config file if exists 128 | if (fs.existsSync(CONFIG_FILE)) { 129 | try { 130 | var fileConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) 131 | extend(true, config, fileConfig) 132 | } catch (e) { 133 | winston.error('Failed to parse config file: ' + CONFIG_FILE + e.message) 134 | process.exit(1) 135 | } 136 | } 137 | winston.level = config.logging.level 138 | } 139 | 140 | // Connect Postgres 141 | function connectPostgres () { 142 | pool = new Pool(config.postgres) 143 | winston.info('Connected to Postgres') 144 | } 145 | 146 | // Initialize database for VerneMQ auth 147 | // We also need: CREATE EXTENSION pgcrypto; 148 | // But that needs superuser privilege to do. 149 | async function initializeDb () { 150 | var sql = ` 151 | CREATE TABLE IF NOT EXISTS block_timestamp ( 152 | hash varchar(64) NOT NULL, 153 | timestamp timestamp default current_timestamp, 154 | PRIMARY KEY (hash) 155 | ); 156 | CREATE TABLE IF NOT EXISTS vmq_auth_acl 157 | ( 158 | mountpoint character varying(10) NOT NULL, 159 | client_id character varying(128) NOT NULL, 160 | username character varying(128) NOT NULL, 161 | password character varying(128), 162 | publish_acl json, 163 | subscribe_acl json, 164 | CONSTRAINT vmq_auth_acl_primary_key PRIMARY KEY (mountpoint, client_id, username) 165 | );` 166 | const client = await pool.connect() 167 | try { 168 | await client.query(sql) 169 | } catch (e) { 170 | winston.error('Error initializing db: ' + e) 171 | } finally { 172 | client.release() 173 | } 174 | await createAccount({ 175 | wallet: config.mqtt.options.username, // This maps to clientId 176 | token: config.mqtt.options.username, 177 | tokenPass: config.mqtt.options.password, 178 | pubacl: '[{"pattern":"#"}]', 179 | subacl: '[{"pattern":"#"}]' 180 | }) 181 | } 182 | 183 | // Connect Redis 184 | function connectRedis () { 185 | winston.info('Connecting to Redis ...') 186 | redisClient = redis.createClient(config.redis.port, config.redis.host, {no_ready_check: true}) 187 | // asyncSet = promisify(redisClient.set).bind(redisClient) 188 | // asyncGet = promisify(redisClient.get).bind(redisClient) 189 | asyncSetAdd = promisify(redisClient.sadd).bind(redisClient) 190 | asyncSetMembers = promisify(redisClient.smembers).bind(redisClient) 191 | redisClient.on('connect', function () { 192 | winston.info('Connected to Redis') 193 | }) 194 | } 195 | 196 | // Connect to MQTT 197 | function connectMQTT () { 198 | mqttClient = mqtt.connect(config.mqtt.url, config.mqtt.options) 199 | mqttClient.on('connect', function () { 200 | winston.info('Connected to MQTT server') 201 | subscribe() 202 | }) 203 | 204 | // Where all subscribed messages come in 205 | mqttClient.on('message', function (topic, message) { 206 | switch (topic) { 207 | case 'sharedconfig': 208 | return handleSharedConfig(message) 209 | case 'control/canoed': 210 | return handleControl(message) 211 | } 212 | var params = walletMqttRegex(topic) 213 | if (params) { 214 | return handleWalletMessage(params.id, params.cmd, message) 215 | } 216 | params = workerMqttRegex(topic) 217 | if (params) { 218 | return handleWorkerMessage(params.id, params.cmd, message) 219 | } 220 | params = broadcastMqttRegex(topic) 221 | if (params) { 222 | return handleBroadcastBlock(params.account, message) 223 | } 224 | winston.error('No handler for topic %s', topic) 225 | }) 226 | } 227 | 228 | function publishSharedConfig (topic, payload) { 229 | winston.debug('Publish: ' + topic + ' payload: ' + JSON.stringify(payload)) 230 | mqttClient.publish(topic, JSON.stringify(payload), config.mqtt.sharedconfig.opts) 231 | } 232 | 233 | function publishBlock (topic, payload) { 234 | winston.debug('Publish: ' + topic + ' block: ' + JSON.stringify(payload)) 235 | mqttClient.publish(topic, JSON.stringify(payload), config.mqtt.block.opts) 236 | } 237 | 238 | // Subscribe to control 239 | function subscribe () { 240 | if (!subscribed) { 241 | mqttClient.subscribe('worker/+/unregister') 242 | mqttClient.subscribe('worker/+/register') 243 | mqttClient.subscribe('worker/+/working') 244 | mqttClient.subscribe('worker/+/answer') 245 | mqttClient.subscribe('sharedconfig') 246 | mqttClient.subscribe('control/canoed') 247 | mqttClient.subscribe('wallet/+/accounts') 248 | mqttClient.subscribe('wallet/+/register') 249 | mqttClient.subscribe('wallet/+/requestwork') 250 | mqttClient.subscribe('broadcast/+') // account number 251 | winston.debug('Subscribed to selected topics') 252 | // /+/ for wildcards 253 | subscribed = true 254 | } 255 | } 256 | 257 | // Old style registering only accounts 258 | function handleWalletAccounts (walletId, accounts) { 259 | setWalletForAccounts(accounts, walletId).then((res) => { 260 | winston.debug('Set wallet ' + walletId + ' for accounts ' + JSON.stringify(accounts)) 261 | }, (err) => { 262 | winston.error('Error setting wallet for accounts: ' + err) 263 | }) 264 | } 265 | 266 | // PoW worker handling 267 | function handleWorkerMessage (workerId, cmd, message) { 268 | var payload = JSON.parse(message) 269 | var worker 270 | switch (cmd) { 271 | case 'register': 272 | if (workers.has(workerId)) { 273 | winston.info('Reregistering worker %s: %s', workerId, payload) 274 | } else { 275 | winston.info('Registering worker %s: %s', workerId, payload) 276 | } 277 | workers.set(workerId, {id: workerId, online: true, request: null, registered: new Date(), meta: payload}) 278 | return 279 | case 'unregister': 280 | worker = workers.get(workerId) 281 | if (worker) { 282 | winston.info('Unregistering worker %s: %s', workerId, payload) 283 | worker.online = false 284 | worker.meta = payload 285 | } else { 286 | winston.warn('Missing worker to unregister: %s ', workerId) 287 | } 288 | return 289 | case 'working': 290 | worker = workers.get(workerId) 291 | if (worker) { 292 | if (worker.request) { 293 | worker.request.started = new Date() 294 | } else { 295 | winston.error('Got working message but missing request') 296 | } 297 | } else { 298 | winston.error('Got working message from non registered worker') 299 | } 300 | return 301 | case 'answer': 302 | worker = workers.get(workerId) 303 | if (worker) { 304 | if (worker.request) { 305 | worker.request.work = payload.work 306 | worker.request.done = new Date() 307 | request.time = worker.request.done - worker.request.started 308 | winston.debug('Calculating PoW took %s ms', request.time) 309 | dispatchWorkAnswer(worker) 310 | } else { 311 | winston.error('Got answer message but missing request') 312 | } 313 | } else { 314 | winston.error('Got answer message from non registered worker') 315 | } 316 | return 317 | } 318 | winston.error('No handler for worker %s, message %s', workerId, cmd) 319 | } 320 | 321 | // Messages coming from wallets 322 | function handleWalletMessage (walletId, cmd, message) { 323 | var payload = JSON.parse(message) 324 | switch (cmd) { 325 | case 'accounts': 326 | return handleWalletAccounts(walletId, payload) 327 | case 'register': 328 | return handleWalletRegister(walletId, payload) 329 | case 'requestwork': 330 | return handleWalletRequestWork(walletId, payload) 331 | } 332 | winston.error('No handler for message %s from wallet %s', cmd, walletId) 333 | } 334 | 335 | function handleWalletRequestWork (walletId, request) { 336 | request.timestamp = new Date() 337 | winston.debug('Received PoW request from ' + walletId + ' hash ' + request.hash) 338 | powRequests.push(request) 339 | dispatchWorkRequest() 340 | } 341 | 342 | function findFreeWorker () { 343 | workers.forEach(function (worker) { 344 | if (worker.online && !worker.request) { 345 | return worker 346 | } 347 | }) 348 | return null 349 | } 350 | 351 | function dispatchWorkRequest () { 352 | var worker = findFreeWorker() 353 | if (worker) { 354 | var request = powRequests.shift() 355 | request.requested = new Date() 356 | worker.request = request 357 | publishWorkRequest(worker) 358 | } 359 | winston.debug('All workers busy') 360 | } 361 | 362 | function dispatchWorkAnswer (worker) { 363 | var request = worker.request 364 | worker.request = null 365 | publishWorkAnswer(request) 366 | dispatchWorkRequest() // Check for next in request queue 367 | } 368 | 369 | function publishWorkRequest (worker) { 370 | var payload = {hash: worker.request.hash} 371 | winston.debug('Publishing pow request to %s', worker.id) 372 | mqttClient.publish('worker/' + worker.id + '/request', JSON.stringify(payload), config.mqtt.powrequest.opts) 373 | } 374 | 375 | function publishWorkAnswer (request) { 376 | var payload = {hash: request.hash, work: request.work, time: request.time, account: request.account} 377 | winston.debug('Publishing pow to %s', request.from) 378 | mqttClient.publish('wallet/' + request.from + '/work', JSON.stringify(payload), config.mqtt.powanswer.opts) 379 | } 380 | 381 | // Protocol v1 style registering 382 | function handleWalletRegister (walletId, meta) { 383 | // Pick out accounts and remove them from meta 384 | var accounts = meta.accounts 385 | delete meta.accounts 386 | // Add wallet id to meta 387 | meta.wallet = walletId 388 | setWalletForAccounts(accounts, JSON.stringify(meta)).then((res) => { 389 | winston.debug('Set meta for ' + walletId + ', accounts ' + accounts) 390 | }, (err) => { 391 | winston.error('Error setting meta for accounts: ' + err) 392 | }) 393 | } 394 | 395 | function handleBroadcastBlock (account, message) { 396 | winston.debug('Broadcast for account ' + account + ' block ' + message) 397 | } 398 | 399 | function handleSharedConfig (message) { 400 | sharedConfig = JSON.parse(message) 401 | winston.debug('Saved new sharedconfig: ' + JSON.stringify(sharedConfig)) 402 | } 403 | 404 | function handleControl (message) { 405 | var control = JSON.parse(message) 406 | winston.debug('PARSED CONTROL: ', control) 407 | // TODO handle control commands 408 | } 409 | 410 | // Start the REST server 411 | function startRESTServer () { 412 | restServer = express() 413 | restServer.use(bodyParser.json({ 414 | inflate: true, 415 | limit: '100kb', 416 | type: function (req) { return true } // Callbacks don't come with a media type so we always presume JSON in body 417 | })) 418 | 419 | // The RPC functions we offer Canoe 420 | restServer.post('/rpc', function (req, res) { 421 | var spec = req.body 422 | var action = req.body.action 423 | 424 | function log (result) { 425 | winston.debug('POST:' + JSON.stringify(spec)) 426 | winston.debug('ANSWER: ' + JSON.stringify(result)) 427 | return result 428 | } 429 | 430 | switch (action) { 431 | case 'create_server_account': 432 | return createAccount(spec).then((r) => { res.json(log(r)) }) 433 | case 'canoe_server_status': 434 | return res.json(canoeServerStatus(spec)) 435 | case 'quota_full': 436 | return res.json(quotaFull(spec)) 437 | case 'process': 438 | return processBlock(spec).then((r) => { res.json(log(r)) }) 439 | case 'available_supply': 440 | return availableSupply(spec).then((r) => { res.json(r) }) 441 | case 'ledger': 442 | return ledger(spec).then((r) => { res.json(r) }) 443 | case 'chain': 444 | return chain(spec).then((r) => { res.json(log(r)) }) 445 | case 'accounts_pending': 446 | return accountsPending(spec).then((r) => { res.json(log(r)) }) 447 | case 'account_history': 448 | return accountHistory(spec).then((r) => { res.json(log(r)) }) 449 | case 'account_get': 450 | return accountGet(spec).then((r) => { res.json(r) }) 451 | case 'account_info': 452 | return accountInfo(spec).then((r) => { res.json(r) }) 453 | case 'account_key': 454 | return accountKey(spec).then((r) => { res.json(r) }) 455 | case 'validate_account_number': 456 | return validateAccountNumber(spec).then((r) => { res.json(r) }) 457 | case 'krai_from_raw': 458 | return doConversion(spec).then((r) => { res.json(r) }) 459 | case 'krai_to_raw': 460 | return doConversion(spec).then((r) => { res.json(r) }) 461 | case 'mrai_from_raw': 462 | return doConversion(spec).then((r) => { res.json(r) }) 463 | case 'mrai_to_raw': 464 | return doConversion(spec).then((r) => { res.json(r) }) 465 | case 'rai_from_raw': 466 | return doConversion(spec).then((r) => { res.json(r) }) 467 | case 'rai_to_raw': 468 | return doConversion(spec).then((r) => { res.json(r) }) 469 | case 'blocks_info': 470 | return blocksInfo(spec).then((r) => { res.json(log(r)) }) 471 | case 'work_generate': 472 | return workGenerate(spec).then((r) => { res.json(log(r)) }) 473 | default: 474 | return res.json({error: 'unknown action'}) 475 | } 476 | }) 477 | 478 | // The rai_node callback entry point 479 | restServer.post('/callback', function (req, res) { 480 | handleRaiCallback(req.body) 481 | // We can return immediately 482 | res.json({}) 483 | }) 484 | 485 | restServer.listen(config.server.port, config.server.host) 486 | winston.info('Http server started on port ' + config.server.port) 487 | } 488 | 489 | function quotaFull (spec) { 490 | return {full: false} 491 | } 492 | 493 | function canoeServerStatus (spec) { 494 | // Called if calls fail to get a message to show 495 | if (fs.existsSync('canoeServerStatus.json')) { 496 | return JSON.parse(fs.readFileSync('canoeServerStatus.json')) 497 | } 498 | return {status: 'ok'} 499 | } 500 | 501 | function availableSupply (spec) { 502 | return callRainode(spec) 503 | } 504 | 505 | function accountsPending (spec) { 506 | return callRainode(spec) 507 | } 508 | 509 | function ledger (spec) { 510 | return callRainode(spec) 511 | } 512 | 513 | function chain (spec) { 514 | return callRainode(spec) 515 | } 516 | 517 | function accountHistory (spec) { 518 | return callRainode(spec) 519 | } 520 | 521 | function accountGet (spec) { 522 | return callRainode(spec) 523 | } 524 | 525 | function accountInfo (spec) { 526 | return callRainode(spec) 527 | } 528 | 529 | function accountKey (spec) { 530 | return callRainode(spec) 531 | } 532 | 533 | function validateAccountNumber (spec) { 534 | return callRainode(spec) 535 | } 536 | 537 | function doConversion (spec) { 538 | return callRainode(spec) 539 | } 540 | 541 | async function blocksInfo (spec) { 542 | return callRainode(spec).then((obj) => { 543 | // Now we want to attach proper timestamps to retreived blocks 544 | var ps = [] 545 | var blocks = obj.blocks 546 | // Using this style to get proper closure 547 | Object.keys(blocks).forEach(function (hash) { 548 | ps.push(getTimestamp(hash).then((ts) => { 549 | if (ts) { 550 | blocks[hash].timestamp = ts 551 | } 552 | })) 553 | }) 554 | return Promise.all(ps).then(() => { return obj }) 555 | }) 556 | } 557 | 558 | function workGenerate (spec) { 559 | return callRainode(spec) 560 | } 561 | 562 | // This function was added as an extra precaution to make sure wallets don't mistakenly 563 | // generates sends when they really meant to make receives. 564 | function checkForBadSend (spec) { 565 | // Get previous hash 566 | try { 567 | var block = JSON.parse(spec.block) 568 | var previous = block.previous 569 | if (previous === '0000000000000000000000000000000000000000000000000000000000000000') { 570 | // Open block, fine 571 | return Promise.resolve(false) 572 | } 573 | // First make a blocks_info call to find previous balance 574 | return callRainode({action: 'blocks_info', hashes: [previous], balance: 'true'}).then((obj) => { 575 | if (!obj.blocks) { 576 | // Previous block is not found, so bad block for a different reason 577 | winston.error('No previous block found when doing bad send check: ' + JSON.stringify(obj)) 578 | return true 579 | } 580 | var blk = obj.blocks[previous] 581 | var balance = bigInt(blk.balance) 582 | var newBalance = bigInt(block.balance) 583 | if (newBalance.lt(balance)) { 584 | // Ok, a send it is. 585 | // Is it a send to burn account? We prevent that. 586 | if (block.link === '0000000000000000000000000000000000000000000000000000000000000000') { 587 | // Ooops! A Burn send 588 | winston.error('Burn send block detected (and prevented): ' + JSON.stringify(spec)) 589 | return true 590 | } 591 | // Do we have a valid block hash in link field? If so, then this was in fact intended as a receive for sure. 592 | var hashToCheck = block.link 593 | return callRainode({action: 'block', hash: hashToCheck}).then((obj) => { 594 | if (obj.contents) { 595 | // Ooops! Bad block 596 | winston.error('Bad send block detected (and prevented): ' + JSON.stringify(spec)) 597 | return true 598 | } else { 599 | // No contents means we did not hit a block, which means send is ok 600 | return false 601 | } 602 | }) 603 | } else { 604 | // Not a send, so we let it go 605 | return false 606 | } 607 | }) 608 | } catch (e) { 609 | winston.error('Exception during bad send check: ' + e) 610 | return Promise.resolve(true) 611 | } 612 | } 613 | 614 | function processBlock (spec) { 615 | // Extra validity check preventing receives turned into bad sends 616 | return checkForBadSend(spec).then((bad) => { 617 | if (bad) { 618 | return {status: 'error', message: 'Processing block failed'} 619 | } else { 620 | return callRainode(spec) 621 | } 622 | }) 623 | } 624 | 625 | // Create a timestamp record for given block hash, in milliseconds 626 | async function createTimestamp (hash) { 627 | var values = [hash] 628 | var sql = `INSERT INTO block_timestamp(hash) VALUES($1) RETURNING *;` 629 | const client = await pool.connect() 630 | try { 631 | const res = await client.query(sql, values) 632 | return new Date(res.rows[0].timestamp).getTime() 633 | } catch (e) { 634 | winston.error('Error creating block: ' + e) 635 | return null 636 | } finally { 637 | client.release() 638 | } 639 | } 640 | 641 | // Get the timestamp for given block hash, in milliseconds 642 | async function getTimestamp (hash) { 643 | var values = [hash] 644 | var sql = `SELECT (timestamp) FROM block_timestamp WHERE hash = $1;` 645 | const client = await pool.connect() 646 | try { 647 | const res = await client.query(sql, values) 648 | if (res.rowCount === 1) { 649 | return new Date(res.rows[0].timestamp).getTime() 650 | } else { 651 | return null 652 | } 653 | } catch (e) { 654 | winston.error('Error getting block: ' + e) 655 | } finally { 656 | client.release() 657 | } 658 | } 659 | 660 | // Semver checks on version 661 | function checkVersion (version, pattern) { 662 | if (pattern.startsWith('<=')) { 663 | return compareVersions(version, pattern.slice(2)) <= 0 664 | } else if (pattern.startsWith('>=')) { 665 | return compareVersions(version, pattern.slice(2)) >= 0 666 | } else if (pattern.startsWith('<')) { 667 | return compareVersions(version, pattern.slice(1)) < 0 668 | } else if (pattern.startsWith('>')) { 669 | return compareVersions(version, pattern.slice(1)) > 0 670 | } else { 671 | return compareVersions(version, pattern) === 0 672 | } 673 | } 674 | 675 | function rejectionMessageForWallet (wallet) { 676 | var name = wallet.name 677 | var version = wallet.version 678 | // Check for rejections first, shortcuts 679 | config.wallets.rejected.forEach(function (walletSpec) { 680 | if (wallet.name === walletSpec.name) { 681 | walletSpec.versions.forEach(function (v) { 682 | if (!checkVersion(version, v)) { 683 | return {status: 'error', error: 'rejected', message: walletSpec.message} 684 | } 685 | }) 686 | } 687 | }) 688 | // If no rejection, we need also a matching accept 689 | config.wallets.accepted.forEach(function (walletSpec) { 690 | if (name === walletSpec.name) { 691 | walletSpec.versions.forEach(function (v) { 692 | if (checkVersion(version, v)) { 693 | return null // All is fine 694 | } 695 | }) 696 | } 697 | }) 698 | return {status: 'error', error: 'not accepted', message: 'This wallet version is not accepted by the backend'} 699 | } 700 | 701 | // Create an account given a walletId, token and a tokenPass 702 | async function createAccount (spec) { 703 | /* Not yet enabled, we need to add this to clients first... 704 | Is this an old wallet not sending us version and name? 705 | if (!spec.name) { 706 | // We do not allow older wallets anymore, delete account 707 | await deleteAccount(spec) 708 | return {status: 'error', message: 'Old wallets no longer can create account without passing name and version'} 709 | } 710 | // Do we reject this wallet? 711 | var rejection = rejectionMessageForWallet(spec.wallet) 712 | if (rejection) { 713 | return rejection 714 | } 715 | */ 716 | var values = [spec.wallet, spec.token, spec.tokenPass, 717 | spec.pubacl || config.mqtt.pubacl, 718 | spec.subacl || config.mqtt.subacl] 719 | var sql = `WITH x AS ( 720 | SELECT 721 | ''::text AS mountpoint, 722 | $1::text AS client_id, 723 | $2::text AS username, 724 | $3::text AS password, 725 | gen_salt('bf')::text AS salt, 726 | $4::json AS publish_acl, 727 | $5::json AS subscribe_acl 728 | ) 729 | INSERT INTO vmq_auth_acl (mountpoint, client_id, username, password, publish_acl, subscribe_acl) 730 | SELECT 731 | x.mountpoint, 732 | x.client_id, 733 | x.username, 734 | crypt(x.password, x.salt), 735 | publish_acl, 736 | subscribe_acl 737 | FROM x ON CONFLICT DO NOTHING;` 738 | const client = await pool.connect() 739 | try { 740 | winston.debug('Creating account: ' + JSON.stringify(values)) 741 | await client.query(sql, values) 742 | winston.debug('Created account: ' + JSON.stringify(values)) 743 | return {status: 'ok'} 744 | } catch (e) { 745 | winston.error('Error creating account: ' + e) 746 | return {error: '' + e} 747 | } finally { 748 | client.release() 749 | } 750 | } 751 | 752 | async function deleteAccount (spec) { 753 | var values = [spec.wallet] 754 | var sql = `DELETE FROM vmq_auth_acl WHERE client_id = $1;` 755 | const client = await pool.connect() 756 | try { 757 | winston.debug('Deleting account: ' + JSON.stringify(values)) 758 | await client.query(sql, values) 759 | winston.debug('Deleted account: ' + JSON.stringify(values)) 760 | return {status: 'ok'} 761 | } catch (e) { 762 | winston.error('Error deleting account: ' + e) 763 | return {error: '' + e} 764 | } finally { 765 | client.release() 766 | } 767 | } 768 | 769 | // function isObject (o) { 770 | // return o !== null && typeof o === 'object' 771 | // } 772 | 773 | function eachWalletForAccount (account, cb) { 774 | redisClient.smembers('accounts:' + account, function (err, values) { 775 | if (err) throw err 776 | if (values) { 777 | for (let v of values) { 778 | // Nasty hack, but we know it's a hex string (old walletId) or a JSON object 779 | if (v[0] === '{') { 780 | var meta = JSON.parse(v) 781 | cb(meta.wallet, meta) 782 | } else { 783 | // v is old walletId (hex), then meta is null 784 | cb(v, null) 785 | } 786 | } 787 | } 788 | }) 789 | } 790 | 791 | function setWalletForAccounts (accounts, value) { 792 | var promises = [] 793 | for (let acc of accounts) { 794 | promises.push(asyncSetAdd('accounts:' + acc, value)) 795 | // promises.push(asyncSet('accounts:' + acc, value)) 796 | } 797 | return Promise.all(promises) 798 | } 799 | 800 | function handleCanary (hash) { 801 | switch (hash) { 802 | case 'B6DC4D64801BEC7D81DAA086A5733D251E8CBA0E9226FD6173D97C0569EC2998': 803 | // Turn on state block generation if not already on 804 | if (!sharedConfig.stateblocks.enable) { 805 | sharedConfig.stateblocks.enable = true 806 | publishSharedConfig('sharedconfig', sharedConfig) 807 | } 808 | } 809 | } 810 | 811 | function handleRaiCallback (blk) { 812 | var blk2 = JSON.parse(blk.block) 813 | 814 | // winston.debug('Block: ' + JSON.stringify(blk)) 815 | 816 | // Check for canaries 817 | handleCanary(blk.hash) 818 | 819 | // Now we can pick out type of block 820 | var blkType = blk2.type 821 | var account = blk.account 822 | 823 | winston.debug('Acc: ' + account + ' block: ' + blkType + ' hash: ' + blk.hash) 824 | 825 | // Create timestamp for this block and add it to the block 826 | createTimestamp(blk.hash).then((ts) => { 827 | if (ts) { 828 | blk.timestamp = ts 829 | } 830 | var publishedTo = new Set() 831 | // Switch on block type 832 | switch (blkType) { 833 | case 'state': 834 | eachWalletForAccount(account, function (wallet, meta) { 835 | if (wallet) { 836 | // We only serve state blocks to Canoe protocol v1+ 837 | // and we know that they register a meta object 838 | if (meta) { 839 | if (!publishedTo.has(wallet)) { 840 | publishedTo.add(wallet) 841 | publishBlock('wallet/' + wallet + '/block/state', blk) 842 | } 843 | } else { 844 | winston.warn('State block received for Canoe not capable of handling it') 845 | } 846 | } 847 | }) 848 | // If a send we also need to publish to receiving wallet, if we have it 849 | if (blk.is_send === 'true') { 850 | eachWalletForAccount(blk2.link_as_account, function (wallet, meta) { 851 | if (wallet) { 852 | // We only serve state blocks to Canoe protocol v1+ 853 | // and we know that they register a meta object 854 | if (meta) { 855 | if (!publishedTo.has(wallet)) { 856 | publishedTo.add(wallet) 857 | publishBlock('wallet/' + wallet + '/block/state', blk) 858 | } 859 | } else { 860 | winston.warn('State block received for Canoe not capable of handling it') 861 | } 862 | } 863 | }) 864 | } 865 | return 866 | case 'open': 867 | eachWalletForAccount(account, function (wallet, meta) { 868 | if (wallet) { 869 | publishBlock('wallet/' + wallet + '/block/open', blk) 870 | } 871 | }) 872 | return 873 | case 'send': 874 | // Is this a send from one of our accounts? 875 | eachWalletForAccount(account, function (wallet, meta) { 876 | if (wallet) { 877 | winston.debug('Send from: ' + account + ' wallet: ' + wallet.toString()) 878 | if (!publishedTo.has(wallet)) { 879 | publishedTo.add(wallet) 880 | publishBlock('wallet/' + wallet + '/block/send', blk) 881 | } 882 | } 883 | }) 884 | // Is this a send to one of our accounts? 885 | eachWalletForAccount(blk2.destination, function (wallet, meta) { 886 | if (wallet) { 887 | winston.debug('Send to: ' + blk2.destination + ' wallet: ' + wallet.toString()) 888 | if (!publishedTo.has(wallet)) { 889 | publishedTo.add(wallet) 890 | publishBlock('wallet/' + wallet + '/block/send', blk) 891 | } 892 | } 893 | }) 894 | return 895 | case 'receive': 896 | eachWalletForAccount(account, function (wallet, meta) { 897 | if (wallet) { 898 | publishBlock('wallet/' + wallet + '/block/receive', blk) 899 | } 900 | }) 901 | return 902 | case 'change': 903 | eachWalletForAccount(account, function (wallet, meta) { 904 | if (wallet) { 905 | publishBlock('wallet/' + wallet + '/block/change', blk) 906 | } 907 | }) 908 | return 909 | } 910 | winston.error('Unknown block type: ' + blkType) 911 | }) 912 | } 913 | 914 | // Make POST call to rai_node 915 | function callRainode (payload) { 916 | return new Promise(function (resolve, reject) { 917 | request.post({ 918 | url: 'http://' + config.rainode.host + ':' + config.rainode.port, 919 | headers: {'content-type': 'application/json'}, 920 | body: JSON.stringify(payload), 921 | timeout: 60000, 922 | callback: function (err, response, body) { 923 | if (err) { 924 | winston.debug('ERROR', err) 925 | reject(err) 926 | } else { 927 | var answer = JSON.parse(body) 928 | resolve(answer) 929 | } 930 | } 931 | }) 932 | }) 933 | } 934 | 935 | // Want to notify before shutting down 936 | function handleAppExit (options, err) { 937 | if (err) { 938 | winston.error(err.stack) 939 | } 940 | if (options.cleanup) { 941 | winston.info('Cleaning up...') 942 | if (mqttClient) { 943 | mqttClient.end(true) 944 | } 945 | } 946 | if (options.exit) { 947 | winston.info('Calling exit...') 948 | process.exit() 949 | } 950 | } 951 | 952 | function configureSignals () { 953 | // Handle the different ways an application can shutdown 954 | process.on('exit', handleAppExit.bind(null, { 955 | cleanup: true 956 | })) 957 | process.on('SIGINT', handleAppExit.bind(null, { 958 | exit: true 959 | })) 960 | process.on('uncaughtException', handleAppExit.bind(null, { 961 | exit: true 962 | })) 963 | } 964 | 965 | // Let's start doing something 966 | configure() 967 | configureSignals() 968 | connectPostgres() 969 | 970 | if (args['--initialize']) { 971 | winston.debug('Initializing database ...') 972 | initializeDb().then( 973 | () => { 974 | winston.debug('Initialization done.') 975 | process.exit(0) 976 | }, 977 | err => { 978 | winston.debug('Initialization failed: ' + err) 979 | process.exit(0) 980 | } 981 | ) 982 | } else { 983 | connectRedis() 984 | connectMQTT() 985 | startRESTServer() 986 | } 987 | -------------------------------------------------------------------------------- /paddle/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paddle", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.5", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 10 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 11 | "requires": { 12 | "mime-types": "2.1.18", 13 | "negotiator": "0.6.1" 14 | } 15 | }, 16 | "ajv": { 17 | "version": "5.5.2", 18 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 19 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 20 | "requires": { 21 | "co": "4.6.0", 22 | "fast-deep-equal": "1.1.0", 23 | "fast-json-stable-stringify": "2.0.0", 24 | "json-schema-traverse": "0.3.1" 25 | } 26 | }, 27 | "ansi-regex": { 28 | "version": "2.1.1", 29 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 30 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 31 | }, 32 | "array-flatten": { 33 | "version": "1.1.1", 34 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 35 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 36 | }, 37 | "asn1": { 38 | "version": "0.2.3", 39 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 40 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 41 | }, 42 | "assert-plus": { 43 | "version": "1.0.0", 44 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 45 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 46 | }, 47 | "async": { 48 | "version": "1.0.0", 49 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 50 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" 51 | }, 52 | "async-limiter": { 53 | "version": "1.0.0", 54 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", 55 | "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" 56 | }, 57 | "asynckit": { 58 | "version": "0.4.0", 59 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 60 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 61 | }, 62 | "aws-sign2": { 63 | "version": "0.7.0", 64 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 65 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 66 | }, 67 | "aws4": { 68 | "version": "1.7.0", 69 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", 70 | "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" 71 | }, 72 | "balanced-match": { 73 | "version": "1.0.0", 74 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 75 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 76 | }, 77 | "bcrypt-pbkdf": { 78 | "version": "1.0.1", 79 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 80 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 81 | "optional": true, 82 | "requires": { 83 | "tweetnacl": "0.14.5" 84 | } 85 | }, 86 | "bl": { 87 | "version": "1.2.2", 88 | "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", 89 | "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", 90 | "requires": { 91 | "readable-stream": "2.3.6", 92 | "safe-buffer": "5.1.1" 93 | } 94 | }, 95 | "body-parser": { 96 | "version": "1.18.2", 97 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 98 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 99 | "requires": { 100 | "bytes": "3.0.0", 101 | "content-type": "1.0.4", 102 | "debug": "2.6.9", 103 | "depd": "1.1.2", 104 | "http-errors": "1.6.3", 105 | "iconv-lite": "0.4.19", 106 | "on-finished": "2.3.0", 107 | "qs": "6.5.1", 108 | "raw-body": "2.3.2", 109 | "type-is": "1.6.16" 110 | } 111 | }, 112 | "boom": { 113 | "version": "4.3.1", 114 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 115 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 116 | "requires": { 117 | "hoek": "4.2.1" 118 | } 119 | }, 120 | "brace-expansion": { 121 | "version": "1.1.11", 122 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 123 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 124 | "requires": { 125 | "balanced-match": "1.0.0", 126 | "concat-map": "0.0.1" 127 | } 128 | }, 129 | "buffer-from": { 130 | "version": "1.0.0", 131 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", 132 | "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==" 133 | }, 134 | "buffer-writer": { 135 | "version": "1.0.1", 136 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", 137 | "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" 138 | }, 139 | "bytes": { 140 | "version": "3.0.0", 141 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 142 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 143 | }, 144 | "callback-stream": { 145 | "version": "1.1.0", 146 | "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz", 147 | "integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=", 148 | "requires": { 149 | "inherits": "2.0.3", 150 | "readable-stream": "2.3.6" 151 | } 152 | }, 153 | "caseless": { 154 | "version": "0.12.0", 155 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 156 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 157 | }, 158 | "co": { 159 | "version": "4.6.0", 160 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 161 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 162 | }, 163 | "colors": { 164 | "version": "1.0.3", 165 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 166 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" 167 | }, 168 | "combined-stream": { 169 | "version": "1.0.6", 170 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 171 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 172 | "requires": { 173 | "delayed-stream": "1.0.0" 174 | } 175 | }, 176 | "commist": { 177 | "version": "1.0.0", 178 | "resolved": "https://registry.npmjs.org/commist/-/commist-1.0.0.tgz", 179 | "integrity": "sha1-wMNSUBz29S6RJOPvicmAbiAi6+8=", 180 | "requires": { 181 | "leven": "1.0.2", 182 | "minimist": "1.2.0" 183 | } 184 | }, 185 | "concat-map": { 186 | "version": "0.0.1", 187 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 188 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 189 | }, 190 | "concat-stream": { 191 | "version": "1.6.2", 192 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 193 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 194 | "requires": { 195 | "buffer-from": "1.0.0", 196 | "inherits": "2.0.3", 197 | "readable-stream": "2.3.6", 198 | "typedarray": "0.0.6" 199 | } 200 | }, 201 | "content-disposition": { 202 | "version": "0.5.2", 203 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 204 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 205 | }, 206 | "content-type": { 207 | "version": "1.0.4", 208 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 209 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 210 | }, 211 | "cookie": { 212 | "version": "0.3.1", 213 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 214 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 215 | }, 216 | "cookie-signature": { 217 | "version": "1.0.6", 218 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 219 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 220 | }, 221 | "core-util-is": { 222 | "version": "1.0.2", 223 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 224 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 225 | }, 226 | "cron-parser": { 227 | "version": "2.4.5", 228 | "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.4.5.tgz", 229 | "integrity": "sha512-J/BXGGFLQCxrLqcGT9Zp1Yz9H1LfZqlUJ7JRgfbtwt6fkWGVKKtMskF/iDxkA7MXJoaYerwyHX48lZuZoOJ1Eg==", 230 | "requires": { 231 | "is-nan": "1.2.1", 232 | "moment-timezone": "0.5.14" 233 | } 234 | }, 235 | "cryptiles": { 236 | "version": "3.1.2", 237 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 238 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 239 | "requires": { 240 | "boom": "5.2.0" 241 | }, 242 | "dependencies": { 243 | "boom": { 244 | "version": "5.2.0", 245 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 246 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", 247 | "requires": { 248 | "hoek": "4.2.1" 249 | } 250 | } 251 | } 252 | }, 253 | "cycle": { 254 | "version": "1.0.3", 255 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 256 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" 257 | }, 258 | "dashdash": { 259 | "version": "1.14.1", 260 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 261 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 262 | "requires": { 263 | "assert-plus": "1.0.0" 264 | } 265 | }, 266 | "debug": { 267 | "version": "2.6.9", 268 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 269 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 270 | "requires": { 271 | "ms": "2.0.0" 272 | } 273 | }, 274 | "define-properties": { 275 | "version": "1.1.2", 276 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 277 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 278 | "requires": { 279 | "foreach": "2.0.5", 280 | "object-keys": "1.0.11" 281 | } 282 | }, 283 | "delayed-stream": { 284 | "version": "1.0.0", 285 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 286 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 287 | }, 288 | "depd": { 289 | "version": "1.1.2", 290 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 291 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 292 | }, 293 | "destroy": { 294 | "version": "1.0.4", 295 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 296 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 297 | }, 298 | "double-ended-queue": { 299 | "version": "2.1.0-0", 300 | "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", 301 | "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" 302 | }, 303 | "duplexify": { 304 | "version": "3.5.4", 305 | "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.4.tgz", 306 | "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", 307 | "requires": { 308 | "end-of-stream": "1.4.1", 309 | "inherits": "2.0.3", 310 | "readable-stream": "2.3.6", 311 | "stream-shift": "1.0.0" 312 | } 313 | }, 314 | "ecc-jsbn": { 315 | "version": "0.1.1", 316 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 317 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 318 | "optional": true, 319 | "requires": { 320 | "jsbn": "0.1.1" 321 | } 322 | }, 323 | "ee-first": { 324 | "version": "1.1.1", 325 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 326 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 327 | }, 328 | "encodeurl": { 329 | "version": "1.0.2", 330 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 331 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 332 | }, 333 | "end-of-stream": { 334 | "version": "1.4.1", 335 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", 336 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", 337 | "requires": { 338 | "once": "1.4.0" 339 | } 340 | }, 341 | "escape-html": { 342 | "version": "1.0.3", 343 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 344 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 345 | }, 346 | "escape-string-regexp": { 347 | "version": "1.0.5", 348 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 349 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 350 | }, 351 | "etag": { 352 | "version": "1.8.1", 353 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 354 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 355 | }, 356 | "express": { 357 | "version": "4.16.3", 358 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 359 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 360 | "requires": { 361 | "accepts": "1.3.5", 362 | "array-flatten": "1.1.1", 363 | "body-parser": "1.18.2", 364 | "content-disposition": "0.5.2", 365 | "content-type": "1.0.4", 366 | "cookie": "0.3.1", 367 | "cookie-signature": "1.0.6", 368 | "debug": "2.6.9", 369 | "depd": "1.1.2", 370 | "encodeurl": "1.0.2", 371 | "escape-html": "1.0.3", 372 | "etag": "1.8.1", 373 | "finalhandler": "1.1.1", 374 | "fresh": "0.5.2", 375 | "merge-descriptors": "1.0.1", 376 | "methods": "1.1.2", 377 | "on-finished": "2.3.0", 378 | "parseurl": "1.3.2", 379 | "path-to-regexp": "0.1.7", 380 | "proxy-addr": "2.0.3", 381 | "qs": "6.5.1", 382 | "range-parser": "1.2.0", 383 | "safe-buffer": "5.1.1", 384 | "send": "0.16.2", 385 | "serve-static": "1.13.2", 386 | "setprototypeof": "1.1.0", 387 | "statuses": "1.4.0", 388 | "type-is": "1.6.16", 389 | "utils-merge": "1.0.1", 390 | "vary": "1.1.2" 391 | }, 392 | "dependencies": { 393 | "statuses": { 394 | "version": "1.4.0", 395 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 396 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 397 | } 398 | } 399 | }, 400 | "extend": { 401 | "version": "3.0.1", 402 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 403 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 404 | }, 405 | "extsprintf": { 406 | "version": "1.3.0", 407 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 408 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 409 | }, 410 | "eyes": { 411 | "version": "0.1.8", 412 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 413 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" 414 | }, 415 | "fast-deep-equal": { 416 | "version": "1.1.0", 417 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 418 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 419 | }, 420 | "fast-json-stable-stringify": { 421 | "version": "2.0.0", 422 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 423 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 424 | }, 425 | "finalhandler": { 426 | "version": "1.1.1", 427 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 428 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 429 | "requires": { 430 | "debug": "2.6.9", 431 | "encodeurl": "1.0.2", 432 | "escape-html": "1.0.3", 433 | "on-finished": "2.3.0", 434 | "parseurl": "1.3.2", 435 | "statuses": "1.4.0", 436 | "unpipe": "1.0.0" 437 | }, 438 | "dependencies": { 439 | "statuses": { 440 | "version": "1.4.0", 441 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 442 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 443 | } 444 | } 445 | }, 446 | "foreach": { 447 | "version": "2.0.5", 448 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 449 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" 450 | }, 451 | "forever-agent": { 452 | "version": "0.6.1", 453 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 454 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 455 | }, 456 | "form-data": { 457 | "version": "2.3.2", 458 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 459 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 460 | "requires": { 461 | "asynckit": "0.4.0", 462 | "combined-stream": "1.0.6", 463 | "mime-types": "2.1.18" 464 | } 465 | }, 466 | "forwarded": { 467 | "version": "0.1.2", 468 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 469 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 470 | }, 471 | "fresh": { 472 | "version": "0.5.2", 473 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 474 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 475 | }, 476 | "fs": { 477 | "version": "0.0.1-security", 478 | "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", 479 | "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" 480 | }, 481 | "fs.realpath": { 482 | "version": "1.0.0", 483 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 484 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 485 | }, 486 | "getpass": { 487 | "version": "0.1.7", 488 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 489 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 490 | "requires": { 491 | "assert-plus": "1.0.0" 492 | } 493 | }, 494 | "glob": { 495 | "version": "7.1.2", 496 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 497 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 498 | "requires": { 499 | "fs.realpath": "1.0.0", 500 | "inflight": "1.0.6", 501 | "inherits": "2.0.3", 502 | "minimatch": "3.0.4", 503 | "once": "1.4.0", 504 | "path-is-absolute": "1.0.1" 505 | } 506 | }, 507 | "glob-parent": { 508 | "version": "3.1.0", 509 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", 510 | "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", 511 | "requires": { 512 | "is-glob": "3.1.0", 513 | "path-dirname": "1.0.2" 514 | } 515 | }, 516 | "glob-stream": { 517 | "version": "6.1.0", 518 | "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", 519 | "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", 520 | "requires": { 521 | "extend": "3.0.1", 522 | "glob": "7.1.2", 523 | "glob-parent": "3.1.0", 524 | "is-negated-glob": "1.0.0", 525 | "ordered-read-streams": "1.0.1", 526 | "pumpify": "1.4.0", 527 | "readable-stream": "2.3.6", 528 | "remove-trailing-separator": "1.1.0", 529 | "to-absolute-glob": "2.0.2", 530 | "unique-stream": "2.2.1" 531 | } 532 | }, 533 | "har-schema": { 534 | "version": "2.0.0", 535 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 536 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 537 | }, 538 | "har-validator": { 539 | "version": "5.0.3", 540 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 541 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 542 | "requires": { 543 | "ajv": "5.5.2", 544 | "har-schema": "2.0.0" 545 | } 546 | }, 547 | "hawk": { 548 | "version": "6.0.2", 549 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 550 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", 551 | "requires": { 552 | "boom": "4.3.1", 553 | "cryptiles": "3.1.2", 554 | "hoek": "4.2.1", 555 | "sntp": "2.1.0" 556 | } 557 | }, 558 | "help-me": { 559 | "version": "1.1.0", 560 | "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz", 561 | "integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=", 562 | "requires": { 563 | "callback-stream": "1.1.0", 564 | "glob-stream": "6.1.0", 565 | "through2": "2.0.3", 566 | "xtend": "4.0.1" 567 | } 568 | }, 569 | "hoek": { 570 | "version": "4.2.1", 571 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 572 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 573 | }, 574 | "http-errors": { 575 | "version": "1.6.3", 576 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 577 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 578 | "requires": { 579 | "depd": "1.1.2", 580 | "inherits": "2.0.3", 581 | "setprototypeof": "1.1.0", 582 | "statuses": "1.5.0" 583 | } 584 | }, 585 | "http-signature": { 586 | "version": "1.2.0", 587 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 588 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 589 | "requires": { 590 | "assert-plus": "1.0.0", 591 | "jsprim": "1.4.1", 592 | "sshpk": "1.14.1" 593 | } 594 | }, 595 | "iconv-lite": { 596 | "version": "0.4.19", 597 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 598 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 599 | }, 600 | "inflight": { 601 | "version": "1.0.6", 602 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 603 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 604 | "requires": { 605 | "once": "1.4.0", 606 | "wrappy": "1.0.2" 607 | } 608 | }, 609 | "inherits": { 610 | "version": "2.0.3", 611 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 612 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 613 | }, 614 | "ipaddr.js": { 615 | "version": "1.6.0", 616 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 617 | "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 618 | }, 619 | "is-absolute": { 620 | "version": "1.0.0", 621 | "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", 622 | "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", 623 | "requires": { 624 | "is-relative": "1.0.0", 625 | "is-windows": "1.0.2" 626 | } 627 | }, 628 | "is-extglob": { 629 | "version": "2.1.1", 630 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 631 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 632 | }, 633 | "is-glob": { 634 | "version": "3.1.0", 635 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", 636 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", 637 | "requires": { 638 | "is-extglob": "2.1.1" 639 | } 640 | }, 641 | "is-nan": { 642 | "version": "1.2.1", 643 | "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz", 644 | "integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=", 645 | "requires": { 646 | "define-properties": "1.1.2" 647 | } 648 | }, 649 | "is-negated-glob": { 650 | "version": "1.0.0", 651 | "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", 652 | "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" 653 | }, 654 | "is-relative": { 655 | "version": "1.0.0", 656 | "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", 657 | "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", 658 | "requires": { 659 | "is-unc-path": "1.0.0" 660 | } 661 | }, 662 | "is-typedarray": { 663 | "version": "1.0.0", 664 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 665 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 666 | }, 667 | "is-unc-path": { 668 | "version": "1.0.0", 669 | "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", 670 | "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", 671 | "requires": { 672 | "unc-path-regex": "0.1.2" 673 | } 674 | }, 675 | "is-windows": { 676 | "version": "1.0.2", 677 | "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", 678 | "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" 679 | }, 680 | "isarray": { 681 | "version": "1.0.0", 682 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 683 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 684 | }, 685 | "isstream": { 686 | "version": "0.1.2", 687 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 688 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 689 | }, 690 | "js-string-escape": { 691 | "version": "1.0.1", 692 | "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", 693 | "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" 694 | }, 695 | "jsbn": { 696 | "version": "0.1.1", 697 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 698 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 699 | "optional": true 700 | }, 701 | "json-schema": { 702 | "version": "0.2.3", 703 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 704 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 705 | }, 706 | "json-schema-traverse": { 707 | "version": "0.3.1", 708 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 709 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 710 | }, 711 | "json-stable-stringify": { 712 | "version": "1.0.1", 713 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 714 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 715 | "requires": { 716 | "jsonify": "0.0.0" 717 | } 718 | }, 719 | "json-stringify-safe": { 720 | "version": "5.0.1", 721 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 722 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 723 | }, 724 | "jsonify": { 725 | "version": "0.0.0", 726 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 727 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 728 | }, 729 | "jsprim": { 730 | "version": "1.4.1", 731 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 732 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 733 | "requires": { 734 | "assert-plus": "1.0.0", 735 | "extsprintf": "1.3.0", 736 | "json-schema": "0.2.3", 737 | "verror": "1.10.0" 738 | } 739 | }, 740 | "leven": { 741 | "version": "1.0.2", 742 | "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz", 743 | "integrity": "sha1-kUS27ryl8dBoAWnxpncNzqYLdcM=" 744 | }, 745 | "long-timeout": { 746 | "version": "0.1.1", 747 | "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz", 748 | "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" 749 | }, 750 | "media-typer": { 751 | "version": "0.3.0", 752 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 753 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 754 | }, 755 | "merge-descriptors": { 756 | "version": "1.0.1", 757 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 758 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 759 | }, 760 | "methods": { 761 | "version": "1.1.2", 762 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 763 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 764 | }, 765 | "mime": { 766 | "version": "1.4.1", 767 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 768 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 769 | }, 770 | "mime-db": { 771 | "version": "1.33.0", 772 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 773 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 774 | }, 775 | "mime-types": { 776 | "version": "2.1.18", 777 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 778 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 779 | "requires": { 780 | "mime-db": "1.33.0" 781 | } 782 | }, 783 | "minimatch": { 784 | "version": "3.0.4", 785 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 786 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 787 | "requires": { 788 | "brace-expansion": "1.1.11" 789 | } 790 | }, 791 | "minimist": { 792 | "version": "1.2.0", 793 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 794 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 795 | }, 796 | "moment": { 797 | "version": "2.22.0", 798 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz", 799 | "integrity": "sha512-1muXCh8jb1N/gHRbn9VDUBr0GYb8A/aVcHlII9QSB68a50spqEVLIGN6KVmCOnSvJrUhC0edGgKU5ofnGXdYdg==" 800 | }, 801 | "moment-timezone": { 802 | "version": "0.5.14", 803 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", 804 | "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", 805 | "requires": { 806 | "moment": "2.22.0" 807 | } 808 | }, 809 | "mqtt": { 810 | "version": "2.17.0", 811 | "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-2.17.0.tgz", 812 | "integrity": "sha512-eYeK5G/GQcdP/AOrGQMUULX7QvBXt3I9bfmgNkzMTsdSR1ywJQhK1iCYPrhh+rtRl3eUSJwEbO+oZx/Q51uHaw==", 813 | "requires": { 814 | "commist": "1.0.0", 815 | "concat-stream": "1.6.2", 816 | "end-of-stream": "1.4.1", 817 | "help-me": "1.1.0", 818 | "inherits": "2.0.3", 819 | "minimist": "1.2.0", 820 | "mqtt-packet": "5.5.0", 821 | "pump": "3.0.0", 822 | "readable-stream": "2.3.6", 823 | "reinterval": "1.1.0", 824 | "split2": "2.2.0", 825 | "websocket-stream": "5.1.2", 826 | "xtend": "4.0.1" 827 | } 828 | }, 829 | "mqtt-packet": { 830 | "version": "5.5.0", 831 | "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-5.5.0.tgz", 832 | "integrity": "sha512-kR+Uw+r9rjUFSLZutmaAhjL4Y1asKLMTwE++PP0iuApJuc+zItE5v2LluQN2K3Pri5e2+K4V++QDjqGTgle/+A==", 833 | "requires": { 834 | "bl": "1.2.2", 835 | "inherits": "2.0.3", 836 | "process-nextick-args": "2.0.0", 837 | "safe-buffer": "5.1.1" 838 | } 839 | }, 840 | "mqtt-regex": { 841 | "version": "1.0.5", 842 | "resolved": "https://registry.npmjs.org/mqtt-regex/-/mqtt-regex-1.0.5.tgz", 843 | "integrity": "sha1-LaLZx7pFdLU0DbZhzrbhA+XeRU0=", 844 | "requires": { 845 | "escape-string-regexp": "1.0.5" 846 | } 847 | }, 848 | "ms": { 849 | "version": "2.0.0", 850 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 851 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 852 | }, 853 | "negotiator": { 854 | "version": "0.6.1", 855 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 856 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 857 | }, 858 | "neodoc": { 859 | "version": "2.0.0", 860 | "resolved": "https://registry.npmjs.org/neodoc/-/neodoc-2.0.0.tgz", 861 | "integrity": "sha512-jnFsE8vHuE7rTrGT+uJMTr3pXLzLQXXillfvPWWlY8ucCCnncsrI4p9rE2+j/b6CDiIF+YEnxf1wwIdksK8cWA==", 862 | "requires": { 863 | "ansi-regex": "2.1.1" 864 | } 865 | }, 866 | "node-schedule": { 867 | "version": "1.3.0", 868 | "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.0.tgz", 869 | "integrity": "sha512-NNwO9SUPjBwFmPh3vXiPVEhJLn4uqYmZYvJV358SRGM06BR4UoIqxJpeJwDDXB6atULsgQA97MfD1zMd5xsu+A==", 870 | "requires": { 871 | "cron-parser": "2.4.5", 872 | "long-timeout": "0.1.1", 873 | "sorted-array-functions": "1.1.0" 874 | } 875 | }, 876 | "oauth-sign": { 877 | "version": "0.8.2", 878 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 879 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 880 | }, 881 | "object-keys": { 882 | "version": "1.0.11", 883 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 884 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" 885 | }, 886 | "on-finished": { 887 | "version": "2.3.0", 888 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 889 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 890 | "requires": { 891 | "ee-first": "1.1.1" 892 | } 893 | }, 894 | "once": { 895 | "version": "1.4.0", 896 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 897 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 898 | "requires": { 899 | "wrappy": "1.0.2" 900 | } 901 | }, 902 | "ordered-read-streams": { 903 | "version": "1.0.1", 904 | "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", 905 | "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", 906 | "requires": { 907 | "readable-stream": "2.3.6" 908 | } 909 | }, 910 | "packet-reader": { 911 | "version": "0.3.1", 912 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", 913 | "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" 914 | }, 915 | "parseurl": { 916 | "version": "1.3.2", 917 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 918 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 919 | }, 920 | "path-dirname": { 921 | "version": "1.0.2", 922 | "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", 923 | "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" 924 | }, 925 | "path-is-absolute": { 926 | "version": "1.0.1", 927 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 928 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 929 | }, 930 | "path-to-regexp": { 931 | "version": "0.1.7", 932 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 933 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 934 | }, 935 | "performance-now": { 936 | "version": "2.1.0", 937 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 938 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 939 | }, 940 | "pg": { 941 | "version": "7.4.1", 942 | "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", 943 | "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", 944 | "requires": { 945 | "buffer-writer": "1.0.1", 946 | "js-string-escape": "1.0.1", 947 | "packet-reader": "0.3.1", 948 | "pg-connection-string": "0.1.3", 949 | "pg-pool": "2.0.3", 950 | "pg-types": "1.12.1", 951 | "pgpass": "1.0.2", 952 | "semver": "4.3.2" 953 | } 954 | }, 955 | "pg-connection-string": { 956 | "version": "0.1.3", 957 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", 958 | "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" 959 | }, 960 | "pg-pool": { 961 | "version": "2.0.3", 962 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", 963 | "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" 964 | }, 965 | "pg-types": { 966 | "version": "1.12.1", 967 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", 968 | "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", 969 | "requires": { 970 | "postgres-array": "1.0.2", 971 | "postgres-bytea": "1.0.0", 972 | "postgres-date": "1.0.3", 973 | "postgres-interval": "1.1.1" 974 | } 975 | }, 976 | "pgpass": { 977 | "version": "1.0.2", 978 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", 979 | "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", 980 | "requires": { 981 | "split": "1.0.1" 982 | } 983 | }, 984 | "postgres-array": { 985 | "version": "1.0.2", 986 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", 987 | "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" 988 | }, 989 | "postgres-bytea": { 990 | "version": "1.0.0", 991 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 992 | "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" 993 | }, 994 | "postgres-date": { 995 | "version": "1.0.3", 996 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", 997 | "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" 998 | }, 999 | "postgres-interval": { 1000 | "version": "1.1.1", 1001 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", 1002 | "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", 1003 | "requires": { 1004 | "xtend": "4.0.1" 1005 | } 1006 | }, 1007 | "process-nextick-args": { 1008 | "version": "2.0.0", 1009 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 1010 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 1011 | }, 1012 | "proxy-addr": { 1013 | "version": "2.0.3", 1014 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", 1015 | "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", 1016 | "requires": { 1017 | "forwarded": "0.1.2", 1018 | "ipaddr.js": "1.6.0" 1019 | } 1020 | }, 1021 | "pump": { 1022 | "version": "3.0.0", 1023 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 1024 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 1025 | "requires": { 1026 | "end-of-stream": "1.4.1", 1027 | "once": "1.4.0" 1028 | } 1029 | }, 1030 | "pumpify": { 1031 | "version": "1.4.0", 1032 | "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.4.0.tgz", 1033 | "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", 1034 | "requires": { 1035 | "duplexify": "3.5.4", 1036 | "inherits": "2.0.3", 1037 | "pump": "2.0.1" 1038 | }, 1039 | "dependencies": { 1040 | "pump": { 1041 | "version": "2.0.1", 1042 | "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", 1043 | "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", 1044 | "requires": { 1045 | "end-of-stream": "1.4.1", 1046 | "once": "1.4.0" 1047 | } 1048 | } 1049 | } 1050 | }, 1051 | "punycode": { 1052 | "version": "1.4.1", 1053 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 1054 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 1055 | }, 1056 | "qs": { 1057 | "version": "6.5.1", 1058 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 1059 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 1060 | }, 1061 | "range-parser": { 1062 | "version": "1.2.0", 1063 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1064 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1065 | }, 1066 | "raw-body": { 1067 | "version": "2.3.2", 1068 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 1069 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 1070 | "requires": { 1071 | "bytes": "3.0.0", 1072 | "http-errors": "1.6.2", 1073 | "iconv-lite": "0.4.19", 1074 | "unpipe": "1.0.0" 1075 | }, 1076 | "dependencies": { 1077 | "depd": { 1078 | "version": "1.1.1", 1079 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 1080 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 1081 | }, 1082 | "http-errors": { 1083 | "version": "1.6.2", 1084 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 1085 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 1086 | "requires": { 1087 | "depd": "1.1.1", 1088 | "inherits": "2.0.3", 1089 | "setprototypeof": "1.0.3", 1090 | "statuses": "1.5.0" 1091 | } 1092 | }, 1093 | "setprototypeof": { 1094 | "version": "1.0.3", 1095 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1096 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1097 | } 1098 | } 1099 | }, 1100 | "readable-stream": { 1101 | "version": "2.3.6", 1102 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1103 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1104 | "requires": { 1105 | "core-util-is": "1.0.2", 1106 | "inherits": "2.0.3", 1107 | "isarray": "1.0.0", 1108 | "process-nextick-args": "2.0.0", 1109 | "safe-buffer": "5.1.1", 1110 | "string_decoder": "1.1.1", 1111 | "util-deprecate": "1.0.2" 1112 | } 1113 | }, 1114 | "redis": { 1115 | "version": "2.8.0", 1116 | "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", 1117 | "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", 1118 | "requires": { 1119 | "double-ended-queue": "2.1.0-0", 1120 | "redis-commands": "1.3.5", 1121 | "redis-parser": "2.6.0" 1122 | } 1123 | }, 1124 | "redis-commands": { 1125 | "version": "1.3.5", 1126 | "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", 1127 | "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" 1128 | }, 1129 | "redis-parser": { 1130 | "version": "2.6.0", 1131 | "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", 1132 | "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" 1133 | }, 1134 | "reinterval": { 1135 | "version": "1.1.0", 1136 | "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", 1137 | "integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=" 1138 | }, 1139 | "remove-trailing-separator": { 1140 | "version": "1.1.0", 1141 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 1142 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" 1143 | }, 1144 | "request": { 1145 | "version": "2.85.0", 1146 | "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", 1147 | "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", 1148 | "requires": { 1149 | "aws-sign2": "0.7.0", 1150 | "aws4": "1.7.0", 1151 | "caseless": "0.12.0", 1152 | "combined-stream": "1.0.6", 1153 | "extend": "3.0.1", 1154 | "forever-agent": "0.6.1", 1155 | "form-data": "2.3.2", 1156 | "har-validator": "5.0.3", 1157 | "hawk": "6.0.2", 1158 | "http-signature": "1.2.0", 1159 | "is-typedarray": "1.0.0", 1160 | "isstream": "0.1.2", 1161 | "json-stringify-safe": "5.0.1", 1162 | "mime-types": "2.1.18", 1163 | "oauth-sign": "0.8.2", 1164 | "performance-now": "2.1.0", 1165 | "qs": "6.5.1", 1166 | "safe-buffer": "5.1.1", 1167 | "stringstream": "0.0.5", 1168 | "tough-cookie": "2.3.4", 1169 | "tunnel-agent": "0.6.0", 1170 | "uuid": "3.2.1" 1171 | } 1172 | }, 1173 | "safe-buffer": { 1174 | "version": "5.1.1", 1175 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1176 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1177 | }, 1178 | "semver": { 1179 | "version": "4.3.2", 1180 | "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", 1181 | "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" 1182 | }, 1183 | "send": { 1184 | "version": "0.16.2", 1185 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 1186 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 1187 | "requires": { 1188 | "debug": "2.6.9", 1189 | "depd": "1.1.2", 1190 | "destroy": "1.0.4", 1191 | "encodeurl": "1.0.2", 1192 | "escape-html": "1.0.3", 1193 | "etag": "1.8.1", 1194 | "fresh": "0.5.2", 1195 | "http-errors": "1.6.3", 1196 | "mime": "1.4.1", 1197 | "ms": "2.0.0", 1198 | "on-finished": "2.3.0", 1199 | "range-parser": "1.2.0", 1200 | "statuses": "1.4.0" 1201 | }, 1202 | "dependencies": { 1203 | "statuses": { 1204 | "version": "1.4.0", 1205 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 1206 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 1207 | } 1208 | } 1209 | }, 1210 | "serve-static": { 1211 | "version": "1.13.2", 1212 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 1213 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 1214 | "requires": { 1215 | "encodeurl": "1.0.2", 1216 | "escape-html": "1.0.3", 1217 | "parseurl": "1.3.2", 1218 | "send": "0.16.2" 1219 | } 1220 | }, 1221 | "setprototypeof": { 1222 | "version": "1.1.0", 1223 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 1224 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 1225 | }, 1226 | "sntp": { 1227 | "version": "2.1.0", 1228 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", 1229 | "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", 1230 | "requires": { 1231 | "hoek": "4.2.1" 1232 | } 1233 | }, 1234 | "sorted-array-functions": { 1235 | "version": "1.1.0", 1236 | "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.1.0.tgz", 1237 | "integrity": "sha512-zq6fLdGQixb9VZfT/tLgU+LzoedJyTbcf1I/TKETFeUVoWIfcs5HNr+SJSvQJLXRlEZjB1gpILTrxamxAdCcgA==" 1238 | }, 1239 | "split": { 1240 | "version": "1.0.1", 1241 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", 1242 | "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", 1243 | "requires": { 1244 | "through": "2.3.8" 1245 | } 1246 | }, 1247 | "split2": { 1248 | "version": "2.2.0", 1249 | "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", 1250 | "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", 1251 | "requires": { 1252 | "through2": "2.0.3" 1253 | } 1254 | }, 1255 | "sshpk": { 1256 | "version": "1.14.1", 1257 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", 1258 | "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", 1259 | "requires": { 1260 | "asn1": "0.2.3", 1261 | "assert-plus": "1.0.0", 1262 | "bcrypt-pbkdf": "1.0.1", 1263 | "dashdash": "1.14.1", 1264 | "ecc-jsbn": "0.1.1", 1265 | "getpass": "0.1.7", 1266 | "jsbn": "0.1.1", 1267 | "tweetnacl": "0.14.5" 1268 | } 1269 | }, 1270 | "stack-trace": { 1271 | "version": "0.0.10", 1272 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 1273 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 1274 | }, 1275 | "statuses": { 1276 | "version": "1.5.0", 1277 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1278 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 1279 | }, 1280 | "stream-shift": { 1281 | "version": "1.0.0", 1282 | "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", 1283 | "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" 1284 | }, 1285 | "string_decoder": { 1286 | "version": "1.1.1", 1287 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1288 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1289 | "requires": { 1290 | "safe-buffer": "5.1.1" 1291 | } 1292 | }, 1293 | "stringstream": { 1294 | "version": "0.0.5", 1295 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 1296 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 1297 | }, 1298 | "through": { 1299 | "version": "2.3.8", 1300 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1301 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 1302 | }, 1303 | "through2": { 1304 | "version": "2.0.3", 1305 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", 1306 | "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", 1307 | "requires": { 1308 | "readable-stream": "2.3.6", 1309 | "xtend": "4.0.1" 1310 | } 1311 | }, 1312 | "through2-filter": { 1313 | "version": "2.0.0", 1314 | "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", 1315 | "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", 1316 | "requires": { 1317 | "through2": "2.0.3", 1318 | "xtend": "4.0.1" 1319 | } 1320 | }, 1321 | "to-absolute-glob": { 1322 | "version": "2.0.2", 1323 | "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", 1324 | "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", 1325 | "requires": { 1326 | "is-absolute": "1.0.0", 1327 | "is-negated-glob": "1.0.0" 1328 | } 1329 | }, 1330 | "tough-cookie": { 1331 | "version": "2.3.4", 1332 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", 1333 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", 1334 | "requires": { 1335 | "punycode": "1.4.1" 1336 | } 1337 | }, 1338 | "tunnel-agent": { 1339 | "version": "0.6.0", 1340 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1341 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1342 | "requires": { 1343 | "safe-buffer": "5.1.1" 1344 | } 1345 | }, 1346 | "tweetnacl": { 1347 | "version": "0.14.5", 1348 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1349 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 1350 | "optional": true 1351 | }, 1352 | "type-is": { 1353 | "version": "1.6.16", 1354 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 1355 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 1356 | "requires": { 1357 | "media-typer": "0.3.0", 1358 | "mime-types": "2.1.18" 1359 | } 1360 | }, 1361 | "typedarray": { 1362 | "version": "0.0.6", 1363 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1364 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 1365 | }, 1366 | "ultron": { 1367 | "version": "1.1.1", 1368 | "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", 1369 | "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" 1370 | }, 1371 | "unc-path-regex": { 1372 | "version": "0.1.2", 1373 | "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", 1374 | "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" 1375 | }, 1376 | "unique-stream": { 1377 | "version": "2.2.1", 1378 | "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", 1379 | "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", 1380 | "requires": { 1381 | "json-stable-stringify": "1.0.1", 1382 | "through2-filter": "2.0.0" 1383 | } 1384 | }, 1385 | "unpipe": { 1386 | "version": "1.0.0", 1387 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1388 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1389 | }, 1390 | "util": { 1391 | "version": "0.10.3", 1392 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 1393 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 1394 | "requires": { 1395 | "inherits": "2.0.1" 1396 | }, 1397 | "dependencies": { 1398 | "inherits": { 1399 | "version": "2.0.1", 1400 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 1401 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" 1402 | } 1403 | } 1404 | }, 1405 | "util-deprecate": { 1406 | "version": "1.0.2", 1407 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1408 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1409 | }, 1410 | "utils-merge": { 1411 | "version": "1.0.1", 1412 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1413 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1414 | }, 1415 | "uuid": { 1416 | "version": "3.2.1", 1417 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 1418 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 1419 | }, 1420 | "vary": { 1421 | "version": "1.1.2", 1422 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1423 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1424 | }, 1425 | "verror": { 1426 | "version": "1.10.0", 1427 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1428 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1429 | "requires": { 1430 | "assert-plus": "1.0.0", 1431 | "core-util-is": "1.0.2", 1432 | "extsprintf": "1.3.0" 1433 | } 1434 | }, 1435 | "websocket-stream": { 1436 | "version": "5.1.2", 1437 | "resolved": "https://registry.npmjs.org/websocket-stream/-/websocket-stream-5.1.2.tgz", 1438 | "integrity": "sha512-lchLOk435iDWs0jNuL+hiU14i3ERSrMA0IKSiJh7z6X/i4XNsutBZrtqu2CPOZuA4G/zabiqVAos0vW+S7GEVw==", 1439 | "requires": { 1440 | "duplexify": "3.5.4", 1441 | "inherits": "2.0.3", 1442 | "readable-stream": "2.3.6", 1443 | "safe-buffer": "5.1.1", 1444 | "ws": "3.3.3", 1445 | "xtend": "4.0.1" 1446 | } 1447 | }, 1448 | "winston": { 1449 | "version": "2.4.2", 1450 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", 1451 | "integrity": "sha512-4S/Ad4ZfSNl8OccCLxnJmNISWcm2joa6Q0YGDxlxMzH0fgSwWsjMt+SmlNwCqdpaPg3ev1HKkMBsIiXeSUwpbA==", 1452 | "requires": { 1453 | "async": "1.0.0", 1454 | "colors": "1.0.3", 1455 | "cycle": "1.0.3", 1456 | "eyes": "0.1.8", 1457 | "isstream": "0.1.2", 1458 | "stack-trace": "0.0.10" 1459 | } 1460 | }, 1461 | "wrappy": { 1462 | "version": "1.0.2", 1463 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1464 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1465 | }, 1466 | "ws": { 1467 | "version": "3.3.3", 1468 | "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", 1469 | "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", 1470 | "requires": { 1471 | "async-limiter": "1.0.0", 1472 | "safe-buffer": "5.1.1", 1473 | "ultron": "1.1.1" 1474 | } 1475 | }, 1476 | "xhr2": { 1477 | "version": "0.1.4", 1478 | "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.1.4.tgz", 1479 | "integrity": "sha1-f4dliEdxbbUCYyOBL4GMras4el8=" 1480 | }, 1481 | "xtend": { 1482 | "version": "4.0.1", 1483 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1484 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" 1485 | } 1486 | } 1487 | } 1488 | --------------------------------------------------------------------------------