Initial commit
This commit is contained in:
158
src/main.ts
Normal file
158
src/main.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import Logging, { LoggingConfiguration, LogTiming, TimeFormat } from "@proxnet/undead-logging";
|
||||
import { getFullPathFromUrl, getSourceAddress } from "./util/net.ts";
|
||||
import { createHonoRoute, routeImporter } from "./util/import.ts";
|
||||
import SocketConsoleHandler from "./server/socket/console/socket.ts";
|
||||
import Server from "./server/server.ts";
|
||||
import Command from "./server/commands/command.ts";
|
||||
import { ServerContentBase } from "./server/ContentBase.ts";
|
||||
import z from "zod";
|
||||
import { verify } from "@hono/hono/jwt";
|
||||
import { type ProfileToken } from "./server/profiles/types/profile.ts";
|
||||
import { SignalRSocketHandler } from "./server/socket/signalr/socket.ts";
|
||||
import { PushNotificationId } from "./server/socket/signalr/types.ts";
|
||||
|
||||
LoggingConfiguration.resetTimeFormat = TimeFormat.Unix;
|
||||
LoggingConfiguration.resetLogTiming = LogTiming.Microtask;
|
||||
|
||||
const log = new Logging("Main");
|
||||
|
||||
log.i(`wsi by zombieb`);
|
||||
|
||||
export function detailedLog(items: (string | number | boolean | null)[]) {
|
||||
return items.filter(val => val !== null).join('\r\n ');
|
||||
}
|
||||
function formatHeader(headers: Headers, name: string) {
|
||||
if (!headers.has(name)) return null;
|
||||
return `${name}: ${headers.get(name)}`;
|
||||
}
|
||||
export const logBlacklist = [
|
||||
'/favicon.ico'
|
||||
];
|
||||
|
||||
export const AppRoot = createHonoRoute('/');
|
||||
|
||||
await routeImporter(AppRoot.app, 'src/', [
|
||||
'routes/root',
|
||||
'routes/api',
|
||||
'routes/auth',
|
||||
'routes/accounts'
|
||||
]);
|
||||
|
||||
// deno-lint-ignore require-await
|
||||
AppRoot.app.use('*', async c => {
|
||||
if (!logBlacklist.includes(c.req.url)) log.e(detailedLog([c.get('srcAddr'),
|
||||
`404 ${c.req.method} ${getFullPathFromUrl(new URL(c.req.url))}`
|
||||
]));
|
||||
c.res = new Response("Not Found", { status: 404 });
|
||||
});
|
||||
|
||||
export const consoleSockets: Set<SocketConsoleHandler> = new Set();
|
||||
const gameSockets: Set<SignalRSocketHandler> = new Set();
|
||||
|
||||
const onListen = async () => {
|
||||
if (!(await Array.fromAsync(Deno.readDir('.'))).find(entry => entry.isDirectory && entry.name == 'persist'))
|
||||
await Deno.mkdir('persist');
|
||||
|
||||
await Promise.all(Object.values(Server).map(base => ((base as ServerContentBase).kvInit ? (base as ServerContentBase).kvInit() : undefined)));
|
||||
Object.values(Server).forEach(base => ((base as ServerContentBase).start ? (base as ServerContentBase).start() : undefined));
|
||||
}
|
||||
|
||||
const server = Deno.serve({ hostname: "10.0.1.39", port: 13370, onListen: addr => {
|
||||
log.n(`Listening info: ${JSON.stringify(addr)}`);
|
||||
|
||||
onListen();
|
||||
}}, async (req, info) => {
|
||||
|
||||
const url = new URL(req.url);
|
||||
const srcAddr = getSourceAddress(req, info.remoteAddr);
|
||||
const unauthRes = new Response("Unauthorized", { status: 401 });
|
||||
|
||||
if (url.pathname == '/notify/hub/v1/negotiate') {
|
||||
return new Response(JSON.stringify({
|
||||
connectionId: "who_said_it",
|
||||
availableTransports: [{transport:"WebSockets",transferFormats:["Text"]}]
|
||||
}), { headers: { 'Content-Type': 'application/json' }});
|
||||
}
|
||||
if (req.headers.get('Connection')?.includes('Upgrade') && req.headers.get('Upgrade') === 'websocket') {
|
||||
const isSignalR = req.headers.has('id');
|
||||
if (isSignalR) {
|
||||
try {
|
||||
const authHeader = req.headers.get('Authorization');
|
||||
if (!authHeader) return unauthRes;
|
||||
|
||||
const splitHeader = authHeader.split(' ')[1];
|
||||
if (!splitHeader) return unauthRes;
|
||||
|
||||
const secret = Deno.env.get('secret');
|
||||
if (!secret) {
|
||||
log.w(`No secret set!`);
|
||||
return unauthRes;
|
||||
}
|
||||
const payload = (await verify(splitHeader, secret)) as ProfileToken;
|
||||
|
||||
const profile = await Server.Profiles.get(payload.sub);
|
||||
if (!profile) return new Response("Internal Server Error (profile)", { status: 500 });;
|
||||
const { response, socket } = Deno.upgradeWebSocket(req);
|
||||
const handler = new SignalRSocketHandler(socket, profile);
|
||||
gameSockets.add(handler);
|
||||
socket.onclose = () => {
|
||||
gameSockets.delete(handler);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
log.w(`Socket authentication error: ${(err as Error).stack}`);
|
||||
return unauthRes;
|
||||
}
|
||||
} else {
|
||||
const pass = url.searchParams.get('pass');
|
||||
if (!pass) return unauthRes;
|
||||
else if (pass !== Deno.env.get('CONSOLESECRET')) return unauthRes;
|
||||
|
||||
const { response, socket } = Deno.upgradeWebSocket(req);
|
||||
const handler = new SocketConsoleHandler(socket, req, info);
|
||||
consoleSockets.add(handler);
|
||||
socket.onclose = () => {
|
||||
consoleSockets.delete(handler);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!logBlacklist.includes(url.pathname)) log.n(detailedLog([srcAddr,
|
||||
`${req.method} ${getFullPathFromUrl(new URL(req.url))}`,
|
||||
formatHeader(req.headers, 'Content-Type'),
|
||||
formatHeader(req.headers, 'Connection'),
|
||||
formatHeader(req.headers, 'User-Agent'),
|
||||
]));
|
||||
|
||||
return await AppRoot.app.fetch(req, { srcAddr });
|
||||
|
||||
});
|
||||
|
||||
let shuttingDown = false;
|
||||
Deno.addSignalListener('SIGINT', () => {
|
||||
if (shuttingDown) return;
|
||||
else shuttingDown = true;
|
||||
server.shutdown();
|
||||
server.unref();
|
||||
|
||||
LoggingConfiguration.resetLogTiming = LogTiming.Sync;
|
||||
log.i('Shutting down');
|
||||
|
||||
for (const socket of consoleSockets) socket.destroy();
|
||||
for (const socket of gameSockets) socket.sendNotification(PushNotificationId.ModerationQuitGame);
|
||||
});
|
||||
|
||||
Server.Commands.addRootCommand(new Command({
|
||||
key: ['ping', 'latency'],
|
||||
exec: (sent: number) => {
|
||||
if (sent) return `${Date.now() - sent}ms`;
|
||||
else return null;
|
||||
},
|
||||
zod: z.tuple([
|
||||
z.string().transform(Number)
|
||||
]),
|
||||
help: 'Get ping (in ms) to the server'
|
||||
}));
|
||||
Reference in New Issue
Block a user