summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore175
-rw-r--r--README.md26
-rw-r--r--bun.lockbbin0 -> 3873 bytes
-rw-r--r--client.html68
-rw-r--r--index.js87
-rw-r--r--jsconfig.json27
-rw-r--r--lib.js15
-rw-r--r--package.json21
8 files changed, 419 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9b1ee42
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,175 @@
+# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
+
+# Logs
+
+logs
+_.log
+npm-debug.log_
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Caches
+
+.cache
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+
+report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
+
+# Runtime data
+
+pids
+_.pid
+_.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+
+lib-cov
+
+# Coverage directory used by tools like istanbul
+
+coverage
+*.lcov
+
+# nyc test coverage
+
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+
+bower_components
+
+# node-waf configuration
+
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+
+build/Release
+
+# Dependency directories
+
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+
+web_modules/
+
+# TypeScript cache
+
+*.tsbuildinfo
+
+# Optional npm cache directory
+
+.npm
+
+# Optional eslint cache
+
+.eslintcache
+
+# Optional stylelint cache
+
+.stylelintcache
+
+# Microbundle cache
+
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+
+.node_repl_history
+
+# Output of 'npm pack'
+
+*.tgz
+
+# Yarn Integrity file
+
+.yarn-integrity
+
+# dotenv environment variable files
+
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+
+.parcel-cache
+
+# Next.js build output
+
+.next
+out
+
+# Nuxt.js build / generate output
+
+.nuxt
+dist
+
+# Gatsby files
+
+# Comment in the public line in if your project uses Gatsby and not Next.js
+
+# https://nextjs.org/blog/next-9-1#public-directory-support
+
+# public
+
+# vuepress build output
+
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+
+.temp
+
+# Docusaurus cache and generated files
+
+.docusaurus
+
+# Serverless directories
+
+.serverless/
+
+# FuseBox cache
+
+.fusebox/
+
+# DynamoDB Local files
+
+.dynamodb/
+
+# TernJS port file
+
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+
+.vscode-test
+
+# yarn v2
+
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
+
+# IntelliJ based IDEs
+.idea
+
+# Finder (MacOS) folder config
+.DS_Store
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..00d38a1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# wsChat
+
+A simple IRC-like chat made using nodejs and webSockets
+
+## Running the server
+
+To install dependencies:
+
+```bash
+npm install
+```
+
+To run:
+
+```bash
+node index.js
+```
+
+## TODO
+
+- Make commands be in an object instead of a switch statement
+- /help command
+- /about command
+- Config (JSON)
+- MOTD
+- Custom commands
diff --git a/bun.lockb b/bun.lockb
new file mode 100644
index 0000000..3cef471
--- /dev/null
+++ b/bun.lockb
Binary files differdiff --git a/client.html b/client.html
new file mode 100644
index 0000000..87d95c3
--- /dev/null
+++ b/client.html
@@ -0,0 +1,68 @@
+<div class="container">
+    <div class="logs">
+    </div>
+    <div class="msg">
+        <form id="msg">
+            <input type="text" id="msg-content" autocomplete="off">
+            <input type="submit">
+        </form>
+    </div>
+</div>
+<script>
+    const logs = document.querySelector(".logs");
+    const msgForm = document.querySelector("#msg");
+    const ws = new WebSocket("ws://127.0.0.1:9933"); // CHANGE ME
+    ws.onopen = openEv => {
+        if(logs.innerText.length != 0) logs.innerText += "\n";
+        logs.innerText += `Connected to wsChat server at ${ws.url}`;
+    }
+    ws.onmessage = msg => {;
+        console.log(msg);
+        if(logs.innerText.length != 0) logs.innerText += "\n";
+        logs.innerText += msg.data;
+    };
+    ws.onclose = cls => {
+        if(logs.innerText.length != 0) logs.innerText += "\n";
+        logs.innerText += `Connection closed with code ${cls.code}, reason: ${cls.reason}`;
+    }
+    msgForm.addEventListener('submit', ev => {
+        ev.preventDefault();
+        const message = document.querySelector("#msg-content");
+        ws.send(message.value);
+        message.value = "";
+    });
+</script>
+<style>
+    * {
+        box-sizing: border-box;
+    }
+    body {
+        margin: 0;
+        width: 100%;
+        min-height: 100vh;
+        position: absolute;
+    }
+    .container {
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        height: calc(100vh - 3em);
+        border: 2px red solid;
+        margin: 1.5em;
+    }
+    .logs {
+        flex-grow: 1;
+        padding: 1em;
+    }
+    #msg {
+        border-top: 2px red solid;
+        padding: 1em;
+        display: flex;
+        margin: 0;
+        height: 2em;
+        box-sizing: content-box;
+    }
+    #msg-content {
+        flex-grow: 1;
+    }
+</style>
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..2043219
--- /dev/null
+++ b/index.js
@@ -0,0 +1,87 @@
+import { WebSocketServer } from "ws";
+import cuid from 'cuid';
+import { getRandomInt } from "./lib.js"
+
+const channels = ["home", "off-topic"]
+
+const ws = new WebSocketServer({
+    port: 9933,
+});
+
+const users = {}
+
+function sendInChannel(msg, channel) {
+    for (const userID in users) {
+        const user = users[userID];
+        if(user.channel == channel) user.socket.send(msg)
+    }
+}
+
+ws.on('connection', (socket, request) => {
+    // console.log(request, socket);
+    let userID = cuid()
+    socket.send("Welcome to WlodekM's wsChat server!")
+    let anonID = getRandomInt(0, 99999)
+    users[userID] = {
+        username: `Anonymous${"0".repeat(5 - anonID.length) + anonID.toString()}`,
+        socket: socket,
+        joinReq: request,
+        t: {
+            js: Number(new Date()),
+            unix: Math.floor(new Date().getTime() / 1000),
+            str: String(new Date())
+        },
+        channel: 'home'
+    }
+    sendInChannel(`${users[userID].username} joined #${users[userID].channel}!`, users[userID].channel)
+    socket.on('close', function(code, reason) {
+        sendInChannel(`${users[userID].username} left.`, users[userID].channel)
+        delete users[userID]
+    })
+    socket.on('message', function (rawData) {
+        if(rawData.toString().startsWith("/")) {
+            let args = String(rawData).replace("/", "").split(" ");
+            let command = args.shift();
+            console.log(`${users[userID].username} used /${command}`)
+            switch (command) {
+                case 'join':
+                    if(args.length < 1) return socket.send("Error: You need to specify a channel (example: /join #home).");
+                    if(!args[0].startsWith("#")) return socket.send("Error: Channel not found, run /channels to see a list of channels.");
+                    if(!channels.includes(args[0].replace("#", ""))) return socket.send("Error: Channel not found, run /channels to see a list of channels.");
+                    sendInChannel(`${users[userID].username} left #${users[userID].channel}.`, users[userID].channel)
+                    users[userID].channel = args[0].replace("#", "");
+                    sendInChannel(`${users[userID].username} joined #${users[userID].channel}!`, users[userID].channel)
+                    break;
+                case 'channels':
+                    socket.send(`Channels:\n${channels.map(ch => ` * #${ch}`).join("\n")}`)
+                    break;
+                case 'name':
+                case 'nickname':
+                case 'nick':
+                    if(args.length < 1) return socket.send("Error: You need to specify a nick (example: /nick WlodekM).");
+                    if(args[0].length < 3 ) return socket.send("Error: Nick too long.");
+                    if(args[0].length > 20) return socket.send("Error: Nick too short.");
+                    if(Object.values(users).find(usr => usr.username == args[0])) return socket.send("Error: Nick already used.");
+                    sendInChannel(`${users[userID].username} changed their nick to ${args[0]}!`, users[userID].channel)
+                    users[userID].username = args[0]
+                    break;
+                case 'users':
+                    socket.send(`Users${args[0] != "global" ? ` in ${users[userID].channel}` : ""}:\n${Object.values(users).filter(usr => (usr.channel == users[userID].channel) || args[0] == "global").map(ch => ` * ${ch}`).join("\n")}`)
+                    break;
+            
+                default:
+                    socket.send(`Error: Command "${command}" not found!`);
+                    break;
+            }
+            return
+        }
+        if(rawData.length < 1) return socket.send("Error: message too short!")
+        if(rawData.length >= 2000) return socket.send("Error: message too long!")
+        sendInChannel(`<${users[userID].username}> ${rawData}`, users[userID].channel)
+        console.log(`(#${users[userID].channel}) <${users[userID].username}> ${rawData}`)
+    })
+})
+
+ws.on('listening', () => {
+    console.info("[INFO] Server started")
+})
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,27 @@
+{
+  "compilerOptions": {
+    // Enable latest features
+    "lib": ["ESNext", "DOM"],
+    "target": "ESNext",
+    "module": "ESNext",
+    "moduleDetection": "force",
+    "jsx": "react-jsx",
+    "allowJs": true,
+
+    // Bundler mode
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "verbatimModuleSyntax": true,
+    "noEmit": true,
+
+    // Best practices
+    "strict": true,
+    "skipLibCheck": true,
+    "noFallthroughCasesInSwitch": true,
+
+    // Some stricter flags (disabled by default)
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noPropertyAccessFromIndexSignature": false
+  }
+}
diff --git a/lib.js b/lib.js
new file mode 100644
index 0000000..64b0d6c
--- /dev/null
+++ b/lib.js
@@ -0,0 +1,15 @@
+/**
+ * Returns a random integer between min (inclusive) and max (inclusive).
+ * The value is no lower than min (or the next integer greater than min
+ * if min isn't an integer) and no greater than max (or the next integer
+ * lower than max if max isn't an integer).
+ * Using Math.round() will give you a non-uniform distribution!
+ * @param {Number} min Min number
+ * @param {Number} max Max number
+ * @returns {Number}
+ */
+export function getRandomInt(min, max) {
+    min = Math.ceil(min);
+    max = Math.floor(max);
+    return Math.floor(Math.random() * (max - min + 1)) + min;
+}
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..28078ba
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+  "name": "wschat",
+  "description": "A simple IRC-like chat made using nodejs and webSockets",
+  "author": {
+    "name": "WlodekM",
+    "email": "[email protected]",
+    "url": "https://wlodekm.nekoweb.org"
+  },
+  "module": "index.js",
+  "type": "module",
+  "devDependencies": {
+    "@types/bun": "latest"
+  },
+  "peerDependencies": {
+    "typescript": "^5.0.0"
+  },
+  "dependencies": {
+    "cuid": "^3.0.0",
+    "ws": "^8.17.1"
+  }
+}
\ No newline at end of file