From f390e8c5862e59cb618d0303506d54bef07f3a4a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 21 Jun 2026 12:50:44 +0000 Subject: [PATCH] Fix Uptime Kuma listResources missing monitorList/heartbeat events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Listeners for monitorList/importantHeartbeatList/heartbeat were being attached after the login ack resolved, but the server pushes that data right after login — sometimes in the same tick as the ack — so Socket.IO dropped it before a listener existed. Listeners now attach before login is sent. --- backend/src/integrations/uptimeKuma.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) 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))