From fc05a8b4598a0c8a218490660bf971bb0839d3d4 Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sat, 9 May 2026 16:18:11 +0800 Subject: [PATCH] feat(bot): add Baileys session wrapper Co-Authored-By: Claude Sonnet 4.6 --- apps/bot/src/whatsapp/session.ts | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 apps/bot/src/whatsapp/session.ts diff --git a/apps/bot/src/whatsapp/session.ts b/apps/bot/src/whatsapp/session.ts new file mode 100644 index 0000000..c0e7940 --- /dev/null +++ b/apps/bot/src/whatsapp/session.ts @@ -0,0 +1,74 @@ +import { mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { + makeWASocket, + useMultiFileAuthState, + type WASocket, + type ConnectionState, + DisconnectReason, + Browsers, +} from "@whiskeysockets/baileys"; +import { logger } from "../logger.js"; +import { env } from "../env.js"; + +export type SessionEvent = + | { type: "qr"; payload: string } + | { type: "open"; phoneNumber: string | undefined } + | { type: "close"; reason: number; loggedOut: boolean }; + +export type SessionEventHandler = (event: SessionEvent) => void | Promise; + +export type Session = { + accountId: string; + socket: WASocket; + close: () => Promise; +}; + +export async function startSession(params: { + accountId: string; + onEvent: SessionEventHandler; +}): Promise { + const { accountId, onEvent } = params; + const sessionDir = join(env.SESSIONS_DIR, accountId); + await mkdir(sessionDir, { recursive: true }); + + const { state, saveCreds } = await useMultiFileAuthState(sessionDir); + + const socket = makeWASocket({ + auth: state, + printQRInTerminal: false, + browser: Browsers.macOS("Safari"), + syncFullHistory: false, + logger: logger.child({ accountId, component: "baileys" }) as never, + }); + + socket.ev.on("creds.update", () => void saveCreds()); + + socket.ev.on("connection.update", (update: Partial) => { + if (update.qr) { + void onEvent({ type: "qr", payload: update.qr }); + } + if (update.connection === "open") { + const phoneNumber = socket.user?.id?.split(":")[0]; + void onEvent({ type: "open", phoneNumber }); + } + if (update.connection === "close") { + const reason = + (update.lastDisconnect?.error as { output?: { statusCode?: number } } | undefined)?.output?.statusCode ?? 0; + const loggedOut = reason === DisconnectReason.loggedOut; + void onEvent({ type: "close", reason, loggedOut }); + } + }); + + return { + accountId, + socket, + close: async () => { + try { + socket.end(undefined); + } catch (err) { + logger.warn({ err, accountId }, "session.close: error closing socket"); + } + }, + }; +}