feat(guild-realtime): add presence and typing websocket events
This commit is contained in:
@@ -21,6 +21,14 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
|
||||
server!: Server;
|
||||
|
||||
private readonly logger = new Logger(RealtimeGateway.name);
|
||||
private readonly onlineUsers = new Set<string>();
|
||||
|
||||
private userIdFromClient(client: Socket): string {
|
||||
const authUser = client.handshake.auth?.userId;
|
||||
const headerUser = client.handshake.headers['x-user-id'];
|
||||
const userId = typeof authUser === 'string' ? authUser : Array.isArray(headerUser) ? headerUser[0] : headerUser;
|
||||
return userId && typeof userId === 'string' && userId.trim() !== '' ? userId : `anon:${client.id}`;
|
||||
}
|
||||
|
||||
handleConnection(client: Socket): void {
|
||||
const expected = process.env.FABRIC_API_KEY;
|
||||
@@ -40,10 +48,27 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
|
||||
}
|
||||
|
||||
this.logger.log(`socket connected: ${client.id}`);
|
||||
|
||||
const userId = this.userIdFromClient(client);
|
||||
client.data.userId = userId;
|
||||
this.onlineUsers.add(userId);
|
||||
this.server.emit('presence.online', {
|
||||
userId,
|
||||
onlineCount: this.onlineUsers.size,
|
||||
occurredAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
handleDisconnect(client: Socket): void {
|
||||
this.logger.log(`socket disconnected: ${client.id}`);
|
||||
|
||||
const userId = typeof client.data.userId === 'string' ? client.data.userId : `anon:${client.id}`;
|
||||
this.onlineUsers.delete(userId);
|
||||
this.server.emit('presence.offline', {
|
||||
userId,
|
||||
onlineCount: this.onlineUsers.size,
|
||||
occurredAt: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('join_channel')
|
||||
@@ -66,6 +91,38 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
@SubscribeMessage('typing.start')
|
||||
typingStart(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody() body: { channelId?: string },
|
||||
): { ok: boolean } {
|
||||
if (!body?.channelId) return { ok: false };
|
||||
|
||||
const userId = typeof client.data.userId === 'string' ? client.data.userId : `anon:${client.id}`;
|
||||
this.server.to(`channel:${body.channelId}`).emit('typing.start', {
|
||||
channelId: body.channelId,
|
||||
userId,
|
||||
occurredAt: new Date().toISOString(),
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
@SubscribeMessage('typing.stop')
|
||||
typingStop(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody() body: { channelId?: string },
|
||||
): { ok: boolean } {
|
||||
if (!body?.channelId) return { ok: false };
|
||||
|
||||
const userId = typeof client.data.userId === 'string' ? client.data.userId : `anon:${client.id}`;
|
||||
this.server.to(`channel:${body.channelId}`).emit('typing.stop', {
|
||||
channelId: body.channelId,
|
||||
userId,
|
||||
occurredAt: new Date().toISOString(),
|
||||
});
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
emitChannelEvent(channelId: string, event: string, data: Record<string, unknown>): void {
|
||||
this.server.to(`channel:${channelId}`).emit(event, data);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
### 2.4 实时通信(MVP 后半)
|
||||
- [x] WebSocket 网关接入
|
||||
- [x] message.created/updated/deleted 事件广播
|
||||
- [ ] 在线状态 + typing 事件
|
||||
- [x] 在线状态 + typing 事件
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user