✨ Base Server
This commit is contained in:
parent
b41ef81ccb
commit
471d1d6b28
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"sourceMaps": true,
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Server",
|
||||||
|
"program": "${workspaceFolder}/dist/index.js",
|
||||||
|
"preLaunchTask": "tsc: build - tsconfig.json",
|
||||||
|
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
33
client.js
33
client.js
@ -1,9 +1,11 @@
|
|||||||
|
require("missing-native-js-functions");
|
||||||
const WebSocket = require("ws");
|
const WebSocket = require("ws");
|
||||||
|
const Constants = require("./dist/util/Constants");
|
||||||
|
|
||||||
const ws = new WebSocket("ws://127.0.0.1:8080");
|
const ws = new WebSocket("ws://127.0.0.1:8080");
|
||||||
|
|
||||||
ws.on("open", () => {
|
ws.on("open", () => {
|
||||||
ws.send(JSON.stringify({ req_type: "new_auth" }));
|
// ws.send(JSON.stringify({ req_type: "new_auth" }));
|
||||||
// ws.send(JSON.stringify({ req_type: "check_auth", token: "" }));
|
// ws.send(JSON.stringify({ req_type: "check_auth", token: "" }));
|
||||||
// op: 0,
|
// op: 0,
|
||||||
// d: {},
|
// d: {},
|
||||||
@ -11,6 +13,33 @@ ws.on("open", () => {
|
|||||||
// t: "GATEWAY_EVENT_NAME",
|
// t: "GATEWAY_EVENT_NAME",
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on("message", (data) => {
|
function send(data) {
|
||||||
|
ws.send(JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.on("message", (buffer) => {
|
||||||
|
let data = JSON.parse(buffer.toString());
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
|
switch (data.op) {
|
||||||
|
case 10:
|
||||||
|
setIntervalNow(() => {
|
||||||
|
send({ op: 1 });
|
||||||
|
}, data.d.heartbeat_interval);
|
||||||
|
|
||||||
|
send({
|
||||||
|
op: 2,
|
||||||
|
d: {
|
||||||
|
token: "",
|
||||||
|
intents: 0n,
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on("close", (code, reason) => {
|
||||||
|
console.log(code, reason, Constants.CLOSECODES[code]);
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
16
src/Server.ts
Normal file
16
src/Server.ts
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/events/Close.ts
Normal file
6
src/events/Close.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
46
src/events/Connection.ts
Normal file
46
src/events/Connection.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
src/events/Message.ts
Normal file
36
src/events/Message.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/index.ts
47
src/index.ts
@ -1,43 +1,8 @@
|
|||||||
import WebSocket from "ws";
|
process.on("uncaughtException", console.error);
|
||||||
import DB from "./extras/Database";
|
process.on("unhandledRejection", console.error);
|
||||||
import { message_dev } from "./assets/datastru";
|
setTimeout(() => {}, 100000000);
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
class Server {
|
import { Server } from "./Server";
|
||||||
db: any;
|
|
||||||
constructor() {
|
|
||||||
this.db = DB;
|
|
||||||
}
|
|
||||||
|
|
||||||
async listen(): Promise<void> {
|
const server = new Server();
|
||||||
await this.db.init();
|
server.listen();
|
||||||
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();
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export enum OPCODES {
|
|||||||
Identify,
|
Identify,
|
||||||
Presence_Update,
|
Presence_Update,
|
||||||
Voice_State_Update,
|
Voice_State_Update,
|
||||||
|
Dummy_Value, // ? What is opcode 5?
|
||||||
Resume,
|
Resume,
|
||||||
Reconnect,
|
Reconnect,
|
||||||
Request_Guild_Members,
|
Request_Guild_Members,
|
||||||
|
|||||||
21
src/util/Send.ts
Normal file
21
src/util/Send.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
13
src/util/WebSocket.ts
Normal file
13
src/util/WebSocket.ts
Normal file
@ -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 };
|
||||||
10
src/util/setHeartbeat.ts
Normal file
10
src/util/setHeartbeat.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user