UNPKG

zumokit

Version:

ZumoKit is a Wallet as a Service SDK

368 lines (322 loc) 9.76 kB
import fetch from 'node-fetch'; import { WebSocket, MessageEvent, CloseEvent } from 'ws'; import { CurrencyCode, TokenSet, HistoricalExchangeRatesJSON, LogLevel, } from './interfaces'; import { errorProxy } from './utility'; import { ExchangeRate, ExchangeRates, TransactionFeeRate, TransactionFeeRates, HistoricalExchangeRates, } from './models'; import { User } from './User'; import { Utils } from './Utils'; import { ZumoKitError } from './ZumoKitError'; /** * ZumoKit instance. Refer to <a href="https://developers.zumo.money/docs/guides/initialize-zumokit">documentation</a> for usage details. * */ export class ZumoKit { private zumoCoreModule: any; private socket: WebSocket | null = null; private wsListener: any = null; private zumoCore: any; private changeListeners: Array<() => void> = []; private changeListenersImpl: Array<any> = []; private disconnected = false; /** ZumoKit SDK semantic version tag if exists, commit hash otherwise. */ version: string; /** Currently signed-in user or null. */ currentUser: User | null = null; /** Crypto utilities. */ utils: Utils; /** Mapping between currency pairs and available exchange rates. */ exchangeRates: ExchangeRates = {}; /** Mapping between cryptocurrencies and available transaction fee rates. */ transactionFeeRates: TransactionFeeRates = {}; private async request( url: string, method: string, headers: any, data: string, callback: any ) { const requestHeaders: Record<string, string> = {}; const mapKeys = headers.keys(); for (let i = 0; i < mapKeys.size(); i++) { const key = mapKeys.get(i); requestHeaders[key] = headers.get(key); } try { const response = await fetch(url, { method, headers: requestHeaders, body: data, }); const result = await response.text(); callback.onSuccess(response.status, result); } catch (exception) { if (typeof exception === 'number') { const error = new ZumoKitError( this.zumoCoreModule.getException(exception) ); callback.onNetworkError(error.message); } else { const { message } = (exception as unknown) as any; callback.onNetworkError(message); } } } private connectWebSocket(url: string) { this.socket = new WebSocket(url); this.socket.on('open', () => { this.wsListener?.onOpen(url); }); this.socket.on('message', (event: MessageEvent) => { this.wsListener?.onMessage(event.toString()); }); this.socket.on('error', () => { if (this.disconnected) { return; } this.wsListener?.onError('WebSocket error observed'); this.socket?.close(); }); this.socket.on('close', (event: CloseEvent) => { if (this.disconnected) { return; } this.wsListener?.onClose( `WebSocket connection closed with exit code ${event.code}, additional info: ${event.reason}` ); // TODO: Reconnect via fuzzing back off generator setTimeout(() => { this.connectWebSocket(url); }, 5000); }); } private subscribe(listener: any) { this.wsListener = listener; } send(message: string) { this.socket?.send(message); } private createWebSocket(url: string) { const boundedConnectWebSocket = this.connectWebSocket.bind(this); const boundedSend = this.send.bind(this); const boundedSubscribe = this.subscribe.bind(this); return new this.zumoCoreModule.WebSocketWrapper({ connect() { boundedConnectWebSocket(url); }, send(message: string) { boundedSend(message); }, subscribe(listener: any) { boundedSubscribe(listener); }, }); } /** @internal */ constructor( zumoCoreModule: any, apiKey: string, apiUrl: string, transactionServiceUrl: string, cardServiceUrl: string, notificationServiceUrl: string, exchangeServiceUrl: string, custodyServiceUrl: string ) { this.zumoCoreModule = zumoCoreModule; this.version = zumoCoreModule.ZumoCore.getVersion(); const boundedRequest = this.request.bind(this); const httpImpl = new zumoCoreModule.HttpProviderWrapper({ async request( url: string, method: string, headers: any, data: string, callback: any ) { boundedRequest(url, method, headers, data, callback); }, }); const boundedCreateWebSocket= this.createWebSocket.bind(this); const wsFactory = new zumoCoreModule.WebSocketFactoryWrapper({ createWebSocket(url: string) { return boundedCreateWebSocket(url); }, }); this.zumoCore = new zumoCoreModule.ZumoCore( httpImpl, wsFactory, apiKey, apiUrl, transactionServiceUrl, cardServiceUrl, notificationServiceUrl, exchangeServiceUrl, custodyServiceUrl ); this.utils = new Utils(this.zumoCoreModule, this.zumoCore.getUtils()); this.addChangeListener(() => { this.exchangeRates = ExchangeRates( JSON.parse(this.zumoCore.getExchangeRates()) ); this.transactionFeeRates = TransactionFeeRates( JSON.parse(this.zumoCore.getTransactionFeeRates()) ); }); } /** * Sets log level for current logger. * * @param logLevel log level, e.g. 'debug' or 'info' */ setLogLevel(logLevel: LogLevel) { this.zumoCoreModule.ZumoCore.setLogLevel(logLevel); } /** * Sets log handler for all ZumoKit related logs. * * @param listener interface to listen to changes * @param logLevel log level, e.g. 'debug' or 'info' */ onLog(listener: (message: string) => void, logLevel: LogLevel) { this.zumoCoreModule.ZumoCore.onLog( new this.zumoCoreModule.LogListenerWrapper({ onLog(message: string) { listener(message); }, }), logLevel ); } /** * Signs in user corresponding to user token set. Sets current user to the newly signed in user. * Refer to <a href="https://developers.zumo.money/docs/setup/server#get-zumokit-user-token">Server</a> guide for details on how to get user token set. * * @param tokenSet user token set */ signIn(userTokenSet: TokenSet) { return errorProxy<User>( this.zumoCoreModule, (resolve: any, reject: any) => { this.zumoCore.signIn( JSON.stringify(userTokenSet), new this.zumoCoreModule.UserCallbackWrapper({ onError: (error: string) => { reject(new ZumoKitError(error)); }, onSuccess: (user: any) => { this.currentUser = new User(this.zumoCoreModule, user); resolve(this.currentUser); }, }) ); } ); } /** Signs out current user. */ signOut() { this.zumoCore.signOut(); this.currentUser = null; } /** * Get exchange rate for selected currency pair. * * @param fromCurrency currency code * @param toCurrency currency code * * @return exchange rate or null */ getExchangeRate(fromCurrency: CurrencyCode, toCurrency: CurrencyCode) { const exchangeRate = this.zumoCore.getExchangeRate( fromCurrency, toCurrency ); if (exchangeRate.hasValue()) return new ExchangeRate(JSON.parse(exchangeRate.get())); return null; } /** * Get transaction fee rate for selected crypto currency. * * @param currency currency code * * @return transaction fee rate or null */ getTransactionFeeRate(currency: CurrencyCode) { const feeRate = this.zumoCore.getTransactionFeeRate(currency); if (feeRate.hasValue()) return new TransactionFeeRate(JSON.parse(feeRate.get())); return null; } /** * Fetch historical exchange rates for supported time intervals. * * @return historical exchange rates */ fetchHistoricalExchangeRates() { return errorProxy<HistoricalExchangeRates>( this.zumoCoreModule, (resolve: any, reject: any) => { this.zumoCore.fetchHistoricalExchangeRates( new this.zumoCoreModule.HistoricalExchangeRatesCallbackWrapper({ onError(error: string) { reject(new ZumoKitError(error)); }, onSuccess(json: string) { const historicalExchangeRatesJSON = JSON.parse( json ) as HistoricalExchangeRatesJSON; resolve(HistoricalExchangeRates(historicalExchangeRatesJSON)); }, }) ); } ); } /** * Listen to changes in current user’s sign in state, exchange rates, exchange settings or transaction fee rates. * * @param listener interface to listen to changes */ addChangeListener(listener: () => void) { const listenerImpl = new this.zumoCoreModule.ChangeListenerWrapper({ onChange() { listener(); }, }); this.zumoCore.addChangeListener(listenerImpl); this.changeListeners.push(listener); this.changeListenersImpl.push(listenerImpl); } /** * Remove change listener. * * @param listener interface to listen to changes */ removeChangeListener(listener: () => void) { let index; // eslint-disable-next-line no-cond-assign while ((index = this.changeListeners.indexOf(listener)) !== -1) { this.changeListeners.splice(index, 1); this.zumoCore.removeChangeListener( this.changeListenersImpl.splice(index, 1)[0] ); } } /** * Stops WebSocket connection to Zumo Enterprise services. */ disconnect() { this.disconnected = true; this.socket?.terminate(); } }