From 02dad1347c3a5e4a1621b99ba0bea5d459661e4d Mon Sep 17 00:00:00 2001 From: maaz519 Date: Fri, 29 May 2026 11:08:51 +0530 Subject: [PATCH] feat: thread QR/status callbacks through session pool; persist to DB in main Co-Authored-By: Claude Sonnet 4.6 --- apps/worker/src/main.ts | 22 +++++++++++++++++++ apps/worker/src/whatsapp/session-pool.test.ts | 17 ++++++++++++++ apps/worker/src/whatsapp/session-pool.ts | 6 +++++ 3 files changed, 45 insertions(+) diff --git a/apps/worker/src/main.ts b/apps/worker/src/main.ts index 3af418d..b163df5 100644 --- a/apps/worker/src/main.ts +++ b/apps/worker/src/main.ts @@ -122,6 +122,28 @@ async function bootstrap() { const map = await syncGroups(groups, accountId, prisma); groupMaps.set(accountId, map); }, + async (qr, accountId) => { + await prisma.account.update({ + where: { id: accountId }, + data: { qrCode: qr }, + }).catch((err) => logger.error({ accountId, err }, 'Failed to store QR in DB')); + logger.info({ accountId }, 'QR code updated'); + }, + async (status, accountId) => { + if (status === 'connected') { + await prisma.account.update({ + where: { id: accountId }, + data: { qrCode: null, status: 'ACTIVE' }, + }).catch((err) => logger.error({ accountId, err }, 'Failed to update account status')); + logger.info({ accountId }, 'Account connected — QR cleared'); + } else if (status === 'logged_out') { + await prisma.account.update({ + where: { id: accountId }, + data: { status: 'DISCONNECTED' }, + }).catch((err) => logger.error({ accountId, err }, 'Failed to update account status')); + logger.info({ accountId }, 'Account logged out — awaiting QR scan'); + } + }, ); } catch (err) { logger.error({ accountId: account.id, err }, 'Failed to start session — skipping account'); diff --git a/apps/worker/src/whatsapp/session-pool.test.ts b/apps/worker/src/whatsapp/session-pool.test.ts index 676183e..18d642d 100644 --- a/apps/worker/src/whatsapp/session-pool.test.ts +++ b/apps/worker/src/whatsapp/session-pool.test.ts @@ -89,4 +89,21 @@ describe('WhatsAppSessionPool', () => { await capturedOnMessage(fakeMsg); expect(onMessage).toHaveBeenCalledWith(fakeMsg, 'acc_1'); }); + + it('add() injects accountId into onQr callback', async () => { + const onQr = jest.fn(); + const { createWhatsAppSession } = require('./session'); + + let capturedOnQr: any; + (createWhatsAppSession as jest.Mock).mockImplementationOnce( + (_id: string, _path: string, _onMsg: any, _onReaction: any, _onGroups: any, _onReconnect: any, qrCb: any) => { + capturedOnQr = qrCb; + return Promise.resolve({ sendMessage: jest.fn(), logout: jest.fn(), end: jest.fn() }); + }, + ); + + await pool.add('acc_1', './sessions/1', jest.fn(), jest.fn(), jest.fn(), onQr); + await capturedOnQr('test-qr'); + expect(onQr).toHaveBeenCalledWith('test-qr', 'acc_1'); + }); }); diff --git a/apps/worker/src/whatsapp/session-pool.ts b/apps/worker/src/whatsapp/session-pool.ts index 938130d..619f892 100644 --- a/apps/worker/src/whatsapp/session-pool.ts +++ b/apps/worker/src/whatsapp/session-pool.ts @@ -11,6 +11,8 @@ export type PoolMessageCallback = (msg: NormalizedMessage, accountId: string) => export type PoolReactionCallback = (reaction: NormalizedReaction, accountId: string) => Promise | void; // groups typed as `any` to avoid leaking GroupMetadata (Baileys type) into main.ts export type PoolGroupsCallback = (groups: any, accountId: string) => Promise | void; +export type PoolQrCallback = (qr: string, accountId: string) => Promise | void; +export type PoolStatusCallback = (status: string, accountId: string) => Promise | void; export class WhatsAppSessionPool { private sessions = new Map(); @@ -21,6 +23,8 @@ export class WhatsAppSessionPool { onMessage: PoolMessageCallback, onReaction: PoolReactionCallback, onGroups: PoolGroupsCallback, + onQr?: PoolQrCallback, + onStatus?: PoolStatusCallback, ): Promise { logger.info({ accountId }, 'Starting session'); const sock = await createWhatsAppSession( @@ -33,6 +37,8 @@ export class WhatsAppSessionPool { logger.info({ accountId }, 'Session reconnected — updating pool'); this.sessions.set(accountId, newSocket); }, + onQr ? (qr) => onQr(qr, accountId) : undefined, + onStatus ? (status) => onStatus(status, accountId) : undefined, ); this.sessions.set(accountId, sock); }