From f60c5229abf1a136b472b0543899967982d17b4f Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 30 May 2026 17:24:16 -0500 Subject: [PATCH] Fix mobile photo upload: resize large camera images + increase body limit Mobile cameras produce 8-12MB photos. Now: - Canvas-based resize to max 2000x1500 before upload - JPEG compression at 0.7-0.85 quality - Express body limit increased to 5MB for id-upload route - Falls back to raw upload for small images and PDFs Co-Authored-By: Claude Opus 4.6 (1M context) --- api/src/index.ts | 2 + site/public/portal/upload-id/index.html | 55 +++++++++++++++++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/api/src/index.ts b/api/src/index.ts index 9729d9e..cadbb84 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -69,6 +69,8 @@ app.use("/api/v1/webhooks/stripe", express.raw({ type: "application/json" })); app.use(identityRouter); // identity webhook uses raw() internally on its specific route +// Photo ID upload needs larger body (resized images up to ~3MB as base64) +app.use("/api/v1/id-upload", express.json({ limit: "5mb" })); app.use(express.json({ limit: "512kb" })); // 512kb for eSign signature PNG base64 // Reject non-JSON content types on POST/PUT/PATCH diff --git a/site/public/portal/upload-id/index.html b/site/public/portal/upload-id/index.html index f388738..f25ee6a 100644 --- a/site/public/portal/upload-id/index.html +++ b/site/public/portal/upload-id/index.html @@ -179,16 +179,57 @@ submitBtn.addEventListener("click", function() { return; } - // Convert image to base64 and send - var reader2 = new FileReader(); - reader2.onload = function(ev) { + // Resize image if too large, then send as base64 + function resizeAndUpload(file) { + var maxWidth = 2000; + var maxHeight = 1500; + var maxBytes = 2 * 1024 * 1024; // 2MB target + + var img = new Image(); + img.onload = function() { + var w = img.width; + var h = img.height; + + // Only resize if image is larger than limits + if (w > maxWidth || h > maxHeight || file.size > maxBytes) { + var scale = Math.min(maxWidth / w, maxHeight / h, 1); + var canvas = document.createElement("canvas"); + canvas.width = Math.round(w * scale); + canvas.height = Math.round(h * scale); + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + + // Compress as JPEG + var quality = file.size > 5 * 1024 * 1024 ? 0.7 : 0.85; + var dataUrl = canvas.toDataURL("image/jpeg", quality); + sendUpload(dataUrl); + } else { + // Small enough — send as-is + var reader3 = new FileReader(); + reader3.onload = function(e) { sendUpload(e.target.result); }; + reader3.readAsDataURL(file); + } + }; + img.onerror = function() { + // Not an image (PDF?) — send as-is + var reader3 = new FileReader(); + reader3.onload = function(e) { sendUpload(e.target.result); }; + reader3.readAsDataURL(file); + }; + + var reader2 = new FileReader(); + reader2.onload = function(e) { img.src = e.target.result; }; + reader2.readAsDataURL(file); + } + + function sendUpload(dataUrl) { fetch(API + "/api/v1/id-upload/" + uploadToken, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - image_data: ev.target.result, + image_data: dataUrl, filename: selectedFile.name, - content_type: selectedFile.type || "image/jpeg", + content_type: "image/jpeg", }), }) .then(function(r) { @@ -204,8 +245,8 @@ submitBtn.addEventListener("click", function() { errorSection.style.display = "block"; document.getElementById("error-msg").textContent = err.message; }); - }; - reader2.readAsDataURL(selectedFile); + } + resizeAndUpload(selectedFile); }); // If no order number, show error