Refactor 4 with bugfixes
This commit is contained in:
37
server/src/websocket/broadcast.ts
Normal file
37
server/src/websocket/broadcast.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { connectedUsers } from './state';
|
||||
|
||||
interface WsMessage {
|
||||
[key: string]: unknown;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export function broadcastToServer(serverId: string, message: WsMessage, excludeOderId?: string): void {
|
||||
console.log(`Broadcasting to server ${serverId}, excluding ${excludeOderId}:`, message.type);
|
||||
|
||||
connectedUsers.forEach((user) => {
|
||||
if (user.serverIds.has(serverId) && user.oderId !== excludeOderId) {
|
||||
console.log(` -> Sending to ${user.displayName} (${user.oderId})`);
|
||||
user.ws.send(JSON.stringify(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function notifyServerOwner(ownerId: string, message: WsMessage): void {
|
||||
const owner = findUserByOderId(ownerId);
|
||||
|
||||
if (owner) {
|
||||
owner.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
export function notifyUser(oderId: string, message: WsMessage): void {
|
||||
const user = findUserByOderId(oderId);
|
||||
|
||||
if (user) {
|
||||
user.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
export function findUserByOderId(oderId: string) {
|
||||
return Array.from(connectedUsers.values()).find(user => user.oderId === oderId);
|
||||
}
|
||||
164
server/src/websocket/handler.ts
Normal file
164
server/src/websocket/handler.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { connectedUsers } from './state';
|
||||
import { ConnectedUser } from './types';
|
||||
import { broadcastToServer, findUserByOderId } from './broadcast';
|
||||
|
||||
interface WsMessage {
|
||||
[key: string]: unknown;
|
||||
type: string;
|
||||
}
|
||||
|
||||
/** Sends the current user list for a given server to a single connected user. */
|
||||
function sendServerUsers(user: ConnectedUser, serverId: string): void {
|
||||
const users = Array.from(connectedUsers.values())
|
||||
.filter(cu => cu.serverIds.has(serverId) && cu.oderId !== user.oderId && cu.displayName)
|
||||
.map(cu => ({ oderId: cu.oderId, displayName: cu.displayName ?? 'Anonymous' }));
|
||||
|
||||
user.ws.send(JSON.stringify({ type: 'server_users', serverId, users }));
|
||||
}
|
||||
|
||||
function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||
user.oderId = String(message['oderId'] || connectionId);
|
||||
user.displayName = String(message['displayName'] || 'Anonymous');
|
||||
connectedUsers.set(connectionId, user);
|
||||
console.log(`User identified: ${user.displayName} (${user.oderId})`);
|
||||
}
|
||||
|
||||
function handleJoinServer(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||
const sid = String(message['serverId']);
|
||||
const isNew = !user.serverIds.has(sid);
|
||||
|
||||
user.serverIds.add(sid);
|
||||
user.viewedServerId = sid;
|
||||
connectedUsers.set(connectionId, user);
|
||||
console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) joined server ${sid} (new=${isNew})`);
|
||||
|
||||
sendServerUsers(user, sid);
|
||||
|
||||
if (isNew) {
|
||||
broadcastToServer(sid, {
|
||||
type: 'user_joined',
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName ?? 'Anonymous',
|
||||
serverId: sid
|
||||
}, user.oderId);
|
||||
}
|
||||
}
|
||||
|
||||
function handleViewServer(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||
const viewSid = String(message['serverId']);
|
||||
|
||||
user.viewedServerId = viewSid;
|
||||
connectedUsers.set(connectionId, user);
|
||||
console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) viewing server ${viewSid}`);
|
||||
|
||||
sendServerUsers(user, viewSid);
|
||||
}
|
||||
|
||||
function handleLeaveServer(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||
const leaveSid = (message['serverId'] as string | undefined) ?? user.viewedServerId;
|
||||
|
||||
if (!leaveSid)
|
||||
return;
|
||||
|
||||
user.serverIds.delete(leaveSid);
|
||||
|
||||
if (user.viewedServerId === leaveSid)
|
||||
user.viewedServerId = undefined;
|
||||
|
||||
connectedUsers.set(connectionId, user);
|
||||
|
||||
broadcastToServer(leaveSid, {
|
||||
type: 'user_left',
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName ?? 'Anonymous',
|
||||
serverId: leaveSid
|
||||
}, user.oderId);
|
||||
}
|
||||
|
||||
function forwardRtcMessage(user: ConnectedUser, message: WsMessage): void {
|
||||
const targetUserId = String(message['targetUserId'] || '');
|
||||
|
||||
console.log(`Forwarding ${message.type} from ${user.oderId} to ${targetUserId}`);
|
||||
|
||||
const targetUser = findUserByOderId(targetUserId);
|
||||
|
||||
if (targetUser) {
|
||||
targetUser.ws.send(JSON.stringify({ ...message, fromUserId: user.oderId }));
|
||||
console.log(`Successfully forwarded ${message.type} to ${targetUserId}`);
|
||||
} else {
|
||||
console.log(
|
||||
`Target user ${targetUserId} not found. Connected users:`,
|
||||
Array.from(connectedUsers.values()).map(cu => ({ oderId: cu.oderId, displayName: cu.displayName }))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleChatMessage(user: ConnectedUser, message: WsMessage): void {
|
||||
const chatSid = (message['serverId'] as string | undefined) ?? user.viewedServerId;
|
||||
|
||||
if (chatSid && user.serverIds.has(chatSid)) {
|
||||
broadcastToServer(chatSid, {
|
||||
type: 'chat_message',
|
||||
serverId: chatSid,
|
||||
message: message['message'],
|
||||
senderId: user.oderId,
|
||||
senderName: user.displayName,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleTyping(user: ConnectedUser, message: WsMessage): void {
|
||||
const typingSid = (message['serverId'] as string | undefined) ?? user.viewedServerId;
|
||||
|
||||
if (typingSid && user.serverIds.has(typingSid)) {
|
||||
broadcastToServer(typingSid, {
|
||||
type: 'user_typing',
|
||||
serverId: typingSid,
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName
|
||||
}, user.oderId);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleWebSocketMessage(connectionId: string, message: WsMessage): void {
|
||||
const user = connectedUsers.get(connectionId);
|
||||
|
||||
if (!user)
|
||||
return;
|
||||
|
||||
switch (message.type) {
|
||||
case 'identify':
|
||||
handleIdentify(user, message, connectionId);
|
||||
break;
|
||||
|
||||
case 'join_server':
|
||||
handleJoinServer(user, message, connectionId);
|
||||
break;
|
||||
|
||||
case 'view_server':
|
||||
handleViewServer(user, message, connectionId);
|
||||
break;
|
||||
|
||||
case 'leave_server':
|
||||
handleLeaveServer(user, message, connectionId);
|
||||
break;
|
||||
|
||||
case 'offer':
|
||||
case 'answer':
|
||||
case 'ice_candidate':
|
||||
forwardRtcMessage(user, message);
|
||||
break;
|
||||
|
||||
case 'chat_message':
|
||||
handleChatMessage(user, message);
|
||||
break;
|
||||
|
||||
case 'typing':
|
||||
handleTyping(user, message);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
49
server/src/websocket/index.ts
Normal file
49
server/src/websocket/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
IncomingMessage,
|
||||
Server,
|
||||
ServerResponse
|
||||
} from 'http';
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { connectedUsers } from './state';
|
||||
import { broadcastToServer } from './broadcast';
|
||||
import { handleWebSocketMessage } from './handler';
|
||||
|
||||
export function setupWebSocket(server: Server<typeof IncomingMessage, typeof ServerResponse>): void {
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
wss.on('connection', (ws: WebSocket) => {
|
||||
const connectionId = uuidv4();
|
||||
|
||||
connectedUsers.set(connectionId, { oderId: connectionId, ws, serverIds: new Set() });
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
|
||||
handleWebSocketMessage(connectionId, message);
|
||||
} catch (err) {
|
||||
console.error('Invalid WebSocket message:', err);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
const user = connectedUsers.get(connectionId);
|
||||
|
||||
if (user) {
|
||||
user.serverIds.forEach((sid) => {
|
||||
broadcastToServer(sid, {
|
||||
type: 'user_left',
|
||||
oderId: user.oderId,
|
||||
displayName: user.displayName,
|
||||
serverId: sid
|
||||
}, user.oderId);
|
||||
});
|
||||
}
|
||||
|
||||
connectedUsers.delete(connectionId);
|
||||
});
|
||||
|
||||
ws.send(JSON.stringify({ type: 'connected', connectionId, serverTime: Date.now() }));
|
||||
});
|
||||
}
|
||||
3
server/src/websocket/state.ts
Normal file
3
server/src/websocket/state.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ConnectedUser } from './types';
|
||||
|
||||
export const connectedUsers = new Map<string, ConnectedUser>();
|
||||
9
server/src/websocket/types.ts
Normal file
9
server/src/websocket/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { WebSocket } from 'ws';
|
||||
|
||||
export interface ConnectedUser {
|
||||
oderId: string;
|
||||
ws: WebSocket;
|
||||
serverIds: Set<string>;
|
||||
viewedServerId?: string;
|
||||
displayName?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user