From e04a5a0a25b5ad1cd20dc1df3fd2b727960b8a2b Mon Sep 17 00:00:00 2001 From: Ponte Date: Sat, 14 Feb 2026 23:00:54 +0100 Subject: [PATCH] Fix: compress cover uploads and raise API payload limit --- api/server.js | 5 +++-- app.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/api/server.js b/api/server.js index 083a0a7..5a10c94 100644 --- a/api/server.js +++ b/api/server.js @@ -27,11 +27,12 @@ function sendJson(response, statusCode, payload) { async function readJsonBody(request) { const chunks = []; let size = 0; + const maxPayloadSizeBytes = 8 * 1024 * 1024; for await (const chunk of request) { size += chunk.length; - if (size > 1024 * 1024) { - throw new Error("Payload too large"); + if (size > maxPayloadSizeBytes) { + throw new Error("Payload too large (max 8MB)"); } chunks.push(chunk); } diff --git a/app.js b/app.js index 3667965..5cf6dd6 100644 --- a/app.js +++ b/app.js @@ -94,11 +94,11 @@ coverFileInput.addEventListener("change", async (event) => { } try { - const dataUrl = await fileToDataUrl(file); + const dataUrl = await imageFileToOptimizedDataUrl(file); coverUrlInput.value = dataUrl; } catch (error) { console.error(error); - alert("Impossible de charger cette image."); + alert("Impossible de charger/comprimer cette image."); } finally { input.value = ""; } @@ -299,7 +299,7 @@ gameForm.addEventListener("submit", async (event) => { return; } catch (error) { console.error(error); - alert("Impossible d'enregistrer ce jeu via l'API."); + alert(`Impossible d'enregistrer ce jeu via l'API: ${error.message}`); } } @@ -977,6 +977,50 @@ function fileToDataUrl(file) { }); } +async function imageFileToOptimizedDataUrl(file) { + const originalDataUrl = await fileToDataUrl(file); + const image = await loadImageFromDataUrl(originalDataUrl); + + const maxWidth = 240; + const maxHeight = 320; + const scale = Math.min(1, maxWidth / image.width, maxHeight / image.height); + const targetWidth = Math.max(1, Math.round(image.width * scale)); + const targetHeight = Math.max(1, Math.round(image.height * scale)); + + const canvas = document.createElement("canvas"); + canvas.width = targetWidth; + canvas.height = targetHeight; + const ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("canvas unavailable"); + } + + ctx.drawImage(image, 0, 0, targetWidth, targetHeight); + let quality = 0.82; + let optimized = canvas.toDataURL("image/jpeg", quality); + + // Stay comfortably below API payload thresholds. + while (optimized.length > 380_000 && quality > 0.45) { + quality -= 0.08; + optimized = canvas.toDataURL("image/jpeg", quality); + } + + if (optimized.length > 520_000) { + throw new Error("image too large after compression"); + } + + return optimized; +} + +function loadImageFromDataUrl(dataUrl) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => resolve(image); + image.onerror = () => reject(new Error("image decode failed")); + image.src = dataUrl; + }); +} + function updateScannerStatus(message) { if (scannerStatus) { scannerStatus.textContent = message;