This commit is contained in:
@@ -7,11 +7,17 @@ const { EventEmitter } = require('events');
|
||||
const SteamUser = require('steam-user');
|
||||
const protobuf = require('protobufjs');
|
||||
|
||||
const APP_VERSION = require('./package.json').version;
|
||||
|
||||
const DOTA_APPID = 570;
|
||||
const DEFAULT_PORT = 3000;
|
||||
const DEFAULT_REFRESH_MS = 3 * 60 * 1000;
|
||||
const READY_TIMEOUT_MS = 60 * 1000;
|
||||
const LOBBY_TIMEOUT_MS = 30 * 1000;
|
||||
const WATCHDOG_INTERVAL_MS = 30 * 1000;
|
||||
const GC_RELAUNCH_AFTER_MS = 2 * 60 * 1000;
|
||||
const GC_RELOG_AFTER_MS = 10 * 60 * 1000;
|
||||
const STEAM_STUCK_AFTER_MS = 10 * 60 * 1000;
|
||||
const AUTH_FILE = process.env.STEAM_AUTH_FILE || path.join(__dirname, '.steam-auth.json');
|
||||
const CREDENTIALS_FILE = process.env.STEAM_CREDENTIALS_FILE || path.join(path.dirname(AUTH_FILE), '.steam-credentials.json');
|
||||
const ADMIN_PASSWORD = process.env.AUTH_ADMIN_PASSWORD || process.env.ADMIN_PASSWORD || '';
|
||||
@@ -263,6 +269,11 @@ class DotaLobbyClient extends EventEmitter {
|
||||
this.pendingLobbyRequest = null;
|
||||
this.steamGuardPrompt = null;
|
||||
this.steamGuardCallback = null;
|
||||
this.gcDownSince = null;
|
||||
this.steamDownSince = null;
|
||||
this.lastGcRelaunchAt = 0;
|
||||
this.watchdogTimer = setInterval(() => this.watchdog(), WATCHDOG_INTERVAL_MS);
|
||||
this.watchdogTimer.unref();
|
||||
this.createSteamUser();
|
||||
}
|
||||
|
||||
@@ -482,6 +493,72 @@ class DotaLobbyClient extends EventEmitter {
|
||||
this.emit('change');
|
||||
}
|
||||
|
||||
// Periodic self-healing: the GC can silently drop our session (e.g. GC
|
||||
// maintenance restarts) while hello messages keep going into the void.
|
||||
// Escalate: relaunch Dota after GC_RELAUNCH_AFTER_MS, then tear down and
|
||||
// rebuild the whole Steam connection after GC_RELOG_AFTER_MS. Also rebuilds
|
||||
// the connection if Steam itself stays offline longer than STEAM_STUCK_AFTER_MS.
|
||||
watchdog() {
|
||||
const now = Date.now();
|
||||
|
||||
if (this.steamGuardCallback) {
|
||||
// Waiting on user input; nothing to heal automatically.
|
||||
this.gcDownSince = null;
|
||||
this.steamDownSince = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.loggedOn) {
|
||||
this.gcDownSince = null;
|
||||
if (!this.steamDownSince) {
|
||||
this.steamDownSince = now;
|
||||
return;
|
||||
}
|
||||
if (now - this.steamDownSince >= STEAM_STUCK_AFTER_MS) {
|
||||
this.steamDownSince = now;
|
||||
console.warn('watchdog: Steam connection stuck offline, rebuilding session...');
|
||||
this.hardReset();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.steamDownSince = null;
|
||||
|
||||
if (this.gcReady) {
|
||||
this.gcDownSince = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.gcDownSince) {
|
||||
this.gcDownSince = now;
|
||||
return;
|
||||
}
|
||||
|
||||
if (now - this.gcDownSince >= GC_RELOG_AFTER_MS) {
|
||||
this.gcDownSince = now;
|
||||
console.warn('watchdog: GC unavailable despite relaunches, rebuilding Steam session...');
|
||||
this.hardReset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (now - this.gcDownSince >= GC_RELAUNCH_AFTER_MS && now - this.lastGcRelaunchAt >= GC_RELAUNCH_AFTER_MS) {
|
||||
this.lastGcRelaunchAt = now;
|
||||
console.warn('watchdog: GC not ready, relaunching Dota 2...');
|
||||
this.clearHelloTimer();
|
||||
this.launchDota(true);
|
||||
}
|
||||
}
|
||||
|
||||
hardReset() {
|
||||
this.gcDownSince = null;
|
||||
this.resetSteamUser();
|
||||
try {
|
||||
this.start();
|
||||
} catch (err) {
|
||||
console.error(`watchdog: could not restart Steam connection: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleReconnect() {
|
||||
if (this.reconnectTimer) return;
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
@@ -569,6 +646,10 @@ class DotaLobbyClient extends EventEmitter {
|
||||
this.readyWaiters.delete(waiter);
|
||||
this.gcReady = false;
|
||||
this.setGcState('timeout');
|
||||
if (this.loggedOn) {
|
||||
this.clearHelloTimer();
|
||||
this.launchDota(true);
|
||||
}
|
||||
reject(new Error('Dota 2 game coordinator did not become ready in time.'));
|
||||
}, timeoutMs),
|
||||
};
|
||||
@@ -741,6 +822,7 @@ let activeRefresh = null;
|
||||
function publicState() {
|
||||
return {
|
||||
...state,
|
||||
version: APP_VERSION,
|
||||
connection: dota.status(),
|
||||
refreshIntervalMs,
|
||||
regions: REGIONS,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "doka-lobby",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"description": "List joinable Dota 2 custom game lobbies, filtered by game name / map",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const els = {
|
||||
statusBadge: document.querySelector('#statusBadge'),
|
||||
appVersion: document.querySelector('#appVersion'),
|
||||
serverFilter: document.querySelector('#serverFilter'),
|
||||
visibleCount: document.querySelector('#visibleCount'),
|
||||
totalCount: document.querySelector('#totalCount'),
|
||||
@@ -213,6 +214,7 @@ function render() {
|
||||
const [label, klass] = statusInfo(data);
|
||||
|
||||
setBadge(label, klass);
|
||||
els.appVersion.textContent = data?.version ? `v${data.version}` : '';
|
||||
els.visibleCount.textContent = String(filtered.length);
|
||||
els.totalCount.textContent = String(data?.totalLobbies || lobbies.length || 0);
|
||||
els.lastUpdated.textContent = formatDate(data?.lastUpdatedAt);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="title">
|
||||
<h1>Dota 2 lobbies</h1>
|
||||
<h1>Dota 2 lobbies <span id="appVersion" class="version"></span></h1>
|
||||
<div class="subline">
|
||||
<span id="statusBadge" class="badge badge-muted">starting</span>
|
||||
<span id="serverFilter">GC: Auto</span>
|
||||
|
||||
@@ -55,6 +55,13 @@ select {
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.title .version {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: var(--muted);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.subline {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user