diff --git a/src/gateway/util/Constants.ts b/src/gateway/util/Constants.ts
new file mode 100644
index 00000000..692f9028
--- /dev/null
+++ b/src/gateway/util/Constants.ts
@@ -0,0 +1,50 @@
+export enum OPCODES {
+ Dispatch = 0,
+ Heartbeat = 1,
+ Identify = 2,
+ Presence_Update = 3,
+ Voice_State_Update = 4,
+ Voice_Server_Ping = 5, // ? What is opcode 5?
+ Resume = 6,
+ Reconnect = 7,
+ Request_Guild_Members = 8,
+ Invalid_Session = 9,
+ Hello = 10,
+ Heartbeat_ACK = 11,
+ Guild_Sync = 12,
+ DM_Update = 13,
+ Lazy_Request = 14,
+ Lobby_Connect = 15,
+ Lobby_Disconnect = 16,
+ Lobby_Voice_States_Update = 17,
+ Stream_Create = 18,
+ Stream_Delete = 19,
+ Stream_Watch = 20,
+ Stream_Ping = 21,
+ Stream_Set_Paused = 22,
+ Request_Application_Commands = 24,
+}
+export enum CLOSECODES {
+ Unknown_error = 4000,
+ Unknown_opcode,
+ Decode_error,
+ Not_authenticated,
+ Authentication_failed,
+ Already_authenticated,
+ Invalid_session,
+ Invalid_seq,
+ Rate_limited,
+ Session_timed_out,
+ Invalid_shard,
+ Sharding_required,
+ Invalid_API_version,
+ Invalid_intent,
+ Disallowed_intent,
+}
+
+export interface Payload {
+ op: OPCODES;
+ d?: any;
+ s?: number;
+ t?: string;
+}
diff --git a/src/gateway/util/Heartbeat.ts b/src/gateway/util/Heartbeat.ts
new file mode 100644
index 00000000..f6871cfe
--- /dev/null
+++ b/src/gateway/util/Heartbeat.ts
@@ -0,0 +1,11 @@
+import { CLOSECODES } from "./Constants";
+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(CLOSECODES.Session_timed_out);
+ }, 1000 * 45);
+}
diff --git a/src/gateway/util/Send.ts b/src/gateway/util/Send.ts
new file mode 100644
index 00000000..2a28d8e0
--- /dev/null
+++ b/src/gateway/util/Send.ts
@@ -0,0 +1,33 @@
+let erlpack: any;
+try {
+ erlpack = require("@yukikaze-bot/erlpack");
+} catch (error) {
+ console.log("Missing @yukikaze-bot/erlpack, electron-based desktop clients designed for discord.com will not be able to connect!");
+}
+import { Payload, WebSocket } from "@fosscord/gateway";
+
+export async function Send(socket: WebSocket, data: Payload) {
+ if(process.env.WS_VERBOSE)
+ console.log(`[Websocket] Outgoing message: ${JSON.stringify(data)}`);
+ 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);
+ else return;
+ // TODO: compression
+ if (socket.deflate) {
+ socket.deflate.write(buffer);
+ socket.deflate.flush();
+ return;
+ }
+
+ return new Promise((res, rej) => {
+ if (socket.readyState !== 1) {
+ return rej("socket not open");
+ }
+ socket.send(buffer, (err: any) => {
+ if (err) return rej(err);
+ return res(null);
+ });
+ });
+}
diff --git a/src/gateway/util/SessionUtils.ts b/src/gateway/util/SessionUtils.ts
new file mode 100644
index 00000000..bf854042
--- /dev/null
+++ b/src/gateway/util/SessionUtils.ts
@@ -0,0 +1,13 @@
+export function genSessionId() {
+ return genRanHex(32);
+}
+
+export function genVoiceToken() {
+ return genRanHex(16);
+}
+
+function genRanHex(size: number) {
+ return [...Array(size)]
+ .map(() => Math.floor(Math.random() * 16).toString(16))
+ .join("");
+}
diff --git a/src/gateway/util/WebSocket.ts b/src/gateway/util/WebSocket.ts
new file mode 100644
index 00000000..9496da85
--- /dev/null
+++ b/src/gateway/util/WebSocket.ts
@@ -0,0 +1,22 @@
+import { Intents, Permissions } from "@fosscord/util";
+import WS from "ws";
+import { Deflate } from "zlib";
+
+export interface WebSocket extends WS {
+ version: number;
+ user_id: string;
+ session_id: string;
+ encoding: "etf" | "json";
+ compress?: "zlib-stream";
+ shard_count?: number;
+ shard_id?: number;
+ deflate?: Deflate;
+ heartbeatTimeout: NodeJS.Timeout;
+ readyTimeout: NodeJS.Timeout;
+ intents: Intents;
+ sequence: number;
+ permissions: Record<string, Permissions>;
+ events: Record<string, Function>;
+ member_events: Record<string, Function>;
+ listen_options: any;
+}
diff --git a/src/gateway/util/index.ts b/src/gateway/util/index.ts
new file mode 100644
index 00000000..0be5ecee
--- /dev/null
+++ b/src/gateway/util/index.ts
@@ -0,0 +1,5 @@
+export * from "./Constants";
+export * from "./Send";
+export * from "./SessionUtils";
+export * from "./Heartbeat";
+export * from "./WebSocket";
|