UNPKG

tradingview-api-adapter

Version:

API Adapter for real-time market data as quoted prices and symbol ticker details from Tradingview

200 lines (144 loc) 5.66 kB
import {WebSocket as NodeWebSocket} from 'ws' import {EventEmitter} from "events"; export enum ProtocolStatus { Constructor, Connecting, Open, SessionOpen, Close } type SocketDriver = WebSocket | NodeWebSocket; interface ReactData { readyState: ProtocolStatus } export class WsProtocol { private readonly $eventBus: EventEmitter; private $data: ReactData; private webSocket!: SocketDriver; private protocolInfo = {}; private messageBuffer: Array<string> = []; private bufferClearing = false; constructor() { this.$eventBus = new EventEmitter(); this.$data = this.setupReactiveData(); this.createConnection(); } /** Section of the protected code **/ public on(eventName: "message", listener: (data: any) => void): void; public on(eventName: string, listener: (data: any) => void): void; public on(eventName: string, listener: (data: any) => void): void { if(eventName === "message"){ this.$eventBus.on('message', listener); } else { this.$eventBus.on(eventName, listener); } } public emit(eventName: string, data: any): void { if(eventName === "send") { this.send(WsProtocol.CollectMessagePacket(data)); } else { this.$eventBus.emit(eventName, data); } } /** End section of the protected code **/ /** Section of the system code **/ private createConnection(): void{ this.webSocket = WsProtocol.Driver( 'wss://widgetdata.tradingview.com/socket.io/websocket', 'https://s.tradingview.com' ); this.$data.readyState = ProtocolStatus.Connecting; this.webSocket.onerror = this.handleErrorEvent.bind(this); this.webSocket.onclose = this.handleCloseEvent.bind(this); this.webSocket.onmessage = this.handleMessageEvent.bind(this); this.webSocket.onopen = this.handleOpenEvent.bind(this); } private handleCloseEvent(ev: CloseEvent): void { this.$data.readyState = ProtocolStatus.Close; } private handleErrorEvent(ev: Event): void { } private handleMessageEvent(ev: MessageEvent): void { const parsedMessageData = WsProtocol.ParseMessagePacket(ev.data); if(!parsedMessageData.length) return; if(typeof parsedMessageData[0] === "number") { this.PingPong(parsedMessageData[0]) } else if(this.$data.readyState === ProtocolStatus.SessionOpen) { // тут нужно учесть момент с выпаданием с сокета this.emit('message', parsedMessageData) } else { this.protocolInfo = parsedMessageData[0]; this.$data.readyState = ProtocolStatus.SessionOpen; } } private handleOpenEvent(ev: Event): void { this.$data.readyState = ProtocolStatus.Open; } private send(packet: string): boolean { if(this.bufferClearing || this.$data.readyState != ProtocolStatus.SessionOpen) { this.messageBuffer.push(packet) return false; } this.webSocket.send(packet); return true; } private sendBufferedMessages(): void { //перепроверить написание функции (возможно я тут ошибся) this.bufferClearing = true; while(this.messageBuffer.length !== 0) { if(this.$data.readyState === ProtocolStatus.SessionOpen){ const packet = this.messageBuffer.shift() as string; this.webSocket.send(packet); } else { break; } } this.bufferClearing = false; } private PingPong(pingCount: number): void { this.emit("send", `~h~${pingCount}`) } /** End section of the system code **/ /** Section of the adjusters function code **/ private setupReactiveData(): ReactData { const observableData = { readyState: ProtocolStatus.Constructor }; return new Proxy(observableData, { set: (target, key: "readyState" , value) => { const previousValue = target[key]; target[key] = value; if(key === 'readyState') this.readyStateInterceptor(value, previousValue); return true; }, }) } private readyStateInterceptor(to: ProtocolStatus, from: ProtocolStatus): void { if(from === ProtocolStatus.Open && to === ProtocolStatus.SessionOpen) { this.sendBufferedMessages(); } } /** End section of the adjusters function code **/ /** Section of the utilities function code **/ private static Driver(URL: string, originURL: string): SocketDriver { if(typeof window === 'undefined'){ return new NodeWebSocket(URL, {origin: originURL}); } else { return new WebSocket(URL) } } private static ParseMessagePacket(packet: string): Array<string | object> { return packet .replace(/~h~/g, '') .split(/~m~[0-9]{1,}~m~/g) .map((p: string) => !p ? false : JSON.parse(p)) .filter((p: string | object) => p); } private static CollectMessagePacket(messageData: string | object): string { const collectedPacket = typeof messageData === 'object' ? JSON.stringify(messageData) : messageData; return `~m~${collectedPacket.length}~m~${collectedPacket}` } /** End section of the utilities function code **/ }