Tailwind read ref literals like [file:12] in prompts and docs as arbitrary-property utilities and emitted invalid CSS; those files hold no className, so they are excluded from the scan. Links in the user's ink bubble get a readable blue instead of the bubble's own ink, and the bubble no longer needs its own CSS class.
178 lines
5.8 KiB
TypeScript
178 lines
5.8 KiB
TypeScript
import bunext from "@moduletrace/bunext";
|
|
import handleMediaServer from "./functions/server/media/handle-media-server";
|
|
import cron from "./cron";
|
|
import { websocket } from "./websocket";
|
|
import userAuth from "./functions/backend/auth/user-auth";
|
|
import type { WebSocketSession } from "./websocket/types";
|
|
import { logError, grabRequestIP } from "./functions/backend/logger";
|
|
import botsQueue from "./bots-queue";
|
|
import fileIngest from "./file-ingest";
|
|
import liveRelay from "./live-relay";
|
|
import enableForeignKeys from "./functions/backend/db/enable-foreign-keys";
|
|
import handleComputerProxy from "./functions/server/computers/handle-computer-proxy";
|
|
import handlePwaFile from "./functions/server/pwa/handle-pwa-file";
|
|
import handleBuildInfo from "./functions/server/pwa/handle-build-info";
|
|
import handlePrecacheGate from "./functions/server/pwa/handle-precache-gate";
|
|
import grabPrecacheManifest from "./functions/server/pwa/grab-precache-manifest";
|
|
import compressResponse from "./functions/server/compress-response";
|
|
import { ComputerProxyPrefix } from "./dict/computer-dict";
|
|
import ensureDockerNetwork from "./functions/backend/docker/ensure-docker-network";
|
|
import { ensureVapidKeys } from "./push/vapid";
|
|
|
|
declare global {}
|
|
|
|
const WEBSOCKET_ENDPOINT = "/ws";
|
|
|
|
const development = process.env.NODE_ENV == "development";
|
|
const port = process.env.PORT || 7227;
|
|
|
|
await enableForeignKeys();
|
|
await bunext.bunextInit();
|
|
|
|
/**
|
|
* The running build's id and files, read once: browsers whose build
|
|
* cookie doesn't match get the loading page first (production only).
|
|
*/
|
|
const precache = grabPrecacheManifest();
|
|
|
|
if (precache) {
|
|
bunext.bunextLog.info(
|
|
`Build ${precache.build_id}: ${precache.files.length} files to precache`,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Web Push needs a key pair; the first start of the main install creates
|
|
* it (in the Environment settings) so notifications work without setup.
|
|
*/
|
|
if (process.env.NODE_ENV != "production" || process.env.MAIN_SERVER) {
|
|
await ensureVapidKeys().catch((error) =>
|
|
logError({ title: "Could not prepare Web Push keys", error }).catch(
|
|
() => {},
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Computers are docker containers on this network, so the app doesn't
|
|
* start without docker.
|
|
*/
|
|
try {
|
|
await ensureDockerNetwork();
|
|
} catch (error) {
|
|
await logError({ title: "Docker isn't available", error }).catch(() => {});
|
|
console.error(
|
|
`Can't start without docker: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
/**
|
|
* The app always serves plain HTTP and leaves TLS to whatever sits in
|
|
* front of it (a reverse proxy or tunnel). `isSecureRequest` reads the
|
|
* proxy's `X-Forwarded-Proto` so cookies still get `Secure`.
|
|
*/
|
|
const server = Bun.serve({
|
|
async fetch(req, server) {
|
|
try {
|
|
const url = new URL(req.url);
|
|
|
|
const pwa_file = await handlePwaFile({ pathname: url.pathname });
|
|
|
|
if (pwa_file) return pwa_file;
|
|
|
|
const build_info = handleBuildInfo({ pathname: url.pathname });
|
|
|
|
if (build_info) return build_info;
|
|
|
|
const loading_page = await handlePrecacheGate({ req, url });
|
|
|
|
if (loading_page) return loading_page;
|
|
|
|
if (url.pathname.startsWith("/media")) {
|
|
try {
|
|
return await handleMediaServer({ req });
|
|
} catch {
|
|
return new Response("Not Found", { status: 404 });
|
|
}
|
|
}
|
|
|
|
if (url.pathname.startsWith(`${ComputerProxyPrefix}/`)) {
|
|
return await handleComputerProxy({ req, server });
|
|
}
|
|
|
|
if (url.pathname === WEBSOCKET_ENDPOINT) {
|
|
const session = await userAuth({ req });
|
|
|
|
if (!session.user?.id) {
|
|
return new Response("Unauthorized", { status: 401 });
|
|
}
|
|
|
|
const upgraded = server.upgrade(req, {
|
|
data: {
|
|
user: session.user,
|
|
user_types: session.user_types ?? [],
|
|
} satisfies WebSocketSession,
|
|
});
|
|
|
|
if (!upgraded) {
|
|
return new Response("Upgrade failed", { status: 400 });
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const response = await bunext.bunextRequestHandler({ req, server });
|
|
|
|
return response
|
|
? await compressResponse({ req, response })
|
|
: response;
|
|
} catch (error: any) {
|
|
await logError({
|
|
title: "Unhandled request error",
|
|
error,
|
|
metadata: { url: req.url, method: req.method },
|
|
ip_address: grabRequestIP(req),
|
|
}).catch(() => {});
|
|
|
|
return Response.json(
|
|
{ success: false, msg: "Internal Server Error" },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
},
|
|
websocket,
|
|
development,
|
|
port,
|
|
/**
|
|
* Fail loudly on a second dev server instead of letting Bun's default
|
|
* port sharing split requests between an old and a new process.
|
|
*/
|
|
reusePort: false,
|
|
idleTimeout: development ? 80 : undefined,
|
|
});
|
|
|
|
bunext.bunextLog.info(
|
|
`Server running on http://${server.hostname}:${server.port} ...`,
|
|
);
|
|
|
|
/**
|
|
* Background services start after the server is up, and one failing to
|
|
* start never takes the server (or the others) down with it.
|
|
*/
|
|
try {
|
|
cron();
|
|
} catch (error) {
|
|
logError({ title: "Cron couldn't start", error }).catch(() => {});
|
|
}
|
|
|
|
botsQueue().catch((error) =>
|
|
logError({ title: "Bots queue couldn't start", error }).catch(() => {}),
|
|
);
|
|
fileIngest().catch((error) =>
|
|
logError({ title: "File ingest couldn't start", error }).catch(() => {}),
|
|
);
|
|
liveRelay().catch((error) =>
|
|
logError({ title: "Live relay couldn't start", error }).catch(() => {}),
|
|
);
|