import { ChildProcess } from "child_process"; import { EventEmitter } from "events"; /** * Provides a client for tsserver. Tsserver does not use standard JSON-RPC * protocol thus the need for this custom client. */ export class Client { private data: Buffer|undefined; private id = 0; private responseEmitter = new EventEmitter(); constructor(private readonly server: ChildProcess) {} listen() { this.server.stdout.on('data', (data: Buffer) => { this.data = this.data ? Buffer.concat([this.data, data]) : data; const CONTENT_LENGTH = 'Content-Length: ' const index = this.data.indexOf(CONTENT_LENGTH); if (index < 0) { return; } let start = index + CONTENT_LENGTH.length; let end = this.data.indexOf('\r\n', start); if (end < start) { return; } const contentLengthStr = this.data.slice(start, end).toString(); const contentLength = Number(contentLengthStr); if (isNaN(contentLength) || contentLength < 0) { return; } start = end + 4; end = start + contentLength; if (end > this.data.length) { return; } const content = this.data.slice(start, end).toString(); this.data = this.data.slice(end); try { const payload = JSON.parse(content); if (payload.type === "event") { return; } this.responseEmitter.emit('response', payload); } catch (error) { this.responseEmitter.emit('error', error); } }); } async send(type: string, command: string, params: {}) { const request = { seq: this.id++, type, command, arguments: params }; this.server.stdin.write(JSON.stringify(request) + '\r\n'); return new Promise((resolve, reject) => { this.responseEmitter.once('response', resolve); this.responseEmitter.once('error', reject); }); } async sendRequest(command: string, params: {}) { return this.send('request', command, params); } }