diff --git a/backend/src/integrations/uptimeKuma.ts b/backend/src/integrations/uptimeKuma.ts index eaefe47..7130da9 100644 --- a/backend/src/integrations/uptimeKuma.ts +++ b/backend/src/integrations/uptimeKuma.ts @@ -23,9 +23,14 @@ function connectAndLogin( baseUrl: string, username: string, password: string, + onSocket?: (socket: Socket) => void, ): Promise { return new Promise((resolve, reject) => { const socket = io(baseUrl, { transports: ['websocket', 'polling'], reconnection: false, timeout: 10000 }) + // Attach event listeners before the login ack arrives — Uptime Kuma's + // server pushes monitorList/heartbeat right after login, sometimes in the + // same tick as the ack, and Socket.IO drops events with no listener yet. + onSocket?.(socket) const timer = setTimeout(() => { socket.disconnect() @@ -74,22 +79,22 @@ export const uptimeKuma: IntegrationAdapter = { const password = secrets.password if (!baseUrl || !username || !password) return [] - const socket = await connectAndLogin(baseUrl, username, password) - try { - const monitors = new Map() - const lastHeartbeat = new Map() + const monitors = new Map() + const lastHeartbeat = new Map() - socket.on('monitorList', (list: Record) => { + const socket = await connectAndLogin(baseUrl, username, password, (s) => { + s.on('monitorList', (list: Record) => { for (const m of Object.values(list)) monitors.set(m.id, m) }) - socket.on('importantHeartbeatList', (monitorId: number, beats: Heartbeat[]) => { + s.on('importantHeartbeatList', (monitorId: number, beats: Heartbeat[]) => { const latest = beats[beats.length - 1] if (latest) lastHeartbeat.set(monitorId, latest) }) - socket.on('heartbeat', (beat: Heartbeat & { monitorID: number }) => { + s.on('heartbeat', (beat: Heartbeat & { monitorID: number }) => { lastHeartbeat.set(beat.monitorID, beat) }) - + }) + try { // Server pushes these events asynchronously right after login — there's no // single "done" signal, so give it a short window to arrive. await new Promise((resolve) => setTimeout(resolve, 2500))