Switch to PNPM, switch from eslint to biome, fix issues

This commit is contained in:
Dojo Coder
2024-09-26 14:16:22 +02:00
parent 687f934533
commit 4859ffbe08
12 changed files with 2206 additions and 8634 deletions

View File

@@ -1,108 +0,0 @@
{
"env": {
"node": true,
"es2021": true,
"mocha": true
},
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"import",
"unicorn"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:unicorn/recommended"
],
"ignorePatterns": ["*.min.js"],
"parserOptions": {
"ecmaVersion": 13,
"sourceType": "module"
},
"rules": {
"array-callback-return": [
"error",
{
"checkForEach": true
}
],
"complexity": "error",
"no-constructor-return": "error",
"no-self-compare": "error",
"no-template-curly-in-string": "warn",
"no-unmodified-loop-condition": "warn",
"no-unreachable-loop": "error",
"require-atomic-updates": "error",
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"never"
],
"max-len": [
"warn",
160
],
"eol-last": "error",
"eqeqeq": [
"error",
"smart"
],
"radix": "error",
"dot-notation": "warn",
"no-array-constructor": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-implicit-coercion": "error",
"no-invalid-this": "error",
"no-iterator": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-multi-assign": "error",
"no-negated-condition": "error",
"no-undef-init": "error",
"no-useless-call": "error",
"no-useless-constructor": "error",
"no-useless-return": "error",
"prefer-arrow-callback": "warn",
"prefer-template": "warn",
"import/no-self-import": "error",
"import/no-cycle": "error",
"import/no-useless-path-segments": "error",
"import/no-deprecated": "warn",
"import/no-extraneous-dependencies": "error",
"import/no-mutable-exports": "error",
"import/first": "error",
"import/no-duplicates": "error",
"import/newline-after-import": "error",
"import/extensions": [
"off",
"ignorePackages"
],
"import/no-unresolved": "off",
"unicorn/no-null": "off",
"unicorn/no-process-exit": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prevent-abbreviations": "off",
"unicorn/no-array-for-each": "warn",
"unicorn/numeric-separators-style": "off",
"unicorn/require-post-message-target-origin": "off",
"unicorn/no-array-reduce": "off",
"unicorn/switch-case-braces": "off",
"unicorn/prefer-event-target": "off"
}
}

33
biome.json Normal file
View File

@@ -0,0 +1,33 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.2/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

7864
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,17 +12,19 @@
],
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"types": "./dist/index.d.ts",
"scripts": {
"test": "vitest run",
"test:watch": "vitest watch",
"lint": "eslint --ext .ts src/",
"typescript": "tsc --noEmit",
"lint": "biome lint src/ test/",
"lint:fix": "biome lint --write src/ test/",
"pretty": "biome format --write src/ test/",
"typescript": "tsc --noEmit",
"build:clean": "rm -rf dist",
"build:esm": "tsc -p tsconfig.build.json",
"build": "npm run build:clean && npm run build:esm",
@@ -39,14 +41,10 @@
"license": "LGPL-3.0",
"homepage": "https://github.com/Dojo-Open-Source-Project/electrum-client",
"devDependencies": {
"@types/node": "^16.18.32",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@typescript-eslint/parser": "^5.59.7",
"@vitest/coverage-c8": "^0.31.1",
"eslint": "^8.41.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-unicorn": "^47.0.0",
"@biomejs/biome": "1.9.2",
"@types/node": "^18.19.53",
"@vitest/coverage-v8": "^2.1.1",
"typescript": "^5.0.4",
"vitest": "^0.31.1"
"vitest": "^2.1.1"
}
}

