Upload signaling server
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* HTTP tunnel over WebRTC - Signaling Server
|
||||
* Author: Konstantinos Drakontidis
|
||||
* Email: gedra100sh@gmail.com
|
||||
*/
|
||||
import WebSocket from "ws";
|
||||
import { P2P } from "./P2P/P2P";
|
||||
import logger from "./logger/logger";
|
||||
import { schemas, Message, Register, Offer, Answer, Candidate } from "./schemas/schemas";
|
||||
import config from "./config/config";
|
||||
import { validate } from "uuid";
|
||||
|
||||
enum WS_CODE {
|
||||
INVALID_CREDENTIALS = 3000,
|
||||
BACKEND_OFFLINE = 3001,
|
||||
UNKNOWN_TYPE = 3002,
|
||||
PARSE_ERROR = 3003,
|
||||
TIMEOUT = 3004,
|
||||
WS_ERROR = 3005
|
||||
};
|
||||
|
||||
type ParseSuccess = {
|
||||
success: true;
|
||||
data: Message;
|
||||
};
|
||||
|
||||
type ParseFailure = {
|
||||
success: false;
|
||||
error: "Invalid JSON." | "Invalid Schema.";
|
||||
};
|
||||
|
||||
function safeParse(raw: string): ParseSuccess | ParseFailure {
|
||||
let parsed;
|
||||
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (error) {
|
||||
return { success: false, error: "Invalid JSON." };
|
||||
}
|
||||
|
||||
parsed = schemas.safeParse(parsed);
|
||||
|
||||
if (parsed.success) {
|
||||
return parsed;
|
||||
} else {
|
||||
return { success: false, error: "Invalid Schema." };
|
||||
}
|
||||
}
|
||||
|
||||
function timeoutHandler(id: string, p2p: P2P, ws_client: WebSocket) {
|
||||
ws_client.close(WS_CODE.TIMEOUT, "Timeout.");
|
||||
logger.info("Kicked client due to inactivity.");
|
||||
p2p.removeClient(id);
|
||||
}
|
||||
|
||||
function register(p2p: P2P, ws_client: WebSocket, data: Register) {
|
||||
const { username, password } = data;
|
||||
|
||||
if ((username == config.authentication.client.username) && (password == config.authentication.client.password)) {
|
||||
if (p2p.getServer() != null) {
|
||||
const id = p2p.addClient(ws_client);
|
||||
|
||||
p2p.setClientTimeout(id, p2p, ws_client, timeoutHandler);
|
||||
logger.info(`Client registered, timeout set and assigned ID: [${id}].`);
|
||||
logger.info(`Registered clients: ${p2p.getMapSize()}.`);
|
||||
ws_client.send(JSON.stringify({ type: "id", id: id }));
|
||||
} else {
|
||||
logger.error("Client connected but server offline.");
|
||||
ws_client.close(WS_CODE.BACKEND_OFFLINE, "Backend offline.");
|
||||
}
|
||||
} else if ((username == config.authentication.server.username) && (password == config.authentication.server.password)) {
|
||||
logger.info("Server set.");
|
||||
p2p.setServer(ws_client);
|
||||
} else {
|
||||
logger.error("Client entered invalid credentials.");
|
||||
ws_client.close(WS_CODE.INVALID_CREDENTIALS, "Invalid credentials.");
|
||||
}
|
||||
}
|
||||
|
||||
function offer(p2p: P2P, ws_client: WebSocket, data: Offer): void {
|
||||
const { id, offer } = data;
|
||||
const peer = p2p.getPeer(id, ws_client);
|
||||
|
||||
if (peer != null) {
|
||||
p2p.clearClientTimeout(id);
|
||||
logger.info(`Forwarded offer, reset timeout ID: [${id}].`);
|
||||
peer.send(JSON.stringify({ type: "offer", id: id, offer: offer }));
|
||||
} else {
|
||||
logger.error(`Couldn't get peer, failed to forward offer ID: [${id}].`);
|
||||
}
|
||||
}
|
||||
|
||||
function answer(p2p: P2P, ws_client: WebSocket, data: Answer): void {
|
||||
const { id, answer } = data;
|
||||
const peer = p2p.getPeer(id, ws_client);
|
||||
|
||||
if (peer != null) {
|
||||
logger.info(`Forwarded answer ID: [${id}].`);
|
||||
peer.send(JSON.stringify({ type: "answer", id: id, answer: answer }));
|
||||
} else {
|
||||
logger.error(`Couldn't get peer, failed to forward answer ID: [${id}].`);
|
||||
}
|
||||
}
|
||||
|
||||
function candidate(p2p: P2P, ws_client: WebSocket, data: Candidate): void {
|
||||
const { id, candidate } = data;
|
||||
const peer = p2p.getPeer(id, ws_client);
|
||||
|
||||
if (peer != null) {
|
||||
logger.info(`Forwarded candidate ID: [${id}].`);
|
||||
peer.send(JSON.stringify({ type: "candidate", id: id, candidate: candidate }));
|
||||
} else {
|
||||
logger.error(`Couldn't get peer, failed to forward candidate ID: [${id}].`);
|
||||
}
|
||||
}
|
||||
|
||||
(() => {
|
||||
const wss = new WebSocket.WebSocketServer({ port: 3000 });
|
||||
|
||||
const p2p = new P2P();
|
||||
logger.info("Created P2P instance.");
|
||||
|
||||
wss.on("connection", async (ws_client) => {
|
||||
ws_client.on("message", async (ws_msg) => {
|
||||
const parsed = safeParse(ws_msg.toString());
|
||||
|
||||
if (parsed.success) {
|
||||
switch (parsed.data.type) {
|
||||
case "register": register(p2p, ws_client, parsed.data); break;
|
||||
case "offer": offer(p2p, ws_client, parsed.data); break;
|
||||
case "answer": answer(p2p, ws_client, parsed.data); break;
|
||||
case "candidate": candidate(p2p, ws_client, parsed.data); break;
|
||||
default:
|
||||
logger.error(`Unknown message type. ${(parsed.data as any).type}`);
|
||||
ws_client.close(WS_CODE.UNKNOWN_TYPE, "Unknown message type.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.error(parsed.error);
|
||||
ws_client.close(WS_CODE.PARSE_ERROR, parsed.error);
|
||||
}
|
||||
});
|
||||
|
||||
ws_client.on("close", (code, reason) => {
|
||||
if ((code != WS_CODE.INVALID_CREDENTIALS) &&
|
||||
(code != WS_CODE.BACKEND_OFFLINE) &&
|
||||
(code != WS_CODE.TIMEOUT)) {
|
||||
if (ws_client == p2p.getServer()) {
|
||||
logger.info("Server disconnected. Closing connections.");
|
||||
p2p.clear();
|
||||
} else if (validate(reason.toString())) {
|
||||
logger.info(`Client disconnected, removed instance ID: [${reason.toString()}].`);
|
||||
p2p.removeClient(reason.toString());
|
||||
p2p.getServer()?.send(JSON.stringify({ type: "terminate", id: reason.toString() }));
|
||||
} else {
|
||||
const id = p2p.getId(ws_client)
|
||||
if (id != null) {
|
||||
logger.error(`Client lost, found and removed instance ID: [${id}].`);
|
||||
p2p.removeClient(id);
|
||||
p2p.getServer()?.send(JSON.stringify({ type: "terminate", id: id }))
|
||||
} else {
|
||||
logger.error("Client lost, couldn't find any trace of existance... Probably a bug?");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws_client.on("error", (ws_error) => {
|
||||
logger.error(`Client caused an error: ${ws_error.message}.`);
|
||||
ws_client.close(WS_CODE.WS_ERROR, ws_error.message);
|
||||
})
|
||||
});
|
||||
|
||||
wss.on("error", (wss_error) => {
|
||||
logger.error(`WebSocket server error: ${wss_error}.`);
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user