diff --git a/packages/server/src/ws/handler.ts b/packages/server/src/ws/handler.ts index 82a99c69..3ce65f83 100644 --- a/packages/server/src/ws/handler.ts +++ b/packages/server/src/ws/handler.ts @@ -936,6 +936,8 @@ function buildReadyPayload(userId: string): { activeCalls: ActiveCallInfo[]; userActivities: Record; rejectedPeerOrigins: string[]; + awaitingApprovalPeerOrigins: string[]; + pendingApprovalCount: number; } { const db = getDb(); @@ -1472,7 +1474,25 @@ function buildReadyPayload(userId: string): { .all(); const rejectedPeerOrigins = rejectedPeers.map(p => p.origin); - return { user, spaces, dmChannels, folders, spaceLayout, layoutUpdatedAt, voiceStates, voiceUserStates, spaceVoiceStates, readStates, activeCalls, userActivities, rejectedPeerOrigins }; + // Awaiting-approval peer origins for softer unreachable indicators + const awaitingApprovalPeers = db + .select({ origin: schema.federationPeers.origin }) + .from(schema.federationPeers) + .where(eq(schema.federationPeers.status, 'awaiting_approval')) + .all(); + const awaitingApprovalPeerOrigins = awaitingApprovalPeers.map(p => p.origin); + + // Pending approval count for admin notification + let pendingApprovalCount = 0; + if (userRow?.isAdmin === 1) { + const countResult = db + .select({ count: sql`count(*)` }) + .from(schema.peerApprovalRequests) + .get(); + pendingApprovalCount = countResult?.count ?? 0; + } + + return { user, spaces, dmChannels, folders, spaceLayout, layoutUpdatedAt, voiceStates, voiceUserStates, spaceVoiceStates, readStates, activeCalls, userActivities, rejectedPeerOrigins, awaitingApprovalPeerOrigins, pendingApprovalCount }; } export async function registerWebSocket(app: FastifyInstance): Promise { diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index ffe96fd2..f1d284f1 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -390,7 +390,7 @@ export type ClientEvent = // Server → Client Events export type ServerEvent = - | { type: 'ready'; user: User; spaces: SpaceWithChannelsAndMembers[]; dmChannels: DmChannel[]; folders?: SpaceFolder[]; spaceLayout?: SpaceLayoutItem[] | null; layoutUpdatedAt?: number; voiceStates?: Record; voiceUserStates?: Record; readStates?: ReadState[]; activeCalls?: ActiveCallInfo[]; spaceVoiceStates?: Record; userActivities?: Record; rejectedPeerOrigins?: string[] } + | { type: 'ready'; user: User; spaces: SpaceWithChannelsAndMembers[]; dmChannels: DmChannel[]; folders?: SpaceFolder[]; spaceLayout?: SpaceLayoutItem[] | null; layoutUpdatedAt?: number; voiceStates?: Record; voiceUserStates?: Record; readStates?: ReadState[]; activeCalls?: ActiveCallInfo[]; spaceVoiceStates?: Record; userActivities?: Record; rejectedPeerOrigins?: string[]; awaitingApprovalPeerOrigins?: string[]; pendingApprovalCount?: number } | { type: 'message_created'; message: MessageWithUser } | { type: 'message_updated'; message: MessageWithUser } | { type: 'message_deleted'; messageId: string; channelId: string }