1381
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,304 +1,334 @@
import {Client} from './lib/client.js'
import {
Callbacks,
ElectrumConfig,
ElectrumRequestBatchParams,
ElectrumRequestParams,
PersistencePolicy,
Protocol
} from './types'
import { Client } from "./lib/client.js";
import type {
Callbacks,
ElectrumConfig,
ElectrumRequestBatchParams,
ElectrumRequestParams,
PersistencePolicy,
Protocol,
} from "./types";
export class ElectrumClient extends Client {
private readonly onConnectCallback: ((client: ElectrumClient, versionInfo: [string, string]) => void) | null
private readonly onCloseCallback: ((client: ElectrumClient) => void) | null
private readonly onLogCallback: (str: string) => void
private timeLastCall: number
private persistencePolicy: Required<PersistencePolicy>
private electrumConfig: ElectrumConfig | null
private pingInterval: NodeJS.Timer | null
versionInfo: [string, string]
constructor(port: number, host: string, protocol: Protocol, callbacks?: Callbacks) {
super(port, host, protocol, callbacks)
this.onConnectCallback = (callbacks && callbacks.onConnect) ? callbacks.onConnect : null
this.onCloseCallback = (callbacks && callbacks.onClose) ? callbacks.onClose : null
this.onLogCallback = (callbacks && callbacks.onLog) ? callbacks.onLog : (str: string) => {
console.log(str)
}
this.timeLastCall = 0
this.persistencePolicy = {
retryPeriod: 10000,
maxRetry: 1000,
pingPeriod: 120000,
callback: null,
}
this.electrumConfig = null
this.pingInterval = null
this.versionInfo = ['', '']
}
async initElectrum(electrumConfig: ElectrumConfig, persistencePolicy?: PersistencePolicy): Promise<ElectrumClient> {
this.persistencePolicy = {
...this.persistencePolicy,
...persistencePolicy
}
this.electrumConfig = electrumConfig
this.timeLastCall = 0
await this.connect()
this.versionInfo = (await this.server_version(electrumConfig.client, electrumConfig.version)) as [string, string]
if (this.onConnectCallback != null) {
this.onConnectCallback(this, this.versionInfo)
}
return this
}
// Override parent
protected async request(method: string, params: ElectrumRequestParams) {
this.timeLastCall = Date.now()
const response = await super.request(method, params)
this.keepAlive()
return response
}
protected async requestBatch(method: string, params: ElectrumRequestParams, secondParam?: ElectrumRequestBatchParams) {
this.timeLastCall = Date.now()
const response = await super.requestBatch(method, params, secondParam)
this.keepAlive()
return response
}
protected onClose(): void {
super.onClose()
const list = [
'server.peers.subscribe',
'blockchain.numblocks.subscribe',
'blockchain.headers.subscribe',
'blockchain.address.subscribe',
]
for (const event of list) this.subscribe.removeAllListeners(event)
let retryPeriod = 10000
if (this.persistencePolicy.retryPeriod > 0) {
retryPeriod = this.persistencePolicy.retryPeriod
}
if (this.onCloseCallback != null) {
this.onCloseCallback(this)
}
setTimeout(() => {
if (this.persistencePolicy.maxRetry > 0) {
this.reconnect().catch((error) => {
this.onError(error)
})
this.persistencePolicy.maxRetry -= 1
} else if (this.persistencePolicy.callback != null) {
this.persistencePolicy.callback()
}
}, retryPeriod)
}
// ElectrumX persistancy
private keepAlive(): void {
if (this.pingInterval != null) {
clearInterval(this.pingInterval)
}
let pingPeriod = 120000
if (this.persistencePolicy.pingPeriod > 0) {
pingPeriod = this.persistencePolicy.pingPeriod
}
this.pingInterval = setInterval(() => {
if (this.timeLastCall !== 0 && Date.now() > this.timeLastCall + pingPeriod) {
this.server_ping().catch((error) => {
this.log(`Keep-Alive ping failed: ${error}`)
})
}
}, pingPeriod)
}
close(): void {
super.close()
if (this.pingInterval != null) {
clearInterval(this.pingInterval)
}
// eslint-disable-next-line no-multi-assign
this.reconnect = this.reconnect = this.onClose = this.keepAlive = () => Promise.resolve(this) // dirty hack to make it stop reconnecting
}
private reconnect(): Promise<ElectrumClient> {
this.log('Electrum attempting reconnect...')
this.initSocket()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.persistencePolicy == null ? this.initElectrum(this.electrumConfig!) : this.initElectrum(this.electrumConfig!, this.persistencePolicy)
}
private log(str: string): void {
this.onLogCallback(str)
}
// ElectrumX API
server_version(client_name: string, protocol_version: string | [string, string]) {
return this.request('server.version', [client_name, protocol_version])
}
server_banner() {
return this.request('server.banner', [])
}
server_features() {
return this.request('server.features', [])
}
server_ping() {
return this.request('server.ping', [])
}
server_addPeer(features: any) {
return this.request('server.add_peer', [features])
}
serverDonation_address() {
return this.request('server.donation_address', [])
}
serverPeers_subscribe() {
return this.request('server.peers.subscribe', [])
}
blockchainAddress_getProof(address: string) {
return this.request('blockchain.address.get_proof', [address])
}
blockchainScripthash_getBalance(scripthash: string) {
return this.request('blockchain.scripthash.get_balance', [scripthash])
}
blockchainScripthash_getBalanceBatch(scripthashes: string[]) {
return this.requestBatch('blockchain.scripthash.get_balance', scripthashes)
}
blockchainScripthash_listunspentBatch(scripthashes: string[]) {
return this.requestBatch('blockchain.scripthash.listunspent', scripthashes)
}
blockchainScripthash_getHistory(scripthash: string) {
return this.request('blockchain.scripthash.get_history', [scripthash])
}
blockchainScripthash_getHistoryBatch(scripthashes: string[]) {
return this.requestBatch('blockchain.scripthash.get_history', scripthashes)
}
blockchainScripthash_getMempool(scripthash: string) {
return this.request('blockchain.scripthash.get_mempool', [scripthash])
}
blockchainScripthash_listunspent(scripthash: string) {
return this.request('blockchain.scripthash.listunspent', [scripthash])
}
blockchainScripthash_subscribe(scripthash: string) {
return this.request('blockchain.scripthash.subscribe', [scripthash])
}
blockchainBlock_getHeader(height: number) {
return this.request('blockchain.block.get_header', [height])
}
blockchainBlock_headers(start_height: number, count: number) {
return this.request('blockchain.block.headers', [start_height, count])
}
blockchainEstimatefee(number: number) {
return this.request('blockchain.estimatefee', [number])
}
blockchainHeaders_subscribe() {
return this.request('blockchain.headers.subscribe', [])
}
blockchain_relayfee() {
return this.request('blockchain.relayfee', [])
}
blockchainTransaction_broadcast(rawtx: string) {
return this.request('blockchain.transaction.broadcast', [rawtx])
}
blockchainTransaction_get(tx_hash: string, verbose = false) {
return this.request('blockchain.transaction.get', [tx_hash, verbose])
}
blockchainTransaction_getBatch(tx_hashes: string[], verbose = false) {
return this.requestBatch('blockchain.transaction.get', tx_hashes, verbose)
}
blockchainTransaction_getMerkle(tx_hash: string, height: number) {
return this.request('blockchain.transaction.get_merkle', [tx_hash, height])
}
mempool_getFeeHistogram() {
return this.request('mempool.get_fee_histogram', [])
}
// ---------------------------------
// protocol 1.1 deprecated method
// ---------------------------------
blockchainUtxo_getAddress(tx_hash: string, index: number) {
return this.request('blockchain.utxo.get_address', [tx_hash, index])
}
blockchainNumblocks_subscribe() {
return this.request('blockchain.numblocks.subscribe', [])
}
// ---------------------------------
// protocol 1.2 deprecated method
// ---------------------------------
blockchainBlock_getChunk(index: number) {
return this.request('blockchain.block.get_chunk', [index])
}
blockchainAddress_getBalance(address: string) {
return this.request('blockchain.address.get_balance', [address])
}
blockchainAddress_getHistory(address: string) {
return this.request('blockchain.address.get_history', [address])
}
blockchainAddress_getMempool(address: string) {
return this.request('blockchain.address.get_mempool', [address])
}
blockchainAddress_listunspent(address: string) {
return this.request('blockchain.address.listunspent', [address])
}
blockchainAddress_subscribe(address: string) {
return this.request('blockchain.address.subscribe', [address])
}
private readonly onConnectCallback:
| ((client: ElectrumClient, versionInfo: [string, string]) => void)
| null;
private readonly onCloseCallback: ((client: ElectrumClient) => void) | null;
private readonly onLogCallback: (str: string) => void;
private timeLastCall: number;
private persistencePolicy: Required<PersistencePolicy>;
private electrumConfig: ElectrumConfig | null;
private pingInterval: NodeJS.Timeout | null;
versionInfo: [string, string];
constructor(
port: number,
host: string,
protocol: Protocol,
callbacks?: Callbacks,
) {
super(port, host, protocol, callbacks);
this.onConnectCallback = callbacks?.onConnect ?? null;
this.onCloseCallback = callbacks?.onClose ?? null;
this.onLogCallback = callbacks?.onLog
? callbacks.onLog
: (str: string) => {
console.log(str);
};
this.timeLastCall = 0;
this.persistencePolicy = {
retryPeriod: 10000,
maxRetry: 1000,
pingPeriod: 120000,
callback: null,
};
this.electrumConfig = null;
this.pingInterval = null;
this.versionInfo = ["", ""];
}
async initElectrum(
electrumConfig: ElectrumConfig,
persistencePolicy?: PersistencePolicy,
): Promise<ElectrumClient> {
this.persistencePolicy = {
...this.persistencePolicy,
...persistencePolicy,
};
this.electrumConfig = electrumConfig;
this.timeLastCall = 0;
await this.connect();
this.versionInfo = (await this.server_version(
electrumConfig.client,
electrumConfig.version,
)) as [string, string];
if (this.onConnectCallback != null) {
this.onConnectCallback(this, this.versionInfo);
}
return this;
}
// Override parent
protected async request<T>(method: string, params: ElectrumRequestParams<T>) {
this.timeLastCall = Date.now();
const response = await super.request<T>(method, params);
this.keepAlive();
return response;
}
protected async requestBatch<T>(
method: string,
params: ElectrumRequestParams<T>,
secondParam?: ElectrumRequestBatchParams,
) {
this.timeLastCall = Date.now();
const response = await super.requestBatch<T>(method, params, secondParam);
this.keepAlive();
return response;
}
protected onClose(): void {
super.onClose();
const list = [
"server.peers.subscribe",
"blockchain.numblocks.subscribe",
"blockchain.headers.subscribe",
"blockchain.address.subscribe",
];
for (const event of list) this.subscribe.removeAllListeners(event);
let retryPeriod = 10000;
if (this.persistencePolicy.retryPeriod > 0) {
retryPeriod = this.persistencePolicy.retryPeriod;
}
if (this.onCloseCallback != null) {
this.onCloseCallback(this);
}
setTimeout(() => {
if (this.persistencePolicy.maxRetry > 0) {
this.reconnect().catch((error) => {
this.onError(error);
});
this.persistencePolicy.maxRetry -= 1;
} else if (this.persistencePolicy.callback != null) {
this.persistencePolicy.callback();
}
}, retryPeriod);
}
// ElectrumX persistancy
private keepAlive(): void {
if (this.pingInterval != null) {
clearInterval(this.pingInterval);
}
let pingPeriod = 120000;
if (this.persistencePolicy.pingPeriod > 0) {
pingPeriod = this.persistencePolicy.pingPeriod;
}
this.pingInterval = setInterval(() => {
if (
this.timeLastCall !== 0 &&
Date.now() > this.timeLastCall + pingPeriod
) {
this.server_ping().catch((error) => {
this.log(`Keep-Alive ping failed: ${error}`);
});
}
}, pingPeriod);
}
close(): void {
super.close();
if (this.pingInterval != null) {
clearInterval(this.pingInterval);
}
this.reconnect =
this.reconnect =
this.onClose =
this.keepAlive =
() => Promise.resolve(this); // dirty hack to make it stop reconnecting
}
private reconnect(): Promise<ElectrumClient> {
this.log("Electrum attempting reconnect...");
this.initSocket();
return this.persistencePolicy == null
? // biome-ignore lint/style/noNonNullAssertion:
this.initElectrum(this.electrumConfig!)
: // biome-ignore lint/style/noNonNullAssertion:
this.initElectrum(this.electrumConfig!, this.persistencePolicy);
}
private log(str: string): void {
this.onLogCallback(str);
}
// ElectrumX API
server_version(
client_name: string,
protocol_version: string | [string, string],
) {
return this.request("server.version", [client_name, protocol_version]);
}
server_banner() {
return this.request("server.banner", []);
}
server_features() {
return this.request("server.features", []);
}
server_ping() {
return this.request("server.ping", []);
}
server_addPeer(features: any) {
return this.request("server.add_peer", [features]);
}
serverDonation_address() {
return this.request("server.donation_address", []);
}
serverPeers_subscribe() {
return this.request("server.peers.subscribe", []);
}
blockchainAddress_getProof(address: string) {
return this.request("blockchain.address.get_proof", [address]);
}
blockchainScripthash_getBalance(scripthash: string) {
return this.request("blockchain.scripthash.get_balance", [scripthash]);
}
blockchainScripthash_getBalanceBatch(scripthashes: string[]) {
return this.requestBatch("blockchain.scripthash.get_balance", scripthashes);
}
blockchainScripthash_listunspentBatch(scripthashes: string[]) {
return this.requestBatch("blockchain.scripthash.listunspent", scripthashes);
}
blockchainScripthash_getHistory(scripthash: string) {
return this.request("blockchain.scripthash.get_history", [scripthash]);
}
blockchainScripthash_getHistoryBatch(scripthashes: string[]) {
return this.requestBatch("blockchain.scripthash.get_history", scripthashes);
}
blockchainScripthash_getMempool(scripthash: string) {
return this.request("blockchain.scripthash.get_mempool", [scripthash]);
}
blockchainScripthash_listunspent(scripthash: string) {
return this.request("blockchain.scripthash.listunspent", [scripthash]);
}
blockchainScripthash_subscribe(scripthash: string) {
return this.request("blockchain.scripthash.subscribe", [scripthash]);
}
blockchainBlock_getHeader(height: number) {
return this.request("blockchain.block.get_header", [height]);
}
blockchainBlock_headers(start_height: number, count: number) {
return this.request("blockchain.block.headers", [start_height, count]);
}
blockchainEstimatefee(number: number) {
return this.request("blockchain.estimatefee", [number]);
}
blockchainHeaders_subscribe() {
return this.request("blockchain.headers.subscribe", []);
}
blockchain_relayfee() {
return this.request("blockchain.relayfee", []);
}
blockchainTransaction_broadcast(rawtx: string) {
return this.request("blockchain.transaction.broadcast", [rawtx]);
}
blockchainTransaction_get(tx_hash: string, verbose = false) {
return this.request("blockchain.transaction.get", [tx_hash, verbose]);
}
blockchainTransaction_getBatch(tx_hashes: string[], verbose = false) {
return this.requestBatch("blockchain.transaction.get", tx_hashes, verbose);
}
blockchainTransaction_getMerkle(tx_hash: string, height: number) {
return this.request("blockchain.transaction.get_merkle", [tx_hash, height]);
}
mempool_getFeeHistogram() {
return this.request("mempool.get_fee_histogram", []);
}
// ---------------------------------
// protocol 1.1 deprecated method
// ---------------------------------
blockchainUtxo_getAddress(tx_hash: string, index: number) {
return this.request("blockchain.utxo.get_address", [tx_hash, index]);
}
blockchainNumblocks_subscribe() {
return this.request("blockchain.numblocks.subscribe", []);
}
// ---------------------------------
// protocol 1.2 deprecated method
// ---------------------------------
blockchainBlock_getChunk(index: number) {
return this.request("blockchain.block.get_chunk", [index]);
}
blockchainAddress_getBalance(address: string) {
return this.request("blockchain.address.get_balance", [address]);
}
blockchainAddress_getHistory(address: string) {
return this.request("blockchain.address.get_history", [address]);
}
blockchainAddress_getMempool(address: string) {
return this.request("blockchain.address.get_mempool", [address]);
}
blockchainAddress_listunspent(address: string) {
return this.request("blockchain.address.listunspent", [address]);
}
blockchainAddress_subscribe(address: string) {
return this.request("blockchain.address.subscribe", [address]);
}
}

