Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/hub/servers/app/next.ts
Views: 687
/*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, "*"),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, "*"),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));99}100101const landingRedirect = createLandingRedirect();102app.all(join(basePath, "index.html"), landingRedirect);103app.all(join(basePath, "doc*"), landingRedirect);104app.all(join(basePath, "policies*"), landingRedirect);105106// The next.js server that serves everything else.107winston.info(108"Now using next.js packages/share handler to handle all endpoints not otherwise handled",109);110111// nextjs listens on everything else112app.all("*", handler);113}114115function parseURL(req: Request, base): { id: string; path: string } {116let url = req.url.slice(base.length + 1);117let i = url.indexOf("/");118if (i == -1) {119url = url + "/";120i = url.length - 1;121}122return { id: url.slice(0, i), path: decodeURI(url.slice(i + 1)) };123}124125async function runShareServer(): Promise<boolean> {126const { rows } = await callback2(database._query, {127query: "SELECT value FROM server_settings WHERE name='share_server'",128});129return rows.length > 0 && rows[0].value == "yes";130}131132133