feat(ws): harden Redis socket adapter lifecycle
This commit is contained in:
@@ -6,13 +6,11 @@ import { PrismaService } from '../../../database/prisma.service';
|
||||
import { UserSocketService } from '../user-socket.service';
|
||||
import { WsNotificationService } from '../ws-notification.service';
|
||||
import { WS_EVENT } from '../ws-events';
|
||||
import { UsersService } from '../../../users/users.service';
|
||||
|
||||
export class ConnectionHandler {
|
||||
constructor(
|
||||
private readonly jwtVerificationService: JwtVerificationService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly userSocketService: UserSocketService,
|
||||
private readonly wsNotificationService: WsNotificationService,
|
||||
private readonly logger: Logger,
|
||||
@@ -94,42 +92,42 @@ export class ConnectionHandler {
|
||||
this.logger.log(
|
||||
`WebSocket authenticated via initialize fallback (Pending Init): ${payload.sub}`,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WebSocket authenticated via initialize fallback (Pending Init): ${payload.sub}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!userTokenData) {
|
||||
throw new WsException('Unauthorized: No user data found');
|
||||
}
|
||||
|
||||
const user = await this.usersService.findOne(userTokenData.userId);
|
||||
|
||||
// 2. Register socket mapping (Redis Write)
|
||||
await this.userSocketService.setSocket(user.id, client.id);
|
||||
client.data.userId = user.id;
|
||||
|
||||
// 3. Fetch initial state (DB Read)
|
||||
const [userWithDoll, friends] = await Promise.all([
|
||||
// 2. Fetch initial state (DB Read)
|
||||
const [userState, friends] = await Promise.all([
|
||||
this.prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { activeDollId: true },
|
||||
where: { id: userTokenData.userId },
|
||||
select: { id: true, name: true, username: true, activeDollId: true },
|
||||
}),
|
||||
this.prisma.friendship.findMany({
|
||||
where: { userId: user.id },
|
||||
where: { userId: userTokenData.userId },
|
||||
select: { friendId: true },
|
||||
}),
|
||||
]);
|
||||
|
||||
client.data.activeDollId = userWithDoll?.activeDollId || null;
|
||||
client.data.friends = new Set(friends.map((f) => f.friendId));
|
||||
if (!userState) {
|
||||
throw new WsException('Unauthorized: No user data found');
|
||||
}
|
||||
|
||||
this.logger.log(`Client initialized: ${user.id} (${client.id})`);
|
||||
// 3. Register socket mapping (Redis Write)
|
||||
await this.userSocketService.setSocket(userState.id, client.id);
|
||||
client.data.userId = userState.id;
|
||||
|
||||
client.data.activeDollId = userState.activeDollId || null;
|
||||
client.data.friends = new Set(friends.map((f) => f.friendId));
|
||||
client.data.senderName = userState.name || userState.username;
|
||||
client.data.senderNameCachedAt = Date.now();
|
||||
|
||||
this.logger.log(`Client initialized: ${userState.id} (${client.id})`);
|
||||
|
||||
// 4. Notify client
|
||||
client.emit(WS_EVENT.INITIALIZED, {
|
||||
userId: user.id,
|
||||
userId: userState.id,
|
||||
activeDollId: client.data.activeDollId,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -157,7 +155,9 @@ export class ConnectionHandler {
|
||||
// Notify friends that this user has disconnected
|
||||
const friends = client.data.friends;
|
||||
if (friends) {
|
||||
const friendIds = Array.from(friends);
|
||||
const friendIds = Array.from(friends).filter(
|
||||
(friendId): friendId is string => typeof friendId === 'string',
|
||||
);
|
||||
const friendSockets =
|
||||
await this.userSocketService.getFriendsSockets(friendIds);
|
||||
|
||||
@@ -179,9 +179,5 @@ export class ConnectionHandler {
|
||||
this.logger.log(
|
||||
`Client id: ${client.id} disconnected (user: ${user?.userId || 'unknown'})`,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Client id: ${client.id} disconnected (user: ${user?.userId || 'unknown'})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Logger, Inject } from '@nestjs/common';
|
||||
import { Logger, Inject, OnModuleDestroy } from '@nestjs/common';
|
||||
import {
|
||||
OnGatewayConnection,
|
||||
OnGatewayDisconnect,
|
||||
@@ -22,7 +22,6 @@ import { PrismaService } from '../../database/prisma.service';
|
||||
import { UserSocketService } from './user-socket.service';
|
||||
import { WsNotificationService } from './ws-notification.service';
|
||||
import { WS_EVENT, REDIS_CHANNEL } from './ws-events';
|
||||
import { UsersService } from '../../users/users.service';
|
||||
import { ConnectionHandler } from './connection/handler';
|
||||
import { CursorHandler } from './cursor/handler';
|
||||
import { StatusHandler } from './status/handler';
|
||||
@@ -31,14 +30,13 @@ import { RedisHandler } from './utils/redis-handler';
|
||||
import { Broadcaster } from './utils/broadcasting';
|
||||
import { Throttler } from './utils/throttling';
|
||||
|
||||
@WebSocketGateway({
|
||||
cors: {
|
||||
origin: true,
|
||||
credentials: true,
|
||||
},
|
||||
})
|
||||
@WebSocketGateway()
|
||||
export class StateGateway
|
||||
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
|
||||
implements
|
||||
OnGatewayInit,
|
||||
OnGatewayConnection,
|
||||
OnGatewayDisconnect,
|
||||
OnModuleDestroy
|
||||
{
|
||||
private readonly logger = new Logger(StateGateway.name);
|
||||
|
||||
@@ -55,7 +53,6 @@ export class StateGateway
|
||||
constructor(
|
||||
private readonly jwtVerificationService: JwtVerificationService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly usersService: UsersService,
|
||||
private readonly userSocketService: UserSocketService,
|
||||
private readonly wsNotificationService: WsNotificationService,
|
||||
@Inject(REDIS_CLIENT) private readonly redisClient: Redis | null,
|
||||
@@ -70,7 +67,6 @@ export class StateGateway
|
||||
this.connectionHandler = new ConnectionHandler(
|
||||
this.jwtVerificationService,
|
||||
this.prisma,
|
||||
this.usersService,
|
||||
this.userSocketService,
|
||||
this.wsNotificationService,
|
||||
this.logger,
|
||||
@@ -156,11 +152,16 @@ export class StateGateway
|
||||
await this.statusHandler.handleClientReportUserStatus(client, data);
|
||||
}
|
||||
|
||||
@SubscribeMessage(WS_EVENT.CLIENT_SEND_INTERACTION)
|
||||
async handleSendInteraction(
|
||||
client: AuthenticatedSocket,
|
||||
data: SendInteractionDto,
|
||||
) {
|
||||
await this.interactionHandler.handleSendInteraction(client, data);
|
||||
}
|
||||
|
||||
onModuleDestroy() {
|
||||
if (this.redisSubscriber) {
|
||||
this.redisSubscriber.removeAllListeners('message');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user