diff --git a/.gitignore b/.gitignore index 6b8d93bf..cad2da45 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ src/apps/officer-web/index.gen.html # scratch scripts — never commit these *.tmp.ts + +# Sidecar assets published at install time — copies of files that live in each sidecar's own tree. +public/plugins/ diff --git a/src/server.tsx b/src/server.tsx index b853b98a..65848d0b 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -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// 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) => { diff --git a/src/servers/app-store/assets.ts b/src/servers/app-store/assets.ts new file mode 100644 index 00000000..1839fd58 --- /dev/null +++ b/src/servers/app-store/assets.ts @@ -0,0 +1,68 @@ +import { cp, rm, stat } from 'node:fs/promises'; +import { join } from 'node:path'; + +// Copying a sidecar's own assets — its icon, and whatever else it ships — to where the browser can +// fetch them. +// +// src/servers/app-store/templates/