mirror of
https://github.com/alexgo-io/electrum-client.git
synced 2026-01-12 22:43:15 +08:00
Switch to PNPM, switch from eslint to biome, fix issues
This commit is contained in:
108
.eslintrc.json
108
.eslintrc.json
@@ -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
33
biome.json
Normal 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
7864
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -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
1381
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
632
src/index.ts
632
src/index.ts
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
142
src/lib/util.ts
142
src/lib/util.ts
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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": [
|
||||
|
||||
Reference in New Issue
Block a user