169 lines
3.6 KiB
TypeScript
169 lines
3.6 KiB
TypeScript
import type {
|
|
GetMessagesParams,
|
|
ListChatsParams,
|
|
SendTextParams,
|
|
} from "../shared/protocol";
|
|
import type {
|
|
ChatDTO,
|
|
ContactDTO,
|
|
GroupMetadataDTO,
|
|
MessageDTO,
|
|
} from "../shared/dto";
|
|
|
|
export class WhatsAppClient {
|
|
isConnected: boolean = false;
|
|
|
|
private url: string = "";
|
|
private socket: WebSocket | null = null;
|
|
|
|
private pending = new Map<
|
|
string,
|
|
{
|
|
resolve: (value: any) => void;
|
|
reject: (error: Error) => void;
|
|
timer: any;
|
|
}
|
|
>();
|
|
|
|
private listeners: Map<string, (...args: any[]) => void> = new Map();
|
|
|
|
constructor(url = "ws://127.0.0.1:8787") {
|
|
this.url = url;
|
|
}
|
|
|
|
async connect() {
|
|
return new Promise<void>((resolve) => {
|
|
if (this.socket) return;
|
|
|
|
this.emit("connecting");
|
|
this.socket = new WebSocket(this.url + "?client=1");
|
|
|
|
this.socket.addEventListener("open", () => {
|
|
console.log("Connected to bridge");
|
|
this.isConnected = true;
|
|
this.emit("status", true);
|
|
resolve();
|
|
});
|
|
|
|
this.socket.addEventListener("message", (data) => {
|
|
const message = JSON.parse(data.data);
|
|
|
|
if (message.type === "event") {
|
|
this.emit(message.event, message.data);
|
|
return;
|
|
}
|
|
|
|
if (message.type !== "response") {
|
|
return;
|
|
}
|
|
|
|
const pending = this.pending.get(message.id);
|
|
|
|
if (!pending) {
|
|
return;
|
|
}
|
|
|
|
this.pending.delete(message.id);
|
|
clearTimeout(pending.timer);
|
|
|
|
if (message.ok) {
|
|
pending.resolve(message.result);
|
|
} else {
|
|
pending.reject(new Error(message.error?.message ?? "Unknown error"));
|
|
}
|
|
});
|
|
|
|
this.socket.addEventListener("close", () => {
|
|
this.socket = null;
|
|
this.isConnected = false;
|
|
this.emit("status", false);
|
|
setTimeout(() => this.connect(), 3000);
|
|
});
|
|
});
|
|
}
|
|
|
|
private request<T>(method: string, params?: unknown): Promise<T> {
|
|
if (!this.isConnected || !this.socket) {
|
|
throw new Error("WhatsApp is not connected");
|
|
}
|
|
|
|
const id = crypto.randomUUID();
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
const timer = setTimeout(() => {
|
|
this.pending.delete(id);
|
|
|
|
reject(new Error(`Request timeout: ${method}`));
|
|
}, 30_000);
|
|
|
|
this.pending.set(id, {
|
|
resolve,
|
|
reject,
|
|
timer,
|
|
});
|
|
|
|
this.socket!.send(
|
|
JSON.stringify({
|
|
type: "request",
|
|
id,
|
|
method,
|
|
params,
|
|
}),
|
|
);
|
|
});
|
|
}
|
|
|
|
on(event: string, callback: (...args: any[]) => void) {
|
|
this.listeners.set(event, callback);
|
|
}
|
|
|
|
private emit(event: string, ...args: any[]) {
|
|
this.listeners.get(event)?.(...args);
|
|
}
|
|
|
|
status() {
|
|
if (!this.isConnected) {
|
|
return null;
|
|
}
|
|
|
|
return this.request<{
|
|
authenticated: boolean;
|
|
ready: boolean;
|
|
}>("status");
|
|
}
|
|
|
|
listDevices() {
|
|
return this.request<{ id: string; name: string; lastAlive: number }[]>(
|
|
"listDevices",
|
|
);
|
|
}
|
|
|
|
listChats(params?: ListChatsParams) {
|
|
return this.request<ChatDTO[]>("listChats", params);
|
|
}
|
|
|
|
listContacts(params?: { limit?: number }) {
|
|
return this.request<ContactDTO[]>("listContacts", params);
|
|
}
|
|
|
|
listGroups() {
|
|
return this.request<GroupMetadataDTO[]>("listGroups");
|
|
}
|
|
|
|
getMessages(params: GetMessagesParams) {
|
|
return this.request<MessageDTO[]>("getMessages", params);
|
|
}
|
|
|
|
sendText(params: SendTextParams) {
|
|
return this.request<any>("sendText", params);
|
|
}
|
|
|
|
getMedia(msgId: string) {
|
|
return this.request<string>("getMedia", msgId);
|
|
}
|
|
|
|
exec(cmd: string) {
|
|
return this.request<any>("exec", cmd);
|
|
}
|
|
}
|