fix: Fix multiple bugs with new authentication flow
This commit is contained in:
@@ -164,7 +164,11 @@ The browser also sends a lightweight `keepalive` message on the signaling socket
|
||||
|
||||
### Server-side connection hygiene
|
||||
|
||||
Browsers do not reliably fire WebSocket close events during page refresh or navigation (especially Chromium). The server's `handleIdentify` now closes any existing connection that shares the same `oderId` but a different `connectionId`. This guarantees `findUserByOderId` always routes offers and presence events to the freshest socket, eliminating a class of bugs where signaling messages landed on a dead tab's socket and were silently lost.
|
||||
Browsers do not reliably fire WebSocket close events during page refresh or navigation (especially Chromium). On `identify`, the server evicts stale sockets that share the same `(oderId, connectionScope, clientInstanceId)` tuple so a refreshed tab does not leave a zombie connection behind.
|
||||
|
||||
Multi-device sessions keep **multiple** open connections for the same `oderId` (different `clientInstanceId` values). Server broadcasts exclude only the sending **connection id**, not the whole identity, so chat/typing/voice-state updates reach every logged-in device. Presence `user_joined` / `user_left` broadcasts still exclude the whole identity so other users never see duplicate join/leave events.
|
||||
|
||||
RTC offers/answers/ICE are routed to the connection marked `voiceActive` for the target user (fallback: any open connection). Voice ownership is tracked per connection from `voice_state` payloads that include `clientInstanceId`.
|
||||
|
||||
Join and leave broadcasts are also identity-aware: `handleJoinServer` only broadcasts `user_joined` when the identity is genuinely new to that server (not just a second WebSocket connection for the same user), and `handleLeaveServer` / dead-connection cleanup only broadcast `user_left` when no other open connection for that identity remains in the server. The `user_left` payload includes `serverIds` listing the rooms the identity still belongs to, so the client can subtract correctly without over-removing.
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import { SignalingManager } from './signaling/signaling.manager';
|
||||
import { SignalingTransportHandler } from './signaling/signaling-transport-handler';
|
||||
import { WebRtcStateController } from './state/webrtc-state-controller';
|
||||
import { AuthTokenStoreService } from '../../domains/authentication';
|
||||
import { ClientInstanceService } from '../../core/platform/client-instance.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -51,6 +52,7 @@ export class WebRTCService implements OnDestroy {
|
||||
private readonly screenShareSourcePicker = inject(ScreenShareSourcePickerService);
|
||||
private readonly iceServerSettings = inject(IceServerSettingsService);
|
||||
private readonly authTokenStore = inject(AuthTokenStoreService);
|
||||
private readonly clientInstance = inject(ClientInstanceService);
|
||||
|
||||
private readonly logger = new WebRTCLogger(() => this.debugging.enabled());
|
||||
private readonly state = new WebRtcStateController();
|
||||
@@ -161,7 +163,8 @@ export class WebRTCService implements OnDestroy {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getClientInstanceId: () => this.clientInstance.getClientInstanceId()
|
||||
});
|
||||
|
||||
// Now wire up cross-references (all managers are instantiated)
|
||||
@@ -691,11 +694,17 @@ export class WebRTCService implements OnDestroy {
|
||||
}
|
||||
|
||||
private relayBroadcastEvent(event: ChatEvent): void {
|
||||
const clientInstanceId = this.clientInstance.getClientInstanceId();
|
||||
|
||||
if (event.type === 'chat-message' && event.message?.roomId) {
|
||||
this.signalingTransportHandler.sendRawMessage({
|
||||
type: 'chat_message',
|
||||
serverId: event.message.roomId,
|
||||
message: event.message
|
||||
message: {
|
||||
...event.message,
|
||||
clientInstanceId
|
||||
},
|
||||
clientInstanceId
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -705,11 +714,27 @@ export class WebRTCService implements OnDestroy {
|
||||
this.signalingTransportHandler.sendRawMessage({
|
||||
...event,
|
||||
type: 'voice_state',
|
||||
serverId: event.voiceState.serverId
|
||||
serverId: event.voiceState.serverId,
|
||||
voiceState: {
|
||||
...event.voiceState,
|
||||
clientInstanceId
|
||||
},
|
||||
clientInstanceId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
requestVoiceClientTakeover(): void {
|
||||
this.signalingTransportHandler.sendRawMessage({
|
||||
type: 'voice_client_takeover',
|
||||
clientInstanceId: this.clientInstance.getClientInstanceId()
|
||||
});
|
||||
}
|
||||
|
||||
getClientInstanceId(): string {
|
||||
return this.clientInstance.getClientInstanceId();
|
||||
}
|
||||
|
||||
/** Disconnect from the signaling server and clean up all state. */
|
||||
disconnect(): void {
|
||||
this.leaveRoom();
|
||||
|
||||
@@ -44,6 +44,8 @@ export interface IdentifyCredentials {
|
||||
profileUpdatedAt?: number;
|
||||
/** Public signal-server URL where this user registered. */
|
||||
homeSignalServerUrl?: string;
|
||||
/** Stable per-install client id used for multi-device session routing. */
|
||||
clientInstanceId?: string;
|
||||
}
|
||||
|
||||
/** Last-joined server info, used for reconnection. */
|
||||
@@ -76,4 +78,6 @@ export interface VoiceStateSnapshot {
|
||||
roomId?: string;
|
||||
/** The voice channel server ID, if applicable. */
|
||||
serverId?: string;
|
||||
/** Install-scoped client id that owns active voice for this snapshot. */
|
||||
clientInstanceId?: string;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ interface SignalingTransportHandlerDependencies<TMessage> {
|
||||
logger: WebRTCLogger;
|
||||
getLocalPeerId(): string;
|
||||
resolveSessionToken(signalUrl?: string): string | null;
|
||||
getClientInstanceId(): string;
|
||||
}
|
||||
|
||||
export class SignalingTransportHandler<TMessage> {
|
||||
@@ -201,13 +202,16 @@ export class SignalingTransportHandler<TMessage> {
|
||||
return;
|
||||
}
|
||||
|
||||
const clientInstanceId = this.dependencies.getClientInstanceId();
|
||||
|
||||
this.lastIdentifyCredentials = {
|
||||
oderId,
|
||||
token,
|
||||
displayName: normalizedDisplayName,
|
||||
description: normalizedDescription,
|
||||
profileUpdatedAt: normalizedProfileUpdatedAt,
|
||||
homeSignalServerUrl: normalizedHomeSignalServerUrl
|
||||
homeSignalServerUrl: normalizedHomeSignalServerUrl,
|
||||
clientInstanceId
|
||||
};
|
||||
|
||||
if (signalUrl) {
|
||||
@@ -219,7 +223,8 @@ export class SignalingTransportHandler<TMessage> {
|
||||
description: normalizedDescription,
|
||||
profileUpdatedAt: normalizedProfileUpdatedAt,
|
||||
homeSignalServerUrl: normalizedHomeSignalServerUrl,
|
||||
connectionScope: signalUrl
|
||||
connectionScope: signalUrl,
|
||||
clientInstanceId
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -240,7 +245,8 @@ export class SignalingTransportHandler<TMessage> {
|
||||
description: normalizedDescription,
|
||||
profileUpdatedAt: normalizedProfileUpdatedAt,
|
||||
homeSignalServerUrl: normalizedHomeSignalServerUrl,
|
||||
connectionScope: managerSignalUrl
|
||||
connectionScope: managerSignalUrl,
|
||||
clientInstanceId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,8 @@ export class SignalingManager {
|
||||
description: credentials.description,
|
||||
profileUpdatedAt: credentials.profileUpdatedAt,
|
||||
homeSignalServerUrl: credentials.homeSignalServerUrl,
|
||||
connectionScope: this.lastSignalingUrl ?? undefined
|
||||
connectionScope: this.lastSignalingUrl ?? undefined,
|
||||
clientInstanceId: credentials.clientInstanceId
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user