cm_whatsapp_bot_v1/apps/bot/src/ipc/send-test-handler.ts
yiekheng ec57a78853 feat(send-test): close the loop — bot reports done back to the form
The send-test form was stuck on "Sending to <Group>…" because the
server action returns the moment it publishes the IPC NOTIFY; the bot
processed the actual WhatsApp send out-of-band and the form had no way
to learn whether it succeeded.

Round-trip now wired end-to-end:

- New WebEvent variant `send_test.done` { groupId, ok, error }.
- bot/src/ipc/send-test-handler emits it on every exit path:
  - missing group   → ok=false, "Group not found"
  - account offline → ok=false, "Account not connected — re-pair first"
  - send threw      → ok=false, error message
  - send succeeded  → ok=true,  null
- web/src/hooks/use-events declares the new event in its type map.
- web SendTestForm subscribes via useEvents, filters by its own
  groupId so a parallel send-test on another group can't move our
  state, and renders one of three pills:
    * Sending…           (in-flight — Loader2 spinner)
    * Sent ✓             (success — emerald CheckCircle2)
    * <error message>    (failure — destructive AlertCircle)
  The "Send Test" button stays disabled while in-flight.

Tests (+5; 110 web tests total):
  send-test-form.test.tsx
  - SSR markup: textarea, submit button, hidden groupId, no premature
    pill on first render.
  - useEvents wiring: form registers a `send_test.done` handler.
  - Handler safely accepts:
    * matching success event
    * matching failure event
    * mismatched groupId (must not throw)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:04:33 +08:00

54 lines
1.7 KiB
TypeScript

import { sessionManager } from "../whatsapp/session-manager.js";
import { sendTextToGroup } from "../whatsapp/sender.js";
import { writeAuditLog } from "../audit.js";
import { db } from "../db.js";
import { logger } from "../logger.js";
import { pgNotifyWeb } from "./notify.js";
export async function handleSendTest(groupId: string, text: string): Promise<void> {
const group = await db.query.whatsappGroups.findFirst({
where: (g, { eq }) => eq(g.id, groupId),
});
if (!group) {
logger.warn({ groupId }, "send-test: group missing");
await pgNotifyWeb({
type: "send_test.done",
groupId,
ok: false,
error: "Group not found",
});
return;
}
const session = sessionManager.getSession(group.accountId);
if (!session) {
logger.warn({ groupId, accountId: group.accountId }, "send-test: account not connected");
await pgNotifyWeb({
type: "send_test.done",
groupId,
ok: false,
error: "Account not connected — re-pair before sending",
});
return;
}
try {
const result = await sendTextToGroup(session.socket, group.waGroupJid, text);
await writeAuditLog(db, {
operatorId: null,
source: "web",
action: "group.send_test",
targetType: "whatsapp_group",
targetId: groupId,
payload: { groupName: group.name, length: text.length, waMessageId: result.messageId ?? null },
});
await pgNotifyWeb({ type: "send_test.done", groupId, ok: true, error: null });
} catch (err) {
logger.error({ err, groupId }, "send-test: failed");
await pgNotifyWeb({
type: "send_test.done",
groupId,
ok: false,
error: err instanceof Error ? err.message : "Send failed",
});
}
}