Fix WS reconnect interval and add abortable BSR/BL requests

This commit is contained in:
Yury
2026-04-22 12:21:31 +05:00
parent d8aee9e804
commit f4c7989a6b
+78 -14
View File
@@ -705,10 +705,13 @@
let lastBlFetch = 0; let lastBlFetch = 0;
let isFetchingBL = false; let isFetchingBL = false;
let blRequestToken = 0;
let blAbortController = null;
let bsrRequestToken = 0;
let bsrAbortController = null;
let currentProxyIdx = 0; let currentProxyIdx = 0;
const proxies = [ const proxies = [
"",
"https://api.codetabs.com/v1/proxy?quest=", "https://api.codetabs.com/v1/proxy?quest=",
"https://api.allorigins.win/raw?url=", "https://api.allorigins.win/raw?url=",
"https://corsproxy.io/?", "https://corsproxy.io/?",
@@ -806,20 +809,26 @@
return proxy ? proxy + encodeURIComponent(originalUrl) : originalUrl; return proxy ? proxy + encodeURIComponent(originalUrl) : originalUrl;
} }
async function fetchJSONWithProxyFallback(originalUrl, label = 'Request') { async function fetchJSONWithProxyFallback(originalUrl, label = 'Request', options = {}) {
const { signal } = options;
const totalAttempts = proxies.length; const totalAttempts = proxies.length;
let lastError = null; let lastError = null;
for (let offset = 0; offset < totalAttempts; offset++) { for (let offset = 0; offset < totalAttempts; offset++) {
if (signal?.aborted) {
throw new DOMException('The operation was aborted.', 'AbortError');
}
const idx = (currentProxyIdx + offset) % totalAttempts; const idx = (currentProxyIdx + offset) % totalAttempts;
const proxy = proxies[idx]; const proxy = proxies[idx];
const targetUrl = buildTargetUrl(originalUrl, proxy); const targetUrl = buildTargetUrl(originalUrl, proxy);
try { try {
showDebug(`${label} [${offset + 1}/${totalAttempts}] via ${proxy ? 'Proxy' : 'Direct'}`); showDebug(`${label} [${offset + 1}/${totalAttempts}] via proxy`);
const res = await fetch(targetUrl, { const res = await fetch(targetUrl, {
headers: { 'Accept': 'application/json' } headers: { 'Accept': 'application/json' },
signal
}); });
if (!res.ok) { if (!res.ok) {
@@ -830,6 +839,10 @@
currentProxyIdx = idx; currentProxyIdx = idx;
return json; return json;
} catch (err) { } catch (err) {
if (err?.name === 'AbortError') {
throw err;
}
lastError = err; lastError = err;
showDebug(`${label} failed: ${err.message}`); showDebug(`${label} failed: ${err.message}`);
@@ -1213,12 +1226,25 @@
} }
async function fetchBSR(hash) { async function fetchBSR(hash) {
const requestToken = ++bsrRequestToken;
if (bsrAbortController) {
bsrAbortController.abort();
}
const controller = new AbortController();
bsrAbortController = controller;
try { try {
els.key.textContent = `BSR: Loading...`; els.key.textContent = `BSR: Loading...`;
els.date.textContent = ``; els.date.textContent = ``;
const res = await fetch(`https://api.beatsaver.com/maps/hash/${hash}`); const res = await fetch(`https://api.beatsaver.com/maps/hash/${hash}`, {
signal: controller.signal
});
if (!res.ok) throw new Error("Not found"); if (!res.ok) throw new Error("Not found");
const data = await res.json(); const data = await res.json();
if (requestToken !== bsrRequestToken) return;
els.key.textContent = `BSR: ${data.id}`; els.key.textContent = `BSR: ${data.id}`;
if (data.uploaded) { if (data.uploaded) {
@@ -1226,16 +1252,37 @@
els.date.textContent = date.toLocaleDateString(); els.date.textContent = date.toLocaleDateString();
} }
} catch (err) { } catch (err) {
if (err?.name === 'AbortError') return;
if (requestToken !== bsrRequestToken) return;
els.key.textContent = `BSR: N/A`; els.key.textContent = `BSR: N/A`;
els.date.textContent = ``; els.date.textContent = ``;
} finally {
if (requestToken === bsrRequestToken && bsrAbortController === controller) {
bsrAbortController = null;
}
} }
} }
async function fetchBL(force = false) { async function fetchBL(force = false) {
if (!config.blId || !config.showBL) return; if (!config.blId || !config.showBL) {
if (isFetchingBL) return; if (blAbortController) {
blAbortController.abort();
blAbortController = null;
}
isFetchingBL = false;
return;
}
if (isFetchingBL && !force) return;
if (!force && Date.now() - lastBlFetch < 900000) return; if (!force && Date.now() - lastBlFetch < 900000) return;
const requestToken = ++blRequestToken;
if (blAbortController) {
blAbortController.abort();
}
const controller = new AbortController();
blAbortController = controller;
isFetchingBL = true; isFetchingBL = true;
try { try {
@@ -1243,7 +1290,9 @@
const isNumeric = /^\d+$/.test(config.blId); const isNumeric = /^\d+$/.test(config.blId);
if (isNumeric) { if (isNumeric) {
const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/player/${config.blId}?stats=true`, 'BL Player'); const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/player/${config.blId}?stats=true`, 'BL Player', {
signal: controller.signal
});
player = json?.data ? json.data[0] : json; player = json?.data ? json.data[0] : json;
config.resolvedBlId = config.blId; config.resolvedBlId = config.blId;
config.resolvedBlQuery = config.blId; config.resolvedBlQuery = config.blId;
@@ -1252,15 +1301,20 @@
if (config.resolvedBlId && normalizeName(config.resolvedBlQuery) === normalizedQuery) { if (config.resolvedBlId && normalizeName(config.resolvedBlQuery) === normalizedQuery) {
try { try {
const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/player/${config.resolvedBlId}?stats=true`, 'BL Resolved Player'); const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/player/${config.resolvedBlId}?stats=true`, 'BL Resolved Player', {
signal: controller.signal
});
player = json?.data ? json.data[0] : json; player = json?.data ? json.data[0] : json;
} catch (_) { } catch (err) {
if (err?.name === 'AbortError') throw err;
player = null; player = null;
} }
} }
if (!player) { if (!player) {
const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/players?search=${encodeURIComponent(config.blId)}`, 'BL Search'); const json = await fetchJSONWithProxyFallback(`https://api.beatleader.com/players?search=${encodeURIComponent(config.blId)}`, 'BL Search', {
signal: controller.signal
});
const candidates = Array.isArray(json?.data) ? json.data : []; const candidates = Array.isArray(json?.data) ? json.data : [];
const resolved = resolveBestPlayer(candidates, config.blId); const resolved = resolveBestPlayer(candidates, config.blId);
@@ -1285,15 +1339,25 @@
throw new Error('Player not found'); throw new Error('Player not found');
} }
if (requestToken !== blRequestToken) return;
renderBLPlayer(player); renderBLPlayer(player);
lastBlFetch = Date.now(); lastBlFetch = Date.now();
persistConfig(); persistConfig();
showDebug(`BL Profile Loaded Successfully!`); showDebug(`BL Profile Loaded Successfully!`);
} catch (err) { } catch (err) {
if (err?.name === 'AbortError') return;
if (requestToken !== blRequestToken) return;
resetBLDisplay(err.message === 'Player not found' ? 'profileNotFound' : 'profileLoadError'); resetBLDisplay(err.message === 'Player not found' ? 'profileNotFound' : 'profileLoadError');
showDebug(`BL Error: ${err.message}`); showDebug(`BL Error: ${err.message}`);
} finally { } finally {
isFetchingBL = false; if (requestToken === blRequestToken) {
isFetchingBL = false;
if (blAbortController === controller) {
blAbortController = null;
}
}
} }
} }
@@ -1472,7 +1536,7 @@
els.app.style.display = 'none'; els.app.style.display = 'none';
if (reconnectAttempts < 10) { if (reconnectAttempts < 10) {
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000); const delay = 5000;
const suffix = error && error.message ? ` (${error.message})` : ''; const suffix = error && error.message ? ` (${error.message})` : '';
showDebug(`❌ WS Lost. Reconnecting in ${delay/1000}s...${suffix}`); showDebug(`❌ WS Lost. Reconnecting in ${delay/1000}s...${suffix}`);
reconnectAttempts++; reconnectAttempts++;
@@ -1491,4 +1555,4 @@
</script> </script>
</body> </body>
</html> </html>