mirror of
https://github.com/spacebarchat/server.git
synced 2026-08-14 13:20:32 +00:00
Prepare to use Express
This commit is contained in:
@@ -40,7 +40,7 @@ server.on("request", app);
|
||||
|
||||
const api = new SpacebarServer({ server, port, production, app });
|
||||
const cdn = new CDNServer({ server, port, production, app });
|
||||
const gateway = new GatewayServer({ server, port, production });
|
||||
const gateway = new GatewayServer({ server, port, production, app });
|
||||
const webrtc = new WebrtcServer({
|
||||
server: undefined,
|
||||
port: wrtcWsPort,
|
||||
|
||||
+19
-129
@@ -17,150 +17,40 @@
|
||||
*/
|
||||
|
||||
import http from "node:http";
|
||||
import { setInterval } from "node:timers";
|
||||
import ws from "ws";
|
||||
import { Server, ServerOptions } from "lambert-server";
|
||||
import { initDatabase } from "@spacebar/database";
|
||||
import { Random } from "@spacebar/extensions";
|
||||
import { checkToken, Config, initEvent, JwtKeypairManager, Rights } from "@spacebar/util";
|
||||
import { Config, initEvent, JwtKeypairManager } from "@spacebar/util";
|
||||
import { ProcessLifecycle, SystemdLifecycle } from "../util/util/ProcessLifecycle";
|
||||
import { Monitoring } from "../util/monitoring/Monitoring";
|
||||
import { Connection, openConnections } from "./events/Connection";
|
||||
import { Connection } from "./events/Connection";
|
||||
import { cleanupOnStartup } from "./util";
|
||||
|
||||
export class GatewayServer {
|
||||
export class GatewayServer extends Server {
|
||||
public ws: ws.Server;
|
||||
public port: number;
|
||||
public server: http.Server;
|
||||
public production: boolean;
|
||||
private monitoringLoop: NodeJS.Timeout;
|
||||
|
||||
constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) {
|
||||
this.port = port;
|
||||
this.production = production || false;
|
||||
constructor(options?: Partial<ServerOptions>) {
|
||||
super(options);
|
||||
|
||||
if (server) this.server = server;
|
||||
else {
|
||||
const elu = [1, 5, 15].map(() => performance.eventLoopUtilization());
|
||||
const eluP = [1, 5, 15].map(() => performance.eventLoopUtilization());
|
||||
const cpu = [1, 5, 15].map(() => process.cpuUsage());
|
||||
let sec = 0;
|
||||
const monitoringLoop = setInterval(() => {
|
||||
sec += 1;
|
||||
// for some reason this behaves differently from cpuUsage, so we need an absolute reference as "previous"
|
||||
const eluC = performance.eventLoopUtilization();
|
||||
this.server = http.createServer(async (req, res) => {
|
||||
if (!req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) {
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
`__sb_sessid=${Random.getString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 32)}; Secure; HttpOnly; SameSite=None; Path=/`,
|
||||
);
|
||||
}
|
||||
const requestUrl = new URL(`http://${req.headers.host}${req.url}`);
|
||||
if (requestUrl.pathname === "/metrics") {
|
||||
return await Monitoring.handleRawRequest(req, res);
|
||||
}
|
||||
|
||||
cpu[0] = process.cpuUsage(cpu[0]);
|
||||
elu[0] = performance.eventLoopUtilization(eluP[0]);
|
||||
eluP[0] = eluC;
|
||||
if (sec % 5 === 0) {
|
||||
cpu[1] = process.cpuUsage(cpu[1]);
|
||||
elu[1] = performance.eventLoopUtilization(eluP[1]);
|
||||
eluP[1] = eluC;
|
||||
}
|
||||
if (sec % 15 === 0) {
|
||||
cpu[2] = process.cpuUsage(cpu[2]);
|
||||
elu[2] = performance.eventLoopUtilization(eluP[2]);
|
||||
eluP[2] = eluC;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.server = http.createServer();
|
||||
this.server = http.createServer(async (req, res) => {
|
||||
if (!req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) {
|
||||
res.setHeader(
|
||||
"Set-Cookie",
|
||||
`__sb_sessid=${Random.getString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 32)}; Secure; HttpOnly; SameSite=None; Path=/`,
|
||||
);
|
||||
}
|
||||
const requestUrl = new URL(`http://${req.headers.host}${req.url}`);
|
||||
if (requestUrl.pathname === "/metrics") {
|
||||
return await Monitoring.handleRawRequest(req, res);
|
||||
} else if (requestUrl.pathname === "/_spacebar/gateway/admin/introspect") {
|
||||
if (!req.headers.authorization) {
|
||||
return res.writeHead(401).end("Unauthorized");
|
||||
} else {
|
||||
const auth = req.headers.authorization.split(" ");
|
||||
const sess = await checkToken(auth[1]);
|
||||
if ((BigInt(sess.user.rights) & BigInt(Rights.FLAGS.OPERATOR)) === BigInt(0)) {
|
||||
return res.writeHead(401).end("Unauthorized");
|
||||
}
|
||||
}
|
||||
const useFullWsObj = requestUrl.searchParams.get("fullWs") === "true";
|
||||
res.setHeader("Content-Type", "application/json")
|
||||
.writeHead(200)
|
||||
.end(
|
||||
JSON.stringify(
|
||||
{
|
||||
uptime: process.uptime(),
|
||||
resourceUsage: process.resourceUsage(),
|
||||
eventLoop: elu,
|
||||
cpu: cpu.map((x) => ({
|
||||
user: x.user / 1000,
|
||||
system: x.system / 1000,
|
||||
})),
|
||||
socketStates: {
|
||||
open: openConnections.length,
|
||||
sessions: openConnections.map((x) =>
|
||||
// console.log(x);
|
||||
useFullWsObj
|
||||
? {
|
||||
...x,
|
||||
...{
|
||||
_events: undefined,
|
||||
_closeTimer: undefined,
|
||||
accessToken: x.accessToken?.split(".")[0] + "." + x.accessToken?.split(".")[1] + ".***",
|
||||
},
|
||||
}
|
||||
: {
|
||||
wsReadystate: x.readyState,
|
||||
version: x.version,
|
||||
user_id: x.user_id,
|
||||
session_id: x.session_id,
|
||||
accessToken: x.accessToken?.split(".")[0] + "." + x.accessToken?.split(".")[1] + +".***",
|
||||
encoding: x.encoding,
|
||||
compress: x.compress,
|
||||
ipAddress: x.ipAddress,
|
||||
userAgent: x.userAgent,
|
||||
fingerprint: x.fingerprint,
|
||||
shard_count: x.shard_count,
|
||||
shard_id: x.shard_id,
|
||||
deflate: x.deflate != null,
|
||||
inflate: x.inflate != null,
|
||||
zstdEncoder: x.zstdEncoder != null,
|
||||
zstdDecoder: x.zstdDecoder != null,
|
||||
heartbeatTimeout: x.heartbeatTimeout,
|
||||
readyTimeout: x.readyTimeout,
|
||||
intents: x.intents,
|
||||
sequence: x.sequence,
|
||||
permissions: x.permissions,
|
||||
events: x.events,
|
||||
member_events: x.member_events,
|
||||
listen_options: x.listen_options,
|
||||
capabilities: x.capabilities,
|
||||
large_threshold: x.large_threshold,
|
||||
qos: x.qos,
|
||||
session: x.session,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
(key, value) => {
|
||||
if (value === null || value === undefined) return value;
|
||||
if (Object.getPrototypeOf(value)?.constructor?.name === "Timeout") return `[Timeout] ${value._idleTimeout}ms, repeat: ${value._repeat}`;
|
||||
if (Object.getPrototypeOf(value)?.constructor?.name === "BigInt") return value.toString() + "n";
|
||||
return value;
|
||||
},
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(200).end("Online");
|
||||
});
|
||||
|
||||
ProcessLifecycle.eventEmitter.on("stopping", () => clearTimeout(monitoringLoop));
|
||||
}
|
||||
res.writeHead(200).end("Online");
|
||||
});
|
||||
|
||||
this.server.on("upgrade", (request, socket, head) => {
|
||||
this.ws.handleUpgrade(request, socket, head, (socket) => {
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2026 Spacebar and Spacebar Contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api/middlewares";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { ProcessLifecycle } from "@spacebar/util/util/ProcessLifecycle";
|
||||
import { openConnections } from "@spacebar/gateway/events/Connection";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
const elu = [1, 5, 15].map(() => performance.eventLoopUtilization());
|
||||
const eluP = [1, 5, 15].map(() => performance.eventLoopUtilization());
|
||||
const cpu = [1, 5, 15].map(() => process.cpuUsage());
|
||||
let sec = 0;
|
||||
const monitoringLoop = setInterval(() => {
|
||||
sec += 1;
|
||||
// for some reason this behaves differently from cpuUsage, so we need an absolute reference as "previous"
|
||||
const eluC = performance.eventLoopUtilization();
|
||||
|
||||
cpu[0] = process.cpuUsage(cpu[0]);
|
||||
elu[0] = performance.eventLoopUtilization(eluP[0]);
|
||||
eluP[0] = eluC;
|
||||
if (sec % 5 === 0) {
|
||||
cpu[1] = process.cpuUsage(cpu[1]);
|
||||
elu[1] = performance.eventLoopUtilization(eluP[1]);
|
||||
eluP[1] = eluC;
|
||||
}
|
||||
if (sec % 15 === 0) {
|
||||
cpu[2] = process.cpuUsage(cpu[2]);
|
||||
elu[2] = performance.eventLoopUtilization(eluP[2]);
|
||||
eluP[2] = eluC;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
ProcessLifecycle.eventEmitter.on("stopping", () => clearTimeout(monitoringLoop));
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
right: "OPERATOR",
|
||||
responses: {
|
||||
200: {},
|
||||
403: {
|
||||
body: "APIErrorResponse",
|
||||
},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
const useFullWsObj = req.params.fullWs == "true";
|
||||
res.set("Content-Type", "application/json").send(
|
||||
JSON.stringify(
|
||||
{
|
||||
uptime: process.uptime(),
|
||||
resourceUsage: process.resourceUsage(),
|
||||
eventLoop: elu,
|
||||
cpu: cpu.map((x) => ({
|
||||
user: x.user / 1000,
|
||||
system: x.system / 1000,
|
||||
})),
|
||||
socketStates: {
|
||||
open: openConnections.length,
|
||||
sessions: openConnections.map((x) =>
|
||||
// console.log(x);
|
||||
useFullWsObj
|
||||
? {
|
||||
...x,
|
||||
...{
|
||||
_events: undefined,
|
||||
_closeTimer: undefined,
|
||||
accessToken: x.accessToken?.split(".")[0] + "." + x.accessToken?.split(".")[1] + ".***",
|
||||
},
|
||||
}
|
||||
: {
|
||||
wsReadystate: x.readyState,
|
||||
version: x.version,
|
||||
user_id: x.user_id,
|
||||
session_id: x.session_id,
|
||||
accessToken: x.accessToken?.split(".")[0] + "." + x.accessToken?.split(".")[1] + +".***",
|
||||
encoding: x.encoding,
|
||||
compress: x.compress,
|
||||
ipAddress: x.ipAddress,
|
||||
userAgent: x.userAgent,
|
||||
fingerprint: x.fingerprint,
|
||||
shard_count: x.shard_count,
|
||||
shard_id: x.shard_id,
|
||||
deflate: x.deflate != null,
|
||||
inflate: x.inflate != null,
|
||||
zstdEncoder: x.zstdEncoder != null,
|
||||
zstdDecoder: x.zstdDecoder != null,
|
||||
heartbeatTimeout: x.heartbeatTimeout,
|
||||
readyTimeout: x.readyTimeout,
|
||||
intents: x.intents,
|
||||
sequence: x.sequence,
|
||||
permissions: x.permissions,
|
||||
events: x.events,
|
||||
member_events: x.member_events,
|
||||
listen_options: x.listen_options,
|
||||
capabilities: x.capabilities,
|
||||
large_threshold: x.large_threshold,
|
||||
qos: x.qos,
|
||||
session: x.session,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
(key, value) => {
|
||||
if (value === null || value === undefined) return value;
|
||||
if (Object.getPrototypeOf(value)?.constructor?.name === "Timeout") return `[Timeout] ${value._idleTimeout}ms, repeat: ${value._repeat}`;
|
||||
if (Object.getPrototypeOf(value)?.constructor?.name === "BigInt") return value.toString() + "n";
|
||||
return value;
|
||||
},
|
||||
2,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user