feat: F5.6b server F as an image: the editor from F itself, one stack per environment

F hands out the editor's page and bundle (editor/ beside main.mjs, or
FILIUS_EDITOR_DIR): GET and HEAD only, one wall against leaving the folder,
F's own routes first, assets/ cached a year, the rest asked for again; the
build lays .br and .gz beside the files and F sends the better one the
browser takes (bundle 695 KB to 212 KB).

deploy/Dockerfile: two stages, Node 24, no node_modules, not root.
deploy/docker-compose.yml with an env file per environment: the file sets
the stack's name, volume, image tag and port (test 8081, production 8080).
Measured with both stacks side by side behind nginx and Chromium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 17:43:51 +00:00
co-authored by Claude Opus 5.5
parent 709146b58a
commit 087721cba3
19 changed files with 714 additions and 10 deletions
+7 -3
View File
@@ -3,8 +3,9 @@
// the dashboard (with the service key), /blobs for the large binary files
// of the projects' computers, the WebSockets /sync for the shared
// documents and /sim?project=… for the simulation rooms (the session's grant
// decides there as at /sync; a refusal closes the socket with its reason);
// anything else is not found. A WebSocket from a page of another
// decides there as at /sync; a refusal closes the socket with its reason),
// and the editor's page and bundle (editorFiles.ts) for anything else; what
// is none of these is not found. A WebSocket from a page of another
// origin is refused (a page elsewhere under the same domain would bring the
// person's cookie along: SameSite only tells sites apart). Closed, it first
// takes no one new (a client lets go and comes back at once, which would
@@ -21,6 +22,7 @@ import { WebSocketServer, type WebSocket } from 'ws';
import { exchangeTicket } from './auth.js';
import { handleBlobs } from './blobRoutes.js';
import type { BlobStore } from './blobs.js';
import type { EditorFiles } from './editorFiles.js';
import { errorText, type Log } from './log.js';
import { handleService } from './service.js';
import { holdMessages, SimRooms } from './simRooms.js';
@@ -50,6 +52,8 @@ export interface ServerOptions extends SyncOptions {
/** The bundled room thread (dist/simWorker.mjs). */
readonly workerUrl: URL;
readonly blobs: BlobStore;
/** The editor's page and bundle; without them only F's own routes answer (development, tests). */
readonly editor?: EditorFiles;
readonly simThreads?: number;
readonly lingerMilliseconds?: number;
readonly pingMilliseconds?: number;
@@ -111,7 +115,7 @@ export async function startServer(options: ServerOptions): Promise<FiliusServer>
else if (request.method === 'GET' && path === '/__auth') await exchangeTicket(request, response, options);
else if (path.startsWith('/svc/')) await handleService(request, response, path, { ...options, sync, sims });
else if (path === '/blobs' || path.startsWith('/blobs/')) await handleBlobs(request, response, new URL(request.url ?? '/', 'http://filius'), options);
else json(response, 404, { error: 'notFound' });
else if (options.editor === undefined || !(await options.editor(request, response, path))) json(response, 404, { error: 'notFound' });
};
const http: Server = createServer((request, response) => {
const path = new URL(request.url ?? '/', 'http://filius').pathname;