diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00cbbdf --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + diff --git a/example/bitcoin.js b/example/bitcoin.js new file mode 100644 index 0000000..b54fb1f --- /dev/null +++ b/example/bitcoin.js @@ -0,0 +1,23 @@ +const ElectrumClient = require('..') + +const peers = require('electrum-host-parse').getDefaultPeers("bitcoin").filter(v => v.ssl) +const getRandomPeer = () => peers[peers.length * Math.random() | 0] + +const main = async () => { + const peer = getRandomPeer() + console.log('begin connection: ' + JSON.stringify(peer)) + const ecl = new ElectrumClient(peer.ssl, peer.host, 'ssl') + await ecl.connect() + try{ + const ver = await ecl.server_version("2.7.11", "1.0") + console.log(ver) + const balance = await ecl.blockchainAddress_getBalance("12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + console.log(balance) + const unspent = await ecl.blockchainAddress_listunspent("12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + console.log(unspent) + }catch(e){ + console.log(e) + } + await ecl.close() +} +main().catch(console.log) diff --git a/example/ex.js b/example/ex.js new file mode 100644 index 0000000..9ee12a8 --- /dev/null +++ b/example/ex.js @@ -0,0 +1,25 @@ +const Client = require("../lib/electrum_cli") + +const proc = async(cl) => { + try{ + const version = await cl.server_version("2.7.11", "1.0") + console.log(version) + const balance = await cl.blockchainAddress_getBalance("MS43dMzRKfEs99Q931zFECfUhdvtWmbsPt") + console.log(balance) + const utxo = await cl.blockchainAddress_listunspent("MS43dMzRKfEs99Q931zFECfUhdvtWmbsPt") + console.log(utxo) + }catch(e){ + console.log(e) + } +} + +const main = async(port, host) => { + const cl = new Client(port, host); + await cl.connect() + for(let i = 0; i<100; ++i){ + await proc(cl) + } + await cl.close() +} + +main(4444, "localhost") diff --git a/example/raii_client_transaction_send.js b/example/raii_client_transaction_send.js new file mode 100644 index 0000000..7a370b0 --- /dev/null +++ b/example/raii_client_transaction_send.js @@ -0,0 +1,40 @@ +'use strict'; +const ElectrumClient = require('..'); + +const createRaiiClient = (port, host, protocol, options) => { + return (params, promise) => { + const name = params.join(':') + const client = new ElectrumClient(port, host, protocol, options) + console.time(name) + return client.connect().then( () => { + return promise(client) + }).catch( e => { + client.close() + console.timeEnd(name) + throw e + }).then( res => { + client.close() + console.timeEnd(name) + return res + }) + } + +} + +const main = async(hex) => { + const hosts = ['electrum-mona.bitbank.cc', 'electrumx.tamami-foundation.org'] + const host = hosts[Math.floor(Math.random() * hosts.length)] + const connect = createRaiiClient(50001, host, 'tcp') + await connect(['blockchainTransaction_broadcast', hex], async(client) => { + const ver = await client.server_version('2.7.11', '1.0') + console.log(ver) + const result = await client.blockchainTransaction_broadcast(hex) + console.log(result) + }) +} + +const getopt = () => { + return process.argv.slice(2)[0] +} + +main(getopt()).catch(console.log) diff --git a/example/raii_timeout.js b/example/raii_timeout.js new file mode 100644 index 0000000..4fecb3b --- /dev/null +++ b/example/raii_timeout.js @@ -0,0 +1,40 @@ +'use strict'; +const ElectrumClient = require('..'); + +const createRaiiClient = (port, host, protocol, options) => { + return (params, promise) => { + const name = params.join(':') + const client = new ElectrumClient(port, host, protocol, options) + console.time(name) + return client.connect().then( () => { + return promise(client) + }).catch( e => { + client.close() + console.timeEnd(name) + throw e + }).then( res => { + client.close() + console.timeEnd(name) + return res + }) + } + +} + +const main = async(hex) => { + const hosts = ['electrum-mona.bitbank.cc', 'electrumx.tamami-foundation.org'] + const host = hosts[Math.floor(Math.random() * hosts.length)] + const connect = createRaiiClient(50000, host, 'tcp') + await connect(['blockchainTransaction_broadcast', hex], async(client) => { + const ver = await client.server_version('2.7.11', '1.0') + console.log(ver) + const result = await client.blockchainTransaction_broadcast(hex) + console.log(result) + }) +} + +const getopt = () => { + return process.argv.slice(2)[0] +} + +main(getopt()).catch(console.log) diff --git a/example/scripthash.js b/example/scripthash.js new file mode 100644 index 0000000..1e16423 --- /dev/null +++ b/example/scripthash.js @@ -0,0 +1,22 @@ +const ElectrumClient = require('..') + +const main = async () => { + const ecl = new ElectrumClient(50002, 'bitcoins.sk', 'tls') + await ecl.connect() + try{ + const ver = await ecl.server_version("3.0.5", "1.1") + console.log(ver) + const balance = await ecl.blockchainScripthash_getBalance("676ca8550e249787290b987e12cebdb2e9b26d88c003d836ffb1cb03ffcbea7c") + console.log(balance) + const unspent = await ecl.blockchainScripthash_listunspent("676ca8550e249787290b987e12cebdb2e9b26d88c003d836ffb1cb03ffcbea7c") + console.log(unspent) + const history = await ecl.blockchainScripthash_getHistory("676ca8550e249787290b987e12cebdb2e9b26d88c003d836ffb1cb03ffcbea7c") + console.log(history) + const mempool = await ecl.blockchainScripthash_getMempool("676ca8550e249787290b987e12cebdb2e9b26d88c003d836ffb1cb03ffcbea7c") + console.log(mempool) + }catch(e){ + console.log(e) + } + await ecl.close() +} +main().catch(console.log) diff --git a/example/simple.js b/example/simple.js new file mode 100644 index 0000000..06c99a1 --- /dev/null +++ b/example/simple.js @@ -0,0 +1,18 @@ +const ElectrumClient = require('..') + +const main = async () => { + const ecl = new ElectrumClient(995, 'btc.smsys.me', 'tls') + await ecl.connect() + try{ + const ver = await ecl.server_version("2.7.11", "1.0") + console.log(ver) + const balance = await ecl.blockchainAddress_getBalance("12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + console.log(balance) + const unspent = await ecl.blockchainAddress_listunspent("12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX") + console.log(unspent) + }catch(e){ + console.log(e) + } + await ecl.close() +} +main().catch(console.log) diff --git a/example/subscribe.js b/example/subscribe.js new file mode 100644 index 0000000..d9b1a8c --- /dev/null +++ b/example/subscribe.js @@ -0,0 +1,30 @@ +const ElectrumClient = require('..') +const sleep = (ms) => new Promise((resolve,_) => setTimeout(() => resolve(), ms)) + +const main = async () => { + try{ + const ecl = new ElectrumClient(50002, 'bitcoins.sk', 'tls') + ecl.subscribe.on('server.peers.subscribe', console.log) + ecl.subscribe.on('blockchain.numblocks.subscribe', console.log) + ecl.subscribe.on('blockchain.headers.subscribe', console.log) + ecl.subscribe.on('blockchain.address.subscribe', console.log) + ecl.subscribe.on('blockchain.scripthash.subscribe', console.log) + await ecl.connect() + await ecl.server_version("3.0.5", "1.1") + const p1 = await ecl.serverPeers_subscribe() + const p2 = await ecl.blockchainHeaders_subscribe() + // Note: blockchain.numblocks.subscribe is deprecated in protocol version 1.1 + const p3 = await ecl.blockchainAddress_subscribe('1BK45iaPrrd26gKagrXytvz6anrj3hQ2pQ') + // Subscribe to corresponding scripthash for the above address + const p4 = await ecl.blockchainScripthash_subscribe('f3aa57a41424146327e5c88c25db8953dd16c6ab6273cdb74a4404ed4d0f5714') + while(true){ + await sleep(1000) + const ver = await ecl.server_version("3.0.5", "1.1") + } + await ecl.close() + }catch(e){ + console.log("error") + console.log(e) + } +} +main() diff --git a/index.js b/index.js new file mode 100644 index 0000000..90b133f --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/electrum_client'); diff --git a/lib/TlsSocketWrapper.js b/lib/TlsSocketWrapper.js new file mode 100644 index 0000000..e1f9a56 --- /dev/null +++ b/lib/TlsSocketWrapper.js @@ -0,0 +1,149 @@ +/** + * Simple wrapper to mimick Socket class from NET package, since TLS package havs slightly different API. + * We implement several methods that TCP sockets are expected to have. We will proxy call them as soon as + * realt TLS socket will be created (TLS socket created after connection). + */ +class TlsSocketWrapper { + constructor({ tls, verbose = false } = {}) { + this._tls = tls; // dependency injection lol + this._socket = false; + // defaults: + this._timeout = 5000; + this._encoding = 'utf8'; + this._keepAliveEnabled = true; + this._keepAliveinitialDelay = 0; + this._noDelay = true; + this._listeners = {}; + this._verbose = verbose; + } + + setTimeout(timeout) { + if (this._socket) this._socket.setTimeout(timeout); + this._timeout = timeout; + } + + setEncoding(encoding) { + if (this._socket) this._socket.setEncoding(encoding); + this._encoding = encoding; + } + + setKeepAlive(enabled, initialDelay) { + if (this._socket) this._socket.setKeepAlive(enabled, initialDelay); + this._keepAliveEnabled = enabled; + this._keepAliveinitialDelay = initialDelay; + } + + setNoDelay(noDelay) { + if (this._socket) this._socket.setNoDelay(noDelay); + this._noDelay = noDelay; + } + + on(event, listener) { + this._listeners[event] = this._listeners[event] || []; + this._listeners[event].push(listener); + } + + removeListener(event, listener) { + this._listeners[event] = this._listeners[event] || []; + let newListeners = []; + + let found = false; + for (let savedListener of this._listeners[event]) { + if (savedListener == listener) { + // found our listener + found = true; + // we just skip it + } else { + // other listeners should go back to original array + newListeners.push(savedListener); + } + } + + if (found) { + this._listeners[event] = newListeners; + } else { + // something went wrong, lets just cleanup all listeners + this._listeners[event] = []; + } + } + + connect(port, host, callback, verbose = false) { + // resulting TLSSocket extends + this._socket = this._tls.connect({ port: port, host: host, rejectUnauthorized: false }, () => { + // socket.write('{ "id": 5, "method": "blockchain.estimatefee", "params": [2] }\n') + if (this._verbose) console.log('TLS Connected to ', host, port); + return callback(); + }); + + // setting everything that was set to this proxy class + + this._socket.setTimeout(this._timeout); + this._socket.setEncoding(this._encoding); + this._socket.setKeepAlive(this._keepAliveEnabled, this._keepAliveinitialDelay); + this._socket.setNoDelay(this._noDelay); + + // resubscribing to events on newly created socket so we could proxy them to already established listeners + + this._socket.on('data', data => { + this._passOnEvent('data', data); + }); + this._socket.on('end', data => { + this._passOnEvent('end', data); + }); + this._socket.on('timeout', () => { + this._passOnEvent('timeout'); + }); + this._socket.on('onerror', data => { + this._passOnEvent('onerror', data); + }); + this._socket.on('error', data => { + this._passOnEvent('error', data); + }); + this._socket.on('close', data => { + this._passOnEvent('close', data); + }); + this._socket.on('connect', data => { + this._passOnEvent('connect', data); + }); + this._socket.on('secureConnect', data => { + this._passOnEvent('secureConnect', data); + }); + this._socket.on('connection', data => { + this._passOnEvent('connection', data); + }); + } + + _passOnEvent(event, data) { + this._listeners[event] = this._listeners[event] || []; + for (let savedListener of this._listeners[event]) { + savedListener(data); + } + } + + emit(event, data) { + if (this._verbose) { + console.log("Emit Data"); + console.log(event); + console.log(data); + } + this._socket.emit(event, data); + } + + end() { + this._socket.end(); + } + + destroy() { + this._socket.destroy(); + } + + write(data) { + if (this._verbose) { + console.log("Write Data..."); + console.log(data); + } + this._socket.write(data); + } +} + +module.exports = TlsSocketWrapper; diff --git a/lib/client.js b/lib/client.js new file mode 100644 index 0000000..56ede32 --- /dev/null +++ b/lib/client.js @@ -0,0 +1,135 @@ +'use strict' +const EventEmitter = require('events').EventEmitter; +const util = require('./util'); +const initSocket = require('./init_socket'); +const connectSocket = require('./connect_socket'); + +class Client { + constructor(port, host, protocol = 'tcp', options = void 0) { + this.id = 0; + this.port = port; + this.host = host; + this.callback_message_queue = {}; + this.subscribe = new EventEmitter(); + this.conn = initSocket(this, protocol, options); + this.mp = new util.MessageParser((body, n) => { + this.onMessage(body, n); + }); + this.status = 0 + } + + async connect() { + if(this.status) { + return Promise.resolve({ error: false, data: "" }); + } + const connectionResponse = await connectSocket(this.conn, this.port, this.host); + this.status = connectionResponse.error === true ? 0 : 1; + return Promise.resolve(connectionResponse); + } + + close(){ + if (!this.status) { + return; + } + this.conn.end(); + this.conn.destroy(); + this.status = 0; + } + + request(method, params) { + if (!this.status) { + return Promise.reject(new Error('ESOCKET')); + } + return new Promise((resolve, reject) => { + const id = ++this.id; + const content = util.makeRequest(method, params, id); + this.callback_message_queue[id] = util.createPromiseResult(resolve, reject); + this.conn.write(content + '\n'); + }); + } + + requestBatch(method, params, secondParam) { + if (!this.status) { + return Promise.reject(new Error('ESOCKET')); + } + return new Promise((resolve, reject) => { + let arguments_far_calls = {}; + let contents = []; + for (let param of params) { + const id = ++this.id; + if (secondParam !== undefined) { + contents.push(util.makeRequest(method, [param, secondParam], id)); + } else { + contents.push(util.makeRequest(method, [param], id)); + } + arguments_far_calls[id] = param; + } + const content = '[' + contents.join(',') + ']'; + this.callback_message_queue[this.id] = util.createPromiseResultBatch(resolve, reject, arguments_far_calls); + // callback will exist only for max id + this.conn.write(content + '\n'); + }); + } + + response(msg) { + let callback; + if (!msg.id && msg[0] && msg[0].id) { + // this is a response from batch request + for (let m of msg) { + if (m.id && this.callback_message_queue[m.id]) { + callback = this.callback_message_queue[m.id]; + delete this.callback_message_queue[m.id]; + } + } + } else { + callback = this.callback_message_queue[msg.id]; + } + + if (callback) { + delete this.callback_message_queue[msg.id]; + if (msg.error) { + callback(msg.error); + } else { + callback(null, msg.result || msg); + } + } else { + console.log("Can't get callback"); // can't get callback + } + } + + onMessage(body, n){ + const msg = JSON.parse(body); + if (msg instanceof Array) { + this.response(msg); + } else { + if (msg.id !== void 0) { + this.response(msg) + } else { + this.subscribe.emit(msg.method, msg.params); + } + } + } + + onConnect(){ + } + + onClose(){ + Object.keys(this.callback_message_queue).forEach((key) => { + this.callback_message_queue[key](new Error('close connect')) + delete this.callback_message_queue[key] + }) + } + + onRecv(chunk){ + this.mp.run(chunk) + } + + onEnd(){ + } + + onError(e){ + } + +} + +module.exports = Client; diff --git a/lib/connect_socket.js b/lib/connect_socket.js new file mode 100644 index 0000000..eb87228 --- /dev/null +++ b/lib/connect_socket.js @@ -0,0 +1,15 @@ +'use strict'; + +const connectSocket = (conn, port, host) => { + return new Promise((resolve) => { + const errorHandler = (e) => resolve({ error: true, data: e }) + conn.connect(port, host, () => { + conn.removeListener('error', errorHandler); + resolve({ error: false, data: { port, host } }) + }); + conn.on('error', errorHandler) + }) +}; + + +module.exports = connectSocket diff --git a/lib/electrum_client.js b/lib/electrum_client.js new file mode 100644 index 0000000..15a4ef2 --- /dev/null +++ b/lib/electrum_client.js @@ -0,0 +1,368 @@ +const Client = require("./client") +class ElectrumClient extends Client{ + + constructor(port, host, protocol, options){ + super(port, host, protocol, options); + } + onClose(){ + super.onClose() + const list = [ + 'server.peers.subscribe', + 'blockchain.numblocks.subscribe', + 'blockchain.headers.subscribe', + 'blockchain.address.subscribe' + ] + list.forEach(event => this.subscribe.removeAllListeners(event)) + } + server_version(client_name, protocol_version){ + try { + return this.request('server.version', [client_name, protocol_version]); + } catch (e) { + return { error: true, data: e }; + } + } + server_banner(){ + try { + return this.request('server.banner', []); + } catch (e) { + return { error: true, data: e }; + } + } + server_ping(){ + try { + return this.request('server.ping', []); + } catch (e) { + return { error: true, data: e }; + } + } + serverDonation_address(){ + try { + return this.request('server.donation_address', []); + } catch (e) { + return { error: true, data: e }; + } + } + serverPeers_subscribe(){ + try { + return this.request('server.peers.subscribe', []); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_getBalance(address){ + try { + return this.request('blockchain.address.get_balance', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_getHistory(address){ + try { + return this.request('blockchain.address.get_history', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_getMempool(address){ + try { + return this.request('blockchain.address.get_mempool', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_getProof(address){ + try { + return this.request('blockchain.address.get_proof', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_listunspent(address){ + try { + return this.request('blockchain.address.listunspent', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainAddress_subscribe(address){ + try { + return this.request('blockchain.address.subscribe', [address]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainScripthash_getBalance(scripthash){ + try { + return this.request('blockchain.scripthash.get_balance', [scripthash]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainScripthash_getHistory(scripthash){ + try { + return this.request('blockchain.scripthash.get_history', [scripthash]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainScripthash_getMempool(scripthash){ + try { + return this.request('blockchain.scripthash.get_mempool', [scripthash]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainScripthash_listunspent(scripthash){ + try { + return this.request('blockchain.scripthash.listunspent', [scripthash]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainScripthash_subscribe(scripthash){ + try { + return this.request('blockchain.scripthash.subscribe', [scripthash]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainBlock_getHeader(height){ + try { + return this.request('blockchain.block.get_header', [height]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainBlock_getBlockHeader(height){ + try { + return this.request('blockchain.block.header', [height]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainBlock_getChunk(index){ + try { + return this.request('blockchain.block.get_chunk', [index]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainEstimatefee(number){ + try { + return this.request('blockchain.estimatefee', [number]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainHeaders_subscribe(){ + try { + return this.request('blockchain.headers.subscribe', []); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainNumblocks_subscribe(){ + try { + return this.request('blockchain.numblocks.subscribe', []); + } catch (e) { + return { error: true, data: e }; + } + } + blockchain_relayfee(){ + try { + return this.request('blockchain.relayfee', []); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainTransaction_broadcast(rawtx){ + try { + return this.request('blockchain.transaction.broadcast', [rawtx]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainTransaction_get(tx_hash, verbose=false, merkle=false){ + try { + return this.request('blockchain.transaction.get', [tx_hash, verbose]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainTransaction_getMerkle(tx_hash, height){ + try { + return this.request('blockchain.transaction.get_merkle', [tx_hash, height]); + } catch (e) { + return { error: true, data: e }; + } + } + blockchainUtxo_getAddress(tx_hash, index){ + try { + return this.request('blockchain.utxo.get_address', [tx_hash, index]); + } catch (e) { + return { error: true, data: e }; + } + } + + /** + * @param {Object[]} scripthashes - [{ scriptHash: "", address: "", path: "" }] + * @param {string} scripthashes[].scriptHash - The address scripthash. + * @param {string} scripthashes[].address The address. + * @param {string} scripthashes[].path The derivation path of the address. + * @returns {Promise<[{ confirmed: Number, unconfirmed: Number, scriptHash: string, address: string }]|{data: *, error: boolean}>} + */ + async blockchainScripthashes_getBalance(scripthashes){ + try { + const result = []; + await Promise.all(scripthashes.map(async ({ scriptHash = "", address = "", path = "" } = {}) => { + try { + const response = await this.request('blockchain.scripthash.get_balance', [scriptHash]); + const {confirmed, unconfirmed} = response; + const data = { confirmed, unconfirmed, scriptHash, address, path } + result.push(data); + } catch (e) {} + })); + return result; + } catch (e) { + return { error: true, data: e } + } + } + /** + * @param {Object[]} scripthashes - [{ scriptHash: "", address: "", path: "" }] + * @param {string} scripthashes[].scriptHash - The address scripthash. + * @param {string} scripthashes[].address The address. + * @param {string} scripthashes[].path The derivation path of the address. + * @returns {Promise<[{ height: Number, tx_hash: string, scriptHash: string, address: string, path: string }]|{data: *, error: boolean}>} + */ + async blockchainScripthashes_getHistory(scripthashes){ + try { + const result = []; + await Promise.all(scripthashes.map(async ({ scriptHash = "", address = "", path = "" } = {}) => { + try { + const response = await this.request('blockchain.scripthash.get_history', [scriptHash]); + const responseLength = response.length; + if (responseLength > 0) { + response.map((res) => { + try { + const { height, tx_hash } = res; + const data = { height, tx_hash, scriptHash, address, path } + result.push(data); + } catch (e) {} + }); + } + } catch (e) {} + })); + return result; + } catch (e) { + return { error: true, data: e } + } + } + /** + * @param {Object[]} scripthashes - [{ scriptHash: "", address: "", path: "" }] + * @param {string} scripthashes[].scriptHash - The address scripthash. + * @param {string} scripthashes[].address The address. + * @param {string} scripthashes[].path The derivation path of the address. + * @returns {Promise<[]|{data: *, error: boolean}>} + */ + async blockchainScripthashes_getMempool(scripthashes){ + try { + const result = []; + await Promise.all(scripthashes.map(async ({ scriptHash = "", address = "", path = "" } = {}) => { + try { + const response = await this.request('blockchain.scripthash.get_mempool', [scriptHash]); + const responseLength = response.length; + if (responseLength > 0) { + response.map((res) => { + try { + const { height, tx_hash } = res; + const data = { height, tx_hash, scriptHash, address, path } + result.push(data); + } catch (e) {} + }); + } + } catch (e) {} + })); + return result; + } catch (e) { + return { error: true, data: e } + } + } + /** + * @param {Object[]} scripthashes - [{ scriptHash: "", address: "", path: "" }] + * @param {string} scripthashes[].scriptHash - The address scripthash. + * @param {string} scripthashes[].address The address. + * @param {string} scripthashes[].path The derivation path of the address. + * @returns {Promise<[{ height: 0, tx_hash: "", tx_pos: 0, value: 0, scriptHash: "", address: "", path: "" }]|{data: *, error: boolean}>} + */ + async blockchainScripthashes_listunspent(scripthashes){ + try { + const result = []; + await Promise.all(scripthashes.map(async (scripthashData) => { + try { + const { scriptHash, address, path } = scripthashData; + const response = await this.request('blockchain.scripthash.listunspent', [scriptHash]); + const responseLength = response.length; + if (responseLength > 0) { + response.map((res) => { + try { + const {height, tx_hash, tx_pos, value} = res; + const data = {height, tx_hash, tx_pos, value, scriptHash, address, path}; + result.push(data); + } catch (e) {} + }); + } + } catch (e) {} + })); + return result; + } catch (e) { + return { error: true, data: e } + } + } + /** + * @param {Object[]} tx_hashes - [{ tx_hash: string }] + * @param {string} tx_hashes[].tx_hash - The transaction's hash. + * @param {boolean} verbose - The address scripthash. + * @param {boolean} merkle + * @returns {Promise<[]|{data: *, error: boolean}>} + */ + async blockchainTransactions_get(tx_hashes, verbose= false, merkle= false){ + try { + const result = []; + await Promise.all(tx_hashes.map(async (tx) => { + try { + const response = await this.request('blockchain.transaction.get', [tx.tx_hash, verbose]); + if (response) { + try { + const data = Object.assign(tx, response); + result.push(data); + } catch (e) {} + } + } catch (e) {} + })); + return result; + } catch (e) { + return { error: true, data: e } + } + } + + requestBatch(method, params, secondParam) { + const parentPromise = super.requestBatch(method, params, secondParam); + return parentPromise.then(response => { + return response; + }); + } + blockchainScripthash_getBalanceBatch(scripthash) { + return this.requestBatch('blockchain.scripthash.get_balance', scripthash); + } + blockchainScripthash_listunspentBatch(scripthash) { + return this.requestBatch('blockchain.scripthash.listunspent', scripthash); + } + blockchainScripthash_getHistoryBatch(scripthash) { + return this.requestBatch('blockchain.scripthash.get_history', scripthash); + } + blockchainTransaction_getBatch(tx_hash, verbose) { + return this.requestBatch('blockchain.transaction.get', tx_hash, verbose); + } +} + +module.exports = ElectrumClient diff --git a/lib/init_socket.js b/lib/init_socket.js new file mode 100644 index 0000000..a057898 --- /dev/null +++ b/lib/init_socket.js @@ -0,0 +1,54 @@ +'use strict' +const net = require('net'); +const tls = require('tls'); +const TlsSocketWrapper = require('./TlsSocketWrapper.js'); +const TIMEOUT = 5000 + +const getSocket = (protocol, options) => { + switch(protocol){ + case 'tcp': + return new net.Socket(); + case 'tls': + case 'ssl': + if (!tls) throw new Error('tls package could not be loaded'); + return new TlsSocketWrapper({ tls, verbose: false }); + } + throw new Error('unknown protocol') +} + +const initSocket = (self, protocol, options) => { + const conn = getSocket(protocol, options); + conn.setTimeout(TIMEOUT) + conn.setEncoding('utf8') + conn.setKeepAlive(true, 0) + conn.setNoDelay(true) + conn.on('connect', () => { + conn.setTimeout(0) + self.onConnect() + }) + conn.on('close', (e) => { + self.onClose(e) + }) + conn.on('timeout', () => { + //self.onClose(e); + /*const e = new Error('ETIMEDOUT') + e.errorno = 'ETIMEDOUT' + e.code = 'ETIMEDOUT' + e.connect = false + conn.emit('error', e)*/ + }) + conn.on('data', (chunk) => { + conn.setTimeout(0) + self.onRecv(chunk) + }) + conn.on('end', (e) => { + conn.setTimeout(0) + self.onEnd(e) + }) + conn.on('error', (e) => { + self.onError(e) + }) + return conn +} + +module.exports = initSocket diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..19bfb60 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,71 @@ +'use strict' + +const makeRequest = exports.makeRequest = (method, params, id) => { + return JSON.stringify({ + jsonrpc : "2.0", + method : method, + params : params, + id : id, + }) +} + +const createRecuesiveParser = exports.createRecuesiveParser = (max_depth, delimiter) => { + const MAX_DEPTH = max_depth + const DELIMITER = delimiter + const recursiveParser = (n, buffer, callback) => { + if(buffer.length === 0) { + return {code:0, buffer:buffer} + } + if(n > MAX_DEPTH) { + return {code:1, buffer:buffer} + } + const xs = buffer.split(DELIMITER) + if(xs.length === 1){ + return {code:0, buffer:buffer} + } + callback(xs.shift(), n) + return recursiveParser(n + 1, xs.join(DELIMITER), callback) + } + return recursiveParser +} + + +const createPromiseResult = exports.createPromiseResult = (resolve, reject) => { + return (err, result) => { + if(err) reject(err) + else resolve(result) + } +} + +const createPromiseResultBatch = (exports.createPromiseResultBatch = (resolve, reject, argz) => { + return (err, result) => { + if (result && result[0] && result[0].id) { + // this is a batch request response + for (let r of result) { + r.param = argz[r.id]; + } + } + if (err) reject(err); + else resolve(result); + }; +}); + +class MessageParser{ + constructor(callback){ + this.buffer = '' + this.callback = callback + this.recursiveParser = createRecuesiveParser(20, '\n') + } + run(chunk){ + this.buffer += chunk + while(true){ + const res = this.recursiveParser(0, this.buffer, this.callback) + this.buffer = res.buffer + if(res.code === 0){ + break; + } + } + } +} +exports.MessageParser = MessageParser + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3b293ab --- /dev/null +++ b/package-lock.json @@ -0,0 +1,846 @@ +{ + "name": "rn-electrum-client", + "version": "0.0.6", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "electrum-host-parse": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/electrum-host-parse/-/electrum-host-parse-0.1.1.tgz", + "integrity": "sha512-N1ua6Xc5aMcVWPqxlIyiSV/tDGlbGP+S4bIR6KKGDv227VnzXibcin+4t25b5spjDmhfbbk0rIqV1T3MV5WlBQ==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.0.tgz", + "integrity": "sha512-lEWEMq2LMfNJMKeuEwb5UELi+OgFDollXaytR5ggQcHpzG3NP/R7rvixAvF+9/lLsTWhWG+4yD2M70GsM06nxw==", + "dev": true, + "requires": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.4.3", + "debug": "4.2.0", + "diff": "4.0.2", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.6", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.14.0", + "log-symbols": "4.0.0", + "minimatch": "3.0.4", + "ms": "2.1.2", + "nanoid": "3.1.12", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "7.2.0", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.0.2", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "2.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "workerpool": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", + "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "dependencies": { + "camelcase": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.1.0.tgz", + "integrity": "sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ==", + "dev": true + }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..00fc89f --- /dev/null +++ b/package.json @@ -0,0 +1,60 @@ +{ + "name": "rn-electrum-client", + "version": "0.0.7", + "description": "Electrum protocol client for React Native & Node.js", + "main": "index.js", + "scripts": { + "test": "mocha --timeout 10000 --exit -b" + }, + "repository": { + "type": "git", + "url": "git://github.com/synonymdev/rn-electrum-client.git" + }, + "author": "Synonym", + "license": "MIT", + "devDependencies": { + "electrum-host-parse": "*", + "mocha": "^8.2.0" + }, + "peerDependencies": { + "@photon-sdk/react-native-tcp": "^6.0.0" + }, + "react-native": { + "net": "@photon-sdk/react-native-tcp", + "tls": "@photon-sdk/react-native-tcp/tls", + "crypto": "react-native-crypto", + "_stream_transform": "readable-stream/transform", + "_stream_readable": "readable-stream/readable", + "_stream_writable": "readable-stream/writable", + "_stream_duplex": "readable-stream/duplex", + "_stream_passthrough": "readable-stream/passthrough", + "stream": "stream-browserify", + "vm": "vm-browserify" + }, + "browser": { + "net": "@photon-sdk/react-native-tcp", + "tls": "@photon-sdk/react-native-tcp/tls", + "crypto": "react-native-crypto", + "_stream_transform": "readable-stream/transform", + "_stream_readable": "readable-stream/readable", + "_stream_writable": "readable-stream/writable", + "_stream_duplex": "readable-stream/duplex", + "_stream_passthrough": "readable-stream/passthrough", + "stream": "stream-browserify", + "vm": "vm-browserify" + }, + "engines": { + "node": ">=6" + }, + "keywords": [ + "client", + "electrum", + "bitcoin", + "nodejs" + ], + "bugs": { + "url": "https://github.com/synonymdev/rn-electrum-client/issues" + }, + "homepage": "https://github.com/synonymdev/rn-electrum-client#readme", + "dependencies": {} +}