broadcast-server-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Harshadd diwate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # 🎙️ Broadcast Server
2
+
3
+ A real-time WebSocket chat server with a beautiful web client interface.
4
+
5
+ ## 🚀 Features
6
+
7
+ - Real-time messaging via WebSocket
8
+ - Random anime protagonist usernames
9
+ - Color-coded users
10
+ - Beautiful, responsive web interface
11
+ - Works on any device with a browser
12
+ - No installation required for clients
13
+
14
+ ## 📦 Installation
15
+
16
+ ### For Users (Recommended)
17
+
18
+ Install globally via npm to use the CLI anywhere:
19
+
20
+ ```bash
21
+ npm install -g broadcast-server-cli
22
+ ```
23
+
24
+ After installation, you can use the `broadcast-server` command from anywhere!
25
+
26
+ ### For Developers
27
+
28
+ Clone the repository and install dependencies:
29
+
30
+ ```bash
31
+ git clone https://github.com/yourusername/broadcast-server.git
32
+ cd broadcast-server
33
+ npm install
34
+ ```
35
+
36
+ ## 🎯 Usage
37
+
38
+ ### Starting the Server
39
+
40
+ **If installed globally via npm:**
41
+ ```bash
42
+ broadcast-server start
43
+ ```
44
+
45
+ **If running locally (for developers):**
46
+ ```bash
47
+ npm start
48
+ # or
49
+ node cli/cli.js start
50
+ ```
51
+
52
+ The server will start on port 8080 (or the PORT environment variable if set).
53
+
54
+ **On Render (or other platforms):**
55
+ The server automatically uses the `PORT` environment variable provided by the hosting platform.
56
+
57
+ ### Connecting to the Server
58
+
59
+ #### Option 1: Web Browser (Recommended) ✨
60
+
61
+ Simply open your browser and navigate to:
62
+ - **Local:** `http://localhost:8080`
63
+ - **Deployed:** `https://broadcastserver.onrender.com`
64
+
65
+ The web interface will load automatically. Click "Connect" to join the chat!
66
+
67
+ #### Option 2: CLI Client (For Developers)
68
+
69
+ **If installed globally:**
70
+ ```bash
71
+ broadcast-server connect wss://broadcastserver.onrender.com
72
+ ```
73
+
74
+ **If running locally:**
75
+ ```bash
76
+ node cli/cli.js connect wss://broadcastserver.onrender.com
77
+ # or for local server
78
+ node cli/cli.js connect ws://localhost:8080
79
+ ```
80
+
81
+ ## 🌐 Deployment
82
+
83
+ ### Render.com
84
+
85
+ 1. Push your code to GitHub
86
+ 2. Create a new Web Service on Render
87
+ 3. Connect your repository
88
+ 4. Render will automatically detect the build and start commands from `package.json`
89
+ 5. Your service will be available at `https://your-service-name.onrender.com`
90
+
91
+ **Important:** The web client will be accessible at the root URL (`https://your-service-name.onrender.com`), and the WebSocket server runs on the same URL with the `wss://` protocol.
92
+
93
+ ## 📱 Sharing with Others
94
+
95
+ To let others join your chat:
96
+
97
+ 1. **If deployed on Render:** Share the URL `https://broadcastserver.onrender.com`
98
+ 2. **If running locally:**
99
+ - Find your local IP address (e.g., `192.168.1.100`)
100
+ - Share `http://YOUR_IP:8080` with people on the same network
101
+ - They can open it in any browser (phone, tablet, computer)
102
+
103
+ ## 🎨 Features of the Web Client
104
+
105
+ - **Modern UI:** Beautiful gradient design with smooth animations
106
+ - **Real-time Status:** See connection status at a glance
107
+ - **User Identity:** Each user gets a unique anime protagonist name and color
108
+ - **Message History:** Scroll through previous messages
109
+ - **Responsive:** Works on desktop, tablet, and mobile
110
+ - **No Installation:** Just open in a browser!
111
+
112
+ ## 🛠️ Technical Details
113
+
114
+ - **Backend:** Node.js with `ws` WebSocket library
115
+ - **Frontend:** Pure HTML/CSS/JavaScript (no frameworks needed)
116
+ - **Protocol:** WebSocket (ws:// for local, wss:// for production)
117
+ - **Port:** Configurable via environment variable or defaults to 8080
118
+
119
+ ## 📝 Commands
120
+
121
+ - **Start server:** `npm start` or `node cli/cli.js start`
122
+ - **Connect via CLI:** `node cli/cli.js connect <websocket-url>`
123
+ - **Exit CLI client:** Type `/exit`
124
+
125
+ ## 🐛 Troubleshooting
126
+
127
+ ### "Cannot connect to server"
128
+ - Make sure the server is running
129
+ - Check that you're using the correct protocol (`ws://` for local, `wss://` for deployed)
130
+ - Verify the URL is correct
131
+
132
+ ### "broadcast-server command not found"
133
+ - Use `node cli/cli.js` instead of `broadcast-server`
134
+ - Or install globally: `npm install -g .` (from the project directory)
135
+
136
+ ### Web client not loading
137
+ - Ensure the `public/index.html` file exists
138
+ - Check server logs for errors
139
+ - Try accessing `/index.html` explicitly
140
+
141
+ ## 📄 License
142
+
143
+ MIT
package/app/app.js ADDED
@@ -0,0 +1,37 @@
1
+ import WebSocket from "ws";
2
+ import readline from "readline";
3
+
4
+ export function startClient(url) {
5
+
6
+
7
+ const ws = new WebSocket(url);
8
+
9
+ ws.on("open", () => {
10
+ console.log("🟢 Connected to server");
11
+ console.log("Type messages. Use /exit to quit.\n");
12
+
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout
16
+ });
17
+
18
+ rl.on("line", (input) => {
19
+ if (input === "/exit") {
20
+ rl.close();
21
+ ws.close();
22
+ process.exit(0);
23
+ }
24
+
25
+ ws.send(input);
26
+ });
27
+ });
28
+
29
+ ws.on("message", (msg) => {
30
+ console.log(msg.toString());
31
+ });
32
+
33
+ ws.on("close", () => {
34
+ console.log("🔴 Disconnected from server");
35
+ process.exit(0);
36
+ });
37
+ }
@@ -0,0 +1,93 @@
1
+ export const animeProtagonists = [
2
+ "Naruto Uzumaki",
3
+ "Monkey D. Luffy",
4
+ "Ichigo Kurosaki",
5
+ "Goku",
6
+ "Eren Yeager",
7
+ "Light Yagami",
8
+ "Edward Elric",
9
+ "Tanjiro Kamado",
10
+ "Izuku Midoriya",
11
+ "Yuji Itadori",
12
+ "Saitama",
13
+ "Kenshin Himura",
14
+ "Gon Freecss",
15
+ "Killua Zoldyck",
16
+ "Lelouch Lamperouge",
17
+ "Natsu Dragneel",
18
+ "Asta",
19
+ "Meliodas",
20
+ "Kaneki Ken",
21
+ "Shinji Ikari",
22
+ "Spike Spiegel",
23
+ "Vash the Stampede",
24
+ "Thorfinn",
25
+ "Denji",
26
+ "Subaru Natsuki",
27
+ "Kirito",
28
+ "Asuna Yuuki",
29
+ "Inuyasha",
30
+ "Yusuke Urameshi",
31
+ "Mob",
32
+ "Reigen Arataka",
33
+ "Ash Ketchum",
34
+ "Joestar Jonathan",
35
+ "Joseph Joestar",
36
+ "Jotaro Kujo",
37
+ "Josuke Higashikata",
38
+ "Giorno Giovanna",
39
+ "Gintoki Sakata",
40
+ "Rintarou Okabe",
41
+ "Senku Ishigami",
42
+ "Hyakkimaru",
43
+ "Alucard",
44
+ "Akira Fudo",
45
+ "Simon",
46
+ "Kamina",
47
+ "Shoyo Hinata",
48
+ "Kageyama Tobio",
49
+ "Ippo Makunouchi",
50
+ "Sakura Kinomoto",
51
+ "Usagi Tsukino",
52
+ "Soma Yukihira",
53
+ "Erza Scarlet",
54
+ "Mikasa Ackerman",
55
+ "Armin Arlert",
56
+ "Boruto Uzumaki",
57
+ "Tatsumi",
58
+ "Esdeath",
59
+ "Rimuru Tempest",
60
+ "Naofumi Iwatani",
61
+ "Kazuma Satou",
62
+ "Shigeo Kageyama",
63
+ "Hachiman Hikigaya",
64
+ "Oreki Houtarou",
65
+ "Zero Two",
66
+ "Shiro",
67
+ "Sora",
68
+ "Ichimatsu",
69
+ "Osomatsu",
70
+ "Deku",
71
+ "Black Star",
72
+ "Maka Albarn",
73
+ "Allen Walker",
74
+ "Koro Sensei",
75
+ "Akame",
76
+ "Tanjirou Kamado",
77
+ "Yato",
78
+ "Nagisa Shiota",
79
+ "Shinra Kusakabe",
80
+ "Lain Iwakura",
81
+ "Tomoya Okazaki",
82
+ "Taichi Yagami",
83
+ "Takemichi Hanagaki",
84
+ "Draken",
85
+ "Mikey",
86
+ "Aang",
87
+ "Korra",
88
+ "Arata Kaizaki",
89
+ "Chisaki Hiradaira",
90
+ "Violet Evergarden",
91
+ "Mash Burnedead",
92
+ "Frieren"
93
+ ];
package/cli/cli.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { startServer } from "../server/server.js";
4
+ import { startClient } from "../app/app.js";
5
+
6
+ const args = process.argv.slice(2);
7
+
8
+
9
+ const command = args[0];
10
+ const url = args[1] || process.env.PORT || 8080;
11
+
12
+ switch (command) {
13
+ case "start":
14
+ startServer(url);
15
+ break;
16
+
17
+ case "connect":
18
+
19
+ startClient(url);
20
+ break;
21
+
22
+ default:
23
+ console.log(`
24
+ Usage:
25
+ broadcast-server start (to start the server)
26
+ broadcast-server connect (to connect to server)
27
+ `);
28
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "broadcast-server-cli",
3
+ "version": "1.0.0",
4
+ "description": "A real-time WebSocket chat server with a beautiful web client interface and CLI tools",
5
+ "type": "module",
6
+ "main": "server/server.js",
7
+ "bin": {
8
+ "broadcast-server": "cli/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node cli/cli.js start"
12
+ },
13
+ "keywords": [
14
+ "websocket",
15
+ "chat",
16
+ "real-time",
17
+ "broadcast",
18
+ "server",
19
+ "cli",
20
+ "messaging",
21
+ "chat-server"
22
+ ],
23
+ "author": "Harshad <diwateavdhoot@gmail.com>",
24
+ "license": "MIT",
25
+ "homepage": "https://broadcastserver.onrender.com",
26
+ "engines": {
27
+ "node": ">=14.0.0"
28
+ },
29
+ "dependencies": {
30
+ "ws": "^8.19.0"
31
+ },
32
+ "files": [
33
+ "cli/",
34
+ "server/",
35
+ "app/",
36
+ "public/",
37
+ "README.md"
38
+ ]
39
+ }
@@ -0,0 +1,459 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Broadcast Chat</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: rgba(255, 255, 255, 0.95);
26
+ backdrop-filter: blur(10px);
27
+ border-radius: 20px;
28
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
29
+ width: 100%;
30
+ max-width: 600px;
31
+ height: 700px;
32
+ display: flex;
33
+ flex-direction: column;
34
+ overflow: hidden;
35
+ }
36
+
37
+ .header {
38
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
+ color: white;
40
+ padding: 20px;
41
+ text-align: center;
42
+ position: relative;
43
+ }
44
+
45
+ .header h1 {
46
+ font-size: 24px;
47
+ margin-bottom: 5px;
48
+ }
49
+
50
+ .status {
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ gap: 8px;
55
+ font-size: 14px;
56
+ margin-top: 10px;
57
+ }
58
+
59
+ .status-dot {
60
+ width: 10px;
61
+ height: 10px;
62
+ border-radius: 50%;
63
+ animation: pulse 2s infinite;
64
+ }
65
+
66
+ .status-dot.connected {
67
+ background: #4ade80;
68
+ }
69
+
70
+ .status-dot.disconnected {
71
+ background: #ef4444;
72
+ animation: none;
73
+ }
74
+
75
+ @keyframes pulse {
76
+ 0%, 100% {
77
+ opacity: 1;
78
+ transform: scale(1);
79
+ }
80
+ 50% {
81
+ opacity: 0.7;
82
+ transform: scale(1.1);
83
+ }
84
+ }
85
+
86
+ .messages {
87
+ flex: 1;
88
+ overflow-y: auto;
89
+ padding: 20px;
90
+ background: #f8fafc;
91
+ }
92
+
93
+ .message {
94
+ margin-bottom: 15px;
95
+ animation: slideIn 0.3s ease-out;
96
+ }
97
+
98
+ @keyframes slideIn {
99
+ from {
100
+ opacity: 0;
101
+ transform: translateY(10px);
102
+ }
103
+ to {
104
+ opacity: 1;
105
+ transform: translateY(0);
106
+ }
107
+ }
108
+
109
+ .message.system {
110
+ text-align: center;
111
+ color: #64748b;
112
+ font-style: italic;
113
+ font-size: 14px;
114
+ }
115
+
116
+ .message.user .username {
117
+ font-weight: bold;
118
+ margin-bottom: 5px;
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 8px;
122
+ }
123
+
124
+ .user-dot {
125
+ width: 8px;
126
+ height: 8px;
127
+ border-radius: 50%;
128
+ display: inline-block;
129
+ }
130
+
131
+ .message.user .text {
132
+ background: white;
133
+ padding: 12px 16px;
134
+ border-radius: 12px;
135
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
136
+ word-wrap: break-word;
137
+ }
138
+
139
+ .input-area {
140
+ padding: 20px;
141
+ background: white;
142
+ border-top: 1px solid #e2e8f0;
143
+ }
144
+
145
+ .input-container {
146
+ display: flex;
147
+ gap: 10px;
148
+ }
149
+
150
+ #messageInput {
151
+ flex: 1;
152
+ padding: 12px 16px;
153
+ border: 2px solid #e2e8f0;
154
+ border-radius: 12px;
155
+ font-size: 16px;
156
+ outline: none;
157
+ transition: border-color 0.3s;
158
+ }
159
+
160
+ #messageInput:focus {
161
+ border-color: #667eea;
162
+ }
163
+
164
+ #sendButton {
165
+ padding: 12px 24px;
166
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
167
+ color: white;
168
+ border: none;
169
+ border-radius: 12px;
170
+ font-size: 16px;
171
+ font-weight: 600;
172
+ cursor: pointer;
173
+ transition: transform 0.2s, box-shadow 0.2s;
174
+ }
175
+
176
+ #sendButton:hover {
177
+ transform: translateY(-2px);
178
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
179
+ }
180
+
181
+ #sendButton:active {
182
+ transform: translateY(0);
183
+ }
184
+
185
+ #sendButton:disabled {
186
+ opacity: 0.5;
187
+ cursor: not-allowed;
188
+ transform: none;
189
+ }
190
+
191
+ .messages::-webkit-scrollbar {
192
+ width: 8px;
193
+ }
194
+
195
+ .messages::-webkit-scrollbar-track {
196
+ background: #f1f5f9;
197
+ }
198
+
199
+ .messages::-webkit-scrollbar-thumb {
200
+ background: #cbd5e1;
201
+ border-radius: 4px;
202
+ }
203
+
204
+ .messages::-webkit-scrollbar-thumb:hover {
205
+ background: #94a3b8;
206
+ }
207
+
208
+ .connection-form {
209
+ padding: 20px;
210
+ text-align: center;
211
+ }
212
+
213
+ .connection-form input {
214
+ width: 100%;
215
+ padding: 12px 16px;
216
+ border: 2px solid #e2e8f0;
217
+ border-radius: 12px;
218
+ font-size: 16px;
219
+ margin-bottom: 15px;
220
+ outline: none;
221
+ }
222
+
223
+ .connection-form input:focus {
224
+ border-color: #667eea;
225
+ }
226
+
227
+ .connection-form button {
228
+ width: 100%;
229
+ padding: 12px 24px;
230
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
231
+ color: white;
232
+ border: none;
233
+ border-radius: 12px;
234
+ font-size: 16px;
235
+ font-weight: 600;
236
+ cursor: pointer;
237
+ transition: transform 0.2s;
238
+ }
239
+
240
+ .connection-form button:hover {
241
+ transform: translateY(-2px);
242
+ }
243
+
244
+ .hidden {
245
+ display: none;
246
+ }
247
+ </style>
248
+ </head>
249
+ <body>
250
+ <div class="container">
251
+ <div class="header">
252
+ <h1>🎙️ Broadcast Chat</h1>
253
+ <div class="status">
254
+ <span class="status-dot disconnected" id="statusDot"></span>
255
+ <span id="statusText">Disconnected</span>
256
+ </div>
257
+ </div>
258
+
259
+ <div id="connectionForm" class="connection-form">
260
+ <h2 style="margin-bottom: 20px; color: #1e293b;">Connect to Server</h2>
261
+ <input
262
+ type="text"
263
+ id="serverUrl"
264
+ placeholder="wss://broadcastserver.onrender.com"
265
+ value="wss://broadcastserver.onrender.com"
266
+ />
267
+ <button onclick="connect()">Connect</button>
268
+ </div>
269
+
270
+ <div id="chatArea" class="hidden">
271
+ <div class="messages" id="messages"></div>
272
+ <div class="input-area">
273
+ <div class="input-container">
274
+ <input
275
+ type="text"
276
+ id="messageInput"
277
+ placeholder="Type your message..."
278
+ onkeypress="handleKeyPress(event)"
279
+ />
280
+ <button id="sendButton" onclick="sendMessage()">Send</button>
281
+ </div>
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <script>
287
+ let ws = null;
288
+ let username = null;
289
+ let userColor = null;
290
+
291
+ const colorMap = {
292
+ '31': '#ef4444', // Red
293
+ '32': '#22c55e', // Green
294
+ '33': '#eab308', // Yellow
295
+ '34': '#3b82f6', // Blue
296
+ '35': '#a855f7', // Magenta
297
+ '36': '#06b6d4', // Cyan
298
+ };
299
+
300
+ function connect() {
301
+ const url = document.getElementById('serverUrl').value.trim();
302
+
303
+ if (!url) {
304
+ alert('Please enter a server URL');
305
+ return;
306
+ }
307
+
308
+ try {
309
+ ws = new WebSocket(url);
310
+
311
+ ws.onopen = () => {
312
+ updateStatus(true);
313
+ document.getElementById('connectionForm').classList.add('hidden');
314
+ document.getElementById('chatArea').classList.remove('hidden');
315
+ addSystemMessage('Connected to server');
316
+ };
317
+
318
+ ws.onmessage = (event) => {
319
+ const message = event.data;
320
+
321
+ // Check if it's a welcome message
322
+ if (message.startsWith('Welcome! You are ')) {
323
+ const match = message.match(/Welcome! You are (\w+) \x1b\[(\d+)m●\x1b\[0m/);
324
+ if (match) {
325
+ username = match[1];
326
+ const colorCode = match[2];
327
+ userColor = colorMap[colorCode] || '#64748b';
328
+ addSystemMessage(`You are ${username}`);
329
+ }
330
+ } else {
331
+ // Parse regular messages
332
+ const match = message.match(/(\w+) \x1b\[(\d+)m●\x1b\[0m : (.+)/);
333
+ if (match) {
334
+ const [, user, colorCode, text] = match;
335
+ const color = colorMap[colorCode] || '#64748b';
336
+ addUserMessage(user, text, color);
337
+ } else {
338
+ addSystemMessage(message);
339
+ }
340
+ }
341
+ };
342
+
343
+ ws.onclose = () => {
344
+ updateStatus(false);
345
+ addSystemMessage('Disconnected from server');
346
+ document.getElementById('connectionForm').classList.remove('hidden');
347
+ document.getElementById('chatArea').classList.add('hidden');
348
+ };
349
+
350
+ ws.onerror = (error) => {
351
+ console.error('WebSocket error:', error);
352
+ addSystemMessage('Connection error occurred');
353
+ };
354
+
355
+ } catch (error) {
356
+ alert('Failed to connect: ' + error.message);
357
+ }
358
+ }
359
+
360
+ function sendMessage() {
361
+ const input = document.getElementById('messageInput');
362
+ const message = input.value.trim();
363
+
364
+ if (message && ws && ws.readyState === WebSocket.OPEN) {
365
+ ws.send(message);
366
+
367
+ // Display own message
368
+ addOwnMessage(message);
369
+ input.value = '';
370
+ }
371
+ }
372
+
373
+ function handleKeyPress(event) {
374
+ if (event.key === 'Enter') {
375
+ sendMessage();
376
+ }
377
+ }
378
+
379
+ function updateStatus(connected) {
380
+ const statusDot = document.getElementById('statusDot');
381
+ const statusText = document.getElementById('statusText');
382
+ const sendButton = document.getElementById('sendButton');
383
+
384
+ if (connected) {
385
+ statusDot.classList.remove('disconnected');
386
+ statusDot.classList.add('connected');
387
+ statusText.textContent = 'Connected';
388
+ sendButton.disabled = false;
389
+ } else {
390
+ statusDot.classList.remove('connected');
391
+ statusDot.classList.add('disconnected');
392
+ statusText.textContent = 'Disconnected';
393
+ sendButton.disabled = true;
394
+ }
395
+ }
396
+
397
+ function addSystemMessage(text) {
398
+ const messagesDiv = document.getElementById('messages');
399
+ const messageDiv = document.createElement('div');
400
+ messageDiv.className = 'message system';
401
+ messageDiv.textContent = text;
402
+ messagesDiv.appendChild(messageDiv);
403
+ scrollToBottom();
404
+ }
405
+
406
+ function addUserMessage(user, text, color) {
407
+ const messagesDiv = document.getElementById('messages');
408
+ const messageDiv = document.createElement('div');
409
+ messageDiv.className = 'message user';
410
+ messageDiv.innerHTML = `
411
+ <div class="username">
412
+ <span class="user-dot" style="background: ${color}"></span>
413
+ ${user}
414
+ </div>
415
+ <div class="text">${escapeHtml(text)}</div>
416
+ `;
417
+ messagesDiv.appendChild(messageDiv);
418
+ scrollToBottom();
419
+ }
420
+
421
+ function addOwnMessage(text) {
422
+ const messagesDiv = document.getElementById('messages');
423
+ const messageDiv = document.createElement('div');
424
+ messageDiv.className = 'message user';
425
+ messageDiv.innerHTML = `
426
+ <div class="username" style="justify-content: flex-end;">
427
+ <span class="user-dot" style="background: ${userColor}"></span>
428
+ ${username} (You)
429
+ </div>
430
+ <div class="text" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
431
+ ${escapeHtml(text)}
432
+ </div>
433
+ `;
434
+ messagesDiv.appendChild(messageDiv);
435
+ scrollToBottom();
436
+ }
437
+
438
+ function escapeHtml(text) {
439
+ const div = document.createElement('div');
440
+ div.textContent = text;
441
+ return div.innerHTML;
442
+ }
443
+
444
+ function scrollToBottom() {
445
+ const messagesDiv = document.getElementById('messages');
446
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
447
+ }
448
+
449
+ // Auto-connect on load if URL is pre-filled
450
+ window.addEventListener('load', () => {
451
+ const urlInput = document.getElementById('serverUrl');
452
+ if (urlInput.value.trim()) {
453
+ // Optional: auto-connect
454
+ // connect();
455
+ }
456
+ });
457
+ </script>
458
+ </body>
459
+ </html>
@@ -0,0 +1,53 @@
1
+ import http from "http";
2
+ import WebSocket, { WebSocketServer } from "ws";
3
+ import { animeProtagonists } from "../app/usernames.js";
4
+
5
+ export function startServer(port = 8080) {
6
+ const server = http.createServer();
7
+ const wss = new WebSocketServer({ server });
8
+ const clients = new Set();
9
+
10
+ const colors = [
11
+ "\x1b[31m", // Red
12
+ "\x1b[32m", // Green
13
+ "\x1b[33m", // Yellow
14
+ "\x1b[34m", // Blue
15
+ "\x1b[35m", // Magenta
16
+ "\x1b[36m", // Cyan
17
+ ];
18
+ const resetColor = "\x1b[0m";
19
+
20
+ function sendMessages(data, ws) {
21
+ for (const client of clients) {
22
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
23
+ client.send(`${ws.username} ${ws.color}●${resetColor} : ${data.toString()}`);
24
+ }
25
+ }
26
+ }
27
+
28
+ wss.on("connection", (ws) => {
29
+ ws.username = animeProtagonists[Math.floor(Math.random() * animeProtagonists.length)];
30
+ ws.color = colors[Math.floor(Math.random() * colors.length)];
31
+
32
+ clients.add(ws);
33
+ console.log(`${ws.username} ${ws.color}●${resetColor} connected`);
34
+
35
+ // Inform the user of their identity
36
+ ws.send(`Welcome! You are ${ws.username} ${ws.color}●${resetColor}`);
37
+
38
+ ws.on("message", (data) => {
39
+ sendMessages(data, ws);
40
+ });
41
+
42
+ ws.on("close", () => {
43
+ clients.delete(ws);
44
+ console.log(`${ws.username} \x1b[31m●${resetColor} Disconnected`);
45
+ });
46
+ });
47
+
48
+ const PORT = process.env.PORT || 8000;
49
+
50
+ server.listen(PORT, () => {
51
+ console.log(`🟢 Server running on port ${PORT}`);
52
+ });
53
+ }