From 3f313f93380f72c5b1df96cf89daa40aab6d3444 Mon Sep 17 00:00:00 2001 From: Michael Kainz <michael.kainz@open-xchange.com> Date: Tue, 2 Apr 2024 10:51:13 +0200 Subject: [PATCH] Improve 404 error message --- src/404.html | 94 +++++++++++++++++++++++++++++++++++++++ src/index.js | 8 ++++ src/routes/serve-files.js | 7 ++- 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/404.html diff --git a/src/404.html b/src/404.html new file mode 100644 index 0000000..c91fa56 --- /dev/null +++ b/src/404.html @@ -0,0 +1,94 @@ +<html lang="en"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <style type="text/css"> + body { + width: 100%; + height: 100%; + display: grid; + font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + } + + #box { + display: flex; + flex-direction: column; + align-items: center; + align-self: center; + } + + img { + margin: 1rem 0 2rem 0; + max-width: 100%; + } + + h1 { + font-weight: 500; + font-size: 1.5rem; + line-height: 2rem; + margin: 0 0 0.5rem 0; + } + + @media screen and (min-width: 540px) { + #box { + padding: 48px; + border: 1px solid #ddd; + border-radius: 16px; + margin: auto; + box-shadow: 0 24px 80px 0 rgba(0, 0, 0, 0.10); + } + } + </style> +</head> + +<body class="unselectable"> + + <div id="box" /> + + <script type="module"> + (async () => { + const messages = { + 'The requested page does not exist.': { + "de": "Die angeforderte Seite existiert nicht.", + "es": "La página solicitada no existe.", + "it": "La pagina richiesta non esiste.", + "fr": "La page demandée n'existe pas.", + "ja": "è¦æ±‚ã•ã‚ŒãŸãƒšãƒ¼ã‚¸ã¯å˜åœ¨ã—ã¾ã›ã‚“.", + "nl": "De gevraagde pagina bestaat niet." + }, + 'Error 404': { + "de": "Fehler 404", + "es": "Error 404", + "it": "Errore 404", + "fr": "Erreur 404", + "ja": "エラー404", + "nl": "Fout 404" + } + } + + let language + const staticGt = (str) => { + if (!messages[str]) return str; + return messages[str][language] || str; + } + + function getLanguage() { + const match = location.hash.match(/language=(\w+)/) || document.cookie.match(/locale=(\w+)/) + return (match?.[1] || document.documentElement.lang || navigator.language || navigator.userLanguage).substr(0, 2) + } + + function updateMessages() { + language = getLanguage() + document.querySelector('#box').innerHTML = '<img src="./themes/default/illustrations/error-generic.svg"></img>' + + `<h1>${staticGt('Error 404')}</h1>` + + `<div>${staticGt('The requested page does not exist.')}</div>` + } + + updateMessages() + document.documentElement.addEventListener('languageChange', updateMessages); + })() + </script> +</body> + +</html> \ No newline at end of file diff --git a/src/index.js b/src/index.js index 8ef11af..1cc3db0 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,7 @@ import { config } from 'dotenv-defaults' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' +import { readFileSync } from 'node:fs' import { randomUUID } from 'node:crypto' import logger from './logger.js' @@ -99,6 +100,13 @@ lightship.whenFirstReady().then(async () => { // This hook is used to signal lightship that the service is ready to receive requests app.addHook('onReady', () => { lightship.signalReady() }) +const NOT_FOUND_HTML = readFileSync('./src/404.html') +app.setNotFoundHandler((req, reply) => { + reply.code(404) + reply.headers({ 'content-type': 'text/html; charset=utf-8' }) + reply.send(NOT_FOUND_HTML) +}) + try { // Binds and listens for connections on the specified host and port await app.listen({ host: '::', port: Number(process.env.PORT) }) diff --git a/src/routes/serve-files.js b/src/routes/serve-files.js index 8d000e3..1ffde08 100644 --- a/src/routes/serve-files.js +++ b/src/routes/serve-files.js @@ -19,6 +19,7 @@ */ import { getFile } from '../files.js' +import logger from '../logger.js' import { isNotAllowedOriginError, isNotFoundError, isVersionMismatchError } from '../errors.js' export default async function serveFilePlugin (fastify, options) { @@ -32,7 +33,11 @@ export default async function serveFilePlugin (fastify, options) { reply.headers(headers) reply.send(body) } catch (err) { - if (isNotFoundError(err) || isVersionMismatchError(err)) throw fastify.httpErrors.createError(404, `File "${req.urlData('path')}" does not exist.`, err) + if (isNotFoundError(err) || isVersionMismatchError(err)) { + reply.callNotFound() + logger.warn(err, `[Files] File "${req.urlData('path')}" does not exist.`) + return + } if (isNotAllowedOriginError(err)) throw fastify.httpErrors.createError(400, err) throw fastify.httpErrors.createError(err.statusCode || 500, err) } -- GitLab