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 +21 -0
- package/README.md +143 -0
- package/app/app.js +37 -0
- package/app/usernames.js +93 -0
- package/cli/cli.js +28 -0
- package/package.json +39 -0
- package/public/index.html +459 -0
- package/server/server.js +53 -0
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
|
+
}
|
package/app/usernames.js
ADDED
|
@@ -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>
|
package/server/server.js
ADDED
|
@@ -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
|
+
}
|