fix(canvas): let headless hosts configure asset display routes

This commit is contained in:
2026-11-11 16:04:12 +08:00
parent 0ced171305
commit db6594f473
5 changed files with 25 additions and 8 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ function status(request: FrozenRequest, response: ServiceResponse, query: boolea
}
/** Immutable bytes and a preallocated resource ID survive response loss and view changes. */
export function createFoundryAssets(options: { workspaceId: Id; transport: ServiceTransport; project?: string; origin?: string }): AssetPort {
export function createFoundryAssets(options: { workspaceId: Id; transport: ServiceTransport; project?: string; origin?: string; displayURL?: (id: Id) => string }): AssetPort {
if (!uuid.test(options.workspaceId)) throw fault('invalid_service_scope');
const origin = new URL(options.origin ?? 'https://api.foundry.entroia.com');
if (origin.protocol !== 'https:' || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/') throw fault('invalid_asset_origin');
@@ -57,7 +57,7 @@ export function createFoundryAssets(options: { workspaceId: Id; transport: Servi
}
}
const authority=Object.freeze({scope:`${origin.origin}/projects/${encodeURIComponent(options.project??'endoleap')}/${path}`,async resolve(id:Id,signal:AbortSignal){if(!uuid.test(id))throw fault('invalid_resource_id');const response=await options.transport({method:'GET',path:`${path}/metadata/${encodeURIComponent(id)}`,signal}),raw=object(response.body);if(response.status!==200)throw fault(typeof raw.error==='string'?raw.error:'asset_authorization_failed');if(raw.metadata_version!==1)throw fault('asset_metadata_interpreter_unavailable');const value={version:1 as const,authority:authority.scope,digest:raw.sha256 as string,asset:raw.asset as StoredAsset};validateSharedAsset(value);if(value.asset.id!==id||value.asset.url!==sourceURL(id))throw fault('asset_authorization_mismatch');return freeze(value);},async read(id:Id,signal:AbortSignal){if(!uuid.test(id))throw fault('invalid_resource_id');const response=await options.transport({method:'GET',path:`${path}/${encodeURIComponent(id)}`,signal,responseFormat:'asset-bytes-v1'});if(response.status!==200||!response.bytes)throw fault('asset_bytes_unavailable');return response.bytes;}});
return Object.freeze({ authority, sourceURL, displayURL: (id: Id) => `/api/foundry/${path}/${encodeURIComponent(id)}?variant=preview`, accept,
return Object.freeze({ authority, sourceURL, displayURL: options.displayURL ?? ((id: Id) => `/api/foundry/${path}/${encodeURIComponent(id)}?variant=preview`), accept,
upload: (request: FrozenRequest, bytes: Blob, signal: AbortSignal) => execute(request, bytes, signal),
query: (request: FrozenRequest, signal: AbortSignal) => execute(request, undefined, signal),
});