@serptech/api
Version:
Library for work with SERP API
184 lines (146 loc) • 4.32 kB
text/typescript
import EventEmitter from "events";
import debounce from "lodash.debounce";
interface EventEmitterInterface {
on: (event: string | symbol, listener: (...args: any[]) => void) => any;
emit: (event: string | symbol, ...args: any[]) => any;
}
interface SocketClientInterface {
send(msg: string): void;
onopen(event: any): void;
onmessage(event: any): void;
close(): void;
onclose(data?: any): void;
terminate(): void;
}
interface SerpWsApiV1SettingsInterface {
token?: string;
version?: number | string;
endpoint?: string;
apiEndpoints: { v1: string };
SocketClient: new (endpoint: string) => SocketClientInterface;
}
export interface SerpWsApiV1Interface extends EventEmitterInterface {
setToken(token: string): void;
setEndpoint(endpoint: string): void;
connect(): void;
disconnect(): void;
terminate(): void;
}
class SerpWsApiV1 extends EventEmitter implements SerpWsApiV1Interface {
private SocketClient: new (endpoint: string) => SocketClientInterface;
socket?: SocketClientInterface;
private isSocketOpen = false;
private token?: string;
private endpoint: string;
private reconnectTimeout?: ReturnType<typeof setTimeout>;
private pingTimeout?: ReturnType<typeof setTimeout>;
private pingBackOffDelay = 1000;
private reconnectTime = 30000;
constructor({
token,
SocketClient,
apiEndpoints,
endpoint,
}: SerpWsApiV1SettingsInterface) {
super();
this.token = token;
this.endpoint = endpoint || apiEndpoints.v1;
this.SocketClient = SocketClient;
}
private heartbeat(): void {
if (!this.socket) return;
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
}
this.socket.send(JSON.stringify({ action: "PING" }));
/**
* Если ответа нет в течение this.pingBackOffDelay
* шлём еще один запрос и так до достижения this.reconnectTime
*/
this.pingTimeout = setTimeout(() => {
this.heartbeat();
this.pingBackOffDelay = this.pingBackOffDelay * 2;
}, this.pingBackOffDelay);
this.reconnectTimeout = setTimeout(() => {
this.disconnect();
this.connect();
}, this.reconnectTime);
}
private debouncedHeartbeat(): () => void {
return debounce(this.heartbeat, 5000);
}
private resetPing(): void {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
}
this.pingBackOffDelay = 1000;
}
setToken(token: string): void {
this.token = token;
if (this.isSocketOpen) {
this.disconnect();
}
}
setEndpoint(endpoint: string): void {
this.endpoint = endpoint;
if (this.isSocketOpen) {
this.disconnect();
}
}
connect(): void {
this.socket = new this.SocketClient(this.endpoint);
this.socket.onopen = (): void => {
if (!this.socket) return;
this.isSocketOpen = true;
this.resetPing();
this.socket.send(
JSON.stringify({
action: "AUTH",
data: {
token: this.token,
},
})
);
this.socket.onmessage = ({ data } = { data: "" }): void => {
try {
const parsedData = JSON.parse(data);
if (parsedData.auth === "ok") {
this.debouncedHeartbeat();
return;
}
if (parsedData.PING) {
this.resetPing();
this.debouncedHeartbeat();
return;
}
this.emit("message", parsedData);
} catch (error) {
this.emit("error", error);
}
};
this.socket.onclose = (data): void => {
this.resetPing();
this.emit("disconnect", data);
this.isSocketOpen = false;
};
this.emit("connect");
};
}
disconnect(): void {
if (this.socket) {
this.socket.close();
}
}
terminate(): void {
if (this.socket) {
this.socket.terminate();
}
}
}
export { SerpWsApiV1 };