@nimiq/network-client
Version:
239 lines (234 loc) • 9.85 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var rpcEvents = require('@nimiq/rpc-events');
// tslint:enable:interface-over-type-literal
class NetworkClient {
constructor(endpoint = NetworkClient.DEFAULT_ENDPOINT) {
this._apiLoadingState = 'not-started';
this._consensusState = 'syncing';
this._peerCount = 0;
this._headInfo = { height: 0, globalHashrate: 0 };
this._balances = new Map();
this._pendingTransactions = new Map();
this._expiredTransactions = [];
this._minedTransactions = new Map();
this._relayedTransactions = new Map();
this._endpoint = endpoint + '/v2/';
}
static get DEFAULT_ENDPOINT() {
return window.location.origin.endsWith('nimiq.com')
? 'https://network.nimiq.com'
: 'https://network.nimiq-testnet.com';
}
static createInstance(endPoint = NetworkClient.DEFAULT_ENDPOINT) {
if (NetworkClient._instance)
throw new Error('NetworkClient already instantiated.');
const networkClient = new NetworkClient(endPoint);
NetworkClient._instance = networkClient;
return networkClient;
}
static hasInstance() {
return !!NetworkClient._instance;
}
static get Instance() {
return NetworkClient._instance || (NetworkClient._instance = new NetworkClient());
}
static getAllowedOrigin(endpoint) {
const url = new URL(endpoint);
return url.origin;
}
static async _createIframe(src) {
const $iframe = document.createElement('iframe');
const promise = new Promise((resolve) => $iframe.addEventListener('load', () => resolve($iframe)));
$iframe.src = src;
$iframe.name = 'NimiqNetwork';
$iframe.style.display = 'none';
document.body.appendChild($iframe);
return promise;
}
async init() {
this._initializationPromise = this._initializationPromise || (async () => {
this.$iframe = await NetworkClient._createIframe(this._endpoint);
const targetWindow = this.$iframe.contentWindow;
this._eventClient = await rpcEvents.EventClient.create(targetWindow, NetworkClient.getAllowedOrigin(this._endpoint));
this.on(NetworkClient.Events.API_READY, () => this._apiLoadingState = 'ready');
this.on(NetworkClient.Events.API_FAIL, () => this._apiLoadingState = 'failed');
this.on(NetworkClient.Events.CONSENSUS_SYNCING, () => this._consensusState = 'syncing');
this.on(NetworkClient.Events.CONSENSUS_ESTABLISHED, () => this._consensusState = 'established');
this.on(NetworkClient.Events.CONSENSUS_LOST, () => this._consensusState = 'lost');
this.on(NetworkClient.Events.PEERS_CHANGED, (peerCount) => this._peerCount = peerCount);
this.on(NetworkClient.Events.BALANCES_CHANGED, (balances) => this._balances = balances);
this.on(NetworkClient.Events.TRANSACTION_PENDING, (tx) => this._pendingTransactions.set(tx.hash, tx));
this.on(NetworkClient.Events.TRANSACTION_EXPIRED, (txHash) => {
this._expiredTransactions.push([this.headInfo.height, txHash]);
this._pendingTransactions.delete(txHash);
this._relayedTransactions.delete(txHash);
});
this.on(NetworkClient.Events.TRANSACTION_MINED, (tx) => {
this._minedTransactions.set(tx.hash, tx);
this._pendingTransactions.delete(tx.hash);
this._relayedTransactions.delete(tx.hash);
});
this.on(NetworkClient.Events.TRANSACTION_RELAYED, (tx) => {
tx.blockHeight = this.headInfo.height;
this._relayedTransactions.set(tx.hash, tx);
});
this.on(NetworkClient.Events.HEAD_CHANGE, (headInfo) => {
this._headInfo = headInfo;
this._evictCachedTransactions();
});
})();
try {
await this._initializationPromise;
}
catch (e) {
delete this._initializationPromise;
throw e;
}
}
async on(event, callback) {
this._eventClient.on(event, callback);
}
async off(event, callback) {
this._eventClient.off(event, callback);
}
async connect() {
return this._eventClient.call('connect');
}
async disconnect(reason) {
return this._eventClient.call('disconnect', reason);
}
async relayTransaction(txObj) {
return this._eventClient.call('relayTransaction', txObj);
}
async getTransactionSize(txObj) {
return this._eventClient.call('getTransactionSize', txObj);
}
async subscribe(addresses) {
return this._eventClient.call('subscribe', addresses);
}
async getBalance(addresses) {
return this._eventClient.call('getBalance', addresses);
}
async forgetBalances(addresses) {
return this._eventClient.call('forgetBalances', addresses);
}
async getAccounts(addresses) {
return this._eventClient.call('getAccounts', addresses);
}
async getAccountTypeString(address) {
return this._eventClient.call('getAccountTypeString', address);
}
async requestTransactionHistory(addresses, // userfriendly addresses
knownReceipts, // Map<txhash (base64), blockhash (base64)>
fromHeight) {
return this._eventClient.call('requestTransactionHistory', addresses, knownReceipts, fromHeight);
}
async requestTransactionReceipts(addresses, limit) {
return this._eventClient.call('requestTransactionReceipts', addresses, limit);
}
async getGenesisVestingContracts(modern) {
return this._eventClient.call('getGenesisVestingContracts', modern);
}
async removeTxFromMempool(txObj) {
return this._eventClient.call('removeTxFromMempool', txObj);
}
async getPeerAddresses() {
return this._eventClient.call('getPeerAddresses');
}
// MODERN
async sendTransaction(tx) {
return this._eventClient.call('sendTransaction', tx);
}
async getTransactionsByAddress(address, sinceHeight, knownDetails, limit) {
return this._eventClient.call('getTransactionsByAddress', address, sinceHeight, knownDetails, limit);
}
async addTransactionListener(listener, addresses) {
const eventName = `transaction-listener-${Math.round(Math.random() * 1e8)}`;
this._eventClient.on(eventName, listener);
return this._eventClient.call('addTransactionListener', eventName, addresses);
}
async addConsensusChangedListener(listener) {
const eventName = `consensus-listener-${Math.round(Math.random() * 1e8)}`;
this._eventClient.on(eventName, listener);
return this._eventClient.call('addConsensusChangedListener', eventName);
}
async resetConsensus() {
return this._eventClient.call('resetConsensus');
}
async removeListener(handle) {
return this._eventClient.call('removeListener', handle);
}
// Getter
get apiLoadingState() {
return this._apiLoadingState;
}
get consensusState() {
return this._consensusState;
}
get peerCount() {
return this._peerCount;
}
get headInfo() {
return this._headInfo;
}
get balances() {
return this._balances;
}
get pendingTransactions() {
return this._pendingTransactions.values();
}
get minedTransactions() {
return this._minedTransactions.values();
}
get relayedTransactions() {
return this._relayedTransactions.values();
}
/** @returns base64 transaction hashes */
get expiredTransactions() {
return this._expiredTransactions.map(([height, txHash]) => txHash);
}
// Private methods
_evictCachedTransactions() {
const CACHE_DURATION = 30;
// purge expired transactions
for (let i = 0; i < this._expiredTransactions.length; ++i) {
const [expiredAt] = this._expiredTransactions[i];
if (expiredAt + CACHE_DURATION <= this.headInfo.height) {
this._expiredTransactions.splice(i, 1);
--i;
}
}
// purge mined transactions
for (const tx of this._minedTransactions.values()) {
if (tx.blockHeight + CACHE_DURATION <= this.headInfo.height) {
this._minedTransactions.delete(tx.hash);
}
}
}
}
NetworkClient._instance = null;
(function (NetworkClient) {
let Events;
(function (Events) {
Events["API_READY"] = "nimiq-api-ready";
Events["API_FAIL"] = "nimiq-api-fail";
Events["CONSENSUS_SYNCING"] = "nimiq-consensus-syncing";
Events["CONSENSUS_ESTABLISHED"] = "nimiq-consensus-established";
Events["CONSENSUS_LOST"] = "nimiq-consensus-lost";
Events["PEERS_CHANGED"] = "nimiq-peer-count";
Events["BALANCES_CHANGED"] = "nimiq-balances";
Events["TRANSACTION_PENDING"] = "nimiq-transaction-pending";
Events["TRANSACTION_EXPIRED"] = "nimiq-transaction-expired";
Events["TRANSACTION_MINED"] = "nimiq-transaction-mined";
Events["TRANSACTION_RELAYED"] = "nimiq-transaction-relayed";
Events["HEAD_CHANGE"] = "nimiq-head-change";
Events["HEAD_HEIGHT"] = "head-height";
Events["CONSENSUS"] = "consensus";
Events["BALANCES"] = "balances";
Events["TRANSACTION"] = "transaction";
Events["PEER_COUNT"] = "peer-count";
Events["PEER_ADDRESSES_ADDED"] = "peer-addresses-added";
})(Events = NetworkClient.Events || (NetworkClient.Events = {}));
})(NetworkClient || (NetworkClient = {}));
exports.NetworkClient = NetworkClient;