diff --git a/Dockerfile b/Dockerfile index 8b33cc1..eac71df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,8 +8,11 @@ COPY . . FROM node:22-slim WORKDIR /app +COPY --from=build /app/package.json /app/package-lock.json ./ +COPY --from=build /app/node_modules ./node_modules +RUN npx --yes playwright install --with-deps chromium \ + && rm -rf /var/lib/apt/lists/* COPY --from=build /app . -RUN npx --yes playwright install --with-deps chromium ENV NODE_ENV=production # De SQLite-database hoort op het persistente volume te staan; zonder deze # default zou een vergeten DB_PATH stilletjes naar de container-fs schrijven diff --git a/README.md b/README.md index 09158d5..b265c21 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ app bij het opstarten met een melding. De UI staat achter basic auth (`BASIC_AUTH_USER` / `BASIC_AUTH_PASS`) op `http://localhost:3000`. +Wil je de headless-browser-fallback ook echt lokaal uitproberen (de +testsuite gebruikt fakes en heeft hier geen echte browser voor nodig), run +dan eerst `npx playwright install chromium` om de Chromium-binary te +downloaden. + ## Environment variables | Variabele | Omschrijving | @@ -43,6 +48,6 @@ controleren"-knop in de UI kan dit ook handmatig getriggerd worden. Als het ophalen van de prijs op de gewone manier mislukt (netwerkfout, of geen prijs gevonden in de HTML), probeert de app het één keer opnieuw met een echte headless-browser (Playwright/Chromium) — nuttig voor pagina's -die de prijs pas met JavaScript renderen, of die eenvoudige HTTP-clients -blokkeren. Geen garantie tegen elke vorm van bot-detectie, maar dekt een -reëel deel van de gevallen die de gewone aanpak mist. +die eenvoudige HTTP-clients blokkeren. Geen garantie tegen elke vorm van +bot-detectie, maar dekt een reëel deel van de gevallen die de gewone +aanpak mist. diff --git a/server.js b/server.js index c75d544..17376bd 100644 --- a/server.js +++ b/server.js @@ -37,7 +37,18 @@ function summarize(results) { return parts.length > 0 ? parts.join(' ') : 'geen producten'; } -async function runCheck() { +let inFlightCheck = null; +function runCheck() { + if (inFlightCheck) { + return inFlightCheck; + } + inFlightCheck = runCheckOnce().finally(() => { + inFlightCheck = null; + }); + return inFlightCheck; +} + +async function runCheckOnce() { const products = listProducts(db); let browserPromise = null; function getBrowser() { @@ -50,8 +61,12 @@ async function runCheck() { const deps = { fetchAndExtractPrice, fetchAndExtractPriceViaBrowser: async (url) => { - const browser = await getBrowser(); - return fetchAndExtractPriceViaBrowser(browser, url); + try { + const browser = await getBrowser(); + return await fetchAndExtractPriceViaBrowser(browser, url); + } catch (err) { + return { price: null, method: null, error: err }; + } }, sendMail: (payload) => sendPriceChangeEmail(transport, { from: process.env.SMTP_FROM,