import WebSocket from 'ws'; import { ClientMessage, safeSend } from '../utils/protocol'; import { sendFriendRequest, acceptFriendRequest, rejectFriendRequest, removeFriend, getFriendList, areFriends, } from '../services/friendService'; import { isOnline } from '../redis/presenceClient'; import { registry } from '../services/connectionRegistry'; import { logger } from '../utils/logger'; /** * Routes friend-related WS actions. * Called from connectionHandler's routeMessage for type: 'friend' * * Payload shape: { action, targetUid, ...extras } * Actions: send-request | accept | reject | remove | list | status */ export async function handleFriend( ws: WebSocket, uid: string, msg: ClientMessage ): Promise { const { action, targetUid } = msg.payload as { action?: string; targetUid?: string }; if (!action) { safeSend(ws, 'error', { message: 'action is required' }, msg.requestId); return; } switch (action) { case 'send-request': { if (!targetUid) { safeSend(ws, 'error', { message: 'targetUid required' }, msg.requestId); return; } if (targetUid === uid) { safeSend(ws, 'error', { message: 'Cannot friend yourself' }, msg.requestId); return; } await sendFriendRequest(uid, targetUid); safeSend(ws, 'message', { ack: true, action: 'send-request', targetUid }, msg.requestId); // Notify the target if online const targetMeta = registry.getByUid(targetUid); if (targetMeta) { safeSend(targetMeta.ws, 'message', { type: 'friend-request', fromUid: uid }); } break; } case 'accept': { if (!targetUid) { safeSend(ws, 'error', { message: 'targetUid required' }, msg.requestId); return; } const ok = await acceptFriendRequest(targetUid, uid); // targetUid sent the request safeSend(ws, 'message', { ack: true, action: 'accept', success: ok }, msg.requestId); if (ok) { const targetMeta = registry.getByUid(targetUid); if (targetMeta) { safeSend(targetMeta.ws, 'message', { type: 'friend-accepted', fromUid: uid }); } } break; } case 'reject': { if (!targetUid) { safeSend(ws, 'error', { message: 'targetUid required' }, msg.requestId); return; } await rejectFriendRequest(targetUid, uid); safeSend(ws, 'message', { ack: true, action: 'reject' }, msg.requestId); break; } case 'remove': { if (!targetUid) { safeSend(ws, 'error', { message: 'targetUid required' }, msg.requestId); return; } await removeFriend(uid, targetUid); safeSend(ws, 'message', { ack: true, action: 'remove' }, msg.requestId); break; } case 'list': { const friends = await getFriendList(uid); // Bulk check online status from Redis const statusChecks = await Promise.allSettled(friends.map(f => isOnline(f))); const withStatus = friends.map((f, i) => ({ uid: f, online: statusChecks[i].status === 'fulfilled' && statusChecks[i].value === true, })); safeSend(ws, 'message', { action: 'list', friends: withStatus }, msg.requestId); break; } case 'status': { if (!targetUid) { safeSend(ws, 'error', { message: 'targetUid required' }, msg.requestId); return; } // Only allow checking status of friends const isFriend = await areFriends(uid, targetUid); if (!isFriend) { safeSend(ws, 'error', { message: 'Not friends with this user' }, msg.requestId); return; } const online = await isOnline(targetUid); safeSend(ws, 'message', { action: 'status', uid: targetUid, online }, msg.requestId); break; } default: safeSend(ws, 'error', { message: `Unknown friend action: ${action}` }, msg.requestId); } }