✨ 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 Constants = require("./dist/util/Constants");
|
||||
|
||||
const ws = new WebSocket("ws://127.0.0.1:8080");
|
||||
|
||||
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: "" }));
|
||||
// op: 0,
|
||||
// d: {},
|
||||
@ -11,6 +13,33 @@ ws.on("open", () => {
|
||||
// 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);
|
||||
|
||||
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";
|
||||
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();
|
||||
|
||||
@ -4,6 +4,7 @@ export enum OPCODES {
|
||||
Identify,
|
||||
Presence_Update,
|
||||
Voice_State_Update,
|
||||
Dummy_Value, // ? What is opcode 5?
|
||||
Resume,
|
||||
Reconnect,
|
||||
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