View File

@@ -1,182 +1,212 @@
import net from 'node:net'
import tls from 'node:tls'
import {EventEmitter} from 'node:events'
import net from "node:net";
import tls from "node:tls";
import { EventEmitter } from "node:events";
import * as util from './util.js'
import type {Protocol, Callbacks, ElectrumRequestBatchParams, ElectrumRequestParams} from '../types'
import * as util from "./util.js";
import type {
Protocol,
Callbacks,
ElectrumRequestBatchParams,
ElectrumRequestParams,
} from "../types";
const TIMEOUT = 60000
const TIMEOUT = 60000;
export abstract class Client {
private id: number
private callback_message_queue: Map<number, (err: Error | null, result?: any) => void>
protected subscribe: EventEmitter
private mp: util.MessageParser
private readonly protocol: Protocol
private conn: net.Socket | tls.TLSSocket | undefined
private readonly host: string
private readonly port: number
private readonly onErrorCallback: ((e: Error) => void) | null
private id: number;
private callback_message_queue: Map<
number,
(err: Error | null, result?: any) => void
>;
protected subscribe: EventEmitter;
private mp: util.MessageParser;
private readonly protocol: Protocol;
private conn: net.Socket | tls.TLSSocket | undefined;
private readonly host: string;
private readonly port: number;
private readonly onErrorCallback: ((e: Error) => void) | null;
protected constructor(port: number, host: string, protocol: Protocol, callbacks?: Callbacks) {
this.id = 0
this.host = host
this.port = port
this.protocol = protocol
this.callback_message_queue = new Map()
this.subscribe = new EventEmitter()
this.mp = new util.MessageParser((body: string | undefined, n: number) => {
this.onMessage(body, n)
})
protected constructor(
port: number,
host: string,
protocol: Protocol,
callbacks?: Callbacks,
) {
this.id = 0;
this.host = host;
this.port = port;
this.protocol = protocol;
this.callback_message_queue = new Map();
this.subscribe = new EventEmitter();
this.mp = new util.MessageParser((body: string | undefined, n: number) => {
this.onMessage(body, n);
});
if (protocol !== 'tcp' && protocol !== 'tls' && protocol !== 'ssl') {
throw new Error('unknown protocol')
}
if (protocol !== "tcp" && protocol !== "tls" && protocol !== "ssl") {
throw new Error("unknown protocol");
}
this.onErrorCallback = callbacks?.onError ?? null
this.onErrorCallback = callbacks?.onError ?? null;
this.initSocket()
}
this.initSocket();
}
protected initSocket(): void {
this.conn = this.protocol === 'tls' || this.protocol === 'ssl'
? tls.connect({ host: this.host, port: this.port, rejectUnauthorized: false }, () => {
this.conn && this.conn.setTimeout(0)
this.onConnect()
})
: net.connect({ host: this.host, port: this.port }, () => {
this.conn && this.conn.setTimeout(0)
this.onConnect()
})
this.conn.setTimeout(TIMEOUT)
this.conn.setEncoding('utf8')
this.conn.setKeepAlive(true, 0)
this.conn.setNoDelay(true)
this.conn.on('close', () => {
this.onClose()
})
this.conn.on('data', (chunk: Buffer) => {
this.conn && this.conn.setTimeout(0)
this.onRecv(chunk)
})
this.conn.on('error', (e: Error) => {
this.onError(e)
})
}
protected initSocket(): void {
this.conn =
this.protocol === "tls" || this.protocol === "ssl"
? tls.connect(
{ host: this.host, port: this.port, rejectUnauthorized: false },
() => {
this.conn?.setTimeout(0);
this.onConnect();
},
)
: net.connect({ host: this.host, port: this.port }, () => {
this.conn?.setTimeout(0);
this.onConnect();
});
this.conn.setTimeout(TIMEOUT);
this.conn.setEncoding("utf8");
this.conn.setKeepAlive(true, 0);
this.conn.setNoDelay(true);
this.conn.on("close", () => {
this.onClose();
});
this.conn.on("data", (chunk: Buffer) => {
this.conn?.setTimeout(0);
this.onRecv(chunk);
});
this.conn.on("error", (e: Error) => {
this.onError(e);
});
}
protected connect(): Promise<void> {
return new Promise<void>((resolve) => {
if (this.conn && this.conn.readyState === 'open') {
resolve()
}
this.protocol === 'tcp'
? this.conn && this.conn.once('connect', () => {
resolve()
})
: this.conn && this.conn.once('secureConnect', () => {
resolve()
})
})
}
protected connect(): Promise<void> {
return new Promise<void>((resolve) => {
if (this.conn && this.conn.readyState === "open") {
resolve();
}
this.protocol === "tcp"
? this.conn?.once("connect", () => {
resolve();
})
: this.conn?.once("secureConnect", () => {
resolve();
});
});
}
close(): void {
if (this.conn && this.conn.readyState === 'closed') {
return
}
this.conn && this.conn.end()
this.conn && this.conn.destroy()
}
close(): void {
if (this.conn && this.conn.readyState === "closed") {
return;
}
this.conn?.end();
this.conn?.destroy();
}
protected request(method: string, params: ElectrumRequestParams) {
if (this.conn && this.conn.readyState === 'closed') {
return Promise.reject(new Error('Connection to server lost, please retry'))
}
return new Promise((resolve, reject) => {
const id = ++this.id
const content = util.makeRequest(method, params, id)
this.callback_message_queue.set(id, util.createPromiseResult(resolve, reject))
this.conn && this.conn.write(`${content}\n`)
})
}
protected request<T>(method: string, params: ElectrumRequestParams<T>) {
if (this.conn && this.conn.readyState === "closed") {
return Promise.reject(
new Error("Connection to server lost, please retry"),
);
}
return new Promise((resolve, reject) => {
const id = ++this.id;
const content = util.makeRequest<T>(method, params, id);
this.callback_message_queue.set(
id,
util.createPromiseResult(resolve, reject),
);
this.conn?.write(`${content}\n`);
});
}
protected requestBatch(method: string, params: ElectrumRequestParams, secondParam: ElectrumRequestBatchParams) {
if (this.conn && this.conn.readyState === 'closed') {
return Promise.reject(new Error('Connection to server lost, please retry'))
}
return new Promise((resolve, reject) => {
const arguments_far_calls: Record<number, any> = {}
const contents = []
for (const param of params) {
const id = ++this.id
if (secondParam == null) {
contents.push(util.makeRequest(method, [param], id))
} else {
contents.push(util.makeRequest(method, [param, secondParam], id))
}
arguments_far_calls[id] = param
}
const content = `[${ contents.join(',') }]`
this.callback_message_queue.set(this.id, util.createPromiseResultBatch(resolve, reject, arguments_far_calls))
// callback will exist only for max id
this.conn && this.conn.write(`${content }\n`)
})
}
protected requestBatch<T>(
method: string,
params: ElectrumRequestParams<T>,
secondParam: ElectrumRequestBatchParams,
) {
if (this.conn && this.conn.readyState === "closed") {
return Promise.reject(
new Error("Connection to server lost, please retry"),
);
}
return new Promise((resolve, reject) => {
const arguments_far_calls: Record<number, any> = {};
const contents = [];
for (const param of params) {
const id = ++this.id;
if (secondParam == null) {
contents.push(util.makeRequest(method, [param], id));
} else {
contents.push(util.makeRequest(method, [param, secondParam], id));
}
arguments_far_calls[id] = param;
}
const content = `[${contents.join(",")}]`;
this.callback_message_queue.set(
this.id,
util.createPromiseResultBatch(resolve, reject, arguments_far_calls),
);
// callback will exist only for max id
this.conn?.write(`${content}\n`);
});
}
private response(msg: any) { // FIXME
let callback
if (!msg.id && msg[0] && msg[0].id) {
// this is a response from batch request
for (const m of msg) {
if (m.id && this.callback_message_queue.has(m.id)) {
callback = this.callback_message_queue.get(m.id)
this.callback_message_queue.delete(m.id)
}
}
} else {
callback = this.callback_message_queue.get(msg.id)
}
private response(msg: any) {
let callback: undefined | ((err: Error | null, result?: any) => void);
if (!msg.id && msg[0] && msg[0].id) {
// this is a response from batch request
for (const m of msg) {
if (m.id && this.callback_message_queue.has(m.id)) {
callback = this.callback_message_queue.get(m.id);
this.callback_message_queue.delete(m.id);
}
}
} else {
callback = this.callback_message_queue.get(msg.id);
}
if (callback) {
this.callback_message_queue.delete(msg.id)
if (msg.error) {
callback(msg.error)
} else {
callback(null, msg.result || msg)
}
} else {
console.log(msg)
throw new Error('Error getting callback while handling response')
}
}
if (callback) {
this.callback_message_queue.delete(msg.id);
if (msg.error) {
callback(msg.error);
} else {
callback(null, msg.result || msg);
}
} else {
console.log(msg);
throw new Error("Error getting callback while handling response");
}
}
private onMessage(body: string | undefined, n: number): void {
const msg = JSON.parse(body || '')
if (Array.isArray(msg)) {
this.response(msg)
} else if (msg.id == null) {
this.subscribe.emit(msg.method, msg.params)
} else {
this.response(msg)
}
}
private onMessage(body: string | undefined, n: number): void {
const msg = JSON.parse(body || "");
if (Array.isArray(msg)) {
this.response(msg);
} else if (msg.id == null) {
this.subscribe.emit(msg.method, msg.params);
} else {
this.response(msg);
}
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onConnect(): void {}
private onConnect(): void {}
protected onClose(): void {
for (const [key, fn] of this.callback_message_queue.entries()) {
fn(new Error('close connect'))
this.callback_message_queue.delete(key)
}
}
protected onClose(): void {
for (const [key, fn] of this.callback_message_queue.entries()) {
fn(new Error("close connect"));
this.callback_message_queue.delete(key);
}
}
private onRecv(chunk: Buffer): void {
this.mp.run(chunk)
}
private onRecv(chunk: Buffer): void {
this.mp.run(chunk);
}
protected onError(e: Error): void {
if (this.onErrorCallback != null) {
this.onErrorCallback(e)
}
}
protected onError(e: Error): void {
if (this.onErrorCallback != null) {
this.onErrorCallback(e);
}
}
}

View File

@@ -1,76 +1,90 @@
import {ElectrumRequestParams} from '../types'
import type { ElectrumRequestParams } from "../types";
export const makeRequest = (method: string, params: ElectrumRequestParams, id: number) => {
return JSON.stringify({
jsonrpc: '2.0',
method: method,
params: params,
id: id,
})
}
export const makeRequest = <T>(
method: string,
params: ElectrumRequestParams<T>,
id: number,
) => {
return JSON.stringify({
jsonrpc: "2.0",
method: method,
params: params,
id: id,
});
};
export const createRecursiveParser = (max_depth: number, delimiter: string) => {
const MAX_DEPTH = max_depth
const DELIMITER = delimiter
const recursiveParser = (n: number, buffer: string, callback: (xs: string | undefined, n: number) => void): { code: number, buffer: string } => {
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 MAX_DEPTH = max_depth;
const DELIMITER = delimiter;
const recursiveParser = (
n: number,
buffer: string,
callback: (xs: string | undefined, n: number) => void,
): { code: number; buffer: string } => {
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;
};
export const createPromiseResult = (resolve: (value?: any) => void, reject: (reason?: any) => void) => {
return (err: Error | null, result?: any) => {
if (err) reject(err)
else resolve(result)
}
}
export const createPromiseResult = (
resolve: (value?: any) => void,
reject: (reason?: any) => void,
) => {
return (err: Error | null, result?: any) => {
if (err) reject(err);
else resolve(result);
};
};
export const createPromiseResultBatch = (resolve: (value?: any) => void, reject: (reason?: any) => void, argz: Record<number, any>) => {
return (err: Error | null, result?: Array<any>) => {
if (result && result[0] && result[0].id) {
// this is a batch request response
for (const r of result) {
r.param = argz[r.id]
}
}
if (err) reject(err)
else resolve(result)
}
}
export const createPromiseResultBatch = (
resolve: (value?: any) => void,
reject: (reason?: any) => void,
argz: Record<number, any>,
) => {
return (err: Error | null, result?: Array<any>) => {
if (result?.[0]?.id) {
// this is a batch request response
for (const r of result) {
r.param = argz[r.id];
}
}
if (err) reject(err);
else resolve(result);
};
};
type MessageParserCallback = (body: string | undefined, n: number) => void;
export class MessageParser {
private buffer: string
private readonly callback: MessageParserCallback
private readonly recursiveParser: ReturnType<typeof createRecursiveParser>
private buffer: string;
private readonly callback: MessageParserCallback;
private readonly recursiveParser: ReturnType<typeof createRecursiveParser>;
constructor(callback: MessageParserCallback) {
this.buffer = ''
this.callback = callback
this.recursiveParser = createRecursiveParser(20, '\n')
}
constructor(callback: MessageParserCallback) {
this.buffer = "";
this.callback = callback;
this.recursiveParser = createRecursiveParser(20, "\n");
}
run(chunk: Buffer) {
this.buffer += chunk
/* eslint-disable-next-line no-constant-condition */
while (true) {
const res = this.recursiveParser(0, this.buffer, this.callback)
this.buffer = res.buffer
if (res.code === 0) {
break
}
}
}
run(chunk: Buffer) {
this.buffer += chunk;
while (true) {
const res = this.recursiveParser(0, this.buffer, this.callback);
this.buffer = res.buffer;
if (res.code === 0) {
break;
}
}
}
}

View File

@@ -1,26 +1,28 @@
import {ElectrumClient} from '../index.js'
import type { ElectrumClient } from "../index.js";
export type Protocol = 'tcp' | 'tls' | 'ssl';
export type Protocol = "tcp" | "tls" | "ssl";
export type Callbacks = {
onConnect?: (client: ElectrumClient, versionInfo: [string, string]) => void;
onClose?: (client: ElectrumClient) => void;
onLog?: (str: string) => void;
onError?: (e: Error) => void;
}
onConnect?: (client: ElectrumClient, versionInfo: [string, string]) => void;
onClose?: (client: ElectrumClient) => void;
onLog?: (str: string) => void;
onError?: (e: Error) => void;
};
export type PersistencePolicy = {
retryPeriod?: number,
maxRetry?: number,
pingPeriod?: number,
callback?: (() => void) | null
}
retryPeriod?: number;
maxRetry?: number;
pingPeriod?: number;
callback?: (() => void) | null;
};
export type ElectrumConfig = {
client: string;
version: string | [string, string];
}
client: string;
version: string | [string, string];
};
export type ElectrumRequestParams = Array<number | string | boolean | Array<any>>;
export type ElectrumRequestParams<T> = Array<
number | string | boolean | Array<T>
>;
export type ElectrumRequestBatchParams = number | string | boolean | undefined;

