diff --git a/.eslintrc.js b/.eslintrc.js
index 677ccea..6f8e58a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,5 +1,6 @@
module.exports = {
"env": {
+ "browser": true,
"commonjs": true,
"es2021": true,
"node": true
diff --git a/brainlet/api/v2/gateway/index.js b/brainlet/api/v2/gateway/index.js
index 1db7270..878ae4a 100644
--- a/brainlet/api/v2/gateway/index.js
+++ b/brainlet/api/v2/gateway/index.js
@@ -17,7 +17,6 @@ class GatewayServer extends EventEmitter {
this.pingInterval = setInterval(() => {
this.wss.clients.forEach((client) => {
if (!client.alive) {
- console.log("gateway: terminating client due to ping timeout");
client.terminate();
}
client.alive = false;
@@ -78,6 +77,7 @@ class GatewayServer extends EventEmitter {
const messageContent = message.data.content.trim();
if (messageContent.length > 2000) return;
+ if (messageContent === "") return;
if (message.data.channel._id.length !== 24) throw new Error("msg: payload has invalid id"); // MONGODB ONLY!!
// Check if the user is in that channel before broadcasting the message
@@ -113,7 +113,7 @@ GatewayServer.prototype.broadcast = function(channelId, data) {
};
GatewayServer.prototype.clientReady = function(ws) {
- return ws.readyState === WebSocket.OPEN && ws.session && ws.session.authenticated;
+ return ws.readyState === websockets.OPEN && ws.session && ws.session.authenticated;
};
GatewayServer.prototype.authMessage = function(ws) {
diff --git a/brainlet/app/gatewaytest/app.js b/brainlet/app/gatewaytest/app.js
new file mode 100644
index 0000000..ef2d8f5
--- /dev/null
+++ b/brainlet/app/gatewaytest/app.js
@@ -0,0 +1,185 @@
+const opcodes = {
+ 0: { name: "HELLO", data: "JSON" },
+ 1: { name: "YOO", data: "JSON" },
+ 2: { name: "YOO_ACK", data: "JSON" },
+ 3: { name: "ACTION_CREATE_MESSAGE", data: "JSON" },
+ 4: { name: "EVENT_CREATE_MESSAGE", data: "JSON" }
+};
+
+const opcodeSeparator = "@";
+
+const parseMessage = (message) => {
+ if (typeof message !== "string") throw new Error("msg: message not a string");
+ const stringParts = message.split(opcodeSeparator);
+ if (stringParts < 2) throw new Error("msg: message does not split into more than 2 parts");
+ const components = [ stringParts.shift(), stringParts.join(opcodeSeparator) ];
+ const op = parseInt(components[0]);
+ if (isNaN(op)) throw new Error(`msg: message does not contain valid opcode: ${op}`);
+
+ const opcodeData = opcodes[op];
+ let data = components[1];
+ if (!opcodeData) throw new Error(`msg: message contains unknown opcode ${op}`);
+ if (opcodeData.data === "JSON") {
+ data = JSON.parse(data);
+ } else if (opcodeData.data === "string") {
+ data = data.toString(); // NOTE: This isnt needed lol
+ } else {
+ throw new Error(`msg: invalid data type on opcode ${op}`);
+ }
+
+ return {
+ opcode: op,
+ data: data,
+ dataType: opcodeData.data,
+ opcodeType: opcodeData.name || null
+ };
+};
+
+const getOpcodeByName = (name) => {
+ for (const [key, value] of Object.entries(opcodes)) if (value.name === name) return key;
+};
+
+
+class GatewayConnection {
+ constructor(token) {
+ this.ws = new WebSocket("ws://localhost:3005/gateway?v=2");
+
+ this.handshakeCompleted = false;
+ this.sessionInformation = null;
+
+ this.ws.onopen = () => console.log("gateway: open");
+ this.ws.onclose = (e) => {
+ this.handshakeCompleted = false;
+ console.log(`gateway: close: ${e.code}:${e.reason}`);
+ this.fire("onclose", e);
+ };
+ this.ws.onmessage = (message) => {
+ try {
+ const packet = parseMessage(message.data);
+ if (!packet) return console.error("gateway: invalid packet from server");
+
+ switch (packet.opcodeType) {
+ case "HELLO": {
+ // Got HELLO from server, send YOO as soon as possible
+ console.log("gateway: got HELLO", packet.data);
+ console.log("gateway: sending YOO");
+ this.ws.send(this.packet("YOO", { token }));
+ break;
+ }
+ case "YOO_ACK": {
+ // Server accepted connection
+ console.log("gateway: got YOO_ACK", packet.data);
+ this.handshakeCompleted = true;
+ this.sessionInformation = packet.data;
+ console.log("gateway: handshake complete");
+ this.fire("onopen", packet.data);
+ break;
+ }
+ case "EVENT_CREATE_MESSAGE": {
+ // New message
+ // console.log("gateway: got new message", packet.data);
+ this.fire("onmessage", packet.data);
+ break;
+ }
+ default: {
+ console.log("gateway: got unknown packet", message.data);
+ break;
+ }
+ }
+ } catch(e) {
+ return console.error("gateway:", e);
+ }
+ };
+ }
+}
+
+GatewayConnection.prototype.sendMessage = function(content, channelId) {
+ if (!this.sessionInformation) throw new Error("gateway: tried to send message before handshake completion");
+
+ this.ws.send(this.packet("ACTION_CREATE_MESSAGE", {
+ content,
+ channel: {
+ _id: channelId
+ }
+ }));
+};
+
+
+GatewayConnection.prototype.packet = function(op, data) {
+ if (typeof op === "string") op = getOpcodeByName(op);
+ return `${op}${opcodeSeparator}${JSON.stringify(data)}`;
+};
+
+GatewayConnection.prototype.fire = function(eventName, ...args) {
+ if (this[eventName]) return this[eventName](...args);
+};
+
+
+class AppState {
+ constructor(token) {
+ this.connection = new GatewayConnection(token);
+ this.tc = new Tricarbon();
+ this.tcEvents = this.tc.useEvents();
+
+ this.messageStore = {};
+ this.selectedChannelId = null;
+
+ this.Sidebar = (channels) => (ev) => `
+ ${Object.keys(channels).map(k => `
+
+ `).join("")}
+ `;
+
+ this.ChannelMessages = (messages) => () => `
+ ${Object.keys(messages).map(k => `
+
+ ${messages[k].author.username}
+ ${messages[k].content}
+
+ `).join("")}
+ `;
+
+ this.ChannelTopBar = (channel) => () => `
+ ${channel.title}
+ `;
+
+ this.connection.onopen = (sessionInfo) => {
+ this.renderSidebar(sessionInfo.channels);
+ this.tc.A("#message-input").addEventListener("keydown", (e) => {
+ if (e.code === "Enter") {
+ if (!this.selectedChannelId) return;
+ const messageContent = this.tc.A("#message-input").value;
+ if (!messageContent) return;
+ this.connection.sendMessage(messageContent, this.selectedChannelId);
+ this.tc.A("#message-input").value = "";
+ }
+ });
+ };
+ this.connection.onmessage = (message) => {
+ this.appendMessage(message);
+ };
+ }
+}
+
+AppState.prototype.appendMessage = function(message) {
+ if (!this.messageStore[message.channel._id]) this.messageStore[message.channel._id] = [];
+ this.messageStore[message.channel._id].push({ content: message.content, author: message.author });
+ if (this.selectedChannelId === message.channel._id) {
+ this.tc.push(this.ChannelMessages(this.messageStore[message.channel._id] || []), "#messages", false, this.tcEvents("#messages"));
+ }
+};
+
+AppState.prototype.navigateToChannel = function(channel) {
+ console.log("app: navigating to channel", channel);
+ if (this.selectedChannelId !== channel._id) {
+ this.selectedChannelId = channel._id;
+ this.tc.push(this.ChannelTopBar(channel), "#top-bar", false);
+ this.tc.push(this.ChannelMessages(this.messageStore[channel._id] || []), "#messages", false, this.tcEvents("#messages"));
+ }
+};
+
+AppState.prototype.renderSidebar = function(channels) {
+ this.tc.push(this.Sidebar(channels), "#channels", false, this.tcEvents("#channels"));
+};
+
+const app = new AppState(localStorage.getItem("token"));
\ No newline at end of file
diff --git a/brainlet/app/gatewaytest/index.html b/brainlet/app/gatewaytest/index.html
index 8771061..4238211 100644
--- a/brainlet/app/gatewaytest/index.html
+++ b/brainlet/app/gatewaytest/index.html
@@ -1,106 +1,45 @@
-
+
+
+
-
+
+
+
+
+ █████
+
+
+
+ █████
+ ██████████
+
+
+ ████
+ ██████
+
+
+ █████
+ ████████
+
+
+ ███
+ █████████████
+
+
+
+
+
diff --git a/brainlet/app/gatewaytest/style.css b/brainlet/app/gatewaytest/style.css
new file mode 100644
index 0000000..98407de
--- /dev/null
+++ b/brainlet/app/gatewaytest/style.css
@@ -0,0 +1,119 @@
+/* This CSS is very hacky and extremely inefficient. No human being, alive or dead, shall go through the pain of reading or maintaining this code */
+
+:root {
+ --bg: #000000;
+ --fg: #ffffff;
+}
+
+body {
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ margin: 0;
+ height: 100%;
+ font-family: Verdana, Geneva, Tahoma, sans-serif;
+
+ background-color: var(--bg);
+ color: var(--fg);
+}
+
+main {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ flex-grow: 0;
+ width: 800px;
+ height: 600px;
+ margin: 10px;
+
+ background-color: var(--bg);
+ color: var(--fg);
+ border: 1px solid var(--fg);
+}
+
+.channel-view {
+ display: flex;
+ flex-direction: column;
+ flex: 10;
+}
+
+.sidebar {
+ min-width: 150px;
+ max-width: 150px;
+ max-height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+
+ border-right: 1px solid var(--fg);
+}
+
+.sidebar-button {
+ max-width: inherit;
+ border: 0;
+ padding: 8px;
+ margin-right: 0;
+ margin-left: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ text-align: left;
+ min-height: 34px;
+ max-height: 34px;
+
+ border-bottom: 1px solid var(--fg);
+ background-color: var(--bg);
+ color: var(--fg);
+}
+
+.sidebar-button:hover, .sidebar-button.selected {
+ font-weight: bold;
+}
+
+.top-bar {
+ display: flex;
+ min-height: 33px;
+ max-height: 33px;
+ flex: 1;
+ align-items: center;
+ justify-content: center;
+
+ background-color: var(--bg);
+ border-bottom: 1px solid var(--fg);
+}
+
+.bottom-bar {
+ min-height: 33px;
+ max-height: 33px;
+ flex: 1;
+ flex-wrap: wrap;
+
+ background-color: var(--bg);
+ border-top: 1px solid var(--fg);
+}
+
+.channel-message-container {
+ padding: 15px;
+ flex: 1;
+ flex-wrap: wrap;
+ overflow-y: auto;
+
+ background-color: var(--bg);
+}
+
+input {
+ background-color: var(--bg);
+ color: var(--fg);
+ border: none;
+}
+
+@media only screen and (max-width: 800px) {
+ main {
+ width: 95%;
+ height: 95%;
+ }
+ .sidebar {
+ min-width: 100px;
+ max-width: 100px;
+ }
+}
\ No newline at end of file