Fix Uptime Kuma listResources missing monitorList/heartbeat events

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.
This commit is contained in:
Claude 2026-06-21 12:50:44 +00:00
parent 89dd3471db
commit f390e8c586
No known key found for this signature in database

View file

@ -23,9 +23,14 @@ function connectAndLogin(
baseUrl: string, baseUrl: string,
username: string, username: string,
password: string, password: string,
onSocket?: (socket: Socket) => void,
): Promise<Socket> { ): Promise<Socket> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const socket = io(baseUrl, { transports: ['websocket', 'polling'], reconnection: false, timeout: 10000 }) 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(() => { const timer = setTimeout(() => {
socket.disconnect() socket.disconnect()
@ -74,22 +79,22 @@ export const uptimeKuma: IntegrationAdapter = {
const password = secrets.password const password = secrets.password
if (!baseUrl || !username || !password) return [] if (!baseUrl || !username || !password) return []
const socket = await connectAndLogin(baseUrl, username, password)
try {
const monitors = new Map<number, UptimeKumaMonitor>() const monitors = new Map<number, UptimeKumaMonitor>()
const lastHeartbeat = new Map<number, Heartbeat>() const lastHeartbeat = new Map<number, Heartbeat>()
socket.on('monitorList', (list: Record<string, UptimeKumaMonitor>) => { const socket = await connectAndLogin(baseUrl, username, password, (s) => {
s.on('monitorList', (list: Record<string, UptimeKumaMonitor>) => {
for (const m of Object.values(list)) monitors.set(m.id, m) 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] const latest = beats[beats.length - 1]
if (latest) lastHeartbeat.set(monitorId, latest) 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) lastHeartbeat.set(beat.monitorID, beat)
}) })
})
try {
// Server pushes these events asynchronously right after login — there's no // Server pushes these events asynchronously right after login — there's no
// single "done" signal, so give it a short window to arrive. // single "done" signal, so give it a short window to arrive.
await new Promise((resolve) => setTimeout(resolve, 2500)) await new Promise((resolve) => setTimeout(resolve, 2500))