friends cursor broadcast system refined 👍

This commit is contained in:
2025-12-16 01:56:43 +08:00
parent 1325f4f879
commit 43edb9e49a
2 changed files with 35 additions and 20 deletions

View File

@@ -8,6 +8,7 @@ export type AuthenticatedSocket = BaseSocket<
DefaultEventsMap, // InterServerEvents DefaultEventsMap, // InterServerEvents
{ {
user?: AuthenticatedUser; user?: AuthenticatedUser;
userId?: string;
friends?: Set<string>; // Set of friend user IDs friends?: Set<string>; // Set of friend user IDs
} }
>; >;

View File

@@ -26,6 +26,7 @@ const WS_EVENT = {
FRIEND_REQUEST_DENIED: 'friend-request-denied', FRIEND_REQUEST_DENIED: 'friend-request-denied',
UNFRIENDED: 'unfriended', UNFRIENDED: 'unfriended',
FRIEND_CURSOR_POSITION: 'friend-cursor-position', FRIEND_CURSOR_POSITION: 'friend-cursor-position',
FRIEND_DISCONNECTED: 'friend-disconnected',
} as const; } as const;
@WebSocketGateway({ @WebSocketGateway({
@@ -88,6 +89,7 @@ export class StateGateway
const user = await this.authService.syncUserFromToken(client.data.user); const user = await this.authService.syncUserFromToken(client.data.user);
this.userSocketMap.set(user.id, client.id); this.userSocketMap.set(user.id, client.id);
client.data.userId = user.id;
// Initialize friends cache // Initialize friends cache
const friends = await this.friendsService.getFriends(user.id); const friends = await this.friendsService.getFriends(user.id);
@@ -110,10 +112,34 @@ export class StateGateway
const user = client.data.user; const user = client.data.user;
if (user) { if (user) {
for (const [userId, socketId] of this.userSocketMap.entries()) { const userId = client.data.userId;
if (socketId === client.id) {
if (userId) {
// Check if this socket is still the active one for the user
const currentSocketId = this.userSocketMap.get(userId);
if (currentSocketId === client.id) {
this.userSocketMap.delete(userId); this.userSocketMap.delete(userId);
break;
// Notify friends that this user has disconnected
const friends = client.data.friends;
if (friends) {
for (const friendId of friends) {
const friendSocketId = this.userSocketMap.get(friendId);
if (friendSocketId) {
this.io.to(friendSocketId).emit(WS_EVENT.FRIEND_DISCONNECTED, {
userId: userId,
});
}
}
}
}
} else {
// Fallback for cases where client.data.userId might not be set
for (const [uid, socketId] of this.userSocketMap.entries()) {
if (socketId === client.id) {
this.userSocketMap.delete(uid);
break;
}
} }
} }
} }
@@ -123,6 +149,10 @@ export class StateGateway
); );
} }
isUserOnline(userId: string): boolean {
return this.userSocketMap.has(userId);
}
@SubscribeMessage(WS_EVENT.CURSOR_REPORT_POSITION) @SubscribeMessage(WS_EVENT.CURSOR_REPORT_POSITION)
handleCursorReportPosition( handleCursorReportPosition(
client: AuthenticatedSocket, client: AuthenticatedSocket,
@@ -134,20 +164,7 @@ export class StateGateway
throw new WsException('Unauthorized'); throw new WsException('Unauthorized');
} }
// Get the user ID from the userSocketMap (keycloakSub -> userId map is handled implicitly by connection logic but let's be safe and get it from map iteration or better store userId in client.data) const currentUserId = client.data.userId;
// Actually we stored user.id -> client.id in userSocketMap. But we don't have direct access to user.id from client.data.user (it has keycloakSub).
// Let's improve this by finding the userId. The user is already synced in handleConnection.
// However, for efficiency, let's reverse lookup or better yet, assume we can get userId.
// In handleConnection we did: const user = await this.authService.syncUserFromToken(client.data.user); this.userSocketMap.set(user.id, client.id);
// So we know the user.id is in the map.
let currentUserId: string | undefined;
for (const [uid, sid] of this.userSocketMap.entries()) {
if (sid === client.id) {
currentUserId = uid;
break;
}
}
if (!currentUserId) { if (!currentUserId) {
this.logger.warn(`Could not find user ID for client ${client.id}`); this.logger.warn(`Could not find user ID for client ${client.id}`);
@@ -164,9 +181,6 @@ export class StateGateway
userId: currentUserId, userId: currentUserId,
position: data, position: data,
}; };
this.logger.debug(
`Sending friend cursor position to user ${friendId}: ${JSON.stringify(payload, null, 0)}`,
);
this.io this.io
.to(friendSocketId) .to(friendSocketId)
.emit(WS_EVENT.FRIEND_CURSOR_POSITION, payload); .emit(WS_EVENT.FRIEND_CURSOR_POSITION, payload);