Reconnection when signal server is not active and minor changes
This commit is contained in:
@@ -563,10 +563,10 @@ export class ServerDirectoryService {
|
||||
);
|
||||
}
|
||||
|
||||
/** Notify the directory that a user has left a server. */
|
||||
notifyLeave(serverId: string, userId: string): Observable<void> {
|
||||
/** Remove a user's remembered membership after leaving a server. */
|
||||
notifyLeave(serverId: string, userId: string, selector?: ServerSourceSelector): Observable<void> {
|
||||
return this.http
|
||||
.post<void>(`${this.buildApiBaseUrl()}/servers/${serverId}/leave`, { userId })
|
||||
.post<void>(`${this.buildApiBaseUrl(selector)}/servers/${serverId}/leave`, { userId })
|
||||
.pipe(
|
||||
catchError((error) => {
|
||||
console.error('Failed to notify leave:', error);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<div class="mx-auto flex min-h-[calc(100vh-8rem)] max-w-4xl items-center justify-center">
|
||||
<div class="w-full overflow-hidden rounded-3xl border border-border bg-card/90 shadow-2xl backdrop-blur">
|
||||
<div class="border-b border-border bg-gradient-to-br from-primary/20 via-transparent to-blue-500/10 px-6 py-8 sm:px-10">
|
||||
<div class="inline-flex items-center rounded-full border border-border bg-secondary/70 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.25em] text-muted-foreground">
|
||||
<div
|
||||
class="inline-flex items-center rounded-full border border-border bg-secondary/70 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.25em] text-muted-foreground"
|
||||
>
|
||||
Invite link
|
||||
</div>
|
||||
<h1 class="mt-4 text-3xl font-semibold tracking-tight text-foreground sm:text-4xl">
|
||||
@@ -51,9 +53,7 @@
|
||||
@if (invite()!.server.hasPassword) {
|
||||
<span class="rounded-full bg-secondary px-2.5 py-1 text-muted-foreground">Password bypassed by invite</span>
|
||||
}
|
||||
<span class="rounded-full bg-primary/10 px-2.5 py-1 text-primary">
|
||||
Expires {{ invite()!.expiresAt | date:'medium' }}
|
||||
</span>
|
||||
<span class="rounded-full bg-primary/10 px-2.5 py-1 text-primary"> Expires {{ invite()!.expiresAt | date: 'medium' }} </span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -165,9 +165,7 @@
|
||||
Owner: <span class="text-foreground/80">{{ server.ownerName || server.ownerId || 'Unknown' }}</span>
|
||||
</div>
|
||||
@if (server.hasPassword && !server.isPrivate && !isServerMarkedBanned(server)) {
|
||||
<div class="text-muted-foreground">
|
||||
Access: <span class="text-foreground/80">Password required</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground">Access: <span class="text-foreground/80">Password required</span></div>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
@@ -327,9 +325,7 @@
|
||||
id="create-server-password"
|
||||
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
Users who already joined keep access even if you change the password later.
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-muted-foreground">Users who already joined keep access even if you change the password later.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ export class ServersRailComponent {
|
||||
this.prepareVoiceContext(room);
|
||||
|
||||
if (this.webrtc.hasJoinedServer(room.id) && roomWsUrl === currentWsUrl) {
|
||||
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: false }));
|
||||
this.store.dispatch(RoomsActions.viewServer({ room }));
|
||||
} else {
|
||||
await this.attemptJoinRoom(room);
|
||||
@@ -301,10 +302,31 @@ export class ServersRailComponent {
|
||||
if (errorCode === 'BANNED') {
|
||||
this.bannedServerName.set(room.name);
|
||||
this.showBannedDialog.set(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shouldFallbackToOfflineView(error)) {
|
||||
this.closePasswordDialog();
|
||||
this.store.dispatch(RoomsActions.setSignalServerReconnecting({ isReconnecting: true }));
|
||||
this.store.dispatch(RoomsActions.viewServer({ room }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private shouldFallbackToOfflineView(error: unknown): boolean {
|
||||
const serverError = error as {
|
||||
error?: { errorCode?: string };
|
||||
status?: number;
|
||||
};
|
||||
const errorCode = serverError?.error?.errorCode;
|
||||
const status = serverError?.status;
|
||||
|
||||
return errorCode === 'SERVER_NOT_FOUND'
|
||||
|| status === 0
|
||||
|| status === 404
|
||||
|| (typeof status === 'number' && status >= 500);
|
||||
}
|
||||
|
||||
private toServerInfo(room: Room) {
|
||||
return {
|
||||
id: room.id,
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
/>
|
||||
<span class="text-sm font-semibold text-foreground truncate">{{ roomName() }}</span>
|
||||
|
||||
@if (showRoomReconnectNotice()) {
|
||||
<span class="inline-flex items-center gap-1 rounded bg-destructive/15 px-2 py-0.5 text-xs text-destructive">
|
||||
<ng-icon
|
||||
name="lucideRefreshCw"
|
||||
class="h-3.5 w-3.5 animate-spin"
|
||||
/>
|
||||
Reconnecting to signal server…
|
||||
</span>
|
||||
}
|
||||
|
||||
@if (roomDescription()) {
|
||||
<span class="hidden md:inline text-sm text-muted-foreground border-l border-border pl-2 truncate">
|
||||
{{ roomDescription() }}
|
||||
|
||||
@@ -15,10 +15,14 @@ import {
|
||||
lucideX,
|
||||
lucideChevronLeft,
|
||||
lucideHash,
|
||||
lucideMenu
|
||||
lucideMenu,
|
||||
lucideRefreshCw
|
||||
} from '@ng-icons/lucide';
|
||||
import { Router } from '@angular/router';
|
||||
import { selectCurrentRoom } from '../../store/rooms/rooms.selectors';
|
||||
import {
|
||||
selectCurrentRoom,
|
||||
selectIsSignalServerReconnecting
|
||||
} from '../../store/rooms/rooms.selectors';
|
||||
import { RoomsActions } from '../../store/rooms/rooms.actions';
|
||||
import { selectCurrentUser } from '../../store/users/users.selectors';
|
||||
import { ServerDirectoryService } from '../../core/services/server-directory.service';
|
||||
@@ -52,7 +56,8 @@ type ElectronWindow = Window & {
|
||||
lucideX,
|
||||
lucideChevronLeft,
|
||||
lucideHash,
|
||||
lucideMenu })
|
||||
lucideMenu,
|
||||
lucideRefreshCw })
|
||||
],
|
||||
templateUrl: './title-bar.component.html'
|
||||
})
|
||||
@@ -80,9 +85,17 @@ export class TitleBarComponent {
|
||||
isReconnecting = computed(() => !this.webrtc.isConnected() && this.webrtc.hasEverConnected());
|
||||
isAuthed = computed(() => !!this.currentUser());
|
||||
currentRoom = this.store.selectSignal(selectCurrentRoom);
|
||||
isSignalServerReconnecting = this.store.selectSignal(selectIsSignalServerReconnecting);
|
||||
inRoom = computed(() => !!this.currentRoom());
|
||||
roomName = computed(() => this.currentRoom()?.name || '');
|
||||
roomDescription = computed(() => this.currentRoom()?.description || '');
|
||||
showRoomReconnectNotice = computed(() =>
|
||||
this.inRoom() && (
|
||||
this.isSignalServerReconnecting()
|
||||
|| this.webrtc.shouldShowConnectionError()
|
||||
|| this.isReconnecting()
|
||||
)
|
||||
);
|
||||
private _showMenu = signal(false);
|
||||
showMenu = computed(() => this._showMenu());
|
||||
showLeaveConfirm = signal(false);
|
||||
|
||||
@@ -67,6 +67,7 @@ export const RoomsActions = createActionGroup({
|
||||
'Rename Channel': props<{ channelId: string; name: string }>(),
|
||||
|
||||
'Clear Search Results': emptyProps(),
|
||||
'Set Connecting': props<{ isConnecting: boolean }>()
|
||||
'Set Connecting': props<{ isConnecting: boolean }>(),
|
||||
'Set Signal Server Reconnecting': props<{ isReconnecting: boolean }>()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -95,6 +95,7 @@ function isWrongServer(
|
||||
|
||||
interface RoomPresenceSignalingMessage {
|
||||
type: string;
|
||||
reason?: string;
|
||||
serverId?: string;
|
||||
users?: { oderId: string; displayName: string }[];
|
||||
oderId?: string;
|
||||
@@ -471,6 +472,15 @@ export class RoomsEffects {
|
||||
});
|
||||
}
|
||||
|
||||
if (currentUser && room) {
|
||||
this.serverDirectory.notifyLeave(roomId, currentUser.id, {
|
||||
sourceId: room.sourceId,
|
||||
sourceUrl: room.sourceUrl
|
||||
}).subscribe({
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
// Delete from local DB
|
||||
this.db.deleteRoom(roomId);
|
||||
|
||||
@@ -764,7 +774,11 @@ export class RoomsEffects {
|
||||
})
|
||||
);
|
||||
|
||||
return [UsersActions.clearUsers(), ...joinActions];
|
||||
return [
|
||||
RoomsActions.setSignalServerReconnecting({ isReconnecting: false }),
|
||||
UsersActions.clearUsers(),
|
||||
...joinActions
|
||||
];
|
||||
}
|
||||
|
||||
case 'user_joined': {
|
||||
@@ -780,6 +794,7 @@ export class RoomsEffects {
|
||||
};
|
||||
|
||||
return [
|
||||
RoomsActions.setSignalServerReconnecting({ isReconnecting: false }),
|
||||
UsersActions.userJoined({
|
||||
user: buildSignalingUser(joinedUser, buildKnownUserExtras(currentRoom, joinedUser.oderId))
|
||||
})
|
||||
@@ -794,7 +809,20 @@ export class RoomsEffects {
|
||||
return EMPTY;
|
||||
|
||||
this.knownVoiceUsers.delete(signalingMessage.oderId);
|
||||
return [UsersActions.userLeft({ userId: signalingMessage.oderId })];
|
||||
return [
|
||||
RoomsActions.setSignalServerReconnecting({ isReconnecting: false }),
|
||||
UsersActions.userLeft({ userId: signalingMessage.oderId })
|
||||
];
|
||||
}
|
||||
|
||||
case 'access_denied': {
|
||||
if (isWrongServer(signalingMessage.serverId, viewedServerId))
|
||||
return EMPTY;
|
||||
|
||||
if (signalingMessage.reason !== 'SERVER_NOT_FOUND')
|
||||
return EMPTY;
|
||||
|
||||
return [RoomsActions.setSignalServerReconnecting({ isReconnecting: true })];
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -82,6 +82,8 @@ export interface RoomsState {
|
||||
isConnecting: boolean;
|
||||
/** Whether the user is connected to a room. */
|
||||
isConnected: boolean;
|
||||
/** Whether the current room is using locally cached data while reconnecting. */
|
||||
isSignalServerReconnecting: boolean;
|
||||
/** Whether rooms are being loaded from local storage. */
|
||||
loading: boolean;
|
||||
/** Most recent error message, if any. */
|
||||
@@ -98,6 +100,7 @@ export const initialState: RoomsState = {
|
||||
isSearching: false,
|
||||
isConnecting: false,
|
||||
isConnected: false,
|
||||
isSignalServerReconnecting: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
activeChannelId: 'general'
|
||||
@@ -159,6 +162,7 @@ export const roomsReducer = createReducer(
|
||||
currentRoom: enriched,
|
||||
savedRooms: upsertRoom(state.savedRooms, enriched),
|
||||
isConnecting: false,
|
||||
isSignalServerReconnecting: false,
|
||||
isConnected: true,
|
||||
activeChannelId: 'general'
|
||||
};
|
||||
@@ -185,6 +189,7 @@ export const roomsReducer = createReducer(
|
||||
currentRoom: enriched,
|
||||
savedRooms: upsertRoom(state.savedRooms, enriched),
|
||||
isConnecting: false,
|
||||
isSignalServerReconnecting: false,
|
||||
isConnected: true,
|
||||
activeChannelId: 'general'
|
||||
};
|
||||
@@ -206,6 +211,7 @@ export const roomsReducer = createReducer(
|
||||
...state,
|
||||
currentRoom: null,
|
||||
roomSettings: null,
|
||||
isSignalServerReconnecting: false,
|
||||
isConnecting: false,
|
||||
isConnected: false
|
||||
})),
|
||||
@@ -279,6 +285,7 @@ export const roomsReducer = createReducer(
|
||||
// Delete room
|
||||
on(RoomsActions.deleteRoomSuccess, (state, { roomId }) => ({
|
||||
...state,
|
||||
isSignalServerReconnecting: state.currentRoom?.id === roomId ? false : state.isSignalServerReconnecting,
|
||||
savedRooms: state.savedRooms.filter((room) => room.id !== roomId),
|
||||
currentRoom: state.currentRoom?.id === roomId ? null : state.currentRoom
|
||||
})),
|
||||
@@ -286,6 +293,7 @@ export const roomsReducer = createReducer(
|
||||
// Forget room (local only)
|
||||
on(RoomsActions.forgetRoomSuccess, (state, { roomId }) => ({
|
||||
...state,
|
||||
isSignalServerReconnecting: state.currentRoom?.id === roomId ? false : state.isSignalServerReconnecting,
|
||||
savedRooms: state.savedRooms.filter((room) => room.id !== roomId),
|
||||
currentRoom: state.currentRoom?.id === roomId ? null : state.currentRoom
|
||||
})),
|
||||
@@ -295,6 +303,7 @@ export const roomsReducer = createReducer(
|
||||
...state,
|
||||
currentRoom: enrichRoom(room),
|
||||
savedRooms: upsertRoom(state.savedRooms, room),
|
||||
isSignalServerReconnecting: false,
|
||||
isConnected: true
|
||||
})),
|
||||
|
||||
@@ -303,6 +312,7 @@ export const roomsReducer = createReducer(
|
||||
...state,
|
||||
currentRoom: null,
|
||||
roomSettings: null,
|
||||
isSignalServerReconnecting: false,
|
||||
isConnected: false
|
||||
})),
|
||||
|
||||
@@ -367,6 +377,11 @@ export const roomsReducer = createReducer(
|
||||
isConnecting
|
||||
})),
|
||||
|
||||
on(RoomsActions.setSignalServerReconnecting, (state, { isReconnecting }) => ({
|
||||
...state,
|
||||
isSignalServerReconnecting: isReconnecting
|
||||
})),
|
||||
|
||||
// Channel management
|
||||
on(RoomsActions.selectChannel, (state, { channelId }) => ({
|
||||
...state,
|
||||
|
||||
@@ -26,6 +26,10 @@ export const selectIsConnected = createSelector(
|
||||
selectRoomsState,
|
||||
(state) => state.isConnected
|
||||
);
|
||||
export const selectIsSignalServerReconnecting = createSelector(
|
||||
selectRoomsState,
|
||||
(state) => state.isSignalServerReconnecting
|
||||
);
|
||||
export const selectRoomsError = createSelector(
|
||||
selectRoomsState,
|
||||
(state) => state.error
|
||||
|
||||
Reference in New Issue
Block a user