71 lines
2.0 KiB
TypeScript
71 lines
2.0 KiB
TypeScript
|
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);
|
||
|
}
|
||
|
}
|