friends cursor broadcast system refined 👍
This commit is contained in:
1
src/types/socket.d.ts
vendored
1
src/types/socket.d.ts
vendored
@@ -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
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user