* Unified profiles, rather than instantiating profiles every time we want to access one
* Socket and live instance changes * Possible problem with Deno's handling of sockets, compatibility issue with Node?
This commit is contained in:
120
src/main.ts
120
src/main.ts
@@ -9,10 +9,8 @@ import express from "express";
|
||||
import WebSocket, { WebSocketServer } from "ws";
|
||||
import { IncomingMessage } from "../../AppData/Local/deno/npm/registry.npmjs.org/@types/connect/3.4.38/index.d.ts";
|
||||
import { decode } from "@gz/jwt";
|
||||
import Profile, { ProfileTokenFormat } from "./data/profiles.ts";
|
||||
import UnifiedProfile, { ProfileTokenFormat } from "./data/profiles.ts";
|
||||
import { SocketHandoff } from "./socket/handoff.ts";
|
||||
import internal from "node:stream";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { SignalRSocketHandler } from "./socket/socket.ts";
|
||||
|
||||
const instanceId = generateRandomString(64);
|
||||
@@ -100,6 +98,47 @@ app.use((rq: express.Request, rs: express.Response) => {
|
||||
|
||||
try {
|
||||
|
||||
/**
|
||||
* Galvanic WebSocket Server
|
||||
*/
|
||||
|
||||
type AuthResult = {
|
||||
token?: ProfileTokenFormat,
|
||||
valid: boolean
|
||||
}
|
||||
const authenticate = async (req: IncomingMessage) => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader) return { valid: false } as AuthResult;
|
||||
|
||||
const token = authHeader.split(" ")[1];
|
||||
if (!token) return { valid: false } as AuthResult;
|
||||
|
||||
const decodedToken = await decode<ProfileTokenFormat>(token, config.auth.secret, {algorithm: 'HS512'});
|
||||
const schemaResult = ProfileTokenSchema.safeParse(decodedToken);
|
||||
if (!schemaResult.success) return { valid: false } as AuthResult;
|
||||
else return { token: decodedToken, valid: true } as AuthResult;
|
||||
}
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true, path: "/notify/hub/v1" });
|
||||
wss.on('connection', (socket: WebSocket, req: IncomingMessage) => {
|
||||
if (!req.token) {
|
||||
socket.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// ID is given as "/notify/hub/v1?&id=pprhdSzJn" by the client.
|
||||
let handoff: SocketHandoff | undefined;
|
||||
if (req.url) {
|
||||
const pathParts = req.url.replace('v1', '').split('/');
|
||||
const query = new URLSearchParams(pathParts[pathParts.length - 1]);
|
||||
const connectionId = query.get('id');
|
||||
if (connectionId) handoff = SocketHandoff.find(connectionId);
|
||||
}
|
||||
|
||||
if (handoff) handoff.complete();
|
||||
new SignalRSocketHandler(socket, UnifiedProfile.get(req.token.sub));
|
||||
});
|
||||
|
||||
const http = app.listen(config.web.port, config.web.host, () => {
|
||||
log.n(`Listening on http://${config.web.host}:${config.web.port}`);
|
||||
|
||||
@@ -110,60 +149,39 @@ try {
|
||||
log.i(`Shutting down`);
|
||||
|
||||
http.close();
|
||||
http.closeAllConnections();
|
||||
});
|
||||
Deno.addSignalListener("SIGINT", () => {
|
||||
for (const handoff of SocketHandoff.all()) handoff.complete();
|
||||
});
|
||||
});
|
||||
|
||||
const wss = new WebSocketServer({
|
||||
server: http,
|
||||
path: "/notify/hub/v1"
|
||||
});
|
||||
wss.on('connection', (ws: WebSocket, rq: IncomingMessage, profile: Profile, connectionId: string) => {
|
||||
const handoff = SocketHandoff.find(connectionId);
|
||||
if (handoff) handoff.complete();
|
||||
log.d(typeof profile);
|
||||
new SignalRSocketHandler(ws, profile);
|
||||
});
|
||||
http.on('upgrade', async (rq: IncomingMessage, socket: internal.Duplex, head: Buffer) => {
|
||||
const errorHandler = (err: Error | undefined) => { log.e(`Socket error: ${err?.stack}`); };
|
||||
socket.on('error', errorHandler);
|
||||
// Currently not working in Deno. Socket problem?
|
||||
/*http.on('upgrade', async (req, socket, head) => {
|
||||
log.d('Handling upgrade');
|
||||
try {
|
||||
const authResult = await authenticate(req);
|
||||
|
||||
function writeUnauthorized() {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
if (authResult.valid) {
|
||||
req.token = authResult.token;
|
||||
|
||||
log.d('Auth result was valid.');
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, req);
|
||||
});
|
||||
} else {
|
||||
// Reject the upgrade
|
||||
log.e(`Socket authentication error (401) from ${APIUtils.getSrcIpDefaultRaw(req)}`);
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.destroy();
|
||||
}
|
||||
} catch (err) {
|
||||
// Handle authentication error
|
||||
log.e(`Socket authentication error (500): ${err}\n from ${APIUtils.getSrcIpDefaultRaw(req)}`);
|
||||
socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
let wrong = false;
|
||||
const unparsedToken = rq.headers['Authorization'];
|
||||
const connectionId = new URL(`http://${rq.headers.host ? rq.headers.host : 'localhost'}${rq.url}`).searchParams.get('connectionId');
|
||||
if (connectionId == null) wrong = true;
|
||||
else {
|
||||
if (typeof unparsedToken == 'string') {
|
||||
const splitToken = unparsedToken.split(' ')[1]
|
||||
if (splitToken) {
|
||||
try {
|
||||
|
||||
const decodedToken = await decode<ProfileTokenFormat>(splitToken, config.auth.secret, {algorithm: 'HS512'});
|
||||
const schemaResult = ProfileTokenSchema.safeParse(decodedToken);
|
||||
if (!schemaResult.success) wrong = true;
|
||||
else {
|
||||
wss.handleUpgrade(rq, socket, head, (ws) => {
|
||||
wss.emit('connection', ws, rq, new Profile(decodedToken.sub), connectionId);
|
||||
});
|
||||
}
|
||||
|
||||
} catch {
|
||||
wrong = true;
|
||||
}
|
||||
} else wrong = true;
|
||||
} else wrong = true;
|
||||
}
|
||||
|
||||
if (wrong) {
|
||||
writeUnauthorized();
|
||||
return;
|
||||
}
|
||||
socket.removeListener('error', errorHandler);
|
||||
});
|
||||
});*/
|
||||
|
||||
} catch (err) {
|
||||
log.e(`Cannot start: Network could not be initalized. ${err}`);
|
||||
|
||||
Reference in New Issue
Block a user