View File

@@ -1,117 +1,168 @@
/* eslint-disable max-len, @typescript-eslint/ban-ts-comment */
import {beforeEach, afterEach, describe, it, assert, expect} from 'vitest'
import { beforeEach, afterEach, describe, it, assert, expect } from "vitest";
import {ElectrumClient} from '../src'
import { ElectrumClient } from "../src";
let tcpClient: ElectrumClient
let tlsClient: ElectrumClient
let tcpClient: ElectrumClient;
let tlsClient: ElectrumClient;
beforeEach(() => {
tcpClient = new ElectrumClient(60001, 'btc.electroncash.dk', 'tcp')
tlsClient = new ElectrumClient(60002, 'btc.electroncash.dk', 'tls')
})
tcpClient = new ElectrumClient(60001, "btc.electroncash.dk", "tcp");
tlsClient = new ElectrumClient(60002, "btc.electroncash.dk", "tls");
});
afterEach(() => {
tcpClient.close()
tlsClient.close()
})
tcpClient.close();
tlsClient.close();
});
describe('ElectrumClient TCP', () => {
it('successfully connects to Electrum server', async () => {
const clt = await tcpClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']}, {retryPeriod: 2000})
describe("ElectrumClient TCP", () => {
it("successfully connects to Electrum server", async () => {
const clt = await tcpClient.initElectrum(
{ client: "electrum-client-js", version: ["1.2", "1.4"] },
{ retryPeriod: 2000 },
);
assert.ok(clt.versionInfo)
})
assert.ok(clt.versionInfo);
});
it('successfully makes a standard request', async () => {
await tcpClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']}, {retryPeriod: 2000})
const response = await tcpClient.blockchainTransaction_get('b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a')
it("successfully makes a standard request", async () => {
await tcpClient.initElectrum(
{ client: "electrum-client-js", version: ["1.2", "1.4"] },
{ retryPeriod: 2000 },
);
const response = await tcpClient.blockchainTransaction_get(
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
);
assert.strictEqual(response, '02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000')
})
assert.strictEqual(
response,
"02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000",
);
});
it('successfully makes a batch request', async () => {
await tcpClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']}, {retryPeriod: 2000})
const response = await tcpClient.blockchainTransaction_getBatch(['b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a', 'f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b'])
it("successfully makes a batch request", async () => {
await tcpClient.initElectrum(
{ client: "electrum-client-js", version: ["1.2", "1.4"] },
{ retryPeriod: 2000 },
);
const response = await tcpClient.blockchainTransaction_getBatch([
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
"f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b",
]);
expect(response).toEqual(expect.arrayContaining([
{
id: 2,
jsonrpc: '2.0',
param: 'b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a',
result: '02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000'
},
{
id: 3,
jsonrpc: '2.0',
param: 'f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b',
result: '01000000000101536c64334a7e7c49713d836ef9230054e6c38b8c086a3e2068d054781aae89050100000000ffffffff025d9200000000000017a91415035c6225abdf351aa90e6a60726f5e2653839f8728751900000000002200209054b26aafa22ff9bff790d0ed9cc9c84079d7e237e1b617042df9a9734d8aec0400473044022059913f1526f2bfd5a236f86ada8f68b4943632ef9d306d44e482935774059aa60220360d783ca765299c114e28dd0304df913315e5297d9f78fd89319c1ddc6177260147304402204a135548f460dd96e3ea7f981c69d52773cec1475c278631a2180a8010b5dd7802206e090c9c6b1734a9757c76f9a1d5e576bf7b805d12e2d50d344800628dde4f420147522102254d4655d0cae8ad2d96a6789d08ac63982171f10795a5146d03a8369ffd51b921029009039142b4de669fe67507bb3a6b17ff38f7798ae4d7bb3826a3cc2527d62252ae00000000'
}
]))
})
expect(response).toEqual(
expect.arrayContaining([
{
id: 2,
jsonrpc: "2.0",
param:
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
result:
"02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000",
},
{
id: 3,
jsonrpc: "2.0",
param:
"f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b",
result:
"01000000000101536c64334a7e7c49713d836ef9230054e6c38b8c086a3e2068d054781aae89050100000000ffffffff025d9200000000000017a91415035c6225abdf351aa90e6a60726f5e2653839f8728751900000000002200209054b26aafa22ff9bff790d0ed9cc9c84079d7e237e1b617042df9a9734d8aec0400473044022059913f1526f2bfd5a236f86ada8f68b4943632ef9d306d44e482935774059aa60220360d783ca765299c114e28dd0304df913315e5297d9f78fd89319c1ddc6177260147304402204a135548f460dd96e3ea7f981c69d52773cec1475c278631a2180a8010b5dd7802206e090c9c6b1734a9757c76f9a1d5e576bf7b805d12e2d50d344800628dde4f420147522102254d4655d0cae8ad2d96a6789d08ac63982171f10795a5146d03a8369ffd51b921029009039142b4de669fe67507bb3a6b17ff38f7798ae4d7bb3826a3cc2527d62252ae00000000",
},
]),
);
});
it('should make a request after reconnection', async () => {
await tcpClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']}, {retryPeriod: 2000})
// @ts-ignore Hijack Typescript to call method on private field in order to simulate connection close
tcpClient.conn?.end()
const promise = await new Promise((resolve) => {
setTimeout(async () => {
const res = await tcpClient.server_banner()
it("should make a request after reconnection", async () => {
await tcpClient.initElectrum(
{ client: "electrum-client-js", version: ["1.2", "1.4"] },
{ retryPeriod: 2000 },
);
// @ts-ignore Hijack Typescript to call method on private field in order to simulate connection close
tcpClient.conn?.end();
const promise = await new Promise((resolve) => {
setTimeout(async () => {
const res = await tcpClient.server_banner();
resolve(res)
}, 10000)
})
resolve(res);
}, 10000);
});
assert.ok(promise)
})
})
assert.ok(promise);
});
});
describe('ElectrumClient TLS', () => {
it('successfully connects to Electrum server', async () => {
const clt = await tlsClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']})
describe("ElectrumClient TLS", () => {
it("successfully connects to Electrum server", async () => {
const clt = await tlsClient.initElectrum({
client: "electrum-client-js",
version: ["1.2", "1.4"],
});
assert.ok(clt.versionInfo)
})
assert.ok(clt.versionInfo);
});
it('successfully makes a standard request', async () => {
await tlsClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']})
const response = await tlsClient.blockchainTransaction_get('b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a')
it("successfully makes a standard request", async () => {
await tlsClient.initElectrum({
client: "electrum-client-js",
version: ["1.2", "1.4"],
});
const response = await tlsClient.blockchainTransaction_get(
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
);
assert.strictEqual(response, '02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000')
})
assert.strictEqual(
response,
"02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000",
);
});
it('successfully makes a batch request', async () => {
await tlsClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']})
const response = await tlsClient.blockchainTransaction_getBatch(['b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a', 'f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b'])
it("successfully makes a batch request", async () => {
await tlsClient.initElectrum({
client: "electrum-client-js",
version: ["1.2", "1.4"],
});
const response = await tlsClient.blockchainTransaction_getBatch([
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
"f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b",
]);
expect(response).toEqual(expect.arrayContaining([
{
id: 2,
jsonrpc: '2.0',
param: 'b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a',
result: '02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000'
},
{
id: 3,
jsonrpc: '2.0',
param: 'f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b',
result: '01000000000101536c64334a7e7c49713d836ef9230054e6c38b8c086a3e2068d054781aae89050100000000ffffffff025d9200000000000017a91415035c6225abdf351aa90e6a60726f5e2653839f8728751900000000002200209054b26aafa22ff9bff790d0ed9cc9c84079d7e237e1b617042df9a9734d8aec0400473044022059913f1526f2bfd5a236f86ada8f68b4943632ef9d306d44e482935774059aa60220360d783ca765299c114e28dd0304df913315e5297d9f78fd89319c1ddc6177260147304402204a135548f460dd96e3ea7f981c69d52773cec1475c278631a2180a8010b5dd7802206e090c9c6b1734a9757c76f9a1d5e576bf7b805d12e2d50d344800628dde4f420147522102254d4655d0cae8ad2d96a6789d08ac63982171f10795a5146d03a8369ffd51b921029009039142b4de669fe67507bb3a6b17ff38f7798ae4d7bb3826a3cc2527d62252ae00000000'
}
]))
})
expect(response).toEqual(
expect.arrayContaining([
{
id: 2,
jsonrpc: "2.0",
param:
"b270b8a113c048ed0024e470e3c7794565c2b3e18c600d20410ec8f454b7d25a",
result:
"02000000016016c6d039cbdeefa655e4b5ac61ec2b5fe16920529f086a2439f59a5994bed6200000006a4730440220421b5daf5f72e514075d256121d6b66e1d9a8993d37ceb0532baca11f6964da502205333d1c4b9be749180bfa8da4da6a07c0e9e2d853c809a75c4b60ef717a628b20121029139c783aa8f31707a67248a6a6b643855e9d1484dbd53cfe7d9e3adf3ce7098ffffffff02c19a01000000000017a9146eda8b447af853746f04b902fea5b2cd959d866d87692c0200000000001976a914443674ce759f8fa2fe83a6608339da782b890c1988ac00000000",
},
{
id: 3,
jsonrpc: "2.0",
param:
"f08474320cb16c34bb6ccbd8ca77b41168589155e8cf0af787d8d8b2a975af0b",
result:
"01000000000101536c64334a7e7c49713d836ef9230054e6c38b8c086a3e2068d054781aae89050100000000ffffffff025d9200000000000017a91415035c6225abdf351aa90e6a60726f5e2653839f8728751900000000002200209054b26aafa22ff9bff790d0ed9cc9c84079d7e237e1b617042df9a9734d8aec0400473044022059913f1526f2bfd5a236f86ada8f68b4943632ef9d306d44e482935774059aa60220360d783ca765299c114e28dd0304df913315e5297d9f78fd89319c1ddc6177260147304402204a135548f460dd96e3ea7f981c69d52773cec1475c278631a2180a8010b5dd7802206e090c9c6b1734a9757c76f9a1d5e576bf7b805d12e2d50d344800628dde4f420147522102254d4655d0cae8ad2d96a6789d08ac63982171f10795a5146d03a8369ffd51b921029009039142b4de669fe67507bb3a6b17ff38f7798ae4d7bb3826a3cc2527d62252ae00000000",
},
]),
);
});
it('should make a request after reconnection', async () => {
await tlsClient.initElectrum({client: 'electrum-client-js', version: ['1.2', '1.4']}, {retryPeriod: 2000})
// @ts-ignore Hijack Typescript to call method on private field in order to simulate connection close
tlsClient.conn?.end()
const promise = await new Promise((resolve) => {
setTimeout(async () => {
const res = await tlsClient.server_banner()
it("should make a request after reconnection", async () => {
await tlsClient.initElectrum(
{ client: "electrum-client-js", version: ["1.2", "1.4"] },
{ retryPeriod: 2000 },
);
// @ts-ignore Hijack Typescript to call method on private field in order to simulate connection close
tlsClient.conn?.end();
const promise = await new Promise((resolve) => {
setTimeout(async () => {
const res = await tlsClient.server_banner();
resolve(res)
}, 10000)
})
resolve(res);
}, 10000);
});
assert.ok(promise)
})
})
assert.ok(promise);
});
});

View File

@@ -1,13 +1,18 @@
import {describe, it, assert} from 'vitest'
import * as util from '../../src/lib/util.js'
import { describe, it, assert } from "vitest";
import * as util from "../../src/lib/util.js";
describe('util package', () => {
describe('makeRequest()', () => {
it('should make a correct request payload', () => {
const expected = '{"jsonrpc":"2.0","method":"testMethod","params":["stringParam",1,true],"id":1}'
const request = util.makeRequest('testMethod', ['stringParam', 1, true], 1)
describe("util package", () => {
describe("makeRequest()", () => {
it("should make a correct request payload", () => {
const expected =
'{"jsonrpc":"2.0","method":"testMethod","params":["stringParam",1,true],"id":1}';
const request = util.makeRequest(
"testMethod",
["stringParam", 1, true],
1,
);
assert.strictEqual(request, expected)
})
})
})
assert.strictEqual(request, expected);
});
});
});

View File

@@ -68,7 +68,7 @@
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": false, /* Skip type checking of declaration files. */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"exclude": [