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