summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Server.ts16
-rw-r--r--src/events/Close.ts6
-rw-r--r--src/events/Connection.ts46
-rw-r--r--src/events/Message.ts36
-rw-r--r--src/index.ts47
-rw-r--r--src/util/Constants.ts1
-rw-r--r--src/util/Send.ts21
-rw-r--r--src/util/WebSocket.ts13
-rw-r--r--src/util/setHeartbeat.ts10
9 files changed, 155 insertions, 41 deletions
diff --git a/src/Server.ts b/src/Server.ts
new file mode 100644

index 00000000..3598c8e1 --- /dev/null +++ b/src/Server.ts
@@ -0,0 +1,16 @@ +import { db } from "discord-server-util"; +import { Server as WebSocketServer } from "ws"; +import { Connection } from "./events/Connection"; + +export class Server { + public ws: WebSocketServer; + constructor() { + this.ws = new WebSocketServer({ port: 8080, maxPayload: 4096 }); + this.ws.on("connection", Connection); + } + + async listen(): Promise<void> { + await db.init(); + console.log("listening"); + } +} diff --git a/src/events/Close.ts b/src/events/Close.ts new file mode 100644
index 00000000..f819b064 --- /dev/null +++ b/src/events/Close.ts
@@ -0,0 +1,6 @@ +import WebSocket from "ws"; +import { Message } from "./Message"; + +export function Close(this: WebSocket, code: number, reason: string) { + this.off("message", Message); +} diff --git a/src/events/Connection.ts b/src/events/Connection.ts new file mode 100644
index 00000000..815d84cf --- /dev/null +++ b/src/events/Connection.ts
@@ -0,0 +1,46 @@ +import WebSocket, { Server } from "../util/WebSocket"; +import { IncomingMessage } from "http"; +import { Close } from "./Close"; +import { Message } from "./Message"; +import { setHeartbeat } from "../util/setHeartbeat"; +import { Send } from "../util/Send"; +import { CLOSECODES, OPCODES } from "../util/Constants"; + +// TODO: check rate limit +// TODO: specify rate limit in config + +export function Connection(this: Server, socket: WebSocket, request: IncomingMessage) { + try { + socket.on("close", Close); + socket.on("message", Message); + + const { searchParams } = new URL(`http://localhost${request.url}`); + // @ts-ignore + socket.encoding = searchParams.get("encoding") || "json"; + if (!["json", "etf"].includes(socket.encoding)) return socket.close(CLOSECODES.Decode_error); + + // @ts-ignore + socket.version = Number(searchParams.get("version")) || 8; + if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); + + // @ts-ignore + socket.compression = searchParams.get("compress") || ""; + // TODO: compression + + setHeartbeat(socket); + + Send(socket, { + op: OPCODES.Hello, + d: { + heartbeat_interval: 1000 * 30, + }, + }); + + socket.readyTimeout = setTimeout(() => { + return socket.close(CLOSECODES.Session_timed_out); + }, 1000 * 30); + } catch (error) { + console.error(error); + return socket.close(CLOSECODES.Unknown_error); + } +} diff --git a/src/events/Message.ts b/src/events/Message.ts new file mode 100644
index 00000000..bc497e94 --- /dev/null +++ b/src/events/Message.ts
@@ -0,0 +1,36 @@ +import WebSocket, { Data } from "../util/WebSocket"; +import erlpack from "erlpack"; +import OPCodeHandlers from "../opcodes"; +import { Payload, CLOSECODES } from "../util/Constants"; +import { instanceOf } from "lambert-server"; + +const PayloadSchema = { + op: Number, + $d: Object, + $s: Number, + $t: String, +}; + +export async function Message(this: WebSocket, buffer: Data) { + // TODO: compression + var data: Payload; + + try { + if (this.encoding === "etf" && buffer instanceof Buffer) data = erlpack.unpack(buffer); + else if (this.encoding === "json" && typeof buffer === "string") data = JSON.parse(buffer); + if (!instanceOf(PayloadSchema, data)) throw "invalid data"; + } catch (error) { + return this.close(CLOSECODES.Decode_error); + } + + // @ts-ignore + const OPCodeHandler = OPCodeHandlers[data.op]; + if (!OPCodeHandler) return this.close(CLOSECODES.Unknown_opcode); + + try { + return await OPCodeHandler.call(this, data); + } catch (error) { + console.error(error); + return this.close(CLOSECODES.Unknown_error); + } +} diff --git a/src/index.ts b/src/index.ts
index 45374721..b267bbfb 100644 --- a/src/index.ts +++ b/src/index.ts
@@ -1,43 +1,8 @@ -import WebSocket from "ws"; -import DB from "./extras/Database"; -import { message_dev } from "./assets/datastru"; -import { v4 } from "uuid"; +process.on("uncaughtException", console.error); +process.on("unhandledRejection", console.error); +setTimeout(() => {}, 100000000); -class Server { - db: any; - constructor() { - this.db = DB; - } +import { Server } from "./Server"; - async listen(): Promise<void> { - await this.db.init(); - const wss = new WebSocket.Server({ port: 8080 }); - - wss.on("connection", (ws) => { - ws.on("message", async (msg: any) => { - const message: message_dev = msg; - - if (message.req_type) { - switch (message.req_type) { - case "new_auth": - const token = v4(); - await this.db.data.auth.push({ token }); - return ws.send({ new_token: token }); - case "check_auth": - if (!message.token) { - return ws.send({ error: "token not providen" }); - } - return this.db.data.auth({ token: message.token }).get(); - } - } else { - ws.send({ error: "req_type not providen" }); - } - }); - - ws.send("connected"); - }); - } -} - -const s = new Server(); -s.listen(); +const server = new Server(); +server.listen(); diff --git a/src/util/Constants.ts b/src/util/Constants.ts
index cdc0d07d..e8e91d69 100644 --- a/src/util/Constants.ts +++ b/src/util/Constants.ts
@@ -4,6 +4,7 @@ export enum OPCODES { Identify, Presence_Update, Voice_State_Update, + Dummy_Value, // ? What is opcode 5? Resume, Reconnect, Request_Guild_Members, diff --git a/src/util/Send.ts b/src/util/Send.ts new file mode 100644
index 00000000..d38865b3 --- /dev/null +++ b/src/util/Send.ts
@@ -0,0 +1,21 @@ +import erlpack from "erlpack"; +import { promisify } from "util"; +import { Payload } from "../util/Constants"; + +import WebSocket from "./WebSocket"; + +export async function Send(socket: WebSocket, data: Payload) { + let buffer: Buffer | string; + if (socket.encoding === "etf") buffer = erlpack.pack(data); + // TODO: encode circular object + else if (socket.encoding === "json") buffer = JSON.stringify(data); + + // TODO: compression + + return new Promise((res, rej) => { + socket.send(buffer, (err) => { + if (err) return rej(err); + return res(null); + }); + }); +} diff --git a/src/util/WebSocket.ts b/src/util/WebSocket.ts new file mode 100644
index 00000000..41ce8851 --- /dev/null +++ b/src/util/WebSocket.ts
@@ -0,0 +1,13 @@ +import WS, { Server, Data } from "ws"; + +interface WebSocket extends WS { + version: number; + userid: bigint; + encoding: "etf" | "json"; + compress?: "zlib-stream"; + heartbeatTimeout: NodeJS.Timeout; + readyTimeout: NodeJS.Timeout; +} + +export default WebSocket; +export { Server, Data }; diff --git a/src/util/setHeartbeat.ts b/src/util/setHeartbeat.ts new file mode 100644
index 00000000..1fe657a8 --- /dev/null +++ b/src/util/setHeartbeat.ts
@@ -0,0 +1,10 @@ +import WebSocket from "./WebSocket"; + +// TODO: make heartbeat timeout configurable +export function setHeartbeat(socket: WebSocket) { + if (socket.heartbeatTimeout) clearTimeout(socket.heartbeatTimeout); + + socket.heartbeatTimeout = setTimeout(() => { + return socket.close(4009); + }, 1000 * 30); +}