app store: publish a sidecar's assets to public/plugins/<id>/
Real PNG icons are coming, so this is the path they arrive by: a sidecar ships its assets beside its own code, and install copies them to public/plugins/<id>/ where one static route serves them. Copied rather than served in place because a sidecar shipping from its own repository has its assets wherever that repository was unpacked, which is not a path the web server can be taught at build time. One predictable destination means the serving rule never has to know how many plugins exist or where any came from. It also makes assets a property of the INSTALL: uninstall removes them, and a plugin nobody installed serves nothing. Needed a new route, and the reason is a trap worth recording. `publicRoutes` in server.tsx is built by globbing ./public at BOOT, so anything copied there afterwards is invisible to it — the first install of a plugin would show a broken image until the server was restarted, and "install it, then restart to see the icon" is not an install. `/plugins/*` resolves per request, like /novnc/* and /vendor/* already do. Unlike those two it answers 404 rather than 500 for a missing file: an unpublished icon is an ordinary state on a fresh machine, and a 500 would put a red line in the log for every dock render. Proven end to end with a real asset: slskd's icon moved from public/slskd.png into the sidecar's own assets/, published against an ALREADY RUNNING server, and fetched at 200 with the right bytes and content-type — 404 before publishing, no restart between. public/plugins/ is gitignored: it holds copies, and the originals live with each sidecar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -221,6 +221,20 @@ const server = serve({
|
||||
const file = Bun.file(`public${new URL(req.url).pathname}`);
|
||||
return new Response(file);
|
||||
},
|
||||
// Icons and assets belonging to installed sidecars, copied to public/plugins/<id>/ by the installer.
|
||||
//
|
||||
// Served by this dynamic route rather than by `publicRoutes` above, which is a snapshot taken by
|
||||
// globbing ./public at BOOT. A plugin installed while the server is running would not be in that map,
|
||||
// so its icon would 404 until the next restart — and "install it, then restart the server to see the
|
||||
// icon" is not an install.
|
||||
'/plugins/*': async (req) => {
|
||||
const file = Bun.file(`public${new URL(req.url).pathname}`);
|
||||
// 404 rather than letting a missing file surface as a 500. An icon that has not been published
|
||||
// yet is an ordinary state — the plugin is not installed — and a 500 would put a red line in the
|
||||
// log for every dock render on a fresh machine.
|
||||
if (!(await file.exists())) return new Response('Not found', { status: 404 });
|
||||
return new Response(file);
|
||||
},
|
||||
// Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket);
|
||||
// everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy.
|
||||
'/api/vault/notifications/*': (req, server) => {
|
||||
|
||||
Reference in New Issue
Block a user