fix: Broken voice states and connectivity drops
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { Router } from 'express';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { getAllPublicServers } from '../cqrs';
|
||||
import { getReleaseManifestUrl } from '../config/variables';
|
||||
import { SERVER_BUILD_VERSION } from '../generated/build-version';
|
||||
import { connectedUsers } from '../websocket/state';
|
||||
|
||||
const router = Router();
|
||||
const SERVER_INSTANCE_ID = typeof process.env.METOYOU_SERVER_INSTANCE_ID === 'string'
|
||||
&& process.env.METOYOU_SERVER_INSTANCE_ID.trim().length > 0
|
||||
? process.env.METOYOU_SERVER_INSTANCE_ID.trim()
|
||||
: randomUUID();
|
||||
|
||||
function getServerProjectVersion(): string {
|
||||
return typeof process.env.METOYOU_SERVER_VERSION === 'string' && process.env.METOYOU_SERVER_VERSION.trim().length > 0
|
||||
@@ -20,6 +25,7 @@ router.get('/health', async (_req, res) => {
|
||||
timestamp: Date.now(),
|
||||
serverCount: servers.length,
|
||||
connectedUsers: connectedUsers.size,
|
||||
serverInstanceId: SERVER_INSTANCE_ID,
|
||||
serverVersion: getServerProjectVersion(),
|
||||
releaseManifestUrl: getReleaseManifestUrl()
|
||||
});
|
||||
|
||||
@@ -10,8 +10,14 @@ interface WsMessage {
|
||||
export function broadcastToServer(serverId: string, message: WsMessage, excludeOderId?: string): void {
|
||||
console.log(`Broadcasting to server ${serverId}, excluding ${excludeOderId}:`, message.type);
|
||||
|
||||
// Deduplicate by oderId so users with multiple connections (e.g. from
|
||||
// different signal URLs routing to the same server) receive the
|
||||
// broadcast only once.
|
||||
const sentToOderIds = new Set<string>();
|
||||
|
||||
connectedUsers.forEach((user) => {
|
||||
if (user.serverIds.has(serverId) && user.oderId !== excludeOderId) {
|
||||
if (user.serverIds.has(serverId) && user.oderId !== excludeOderId && !sentToOderIds.has(user.oderId)) {
|
||||
sentToOderIds.add(user.oderId);
|
||||
console.log(` -> Sending to ${user.displayName} (${user.oderId})`);
|
||||
user.ws.send(JSON.stringify(message));
|
||||
}
|
||||
|
||||
@@ -44,12 +44,18 @@ function sendServerUsers(user: ConnectedUser, serverId: string): void {
|
||||
|
||||
function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: string): void {
|
||||
const newOderId = readMessageId(message['oderId']) ?? connectionId;
|
||||
const newScope = typeof message['connectionScope'] === 'string' ? message['connectionScope'] : undefined;
|
||||
|
||||
// Close stale connections from the same identity so offer routing
|
||||
// always targets the freshest socket (e.g. after page refresh).
|
||||
// Close stale connections from the same identity AND the same connection
|
||||
// scope so offer routing always targets the freshest socket (e.g. after
|
||||
// page refresh). Connections with a *different* scope (= a different
|
||||
// signal URL that happens to route to this server) are left untouched so
|
||||
// multi-signal-URL setups don't trigger an eviction loop.
|
||||
connectedUsers.forEach((existing, existingId) => {
|
||||
if (existingId !== connectionId && existing.oderId === newOderId) {
|
||||
console.log(`Closing stale connection for ${newOderId} (old=${existingId}, new=${connectionId})`);
|
||||
if (existingId !== connectionId
|
||||
&& existing.oderId === newOderId
|
||||
&& existing.connectionScope === newScope) {
|
||||
console.log(`Closing stale connection for ${newOderId} (old=${existingId}, new=${connectionId}, scope=${newScope ?? 'none'})`);
|
||||
|
||||
try {
|
||||
existing.ws.close();
|
||||
@@ -61,6 +67,7 @@ function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: s
|
||||
|
||||
user.oderId = newOderId;
|
||||
user.displayName = normalizeDisplayName(message['displayName'], normalizeDisplayName(user.displayName));
|
||||
user.connectionScope = newScope;
|
||||
connectedUsers.set(connectionId, user);
|
||||
console.log(`User identified: ${user.displayName} (${user.oderId})`);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,13 @@ export interface ConnectedUser {
|
||||
serverIds: Set<string>;
|
||||
viewedServerId?: string;
|
||||
displayName?: string;
|
||||
/**
|
||||
* Opaque scope string sent by the client (typically the signal URL it
|
||||
* connected through). Stale-connection eviction only targets connections
|
||||
* that share the same (oderId, connectionScope) pair, so multiple signal
|
||||
* URLs routing to the same server coexist without an eviction loop.
|
||||
*/
|
||||
connectionScope?: string;
|
||||
/** Timestamp of the last pong received (used to detect dead connections). */
|
||||
lastPong: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user