init
This commit is contained in:
commit
83e164ef95
10 changed files with 647 additions and 0 deletions
179
.gitignore
vendored
Normal file
179
.gitignore
vendored
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Build
|
||||||
|
a.out
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# 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
|
||||||
|
\*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
\*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
\*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.\*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Philip Ahlqvist
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
55
README.md
Normal file
55
README.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Simple SPV Client
|
||||||
|
|
||||||
|
A simple SPV client that aims for easy integration primarily with LBRY Hub servers.
|
||||||
|
|
||||||
|
To see what methods that the SPV will answer on, check [this website](https://electrumx.readthedocs.io/en/latest/protocol-methods.html).
|
||||||
|
This client doesn't try to decode the data recieved, rather it gives you the raw JSONRPC that the SPV replied with.
|
||||||
|
|
||||||
|
The claimtrie methods specific to LBRY will return a Base64 encoded protobuf as the result. You'll need to decode the result according to the [LBRY types](https://github.com/lbryio/types).
|
||||||
|
|
||||||
|
## Good Resources
|
||||||
|
* [Protocol Methods](https://electrumx.readthedocs.io/en/latest/protocol-methods.html)
|
||||||
|
* [Documentation of the full Electrum Protocol](https://github.com/ben221199/Electrum-Protocol)
|
||||||
|
* [SPV Monitor](https://1209k.com/bitcoin-eye/ele.php?chain=lbc)
|
||||||
|
* [LBRY types](https://github.com/lbryio/types).
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Get started:
|
||||||
|
> You need to have [Bun](https://bun.sh/) installed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/pigges/simple-spv-client.git
|
||||||
|
cd simple-spv-client
|
||||||
|
bun install
|
||||||
|
bun start
|
||||||
|
```
|
||||||
|
|
||||||
|
The startup looks like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|-------------------|
|
||||||
|
| Simple SPV Client |
|
||||||
|
|-------------------|
|
||||||
|
|
||||||
|
Which SPV server should be used?
|
||||||
|
1. Default (a-hub1.odysee.com:50001)
|
||||||
|
2. Custom
|
||||||
|
Answer:
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also choose to use arguments to directly get a response:
|
||||||
|
```bash
|
||||||
|
bun start --server "server" command {your command}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
You can build the project to get an executable:
|
||||||
|
> This will give you a file named a.out
|
||||||
|
```bash
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
22
jsconfig.json
Normal file
22
jsconfig.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"composite": true,
|
||||||
|
"strict": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"types": [
|
||||||
|
"bun-types" // add Bun global
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
20
package.json
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "simple-spv-client",
|
||||||
|
"module": "src/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "bun run src/index.js",
|
||||||
|
"dev": "bun --watch run src/index.js",
|
||||||
|
"build": "bun build src/index.js --compile --outfile a.out"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bun-types": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.8",
|
||||||
|
"protobufjs": "^7.2.5"
|
||||||
|
}
|
||||||
|
}
|
83
src/Cli.js
Normal file
83
src/Cli.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Class to handle the CLI interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DEFAULT_SPV = ["a-hub1.odysee.com", 50001];
|
||||||
|
|
||||||
|
async function input(msg) {
|
||||||
|
process.stdout.write(msg);
|
||||||
|
for await (const line of console) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function options(question, opts) {
|
||||||
|
let answer;
|
||||||
|
|
||||||
|
while (!answer) {
|
||||||
|
console.log(question);
|
||||||
|
for (let i = 0; i < opts.length; i++) console.log(`${i+1}. ${opts[i]}`);
|
||||||
|
|
||||||
|
answer = await input("Answer: ");
|
||||||
|
|
||||||
|
if (isNaN(answer) || (answer < 1 || answer > opts.length)) {
|
||||||
|
console.log("\nInvalid input!\n");
|
||||||
|
answer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("");
|
||||||
|
|
||||||
|
return parseInt(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Cli {
|
||||||
|
|
||||||
|
constructor(client) {
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
// this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.server = await this.askServer();
|
||||||
|
|
||||||
|
await this.client.connect(this.server);
|
||||||
|
await this.client.ping();
|
||||||
|
|
||||||
|
this.mainLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
async askServer() {
|
||||||
|
let server;
|
||||||
|
|
||||||
|
server = await options("Which SPV server should be used?", [`Default (${DEFAULT_SPV.join(':')})`, "Custom"]);
|
||||||
|
|
||||||
|
if (server === 1) return DEFAULT_SPV;
|
||||||
|
server = null;
|
||||||
|
|
||||||
|
while (!server) {
|
||||||
|
let resp = await input("Choose an SPV server: ");
|
||||||
|
resp = resp.split(':');
|
||||||
|
if (resp.length !== 2 || isNaN(resp[1])) continue;
|
||||||
|
resp[1] = parseInt(resp[1]);
|
||||||
|
|
||||||
|
server = resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mainLoop() {
|
||||||
|
let cmd;
|
||||||
|
while (true) {
|
||||||
|
cmd = (await input("Send command: ")).split(' ');
|
||||||
|
const method = cmd[0];
|
||||||
|
|
||||||
|
const params = cmd.length > 1 ? cmd.shift() && cmd : "";
|
||||||
|
|
||||||
|
console.log(await this.client.sendRequest(method, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
src/Client.js
Normal file
60
src/Client.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Class that manages the SPV connection
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Spv from "./Spv.js";
|
||||||
|
|
||||||
|
export default class Client {
|
||||||
|
|
||||||
|
constructor(log=true, timeout=5) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
this.running = false;
|
||||||
|
this.log = log;
|
||||||
|
this.keepAliveId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(server) {
|
||||||
|
this.connection = new Spv(server, this.log, this.timeout);
|
||||||
|
await this.connection.connect();
|
||||||
|
this.running = true;
|
||||||
|
this.keepAlive();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.running = false;
|
||||||
|
clearInterval(this.keepAliveId);
|
||||||
|
this.connection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
kill() {
|
||||||
|
delete this.connection;
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ping() {
|
||||||
|
const p = await this.connection.ping();
|
||||||
|
console.log(`Latency to ${this.connection.server.join(':')} was ${p}ms.`);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendRequest(method, params=[]) {
|
||||||
|
const startTime = performance.now();
|
||||||
|
const resp = this.connection.sendRequest(method, params);
|
||||||
|
const endTime = performance.now();
|
||||||
|
// this.responseTime = endTime - startTime;
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
async keepAlive() {
|
||||||
|
try {
|
||||||
|
this.keepAliveId = setInterval(async ()=>{
|
||||||
|
// if (!this.running) return clearInterval(this.keepAliveId);
|
||||||
|
await this.sendRequest('server.ping');
|
||||||
|
// logger.info("sent keepalive ping");
|
||||||
|
}, 1000 * 30);
|
||||||
|
} catch (err) {
|
||||||
|
logger.err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
src/Spv.js
Normal file
161
src/Spv.js
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Class that connects to the SPV server and handles communication
|
||||||
|
*/
|
||||||
|
|
||||||
|
function genHexString(len) {
|
||||||
|
let output = '';
|
||||||
|
for (let i = 0; i < len; ++i) {
|
||||||
|
output += (Math.floor(Math.random() * 16)).toString(16);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Spv {
|
||||||
|
#socket;
|
||||||
|
|
||||||
|
constructor(server, log=true, timeout) {
|
||||||
|
this.server = server;
|
||||||
|
this.timeout = timeout * 1000;
|
||||||
|
this.log = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect() {
|
||||||
|
|
||||||
|
let t; // Id for the connection timeout
|
||||||
|
|
||||||
|
// Establish a connection to the server
|
||||||
|
this.#socket = await Promise.race([
|
||||||
|
Bun.connect({
|
||||||
|
hostname: this.server[0],
|
||||||
|
port: this.server[1],
|
||||||
|
|
||||||
|
socket: {
|
||||||
|
// Handle incomming data stream
|
||||||
|
data(socket, data) {
|
||||||
|
// Clear old data
|
||||||
|
if ((socket.lastData - performance.now()) / 1000 > 5) return socket.data = new Array();
|
||||||
|
|
||||||
|
// Append new data
|
||||||
|
socket.data.push(data);
|
||||||
|
|
||||||
|
// Only do more if the data is the end of a message
|
||||||
|
if (!data.includes(Buffer.from('\n'))) return;
|
||||||
|
|
||||||
|
// Concatenate the full data into one Buffer
|
||||||
|
const buffer = Buffer.concat(socket.data);
|
||||||
|
socket.data = new Array(); // Clear data
|
||||||
|
|
||||||
|
// Try to recreate a JSONRPC response from the recieved data;
|
||||||
|
let rpc;
|
||||||
|
try {
|
||||||
|
rpc = JSON.parse(buffer.toString());
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Recieved data was corrupt!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(socket.events).includes(rpc.method)) {
|
||||||
|
socket.events[rpc.method](rpc.params[0]);
|
||||||
|
return socket.data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(socket.requests).includes(rpc.id)) {
|
||||||
|
// console.log(rpc);
|
||||||
|
socket.requests[rpc.id](rpc);
|
||||||
|
return socket.data = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
open(socket) {
|
||||||
|
socket.requests = {}; // Store handlers for waiting requests
|
||||||
|
socket.events = {}; // Store handlers for subscribed events
|
||||||
|
socket.data = new Array(); // Temporary hold incomming data
|
||||||
|
socket.lastData = performance.now();
|
||||||
|
},
|
||||||
|
close(socket) {
|
||||||
|
//logger.info(`connection lost to ${socket.remoteAddress}`)
|
||||||
|
//console.log(socket);
|
||||||
|
},
|
||||||
|
drain(socket) {
|
||||||
|
//console.log(socket);
|
||||||
|
},
|
||||||
|
error(socket, error) {
|
||||||
|
console.log(error);
|
||||||
|
},
|
||||||
|
|
||||||
|
// client-specific handlers
|
||||||
|
connectError(socket, error) {
|
||||||
|
console.log("connection error");
|
||||||
|
}, // connection failed
|
||||||
|
end(socket) {
|
||||||
|
console.log(`connection to ${socket.remoteAddress} closed by server`);
|
||||||
|
// console.log("end");
|
||||||
|
}, // connection closed by server
|
||||||
|
timeout(socket) {
|
||||||
|
console.log(`connection to ${socket.remoteAddress} timed out`);
|
||||||
|
}, // connection timed out
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
t = setTimeout(() => {
|
||||||
|
if (log) logger.warn(`could not reach ${this.hostname}:${this.port}`)
|
||||||
|
reject();
|
||||||
|
}, this.timeout)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
clearTimeout(t);
|
||||||
|
|
||||||
|
if (this.log && this.available()) console.log(`Connected to ${this.server.join(':')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the TCP connection
|
||||||
|
disconnect(log=true) {
|
||||||
|
this.#socket.flush();
|
||||||
|
this.#socket.end();
|
||||||
|
if (this.log) console.log(`Closed connection to ${this.server.join(':')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
available() {
|
||||||
|
return this.#socket.readyState === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRemoteAddress() {
|
||||||
|
return this.#socket.remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time the time it takes to ping the server (returns in milliseconds)
|
||||||
|
async ping() {
|
||||||
|
const startTime = performance.now();
|
||||||
|
await this.sendRequest('server.ping');
|
||||||
|
const endTime = performance.now();
|
||||||
|
const time = endTime - startTime;
|
||||||
|
return Math.floor((time - Math.floor(time)) * 10) > 4 ? Math.ceil(time) : Math.floor(time); // Round up if first decimal is over 4
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendRequest(method, params) {
|
||||||
|
const id = genHexString(32);
|
||||||
|
let data;
|
||||||
|
|
||||||
|
const resp = new Promise((resolve, reject) => {
|
||||||
|
this.#socket.requests[id] = (e) => {
|
||||||
|
data = e;
|
||||||
|
resolve();
|
||||||
|
// TODO you might want to reject in case an error occurs here, so that your application won't halt
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#socket.write(JSON.stringify({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": method,
|
||||||
|
"params": params,
|
||||||
|
"id": id
|
||||||
|
}) + '\n');
|
||||||
|
|
||||||
|
setTimeout(() => reject(new Error("Timeout")), this.timeout);
|
||||||
|
})
|
||||||
|
|
||||||
|
await resp;
|
||||||
|
this.#socket.requests[id] = undefined;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/index.js
Normal file
46
src/index.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* A simple SPV client that aims for easy integration primarily with LBRY Hub servers.
|
||||||
|
* The codebase for the hub server: https://github.com/lbryio/hub
|
||||||
|
*/
|
||||||
|
|
||||||
|
import minimist from "minimist";
|
||||||
|
import Client from "./Client.js";
|
||||||
|
import Cli from "./Cli.js";
|
||||||
|
|
||||||
|
main();
|
||||||
|
async function main() {
|
||||||
|
const args = minimist(Bun.argv);
|
||||||
|
|
||||||
|
// Handle command directly with flags
|
||||||
|
// Command structure: bin --server "serverAddress" command "command"
|
||||||
|
if (args['server'] && args._.includes('command')) {
|
||||||
|
const server = args['server'].split(':');
|
||||||
|
const cmd = args._.splice(args._.indexOf('command')+1);
|
||||||
|
|
||||||
|
const method = cmd[0];
|
||||||
|
|
||||||
|
const params = cmd.length > 1 ? cmd.shift() && cmd : "";
|
||||||
|
|
||||||
|
const client = new Client(false);
|
||||||
|
await client.connect([server[0], parseInt(server[1])]);
|
||||||
|
|
||||||
|
console.log(await client.sendRequest(method, params));
|
||||||
|
|
||||||
|
client.disconnect();
|
||||||
|
// client.kill();
|
||||||
|
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the Client
|
||||||
|
const client = new Client();
|
||||||
|
|
||||||
|
// Initialize the CLI interface
|
||||||
|
const cli = new Cli(client);
|
||||||
|
|
||||||
|
console.log("|-------------------|");
|
||||||
|
console.log("| Simple SPV Client |");
|
||||||
|
console.log("|-------------------|\n");
|
||||||
|
|
||||||
|
cli.start();
|
||||||
|
}
|
Loading…
Reference in a new issue