import type { SidecarEvent } from '../protocol'; import { initEmailCron, stopEmailCron } from './email-cron'; import { initEmailIdle, stopEmailIdle } from './email-idle'; import { broadcastEmailNew } from './routes'; import { startEmailServer } from './http'; import { createSidecarConnector } from '../connect'; import { API_URL } from '../../officer-url.mjs'; // The sidecar used to reach BACK into the platform's queue over this socket to get a sync run — // enqueueViaWs / listJobsViaWs and a pending-response map. Syncs run in this process now // (sync-runner.ts), so the shim is gone and nothing but a port crosses the socket at startup. // ── Command handlers ── type ReplyFn = (msg: SidecarEvent) => void; function handleCommand(cmd: Record, reply: ReplyFn) { switch (cmd.type) { case 'ping': reply({ type: 'pong', id: cmd.id as string }); break; default: reply({ type: 'error', id: cmd.id as string, error: `Unknown command type: ${cmd.type}`, }); } } // ── HTTP server ── // // Started before the registration socket so the port is known by the time we announce it. `/api/email/*` // on the platform is a proxy onto this. const serverPort = startEmailServer(); // ── Connect to API server ── const connection = createSidecarConnector({ apiUrl: `${API_URL}/api/sidecar/register`, name: 'email', handles: ['email'], onCommand(cmd, reply) { handleCommand(cmd as Record, reply as ReplyFn); }, onConnected() { // The platform forgets the port when the socket drops, and this listener outlives an officer restart, // so re-announce on every reconnect. connection.send({ type: 'email:server', port: serverPort }); // Start email cron once connected (so queue commands can reach API server) initEmailCron(); // Real-time push via IMAP IDLE; the cron above is the slow backstop. On new mail, tell the API // server so it can push an SSE event to that user's open /email page. // Straight to this process's own SSE clients — the /email/events stream lives here now, so the // round trip out to officer and back (the `email:new` wire event) is gone. initEmailIdle((userEmail) => broadcastEmailNew(userEmail)); }, }); // ── Graceful shutdown ── function shutdown(signal: string) { console.log(`[email] ${signal} received, shutting down...`); stopEmailIdle(); stopEmailCron(); connection.destroy(); process.exit(0); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT'));