This commit is contained in:
Frank Elsinga 2026-01-20 06:03:21 +00:00 committed by GitHub
commit 9df6dbe797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 190 additions and 50 deletions

View File

@ -770,56 +770,6 @@ class Monitor extends BeanModel {
bean.duration = beatInterval;
throw new Error("No heartbeat in the time window");
}
} else if (this.type === "steam") {
const steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
const steamAPIKey = await setting("steamAPIKey");
const filter = `addr\\${this.hostname}:${this.port}`;
if (!steamAPIKey) {
throw new Error("Steam API Key not found");
}
let res = await axios.get(steamApiUrl, {
timeout: this.timeout * 1000,
headers: {
Accept: "*/*",
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: !this.getIgnoreTls(),
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
}),
httpAgent: new http.Agent({
maxCachedSessions: 0,
}),
maxRedirects: this.maxredirects,
validateStatus: (status) => {
return checkStatusCode(status, this.getAcceptedStatuscodes());
},
params: {
filter: filter,
key: steamAPIKey,
},
});
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
bean.status = UP;
bean.msg = res.data.response.servers[0].name;
try {
bean.ping = await ping(
this.hostname,
PING_COUNT_DEFAULT,
"",
true,
this.packetSize,
PING_GLOBAL_TIMEOUT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_DEFAULT
);
} catch (_) {}
} else {
throw new Error("Server not found on Steam");
}
} else if (this.type === "docker") {
log.debug("monitor", `[${this.name}] Prepare Options for Axios`);

View File

@ -0,0 +1,84 @@
const { MonitorType } = require("./monitor-type");
const {
UP,
PING_COUNT_DEFAULT,
PING_GLOBAL_TIMEOUT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_DEFAULT,
} = require("../../src/util");
const { ping, checkStatusCode, setting } = require("../util-server");
const axios = require("axios");
const https = require("https");
const http = require("http");
const crypto = require("crypto");
class SteamMonitorType extends MonitorType {
name = "steam";
steamApiUrl = "https://api.steampowered.com/IGameServersService/GetServerList/v1/";
/**
* @inheritdoc
*/
async check(monitor, heartbeat, _server) {
const res = await this.getServerList(monitor);
if (res.data.response && res.data.response.servers && res.data.response.servers.length > 0) {
heartbeat.status = UP;
heartbeat.msg = res.data.response.servers[0].name;
try {
heartbeat.ping = await ping(
monitor.hostname,
PING_COUNT_DEFAULT,
"",
true,
monitor.packetSize,
PING_GLOBAL_TIMEOUT_DEFAULT,
PING_PER_REQUEST_TIMEOUT_DEFAULT
);
} catch (_) {}
} else {
throw new Error("Server not found on Steam");
}
}
/**
* Get server list from Steam API
* @param {Monitor} monitor Monitor object
* @returns {Promise<axios.AxiosResponse>} Axios response object containing server list data
* @throws {Error} If Steam API Key is not configured
*/
async getServerList(monitor) {
const steamAPIKey = await setting("steamAPIKey");
const filter = `addr\\${monitor.hostname}:${monitor.port}`;
if (!steamAPIKey) {
throw new Error("Steam API Key not found");
}
const options = {
timeout: monitor.timeout * 1000,
headers: {
Accept: "*/*",
},
httpsAgent: new https.Agent({
maxCachedSessions: 0, // Use Custom agent to disable session reuse (https://github.com/nodejs/node/issues/3940)
rejectUnauthorized: !monitor.ignoreTls,
secureOptions: crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT,
}),
httpAgent: new http.Agent({
maxCachedSessions: 0,
}),
maxRedirects: monitor.maxredirects,
validateStatus: (status) => {
return checkStatusCode(status, monitor.getAcceptedStatuscodes());
},
params: {
filter: filter,
key: steamAPIKey,
},
};
return await axios.get(this.steamApiUrl, options);
}
}
module.exports = {
SteamMonitorType,
};

View File

@ -114,6 +114,7 @@ class UptimeKumaServer {
UptimeKumaServer.monitorTypeList["tailscale-ping"] = new TailscalePing();
UptimeKumaServer.monitorTypeList["websocket-upgrade"] = new WebSocketMonitorType();
UptimeKumaServer.monitorTypeList["dns"] = new DnsMonitorType();
UptimeKumaServer.monitorTypeList["steam"] = new SteamMonitorType();
UptimeKumaServer.monitorTypeList["postgres"] = new PostgresMonitorType();
UptimeKumaServer.monitorTypeList["mqtt"] = new MqttMonitorType();
UptimeKumaServer.monitorTypeList["smtp"] = new SMTPMonitorType();
@ -564,6 +565,7 @@ const { RealBrowserMonitorType } = require("./monitor-types/real-browser-monitor
const { TailscalePing } = require("./monitor-types/tailscale-ping");
const { WebSocketMonitorType } = require("./monitor-types/websocket-upgrade");
const { DnsMonitorType } = require("./monitor-types/dns");
const { SteamMonitorType } = require("./monitor-types/steam");
const { PostgresMonitorType } = require("./monitor-types/postgres");
const { MqttMonitorType } = require("./monitor-types/mqtt");
const { SMTPMonitorType } = require("./monitor-types/smtp");

View File

@ -0,0 +1,104 @@
process.env.UPTIME_KUMA_HIDE_LOG = ["info_db", "info_server"].join(",");
const { describe, test, before, after } = require("node:test");
const assert = require("node:assert");
const express = require("express");
const { UP, PENDING } = require("../../../src/util");
const { SteamMonitorType } = require("../../../server/monitor-types/steam");
const { setSetting } = require("../../../server/util-server");
const TestDB = require("../../mock-testdb");
const testDb = new TestDB();
const TEST_PORT = 30158;
let mockServer;
describe("Steam Monitor", () => {
before(async () => {
await testDb.create();
await setSetting("steamAPIKey", "test-steam-api-key");
// Create shared mock Steam API server with different endpoints
const app = express();
app.use(express.json());
app.get("/GetServerList/", (req, res) => {
res.json({
response: {
servers: [
{
name: "Test Game Server",
addr: "127.0.0.1:27015",
},
],
},
});
});
app.get("/EmptyGetServerList/", (req, res) => {
res.json({
response: {
servers: [],
},
});
});
mockServer = await new Promise((resolve) => {
const server = app.listen(TEST_PORT, () => resolve(server));
});
});
after(async () => {
if (mockServer) {
await new Promise((resolve) => mockServer.close(resolve));
}
await testDb.destroy();
});
test("check() sets status to UP when Steam API returns valid server response", async () => {
const steamMonitor = new SteamMonitorType();
steamMonitor.steamApiUrl = `http://127.0.0.1:${TEST_PORT}/GetServerList/`;
const monitor = {
hostname: "127.0.0.1",
port: 27015,
timeout: 2,
packetSize: 56,
ignoreTls: false,
maxredirects: 10,
getAcceptedStatuscodes: () => ["200-299"],
};
const heartbeat = {
msg: "",
status: PENDING,
ping: null,
};
await steamMonitor.check(monitor, heartbeat, {});
assert.strictEqual(heartbeat.status, UP);
assert.strictEqual(heartbeat.msg, "Test Game Server");
// Note: ping may be null or a value depending on if ICMP ping succeeds
});
test("check() throws error when Steam API returns empty server list", async () => {
const steamMonitor = new SteamMonitorType();
steamMonitor.steamApiUrl = `http://127.0.0.1:${TEST_PORT}/EmptyGetServerList/`;
const monitor = {
hostname: "127.0.0.1",
port: 27015,
timeout: 2,
ignoreTls: false,
maxredirects: 10,
getAcceptedStatuscodes: () => ["200-299"],
};
const heartbeat = {
msg: "",
status: PENDING,
};
await assert.rejects(steamMonitor.check(monitor, heartbeat, {}), {
message: "Server not found on Steam",
});
});
});