Refresh docs and client for backend-routed asset uploads.
CI / test (push) Successful in 5s

This updates developer docs and web demos to use backend upload endpoints, adds a client upload helper, and aligns integration tests with the no-direct-MinIO URL flow.

Made-with: Cursor
This commit is contained in:
2026-03-02 21:51:47 +00:00
parent e981a334ea
commit a666f1233d
9 changed files with 92 additions and 84 deletions
+2 -33
View File
@@ -57,13 +57,6 @@ function kindFromExt(ext) {
return ext === "gltf" || ext === "glb" ? "3d" : "image";
}
function isLikelyInternalHostname(hostname) {
if (!hostname) return false;
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true;
if (hostname.endsWith(".local") || hostname.endsWith(".internal")) return true;
return hostname.includes("minio") || hostname.includes("docker") || hostname.includes("kubernetes");
}
async function sha256Hex(file) {
const buffer = await file.arrayBuffer();
const digest = await crypto.subtle.digest("SHA-256", buffer);
@@ -208,34 +201,10 @@ async function createFeatureAndUpload() {
description: assetDescEl.value.trim(),
isPublic: true,
});
const signedUpload = await client.getAssetSignedUploadUrl(created.asset.id, file.type || "application/octet-stream");
let signedHost = "";
try {
signedHost = new URL(signedUpload.url).hostname;
} catch {
signedHost = "";
}
if (signedHost && isLikelyInternalHostname(signedHost) && signedHost !== window.location.hostname) {
throw new Error(
`Upload URL host "${signedHost}" is not browser-reachable from this page. ` +
`Configure S3 endpoint/signing host to a public domain (for example s3.tenerife.baby) or proxy uploads through the API.`
);
}
let uploadRes;
try {
uploadRes = await fetch(signedUpload.url, {
method: signedUpload.method || "PUT",
headers: file.type ? { "Content-Type": file.type } : undefined,
body: file,
});
await client.uploadAssetBinary(created.asset.id, file, file.type || "application/octet-stream");
} catch (error) {
throw new Error(
`Network error while uploading to signed URL. ` +
`Check that object storage endpoint is publicly reachable and CORS allows browser PUT requests.`
);
}
if (!uploadRes.ok) {
throw new Error(`Upload failed with status ${uploadRes.status}`);
throw new Error(`Upload failed: ${error.message}`);
}
await refreshFeatures();
+3 -32
View File
@@ -51,13 +51,6 @@ function kindFromExt(ext) {
return ext === "gltf" || ext === "glb" ? "3d" : "image";
}
function isLikelyInternalHostname(hostname) {
if (!hostname) return false;
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") return true;
if (hostname.endsWith(".local") || hostname.endsWith(".internal")) return true;
return hostname.includes("minio") || hostname.includes("docker") || hostname.includes("kubernetes");
}
async function sha256Hex(file) {
const buffer = await file.arrayBuffer();
const digest = await crypto.subtle.digest("SHA-256", buffer);
@@ -273,32 +266,10 @@ async function createFeatureAndUpload() {
isPublic: true,
});
const signedUpload = await client.getAssetSignedUploadUrl(created.asset.id, file.type || "application/octet-stream");
let signedHost = "";
try {
signedHost = new URL(signedUpload.url).hostname;
} catch {
signedHost = "";
}
if (signedHost && isLikelyInternalHostname(signedHost) && signedHost !== window.location.hostname) {
throw new Error(
`Upload URL host "${signedHost}" is not browser-reachable. ` +
`Use a public S3 endpoint host for signed URLs or add an API upload proxy.`
);
}
let uploadRes;
try {
uploadRes = await fetch(signedUpload.url, {
method: signedUpload.method || "PUT",
headers: file.type ? { "Content-Type": file.type } : undefined,
body: file,
});
} catch {
throw new Error("Network error while uploading. Check S3 endpoint reachability and CORS policy.");
}
if (!uploadRes.ok) {
throw new Error(`Upload failed with status ${uploadRes.status}`);
await client.uploadAssetBinary(created.asset.id, file, file.type || "application/octet-stream");
} catch (error) {
throw new Error(`Upload failed: ${error.message}`);
}
await refreshFeatures();