Path: blob/master/src/packages/hub/servers/app/next.ts
5837 views
/*1Serve the Next.js application server, which provides:23- the share server for public_paths4- the landing pages5- ... and more?!6*/78import { Application, NextFunction, Request, Response } from "express";9import { join } from "path";1011// @ts-ignore -- TODO: typescript doesn't like @cocalc/next/init (it is a js file).12import initNextServer from "@cocalc/next/init";1314import basePath from "@cocalc/backend/base-path";15import { getLogger } from "@cocalc/hub/logger";16import handleRaw from "@cocalc/next/lib/share/handle-raw";17import { callback2 } from "@cocalc/util/async-utils";18import { database } from "../database";19import createLandingRedirect from "./landing-redirect";20import shareRedirect from "./share-redirect";21import { separate_file_extension } from "@cocalc/util/misc";2223export default async function init(app: Application) {24const winston = getLogger("nextjs");2526winston.info("Initializing the nextjs server...");2728// the Hot module reloader triggers some annoying "fetch" warnings, so29// we temporarily disable all warnings, then re-enable them immediately below.30// https://www.phind.com/search?cache=3a6edffa-ce34-4d0d-b448-6ea45f32578331const originalWarningListeners = process.listeners("warning").slice();32process.removeAllListeners("warning");33const handler = await initNextServer({ basePath });34originalWarningListeners.forEach((listener) => {35process.on("warning", listener);36});3738winston.info("Initializing the nextjs share server...");39const shareServer = await runShareServer();40const shareBasePath = join(basePath, "share");4142if (shareServer) {43// We create a redirect middleware and a raw/download44// middleware, since the share server will be fully available.45// IMPORTANT: all files are also served with download:true, so that46// they don't get rendered with potentially malicious content.47// The only way we could allow this is to serve all raw content48// from a separate domain, e.g., raw.cocalc.com. That would be49// reasonable on cocalc.com, but to ensure this for all on-prem,50// etc. servers is definitely too much, so we just disable this.51// For serving actual raw content, the solution will be to use52// a vhost.5354// 1: The raw static server:55const raw = join(shareBasePath, "raw");56app.all(57join(raw, "{*splat}"),58(req: Request, res: Response, next: NextFunction) => {59// Embedding only enabled for PDF files -- see note above60const download =61separate_file_extension(req.path).ext.toLowerCase() !== "pdf";62try {63handleRaw({64...parseURL(req, raw),65req,66res,67next,68download,69});70} catch (_err) {71res.status(404).end();72}73},74);7576// 2: The download server -- just like raw, but files always get sent via download.77const download = join(shareBasePath, "download");78app.all(79join(download, "{*splat}"),80(req: Request, res: Response, next: NextFunction) => {81try {82handleRaw({83...parseURL(req, download),84req,85res,86next,87download: true,88});89} catch (_err) {90res.status(404).end();91}92},93);9495// 3: Redirects for backward compat; unfortunately there's slight96// overhead for doing this on every request.9798app.all(join(shareBasePath), shareRedirect(shareBasePath));99app.all(join(shareBasePath, "{*splat}"), shareRedirect(shareBasePath));100}101102const landingRedirect = createLandingRedirect();103app.all(join(basePath, "index.html"), landingRedirect);104app.all(join(basePath, "doc"), landingRedirect);105app.all(join(basePath, "doc", "{*splat}"), landingRedirect);106app.all(join(basePath, "policies"), landingRedirect);107app.all(join(basePath, "policies", "{*splat}"), landingRedirect);108109// The next.js server that serves everything else.110winston.info(111"Now using next.js packages/share handler to handle all endpoints not otherwise handled",112);113114// nextjs listens on everything else115app.all("{*splat}", handler);116}117118function parseURL(req: Request, base): { id: string; path: string } {119let url = req.url.slice(base.length + 1);120let i = url.indexOf("/");121if (i == -1) {122url = url + "/";123i = url.length - 1;124}125return { id: url.slice(0, i), path: decodeURI(url.slice(i + 1)) };126}127128async function runShareServer(): Promise<boolean> {129const { rows } = await callback2(database._query, {130query: "SELECT value FROM server_settings WHERE name='share_server'",131});132return rows.length > 0 && rows[0].value == "yes";133}134135136