feat: F5.5 large binary files as blobs on server F
From 16 KB a picture's or binary file's entry names its bytes by their SHA-256 instead of holding them; the bytes are on F, content-addressed (GET/POST /blobs with the session's grant, from the editor's origin, at most 16 MiB). A document's blob side says where they are: the editor's fetches and uploads, the room thread's asks F's main thread, and a start waits for the ones asked for; without a side everything stays in the document. Text stays Y.Text. A blob whose bytes are not there is never written back empty. A blob F has not got yet is asked for again a few times, and a running room reads late bytes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// Server F's front door (plan §8.2): one HTTP server — /healthz for the
|
||||
// proxy, /__auth for the editor's frame (ticket in, cookie out), /svc/* for
|
||||
// the dashboard (with the service key), the WebSockets /sync for the shared
|
||||
// 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
|
||||
@@ -18,6 +19,8 @@ import type { AddressInfo, Socket } from 'node:net';
|
||||
import { SIM_REFUSED_CODE, type SyncRefusal } from '@mdg-filius/protocol';
|
||||
import { WebSocketServer, type WebSocket } from 'ws';
|
||||
import { exchangeTicket } from './auth.js';
|
||||
import { handleBlobs } from './blobRoutes.js';
|
||||
import type { BlobStore } from './blobs.js';
|
||||
import { errorText, type Log } from './log.js';
|
||||
import { handleService } from './service.js';
|
||||
import { holdMessages, SimRooms } from './simRooms.js';
|
||||
@@ -46,6 +49,7 @@ export interface ServerOptions extends SyncOptions {
|
||||
readonly secureCookie: boolean;
|
||||
/** The bundled room thread (dist/simWorker.mjs). */
|
||||
readonly workerUrl: URL;
|
||||
readonly blobs: BlobStore;
|
||||
readonly simThreads?: number;
|
||||
readonly lingerMilliseconds?: number;
|
||||
readonly pingMilliseconds?: number;
|
||||
@@ -65,6 +69,7 @@ export async function startServer(options: ServerOptions): Promise<FiliusServer>
|
||||
const { hocuspocus } = sync;
|
||||
const sims = new SimRooms({
|
||||
hocuspocus,
|
||||
blobs: options.blobs,
|
||||
log: options.log,
|
||||
workerUrl: options.workerUrl,
|
||||
...(options.simThreads === undefined ? {} : { threads: options.simThreads }),
|
||||
@@ -105,6 +110,7 @@ export async function startServer(options: ServerOptions): Promise<FiliusServer>
|
||||
if (request.method === 'GET' && path === '/healthz') json(response, 200, { status: 'ok', documents: hocuspocus.getDocumentsCount(), connections: hocuspocus.getConnectionsCount(), rooms: sims.stats().rooms });
|
||||
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' });
|
||||
};
|
||||
const http: Server = createServer((request, response) => {
|
||||
|
||||
Reference in New Issue
Block a user