diff --git a/script.js b/script.js index 7579d2b..28ee756 100644 --- a/script.js +++ b/script.js @@ -29,7 +29,7 @@ const STATS_KEY = "fancy-wordle-stats-v2" const LOCAL_ROUND_KEY = "fancy-wordle-hourly-round-v1" const DEFINITION_CACHE_KEY = "fancy-wordle-definitions-v1" const DEFINITION_CACHE_LIMIT = 200 -const DEFINITION_RETRY_DELAY_MS = 1500 +const DEFINITION_REQUEST_TIMEOUT_MS = 5000 const PLAY_INTERVAL_MS = 60 * 60 * 1000 const GUESS_TIMEOUT_MS = 10000 const INTRO_SEEN_KEY = "fancy-wordle-intro-seen-v1" @@ -1400,7 +1400,7 @@ function stripDefinitionHtml(value) { } async function fetchDictionaryApiDefinition(word) { - const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`) + const response = await fetchDefinition(`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`) if (!response.ok) { const error = new Error(`API request failed: ${response.status}`) error.status = response.status @@ -1422,7 +1422,7 @@ async function fetchDictionaryApiDefinition(word) { } async function fetchWiktionaryDefinition(word) { - const response = await fetch(`https://en.wiktionary.org/api/rest_v1/page/definition/${encodeURIComponent(word)}`) + const response = await fetchDefinition(`https://en.wiktionary.org/api/rest_v1/page/definition/${encodeURIComponent(word)}`) if (!response.ok) { const error = new Error(`Wiktionary request failed: ${response.status}`) error.status = response.status @@ -1445,8 +1445,18 @@ async function fetchWiktionaryDefinition(word) { } } -function waitForDuration(duration) { - return new Promise(resolve => setTimeout(resolve, duration)) +async function fetchDefinition(url) { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), DEFINITION_REQUEST_TIMEOUT_MS) + + try { + return await fetch(url, { signal: controller.signal }) + } catch (error) { + if (error.name === "AbortError") throw new Error("Definition request timed out") + throw error + } finally { + clearTimeout(timeoutId) + } } async function loadWordDefinition(word, isWin = true) { @@ -1463,14 +1473,6 @@ async function loadWordDefinition(word, isWin = true) { definitionEntry = await fetchDictionaryApiDefinition(word) } catch (error) { console.info("Primary definition lookup failed:", error) - if (error.status !== 404) { - await waitForDuration(DEFINITION_RETRY_DELAY_MS) - try { - definitionEntry = await fetchDictionaryApiDefinition(word) - } catch (retryError) { - console.info("Primary definition retry failed:", retryError) - } - } } if (!definitionEntry) { diff --git a/tests/fancy-wordle.spec.js b/tests/fancy-wordle.spec.js index 6657af4..ed385ee 100644 --- a/tests/fancy-wordle.spec.js +++ b/tests/fancy-wordle.spec.js @@ -100,3 +100,19 @@ test("stats and leaderboard modals open", async ({ page }) => { await page.locator("#leaderboard-button").click() await expect(page.locator("#leaderboard-modal")).toBeVisible() }) + +test("definition lookup leaves loading state when a provider times out", async ({ page }) => { + await page.route("https://api.dictionaryapi.dev/**", () => new Promise(() => {})) + await page.route("https://en.wiktionary.org/**", route => route.abort()) + await openApp(page) + await skipIntro(page) + + await page.evaluate(() => { + lastResult = { won: true, guesses: 1, word: "apple" } + loadWordDefinition("apple", true) + }) + + await expect(page.locator("#stats-definition")).toContainText("Definition not available at the moment.", { + timeout: 6500 + }) +})