const els = { adminStatus: document.querySelector('#adminStatus'), credentialsSource: document.querySelector('#credentialsSource'), tokenStatus: document.querySelector('#tokenStatus'), steamStatus: document.querySelector('#steamStatus'), gcStatus: document.querySelector('#gcStatus'), loginPanel: document.querySelector('#loginPanel'), credentialsPanel: document.querySelector('#credentialsPanel'), guardPanel: document.querySelector('#guardPanel'), tokenPanel: document.querySelector('#tokenPanel'), adminPassword: document.querySelector('#adminPassword'), unlockBtn: document.querySelector('#unlockBtn'), adminError: document.querySelector('#adminError'), adminMessage: document.querySelector('#adminMessage'), steamAccount: document.querySelector('#steamAccount'), steamPassword: document.querySelector('#steamPassword'), clearToken: document.querySelector('#clearToken'), reloadBtn: document.querySelector('#reloadBtn'), saveBtn: document.querySelector('#saveBtn'), steamGuardCode: document.querySelector('#steamGuardCode'), submitGuardBtn: document.querySelector('#submitGuardBtn'), guardHint: document.querySelector('#guardHint'), tokenSteamId: document.querySelector('#tokenSteamId'), tokenAccount: document.querySelector('#tokenAccount'), tokenUpdated: document.querySelector('#tokenUpdated'), tokenExpires: document.querySelector('#tokenExpires'), clearTokenBtn: document.querySelector('#clearTokenBtn'), }; let adminPassword = sessionStorage.getItem('dokaAdminPassword') || ''; let credentialsDirty = false; const dateTimeFormat = new Intl.DateTimeFormat('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', }); function formatDate(iso) { if (!iso) return '-'; const date = new Date(iso); if (Number.isNaN(date.getTime())) return '-'; return dateTimeFormat.format(date); } function setBadge(label, klass) { els.adminStatus.textContent = label; els.adminStatus.className = `badge ${klass}`; } function showError(message) { els.adminError.hidden = !message; els.adminError.textContent = message || ''; } function showMessage(message) { els.adminMessage.hidden = !message; els.adminMessage.textContent = message || ''; } async function adminFetch(url, options = {}) { const res = await fetch(url, { ...options, cache: 'no-store', headers: { 'Content-Type': 'application/json', 'X-Admin-Password': adminPassword, ...(options.headers || {}), }, }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`); return data; } function unlocked() { return Boolean(adminPassword); } function render(data, options = {}) { const preserveEditedInputs = options.preserveEditedInputs !== false; const connection = data.connection || {}; const credentials = data.credentials || {}; const token = data.refreshToken || {}; const steamGuard = connection.steamGuard || {}; els.loginPanel.hidden = unlocked(); els.credentialsPanel.hidden = !unlocked(); els.tokenPanel.hidden = !unlocked(); els.guardPanel.hidden = !unlocked() || !steamGuard.required; setBadge(unlocked() ? 'unlocked' : 'locked', unlocked() ? 'badge-ok' : 'badge-muted'); els.credentialsSource.textContent = credentials.source || '-'; els.tokenStatus.textContent = token.exists ? 'saved' : 'missing'; els.steamStatus.textContent = connection.steam || '-'; els.gcStatus.textContent = connection.gc || '-'; const editingCredentials = document.activeElement === els.steamAccount || document.activeElement === els.steamPassword || credentialsDirty; if (!preserveEditedInputs || !editingCredentials) { els.steamAccount.value = credentials.accountName || ''; els.steamPassword.value = credentials.password || ''; credentialsDirty = false; } els.tokenSteamId.textContent = token.steamID || '-'; els.tokenAccount.textContent = token.accountName || '-'; els.tokenUpdated.textContent = formatDate(token.updatedAt); els.tokenExpires.textContent = formatDate(token.expiresAt); if (steamGuard.required) { const where = steamGuard.domain ? `Email domain: ${steamGuard.domain}` : 'Mobile authenticator code required'; els.guardHint.textContent = steamGuard.lastCodeWrong ? `Last code was rejected. ${where}.` : where; } else { els.guardHint.textContent = ''; } } async function loadAuth(options = {}) { if (!unlocked()) { render({}); return; } try { showError(''); const data = await adminFetch('/api/admin/auth'); render(data, options); } catch (err) { showError(err.message); if (err.message.includes('Invalid admin password')) { adminPassword = ''; sessionStorage.removeItem('dokaAdminPassword'); } render({}); } } async function saveCredentials() { try { showError(''); showMessage(''); const data = await adminFetch('/api/admin/auth', { method: 'PUT', body: JSON.stringify({ accountName: els.steamAccount.value, password: els.steamPassword.value, clearRefreshToken: els.clearToken.checked, }), }); credentialsDirty = false; render(data); showMessage('Credentials saved. Reconnect started.'); } catch (err) { showError(err.message); } } async function submitSteamGuard() { try { showError(''); showMessage(''); const data = await adminFetch('/api/admin/steam-guard', { method: 'POST', body: JSON.stringify({ code: els.steamGuardCode.value }), }); els.steamGuardCode.value = ''; render(data); showMessage('Steam Guard code submitted.'); } catch (err) { showError(err.message); } } async function clearRefreshToken() { try { showError(''); showMessage(''); const data = await adminFetch('/api/admin/clear-token', { method: 'POST', body: '{}' }); render(data); showMessage('Refresh token cleared.'); } catch (err) { showError(err.message); } } els.unlockBtn.addEventListener('click', () => { adminPassword = els.adminPassword.value; sessionStorage.setItem('dokaAdminPassword', adminPassword); loadAuth({ preserveEditedInputs: false }); }); els.adminPassword.addEventListener('keydown', (event) => { if (event.key === 'Enter') els.unlockBtn.click(); }); els.reloadBtn.addEventListener('click', () => { credentialsDirty = false; loadAuth({ preserveEditedInputs: false }); }); els.saveBtn.addEventListener('click', saveCredentials); els.submitGuardBtn.addEventListener('click', submitSteamGuard); els.clearTokenBtn.addEventListener('click', clearRefreshToken); els.steamAccount.addEventListener('input', () => { credentialsDirty = true; }); els.steamPassword.addEventListener('input', () => { credentialsDirty = true; }); els.steamGuardCode.addEventListener('keydown', (event) => { if (event.key === 'Enter') els.submitGuardBtn.click(); }); if (adminPassword) { els.adminPassword.value = adminPassword; } loadAuth({ preserveEditedInputs: false }); setInterval(loadAuth, 5000);