CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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