Task #615: surface real upload errors in shared upload hook
The admin Edit Service image picker and the executive-meetings logo
uploader on the Mac deployment were both failing with a generic toast
("Failed to generate upload URL" / "فشل رفع الصورة") that swallowed
the actual server cause, making the iPhone-reported failure
impossible to diagnose from the UI alone.
Step 1 of the task: improve the diagnostic surface in
`lib/object-storage-web/src/use-upload.ts` so both call sites (which
already toast `err.message`) show what actually went wrong:
- `requestUploadUrl`: include the server's `error`/`message` field
AND the HTTP status code in the thrown Error.
- `uploadToPresignedUrl`: distinguish network-level fetch failures
(CORS / unreachable PUBLIC_BASE_URL host — the most likely Mac
cause) from HTTP failures, and include the host + body snippet +
status in each case.
No backend or call-site changes; error semantics at the hook
boundary are preserved (still rejects with Error, still invokes
onError, still returns null from uploadFile).
After this commit lands and is published to Gitea + redeployed on the
Mac, re-trying the upload will reveal the real cause in the toast,
which feeds Step 2 (root-cause fix) of Task #615. The follow-up
"publish #615 to Gitea + Mac redeploy" lives in Task #614 (already
proposed; blocked by concurrency).
Code review (architect): PASS. Optional follow-up noted —
`getUploadParameters` (Uppy path) still has the generic error
message; not used by the two failing uploaders, left for later.
This commit is contained in:
@@ -75,7 +75,14 @@ export function useUpload(options: UseUploadOptions = {}) {
|
|||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json().catch(() => ({}));
|
const errorData = await response.json().catch(() => ({}));
|
||||||
throw new Error(errorData.error || "Failed to get upload URL");
|
const serverMsg =
|
||||||
|
(typeof errorData?.error === "string" && errorData.error) ||
|
||||||
|
(typeof errorData?.message === "string" && errorData.message) ||
|
||||||
|
"";
|
||||||
|
const detail = serverMsg
|
||||||
|
? `${serverMsg} (HTTP ${response.status})`
|
||||||
|
: `HTTP ${response.status} ${response.statusText || ""}`.trim();
|
||||||
|
throw new Error(`Failed to get upload URL: ${detail}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
@@ -85,16 +92,39 @@ export function useUpload(options: UseUploadOptions = {}) {
|
|||||||
|
|
||||||
const uploadToPresignedUrl = useCallback(
|
const uploadToPresignedUrl = useCallback(
|
||||||
async (file: File, uploadURL: string): Promise<void> => {
|
async (file: File, uploadURL: string): Promise<void> => {
|
||||||
const response = await fetch(uploadURL, {
|
let response: Response;
|
||||||
method: "PUT",
|
try {
|
||||||
body: file,
|
response = await fetch(uploadURL, {
|
||||||
headers: {
|
method: "PUT",
|
||||||
"Content-Type": file.type || "application/octet-stream",
|
body: file,
|
||||||
},
|
headers: {
|
||||||
});
|
"Content-Type": file.type || "application/octet-stream",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// Network-level failure (CORS, DNS, unreachable host). This is
|
||||||
|
// the typical symptom when PUBLIC_BASE_URL on the server points
|
||||||
|
// at an origin the browser can't actually reach (e.g. localhost
|
||||||
|
// while the user is on Tailscale).
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
let host = "";
|
||||||
|
try {
|
||||||
|
host = new URL(uploadURL).host;
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Failed to upload file to storage: network error reaching ${host || "the storage endpoint"} — ${msg}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Failed to upload file to storage");
|
const bodyText = await response.text().catch(() => "");
|
||||||
|
const snippet = bodyText.slice(0, 200).trim();
|
||||||
|
const detail = snippet
|
||||||
|
? `${snippet} (HTTP ${response.status})`
|
||||||
|
: `HTTP ${response.status} ${response.statusText || ""}`.trim();
|
||||||
|
throw new Error(`Failed to upload file to storage: ${detail}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
|
|||||||
Reference in New Issue
Block a user