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;
|
server!: Server;
|
||||||
|
|
||||||
private readonly logger = new Logger(RealtimeGateway.name);
|
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 {
|
handleConnection(client: Socket): void {
|
||||||
const expected = process.env.FABRIC_API_KEY;
|
const expected = process.env.FABRIC_API_KEY;
|
||||||
@@ -40,10 +48,27 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(`socket connected: ${client.id}`);
|
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 {
|
handleDisconnect(client: Socket): void {
|
||||||
this.logger.log(`socket disconnected: ${client.id}`);
|
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')
|
@SubscribeMessage('join_channel')
|
||||||
@@ -66,6 +91,38 @@ export class RealtimeGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
return { ok: true };
|
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 {
|
emitChannelEvent(channelId: string, event: string, data: Record<string, unknown>): void {
|
||||||
this.server.to(`channel:${channelId}`).emit(event, data);
|
this.server.to(`channel:${channelId}`).emit(event, data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
### 2.4 实时通信(MVP 后半)
|
### 2.4 实时通信(MVP 后半)
|
||||||
- [x] WebSocket 网关接入
|
- [x] WebSocket 网关接入
|
||||||
- [x] message.created/updated/deleted 事件广播
|
- [x] message.created/updated/deleted 事件广播
|
||||||
- [ ] 在线状态 + typing 事件
|
- [x] 在线状态 + typing 事件
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user