import type { BridgeRequest } from "../shared/protocol"; import { serialize, serializeChat, serializeContact, serializeGroupMetadata, serializeMessage, } from "./serializer"; let socket: WebSocket | undefined; let consts: Record = { DEVICE_ID: String(Date.now()), DEVICE_NAME: "", }; function init() { try { const _consts = localStorage.getItem("wa-bridge-consts"); if (!_consts) { localStorage.setItem("wa-bridge-consts", JSON.stringify(consts)); } else { consts = JSON.parse(_consts); } } catch (_) {} } function send(message: unknown) { if (socket?.readyState !== WebSocket.OPEN) { throw new Error("WebSocket is not connected"); } socket.send(JSON.stringify(message)); } function reply(id: string, result: unknown) { send({ type: "response", id, ok: true, result: serialize(result), }); } function error(id: string, err: unknown) { send({ type: "response", id, ok: false, error: { code: "ERROR", message: typeof err === "object" && "message" in err! ? err.message : String(err), }, }); } async function handleRequest(request: BridgeRequest) { switch (request.method) { case "status": return { authenticated: WPP.conn.isAuthenticated(), ready: WPP.isReady, deviceId: consts.DEVICE_ID, deviceName: consts.DEVICE_NAME, }; case "listChats": { const chats = await WPP.chat.list({ ...(request.params?.limit != null ? { count: request.params.limit } : {}), ...(request.params?.groupsOnly ? { onlyGroups: true } : {}), ...(request.params?.usersOnly ? { onlyUsers: true } : {}), }); return chats.filter(Boolean).map(serializeChat); } case "listContacts": { const contacts = await WPP.contact.list(); const result = request.params?.limit ? contacts.slice(0, request.params.limit) : contacts; return result.filter(Boolean).map(serializeContact); } case "listGroups": { const groups = await WPP.group.getAllGroups(); return groups .filter((i) => i.groupMetadata != null) .map((i) => serializeGroupMetadata(i.groupMetadata)); } case "getMessages": { const messages = await WPP.chat.getMessages(request.params.chatId, { count: request.params.count ?? 50, direction: request.params.direction, id: request.params.id, }); return messages.filter(Boolean).map(serializeMessage); } case "sendText": { const result = await WPP.chat.sendTextMessage( request.params.chatId, request.params.text, request.params.options, ); return serialize(result); } case "sendFile": { const result = await WPP.chat.sendFileMessage( request.params.chatId, request.params.data, request.params.options, ); return serialize(result); } case "getMedia": const media = await WPP.chat .downloadMedia(request.params) .then(WPP.util.blobToBase64); return media; case "exec": const fn = new Function(request.params); return fn(); default: throw new Error(`Unknown method: ${(request as any).method}`); } } function connect() { if ( socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) ) { return; } socket = new WebSocket(__WS_URL__); socket.onopen = () => { console.log("[WA Bridge] connected", __WS_URL__); send({ type: "event", event: "ready", data: { version: __VERSION__, authenticated: WPP.conn.isAuthenticated(), deviceId: consts.DEVICE_ID, deviceName: consts.DEVICE_NAME, }, }); }; socket.onmessage = async (event) => { try { const request = JSON.parse(event.data) as BridgeRequest; if (request.type === "ping") { send({ type: "pong" }); } if (request.type !== "request") { return; } try { const result = await handleRequest(request); reply(request.id, JSON.stringify(result)); } catch (err) { error(request.id, err); } } catch (err) { console.error("[WA Bridge] invalid message", err); } }; socket.onclose = (event) => { console.log("[WA Bridge] disconnected", { code: event.code, reason: event.reason, wasClean: event.wasClean, }); socket = undefined; setTimeout(connect, 3000); }; socket.onerror = (event) => { console.error("[WA Bridge] websocket error", event); }; } function start() { WPP.loader.onReady(() => { console.log("[WA Bridge] WA-JS ready"); connect(); /* * Forward new-message events to Node. */ WPP.on("chat.new_message", (message: unknown) => { if (socket?.readyState !== WebSocket.OPEN) { return; } send({ type: "event", event: "message", data: serializeMessage(message), }); }); }); } init(); start();