UNPKG

blinktrade

Version:

BlinkTrade client for node.js

258 lines (210 loc) 6.57 kB
/** * BlinkTradeJS SDK * (c) 2016-present BlinkTrade, Inc. * * This file is part of BlinkTradeJS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * @flow */ import WS from 'ws'; import nodeify from 'nodeify'; import EventEmitter from 'eventemitter2'; import { getMac } from '../util/macaddress'; import { getFingerPrint } from '../util/fingerPrint'; import { getStun, closeStun } from '../util/stun'; import { MsgActionRes } from '../constants/messages'; import BROKERS from '../constants/brokers'; import Transport, { IS_NODE, IS_BROWSER } from './transport'; import type { Stun, Message, ResolveReject, PromiseEmitter, BlinkTradeWSParams, } from '../types'; import { getRequest, setRequest, deleteRequest, } from '../listener'; const RECONNECT_INTERVAL = 5000; class WebSocketTransport extends Transport { /* * WebSocket Instance */ socket: WebSocket; /* * FingerPrint */ fingerPrint: string; /* * Stun object */ stun: Stun; connection: ResolveReject; lastPromise: ResolveReject; lastMessageSent: Message; /* * Event emitter to dispatch websocket updates */ eventEmitter: EventEmitter; headers: ?Object; autoReconnect: boolean; reconnectInterval: number; constructor(params?: BlinkTradeWSParams = {}) { super(params, params.brokerId === BROKERS.BITCAMBIO ? 'wsBitcambio' : 'ws'); this.stun = { local: null, public: [] }; this.getFingerPrint(params.fingerPrint); this.headers = params.headers; this.autoReconnect = params.reconnect || false; this.reconnectInterval = params.reconnectInterval || RECONNECT_INTERVAL; this.eventEmitter = new EventEmitter({ wildcard: true, delimiter: ':' }); } connect(callback?: Function): Promise<Object> { return nodeify.extend(new Promise((resolve, reject) => { this.connection = { resolve, reject }; const WebSocket = IS_NODE ? WS : window.WebSocket; this.getStun(); this.socket = new WebSocket(this.endpoint, [], this.headers); this.socket.onopen = this.onOpen.bind(this); this.socket.onclose = this.onClose.bind(this); this.socket.onerror = this.onError.bind(this); this.socket.onmessage = this.onMessage.bind(this); })).nodeify(callback); } disconnect(): void { this.socket.close(); this.closeStun(); } onOpen(e: Event): void { this.eventEmitter.emit('OPEN', e); this.connection.resolve({ connected: true }); } onClose(e: Event): void { this.eventEmitter.emit('CLOSE', e, this.lastMessageSent); this.closeStun(); this.reconnect(); } onError(error: Event): void { this.eventEmitter.emit('ERROR', error, this.lastMessageSent); } reconnect() { if (this.autoReconnect) { setTimeout(() => this.connect(), this.reconnectInterval); } } sendMessage(msg: Message): void { if (this.socket.readyState === 1) { const data = msg; data.STUNTIP = this.stun; data.FingerPrint = this.fingerPrint; this.lastMessageSent = data; this.eventEmitter.emit('send', data); this.socket.send(JSON.stringify(data)); } } sendMessageAsPromise(msg: Message): Promise<Message> { return new Promise((resolve, reject) => { this.lastPromise = { resolve, reject }; setRequest(msg, { resolve, reject }); this.sendMessage(msg); }); } onMessage(msg: Object): void { const data = JSON.parse(msg.data); this.eventEmitter.emit('receive', data); if (!MsgActionRes[data.MsgType]) { if (data.MsgType === 'ERROR') { this.eventEmitter.emit('ERROR', data, this.lastMessageSent); } return; } this.dispatchPromise(data); this.dispatchEventEmitters(data); } dispatchPromise(data: Object): any { const request = getRequest(data); if (request && request.resolve) { deleteRequest(data); return request.resolve(data); } } dispatchEventEmitters(data: Object): any { const type = data.MsgType; const reqId = MsgActionRes[type][1]; this.eventEmitter.emit(type, data); if (data[reqId]) { this.eventEmitter.emit(type + ':' + data[reqId], data); } } getFingerPrint(customFingerprint?: string): void { if (IS_NODE) { return getMac(macAddress => { this.fingerPrint = macAddress; }); } else if (IS_BROWSER) { this.fingerPrint = getFingerPrint(); } else if (customFingerprint) { this.fingerPrint = customFingerprint; } else { throw new Error('FingerPrint not provided'); } } getStun(): void { if (IS_NODE) { getStun(data => { this.stun = data; }); } } closeStun(): void { if (IS_NODE) { closeStun(); } } /* eslint-disable no-param-reassign */ emitterPromise<T>(promise: Object, callback?: Function): PromiseEmitter<T> { promise.on = (event: string, listener: Function) => { this.eventEmitter.on(event, listener); return promise; }; promise.onAny = (listener: Function) => { this.eventEmitter.onAny(listener); return promise; }; promise.offAny = (listener: Function) => { this.eventEmitter.offAny(listener); return promise; }; promise.once = (event: string, listener: Function) => { this.eventEmitter.once(event, listener); return promise; }; promise.many = (event: string, times: number, listener: Function) => { this.eventEmitter.many(event, times, listener); return promise; }; promise.removeListener = (event: string, listener: Function) => { this.eventEmitter.removeListener(event, listener); return promise; }; promise.removeAllListeners = (events: Array<string>) => { this.eventEmitter.removeAllListeners(events); return promise; }; return nodeify.extend(promise).nodeify(callback); } /* eslint-enable no-param-reassign */ } export default WebSocketTransport;