utils.js
1 let http = require('follow-redirects').http; 2 let https = require('follow-redirects').https; 3 const {canonicalHost} = require('./host'); 4 5 const balanceRegex = /([0-9]+) ?([a-zA-Z]*)/; 6 7 function joinPath() { 8 const path = require('path'); 9 return path.join.apply(path.join, arguments); 10 } 11 12 function dirname() { 13 const path = require('path'); 14 return path.dirname.apply(path.dirname, arguments); 15 } 16 17 function filesMatchingPattern(files) { 18 const globule = require('globule'); 19 return globule.find(files, {nonull: true}); 20 } 21 22 function fileMatchesPattern(patterns, intendedPath) { 23 const globule = require('globule'); 24 return globule.isMatch(patterns, intendedPath); 25 } 26 27 function recursiveMerge(target, source) { 28 const merge = require('merge'); 29 return merge.recursive(target, source); 30 } 31 32 function checkIsAvailable(url, callback) { 33 http.get(url, function (_res) { 34 callback(true); 35 }).on('error', function (_res) { 36 callback(false); 37 }); 38 } 39 40 function httpGetRequest(httpObj, url, callback) { 41 httpObj.get(url, function (res) { 42 let body = ''; 43 res.on('data', function (d) { 44 body += d; 45 }); 46 res.on('end', function () { 47 callback(null, body); 48 }); 49 }).on('error', function (err) { 50 callback(err); 51 }); 52 } 53 54 function httpGet(url, callback) { 55 httpGetRequest(http, url, callback); 56 } 57 58 function httpsGet(url, callback) { 59 httpGetRequest(https, url, callback); 60 } 61 62 function httpGetJson(url, callback) { 63 httpGetRequest(http, url, function (err, body) { 64 try { 65 let parsed = JSON.parse(body); 66 return callback(err, parsed); 67 } catch (e) { 68 return callback(e); 69 } 70 }); 71 } 72 73 function httpsGetJson(url, callback) { 74 httpGetRequest(https, url, function (err, body) { 75 try { 76 let parsed = JSON.parse(body); 77 return callback(err, parsed); 78 } catch (e) { 79 return callback(e); 80 } 81 }); 82 } 83 84 function getJson(url, cb) { 85 if (url.indexOf('https') === 0) { 86 return httpsGetJson(url, cb); 87 } 88 httpGetJson(url, cb); 89 } 90 91 function pingEndpoint(host, port, type, protocol, origin, callback) { 92 const options = { 93 protocolVersion: 13, 94 perMessageDeflate: true, 95 origin: origin, 96 host: host, 97 port: port 98 }; 99 if (type === 'ws') { 100 options.headers = { 101 'Sec-WebSocket-Version': 13, 102 Connection: 'Upgrade', 103 Upgrade: 'websocket', 104 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits', 105 Origin: origin 106 }; 107 } 108 let req; 109 // remove trailing api key from infura, ie rinkeby.infura.io/nmY8WtT4QfEwz2S7wTbl 110 if (options.host.indexOf('/') > -1) { 111 options.host = options.host.split('/')[0]; 112 } 113 if (protocol === 'https') { 114 req = require('https').get(options); 115 } else { 116 req = require('http').get(options); 117 } 118 119 req.on('error', (err) => { 120 callback(err); 121 }); 122 123 req.on('response', (_response) => { 124 callback(); 125 }); 126 127 req.on('upgrade', (_res, _socket, _head) => { 128 callback(); 129 }); 130 } 131 132 function runCmd(cmd, options, callback) { 133 const shelljs = require('shelljs'); 134 options = Object.assign({silent: true, exitOnError: true, async: true}, options || {}); 135 const outputToConsole = !options.silent; 136 options.silent = true; 137 let result = shelljs.exec(cmd, options, function (code, stdout) { 138 if(code !== 0) { 139 if (options.exitOnError) { 140 return exit(); 141 } 142 if(typeof callback === 'function') { 143 callback(`shell returned code ${code}`); 144 } 145 } else { 146 if(typeof callback === 'function') { 147 return callback(null, stdout); 148 } 149 } 150 }); 151 152 result.stdout.on('data', function(data) { 153 if(outputToConsole) { 154 console.log(data); 155 } 156 }); 157 158 result.stderr.on('data', function(data) { 159 if (outputToConsole) { 160 console.log(data); 161 } 162 }); 163 } 164 165 function cd(folder) { 166 const shelljs = require('shelljs'); 167 shelljs.cd(folder); 168 } 169 170 function sed(file, pattern, replace) { 171 const shelljs = require('shelljs'); 172 shelljs.sed('-i', pattern, replace, file); 173 } 174 175 function exit(code) { 176 process.exit(code); 177 } 178 179 function downloadFile(url, dest, cb) { 180 const o_fs = require('fs-extra'); 181 var file = o_fs.createWriteStream(dest); 182 (url.substring(0, 5) === 'https' ? https : http).get(url, function (response) { 183 if (response.statusCode !== 200) { 184 cb(`Download failed, response code ${response.statusCode}`); 185 return; 186 } 187 response.pipe(file); 188 file.on('finish', function () { 189 file.close(cb); 190 }); 191 }).on('error', function (err) { 192 o_fs.unlink(dest); 193 cb(err.message); 194 }); 195 } 196 197 function extractTar(filename, packageDirectory, cb) { 198 const o_fs = require('fs-extra'); 199 const tar = require('tar'); 200 o_fs.createReadStream(filename).pipe( 201 tar.x({ 202 strip: 1, 203 C: packageDirectory 204 }).on('end', function () { 205 cb(); 206 }) 207 ); 208 } 209 210 function extractZip(filename, packageDirectory, opts, cb) { 211 const decompress = require('decompress'); 212 213 decompress(filename, packageDirectory, opts).then((_files) => { 214 cb(); 215 }); 216 } 217 218 function proposeAlternative(word, _dictionary, _exceptions) { 219 const propose = require('propose'); 220 let exceptions = _exceptions || []; 221 let dictionary = _dictionary.filter((entry) => { 222 return exceptions.indexOf(entry) < 0; 223 }); 224 return propose(word, dictionary, {threshold: 0.3}); 225 } 226 227 function getExternalContractUrl(file) { 228 const constants = require('../constants'); 229 let url; 230 const RAW_URL = 'https://raw.githubusercontent.com/'; 231 const MALFORMED_ERROR = 'Malformed Github URL for '; 232 if (file.startsWith('https://github')) { 233 const match = file.match(/https:\/\/github\.[a-z]+\/(.*)/); 234 if (!match) { 235 console.error(MALFORMED_ERROR + file); 236 return null; 237 } 238 url = `${RAW_URL}${match[1].replace('blob/', '')}`; 239 } else if (file.startsWith('git')) { 240 // Match values 241 // [0] entire input 242 // [1] git:// 243 // [2] user 244 // [3] repository 245 // [4] path 246 // [5] branch 247 const match = file.match( 248 /(git:\/\/)?github\.[a-z]+\/([-a-zA-Z0-9@:%_+.~#?&=]+)\/([-a-zA-Z0-9@:%_+.~#?&=]+)\/([-a-zA-Z0-9@:%_+.~?\/&=]+)#?([a-zA-Z0-9\/_.-]*)?/ 249 ); 250 if (!match) { 251 console.error(MALFORMED_ERROR + file); 252 return null; 253 } 254 let branch = match[5]; 255 if (!branch) { 256 branch = 'master'; 257 } 258 url = `${RAW_URL}${match[2]}/${match[3]}/${branch}/${match[4]}`; 259 } else if (file.startsWith('http')) { 260 url = file; 261 } else { 262 return null; 263 } 264 const match = url.match( 265 /\.[a-z]+\/([-a-zA-Z0-9@:%_+.~#?&\/=]+)/ 266 ); 267 return { 268 url, 269 filePath: constants.httpContractsDirectory + match[1] 270 }; 271 } 272 273 function hexToNumber(hex) { 274 const Web3 = require('web3'); 275 return Web3.utils.hexToNumber(hex); 276 } 277 278 function isHex(hex) { 279 const Web3 = require('web3'); 280 return Web3.utils.isHex(hex); 281 } 282 283 function hashTo32ByteHexString(hash) { 284 if (isHex(hash)) { 285 if (!hash.startsWith('0x')) { 286 hash = '0x' + hash; 287 } 288 return hash; 289 } 290 const multihash = require('multihashes'); 291 let buf = multihash.fromB58String(hash); 292 let digest = multihash.decode(buf).digest; 293 return '0x' + multihash.toHexString(digest); 294 } 295 296 function isValidDomain(domain) { 297 const isValidDomain = require('is-valid-domain'); 298 return isValidDomain(domain); 299 } 300 301 function isValidEthDomain(ethDomain) { 302 if (!isValidDomain(ethDomain)) { 303 return false; 304 } 305 return ethDomain.substring(ethDomain.lastIndexOf('.'), ethDomain.length) === '.eth'; 306 } 307 308 function decodeParams(typesArray, hexString) { 309 var Web3EthAbi = require('web3-eth-abi'); 310 return Web3EthAbi.decodeParameters(typesArray, hexString); 311 } 312 313 function toChecksumAddress(address) { 314 const Web3 = require('web3'); 315 return Web3.utils.toChecksumAddress(address); 316 } 317 318 function sha3(arg) { 319 const Web3 = require('web3'); 320 return Web3.utils.sha3(arg); 321 } 322 323 function soliditySha3(arg) { 324 const Web3 = require('web3'); 325 return Web3.utils.soliditySha3(arg); 326 } 327 328 function normalizeInput(input) { 329 if(typeof input === 'string') return input; 330 let args = Object.values(input); 331 if (args.length === 0) { 332 return ""; 333 } 334 if (args.length === 1) { 335 if (Array.isArray(args[0])) { 336 return args[0].join(','); 337 } 338 return args[0] || ""; 339 } 340 return ('[' + args.map((x) => { 341 if (x === null) { 342 return "null"; 343 } 344 if (x === undefined) { 345 return "undefined"; 346 } 347 if (Array.isArray(x)) { 348 return x.join(','); 349 } 350 return x; 351 }).toString() + ']'); 352 } 353 354 /** 355 * Builds a URL 356 * 357 * @param {string} protocol 358 * The URL protocol, defaults to http. 359 * @param {string} host 360 * The URL host, required. 361 * @param {string} port 362 * The URL port, default to empty string. 363 * @param {string} [type] 364 * Type of connection 365 * @returns {string} the constructued URL, with defaults 366 */ 367 function buildUrl(protocol, host, port, type) { 368 if (!host) throw new Error('utils.buildUrl: parameter \'host\' is required'); 369 if (port) port = ':' + port; 370 else port = ''; 371 if (!protocol) { 372 protocol = type === 'ws' ? 'ws' : 'http'; 373 } 374 return `${protocol}://${host}${port}`; 375 } 376 377 /** 378 * Builds a URL 379 * 380 * @param {object} configObj Object containing protocol, host, and port to be used to construct the url. 381 * * protocol {String} (optional) The URL protocol, defaults to http. 382 * * host {String} (required) The URL host. 383 * * port {String} (optional) The URL port, default to empty string. 384 * @returns {string} the constructued URL, with defaults 385 */ 386 function buildUrlFromConfig(configObj) { 387 if (!configObj) throw new Error('[utils.buildUrlFromConfig]: config object must cannot be null'); 388 if (!configObj.host) throw new Error('[utils.buildUrlFromConfig]: object must contain a \'host\' property'); 389 return this.buildUrl(configObj.protocol, canonicalHost(configObj.host), configObj.port, configObj.type); 390 } 391 392 function deconstructUrl(endpoint) { 393 const matches = endpoint.match(/(ws|https?):\/\/([a-zA-Z0-9_.-]*):?([0-9]*)?/); 394 return { 395 protocol: matches[1], 396 host: matches[2], 397 port: matches[3], 398 type: matches[1] === 'ws' ? 'ws' : 'rpc' 399 }; 400 } 401 402 function getWeiBalanceFromString(balanceString, web3){ 403 if(!web3){ 404 throw new Error(__('[utils.getWeiBalanceFromString]: Missing parameter \'web3\'')); 405 } 406 if (!balanceString) { 407 return 0; 408 } 409 const match = balanceString.match(balanceRegex); 410 if (!match) { 411 throw new Error(__('Unrecognized balance string "%s"', balanceString)); 412 } 413 if (!match[2]) { 414 return web3.utils.toHex(parseInt(match[1], 10)); 415 } 416 417 return web3.utils.toWei(match[1], match[2]); 418 } 419 420 function getHexBalanceFromString(balanceString, web3) { 421 if(!web3){ 422 throw new Error(__('[utils.getWeiBalanceFromString]: Missing parameter \'web3\'')); 423 } 424 if (!balanceString) { 425 return 0xFFFFFFFFFFFFFFFFFF; 426 } 427 if (web3.utils.isHexStrict(balanceString)) { 428 return balanceString; 429 } 430 const match = balanceString.match(balanceRegex); 431 if (!match) { 432 throw new Error(__('Unrecognized balance string "%s"', balanceString)); 433 } 434 if (!match[2]) { 435 return web3.utils.toHex(parseInt(match[1], 10)); 436 } 437 438 return web3.utils.toHex(web3.utils.toWei(match[1], match[2])); 439 } 440 441 function compact(array) { 442 return array.filter(n => n); 443 } 444 445 function groupBy(array, key) { 446 return array.reduce(function (rv, x) { 447 (rv[x[key]] = rv[x[key]] || []).push(x); 448 return rv; 449 }, {}); 450 } 451 452 function sample(array) { 453 return array[Math.floor(Math.random() * array.length)]; 454 } 455 456 function last(array) { 457 return array[array.length - 1]; 458 } 459 460 function interceptLogs(consoleContext, logger) { 461 let context = {}; 462 context.console = consoleContext; 463 464 context.console.log = function () { 465 logger.info(normalizeInput(arguments)); 466 }; 467 context.console.warn = function () { 468 logger.warn(normalizeInput(arguments)); 469 }; 470 context.console.info = function () { 471 logger.info(normalizeInput(arguments)); 472 }; 473 context.console.debug = function () { 474 // TODO: ue JSON.stringify 475 logger.debug(normalizeInput(arguments)); 476 }; 477 context.console.trace = function () { 478 logger.trace(normalizeInput(arguments)); 479 }; 480 context.console.dir = function () { 481 logger.dir(normalizeInput(arguments)); 482 }; 483 } 484 485 function errorMessage(e) { 486 if (typeof e === 'string') { 487 return e; 488 } else if (e && e.message) { 489 return e.message; 490 } 491 return e; 492 } 493 494 function timer(ms) { 495 return new Promise(resolve => setTimeout(resolve, ms)); 496 } 497 498 function isFolder(node) { 499 return node.children && node.children.length; 500 } 501 502 function isNotFolder(node){ 503 return !isFolder(node); 504 } 505 506 function byName(a, b) { 507 return a.name.localeCompare(b.name); 508 } 509 510 function fileTreeSort(nodes){ 511 const folders = nodes.filter(isFolder).sort(byName); 512 const files = nodes.filter(isNotFolder).sort(byName); 513 514 return folders.concat(files); 515 } 516 517 function copyToClipboard(text) { 518 const clipboardy = require('clipboardy'); 519 clipboardy.writeSync(text); 520 } 521 522 function fuzzySearch(text, list, filter) { 523 const fuzzy = require('fuzzy'); 524 return fuzzy.filter(text, list, {extract: (filter || function () {})}) 525 } 526 527 module.exports = { 528 joinPath, 529 dirname, 530 filesMatchingPattern, 531 fileMatchesPattern, 532 recursiveMerge, 533 checkIsAvailable, 534 httpGet, 535 httpsGet, 536 httpGetJson, 537 httpsGetJson, 538 getJson, 539 hexToNumber, 540 isHex, 541 hashTo32ByteHexString, 542 isValidDomain, 543 isValidEthDomain, 544 pingEndpoint, 545 decodeParams, 546 runCmd, 547 cd, 548 sed, 549 exit, 550 downloadFile, 551 extractTar, 552 extractZip, 553 proposeAlternative, 554 getExternalContractUrl, 555 toChecksumAddress, 556 sha3, 557 soliditySha3, 558 normalizeInput, 559 buildUrl, 560 buildUrlFromConfig, 561 deconstructUrl, 562 getWeiBalanceFromString, 563 getHexBalanceFromString, 564 compact, 565 groupBy, 566 sample, 567 last, 568 interceptLogs, 569 errorMessage, 570 timer, 571 fileTreeSort, 572 copyToClipboard, 573 fuzzySearch 574 };