fix: Fix multiple bugs with new authentication flow

This commit is contained in:
2026-06-07 15:04:21 +02:00
parent 9fc26b1ccf
commit 83456c018c
137 changed files with 4710 additions and 281 deletions

View File

@@ -5,7 +5,8 @@ import {
findUserByOderId,
getServerIdsForOderId,
getUniqueUsersInServer,
isOderIdConnectedToServer
isOderIdConnectedToServer,
notifyOtherConnectionsForOderId
} from './broadcast';
import {
authorizeWebSocketJoin,
@@ -72,6 +73,74 @@ function buildPresenceUserPayload(user: ConnectedUser): {
};
}
function normalizeClientInstanceId(value: unknown): string | undefined {
if (typeof value !== 'string') {
return undefined;
}
const normalized = value.trim();
return normalized || undefined;
}
function readVoiceConnected(message: WsMessage): boolean {
const voiceState = message['voiceState'];
if (!voiceState || typeof voiceState !== 'object') {
return message['isConnected'] === true;
}
return (voiceState as { isConnected?: boolean }).isConnected === true;
}
function evictStaleClientInstanceConnections(
oderId: string,
connectionScope: string | undefined,
clientInstanceId: string | undefined,
keepConnectionId: string
): void {
if (!clientInstanceId) {
return;
}
connectedUsers.forEach((candidate, connectionId) => {
if (
connectionId === keepConnectionId
|| candidate.oderId !== oderId
|| candidate.connectionScope !== connectionScope
|| candidate.clientInstanceId !== clientInstanceId
) {
return;
}
try {
candidate.ws.close();
} catch {
console.warn(`Failed to close stale connection ${connectionId} for ${oderId}`);
}
connectedUsers.delete(connectionId);
});
}
function clearVoiceActiveForOderId(oderId: string, exceptConnectionId?: string): void {
connectedUsers.forEach((candidate, connectionId) => {
if (candidate.oderId !== oderId || connectionId === exceptConnectionId) {
return;
}
candidate.voiceActive = false;
connectedUsers.set(connectionId, candidate);
});
}
function sendVoiceStateSnapshotToConnection(user: ConnectedUser, snapshot: Record<string, unknown>): void {
user.ws.send(JSON.stringify({
type: 'voice_state',
...snapshot
}));
}
function readMessageId(value: unknown): string | undefined {
if (typeof value !== 'string') {
return undefined;
@@ -198,13 +267,17 @@ async function handleIdentify(user: ConnectedUser, message: WsMessage, connectio
const newOderId = session.user.id;
const newScope = typeof message['connectionScope'] === 'string' ? message['connectionScope'] : undefined;
const newClientInstanceId = normalizeClientInstanceId(message['clientInstanceId']);
const previousDisplayName = normalizeDisplayName(user.displayName);
const previousDescription = user.description;
const previousProfileUpdatedAt = user.profileUpdatedAt;
const previousHomeSignalServerUrl = user.homeSignalServerUrl;
evictStaleClientInstanceConnections(newOderId, newScope, newClientInstanceId, connectionId);
user.oderId = newOderId;
user.authenticated = true;
user.clientInstanceId = newClientInstanceId;
user.displayName = normalizeDisplayName(message['displayName'], normalizeDisplayName(user.displayName));
if (Object.prototype.hasOwnProperty.call(message, 'description')) {
@@ -223,6 +296,17 @@ async function handleIdentify(user: ConnectedUser, message: WsMessage, connectio
connectedUsers.set(connectionId, user);
console.log(`User identified: ${user.displayName} (${user.oderId})`);
const voiceSnapshot = Array.from(connectedUsers.entries()).find(([otherConnectionId, otherUser]) =>
otherConnectionId !== connectionId
&& otherUser.oderId === newOderId
&& otherUser.voiceActive
&& otherUser.voiceStateSnapshot
)?.[1]?.voiceStateSnapshot;
if (voiceSnapshot) {
sendVoiceStateSnapshotToConnection(user, voiceSnapshot);
}
if (
user.displayName === previousDisplayName
&& user.description === previousDescription
@@ -240,7 +324,7 @@ async function handleIdentify(user: ConnectedUser, message: WsMessage, connectio
...buildPresenceUserPayload(user),
serverId
},
user.oderId
{ excludeConnectionId: connectionId }
);
}
}
@@ -287,7 +371,7 @@ async function handleJoinServer(user: ConnectedUser, message: WsMessage, connect
...buildPresenceUserPayload(user),
serverId: sid
},
user.oderId
{ excludeIdentityOderId: user.oderId }
);
}
}
@@ -338,7 +422,7 @@ function handleLeaveServer(user: ConnectedUser, message: WsMessage, connectionId
serverId: leaveSid,
serverIds: remainingServerIds
},
user.oderId
{ excludeIdentityOderId: user.oderId }
);
}
@@ -394,7 +478,7 @@ async function forwardRtcMessage(user: ConnectedUser, message: WsMessage): Promi
}
}
function handleChatMessage(user: ConnectedUser, message: WsMessage): void {
function handleChatMessage(user: ConnectedUser, message: WsMessage, connectionId: string): void {
const chatSid = (message['serverId'] as string | undefined) ?? user.viewedServerId;
if (chatSid && user.serverIds.has(chatSid)) {
@@ -404,18 +488,38 @@ function handleChatMessage(user: ConnectedUser, message: WsMessage): void {
message: message['message'],
senderId: user.oderId,
senderName: user.displayName,
clientInstanceId: user.clientInstanceId,
timestamp: Date.now()
});
}, { excludeConnectionId: connectionId });
}
}
function handleVoiceState(user: ConnectedUser, message: WsMessage): void {
function handleVoiceState(user: ConnectedUser, message: WsMessage, connectionId: string): void {
const serverId = readMessageId(message['serverId']) ?? user.viewedServerId;
if (!serverId || !user.serverIds.has(serverId)) {
return;
}
const isConnected = readVoiceConnected(message);
if (isConnected) {
clearVoiceActiveForOderId(user.oderId, connectionId);
user.voiceActive = true;
user.voiceStateSnapshot = {
...message,
type: 'voice_state',
serverId,
oderId: user.oderId,
displayName: normalizeDisplayName(user.displayName)
};
} else {
user.voiceActive = false;
user.voiceStateSnapshot = undefined;
}
connectedUsers.set(connectionId, user);
broadcastToServer(
serverId,
{
@@ -425,11 +529,19 @@ function handleVoiceState(user: ConnectedUser, message: WsMessage): void {
oderId: user.oderId,
displayName: normalizeDisplayName(user.displayName)
},
user.oderId
{ excludeConnectionId: connectionId }
);
}
function handleTyping(user: ConnectedUser, message: WsMessage): void {
function handleVoiceClientTakeover(user: ConnectedUser, message: WsMessage, connectionId: string): void {
notifyOtherConnectionsForOderId(user.oderId, {
type: 'voice_client_takeover',
clientInstanceId: normalizeClientInstanceId(message['clientInstanceId']) ?? user.clientInstanceId,
requestedByClientInstanceId: normalizeClientInstanceId(message['clientInstanceId']) ?? user.clientInstanceId
}, connectionId);
}
function handleTyping(user: ConnectedUser, message: WsMessage, connectionId: string): void {
const typingSid = (message['serverId'] as string | undefined) ?? user.viewedServerId;
const channelId = typeof message['channelId'] === 'string' && message['channelId'].trim() ? message['channelId'].trim() : 'general';
const isTyping = message['isTyping'] !== false;
@@ -443,9 +555,10 @@ function handleTyping(user: ConnectedUser, message: WsMessage): void {
channelId,
isTyping,
oderId: user.oderId,
displayName: user.displayName
displayName: user.displayName,
clientInstanceId: user.clientInstanceId
},
user.oderId
{ excludeConnectionId: connectionId }
);
}
}
@@ -475,7 +588,7 @@ function handleStatusUpdate(user: ConnectedUser, message: WsMessage, connectionI
oderId: user.oderId,
status
},
user.oderId
{ excludeConnectionId: connectionId }
);
}
}
@@ -520,7 +633,7 @@ function handleServerIconSyncRequest(user: ConnectedUser, message: WsMessage): v
user.ws.send(JSON.stringify({ type: 'server_icon_sync_peers', serverId, users }));
}
async function handlePluginEvent(user: ConnectedUser, message: WsMessage): Promise<void> {
async function handlePluginEvent(user: ConnectedUser, message: WsMessage, connectionId: string): Promise<void> {
const serverId = readMessageId(message['serverId']) ?? user.viewedServerId;
const pluginId = readMessageId(message['pluginId']);
const eventName = readMessageId(message['eventName']);
@@ -565,7 +678,7 @@ async function handlePluginEvent(user: ConnectedUser, message: WsMessage): Promi
sourceUserId: user.oderId,
emittedAt: Date.now()
},
user.oderId
{ excludeConnectionId: connectionId }
);
} catch (error) {
sendPluginError(user, error, message);
@@ -623,15 +736,19 @@ export async function handleWebSocketMessage(connectionId: string, message: WsMe
break;
case 'chat_message':
handleChatMessage(user, message);
handleChatMessage(user, message, connectionId);
break;
case 'voice_state':
handleVoiceState(user, message);
handleVoiceState(user, message, connectionId);
break;
case 'voice_client_takeover':
handleVoiceClientTakeover(user, message, connectionId);
break;
case 'typing':
handleTyping(user, message);
handleTyping(user, message, connectionId);
break;
case 'status_update':
@@ -647,7 +764,7 @@ export async function handleWebSocketMessage(connectionId: string, message: WsMe
break;
case 'plugin_event':
await handlePluginEvent(user, message);
await handlePluginEvent(user, message, connectionId);
break;
default: