init
This commit is contained in:
Generated
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "chat-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "chat-server",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "chat-server",
|
||||
"version": "1.0.0",
|
||||
"description": "WebSocket chat server with file sharing",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.16.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { WebSocketServer } = require('ws');
|
||||
|
||||
const PORT = parseInt(process.env.PORT, 10) || 8080;
|
||||
const MAX_FILE_SIZE = parseInt(process.env.MAX_FILE_SIZE, 10) || 10 * 1024 * 1024;
|
||||
|
||||
const usersPath = path.join(__dirname, process.env.USERS_FILE || 'users.json');
|
||||
let validUsers;
|
||||
try {
|
||||
validUsers = JSON.parse(fs.readFileSync(usersPath, 'utf-8'));
|
||||
console.log(`Loaded ${validUsers.length} users from users.json`);
|
||||
} catch (err) {
|
||||
console.error('Failed to load users.json:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function authenticate(login, password) {
|
||||
return validUsers.find(u => u.login === login && u.password === password) || null;
|
||||
}
|
||||
|
||||
const wss = new WebSocketServer({ port: PORT });
|
||||
console.log(`WebSocket server running on ws://localhost:${PORT}`);
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
const clientIp = req.socket.remoteAddress;
|
||||
console.log(`New connection from ${clientIp}`);
|
||||
|
||||
let authenticated = false;
|
||||
let userLogin = null;
|
||||
|
||||
ws.on('message', (data) => {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(data.toString());
|
||||
} catch (e) {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!authenticated) {
|
||||
if (parsed.type === 'auth') {
|
||||
const user = authenticate(parsed.login, parsed.password);
|
||||
if (user) {
|
||||
authenticated = true;
|
||||
userLogin = parsed.login;
|
||||
console.log(`User authenticated: ${userLogin}`);
|
||||
ws.send(JSON.stringify({ type: 'auth_result', success: true }));
|
||||
broadcast({
|
||||
type: 'system',
|
||||
text: `${userLogin} joined the chat`
|
||||
}, null);
|
||||
} else {
|
||||
console.log(`Auth failed for login: ${parsed.login}`);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'auth_result',
|
||||
success: false,
|
||||
reason: 'Invalid login or password'
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Authenticate first' }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.type === 'text') {
|
||||
if (!parsed.text || typeof parsed.text !== 'string') {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid text message' }));
|
||||
return;
|
||||
}
|
||||
console.log(`Text from ${userLogin}: ${parsed.text.substring(0, 50)}`);
|
||||
broadcast({
|
||||
type: 'text',
|
||||
from: userLogin,
|
||||
timestamp: Date.now(),
|
||||
text: parsed.text
|
||||
}, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.type === 'file') {
|
||||
if (!parsed.filename || !parsed.mime || !parsed.data) {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Invalid file message' }));
|
||||
return;
|
||||
}
|
||||
const size = Math.ceil((parsed.data.length * 3) / 4);
|
||||
if (size > MAX_FILE_SIZE) {
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'File too large (max 10 MB)' }));
|
||||
return;
|
||||
}
|
||||
console.log(`File from ${userLogin}: ${parsed.filename} (${parsed.mime}, ~${size} bytes)`);
|
||||
broadcast({
|
||||
type: 'file',
|
||||
from: userLogin,
|
||||
timestamp: Date.now(),
|
||||
filename: parsed.filename,
|
||||
mime: parsed.mime,
|
||||
data: parsed.data
|
||||
}, null);
|
||||
return;
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({ type: 'error', message: 'Unknown message type' }));
|
||||
});
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log(`Connection closed${userLogin ? ` for ${userLogin}` : ''} (code: ${code})`);
|
||||
if (userLogin) {
|
||||
broadcast({
|
||||
type: 'system',
|
||||
text: `${userLogin} left the chat`
|
||||
}, null);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.error(`WebSocket error${userLogin ? ` for ${userLogin}` : ''}:`, err.message);
|
||||
});
|
||||
});
|
||||
|
||||
function broadcast(message, excludeWs) {
|
||||
const json = JSON.stringify(message);
|
||||
wss.clients.forEach(client => {
|
||||
if (client !== excludeWs && client.readyState === 1) {
|
||||
client.send(json);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[
|
||||
{ "login": "alice", "password": "123" },
|
||||
{ "login": "bob", "password": "456" }
|
||||
]
|
||||
Reference in New Issue
Block a user