diff --git a/src/packages/settings/src/types/server.ts b/src/packages/settings/src/types/server.ts index 165d12d7d..c497ad33a 100644 --- a/src/packages/settings/src/types/server.ts +++ b/src/packages/settings/src/types/server.ts @@ -81,6 +81,8 @@ export type serverDetails = { voice_capacity_max?: number | null; clients?: Record; server_info?: { + /** Where it answers from anywhere, when the operator set one (GRYT-2007). */ + public_host?: string; server_id?: string; name?: string; description?: string; @@ -160,6 +162,8 @@ export type Server = { * means outstanding; a timestamp so the entry can say how long (GRYT-289). */ approvalRequestedAt?: number; + /** The server's public address, remembered for when this one doesn't answer (GRYT-2007). */ + publicHost?: string; }; export type Servers = { diff --git a/src/packages/socket/src/components/UnreachableServer.tsx b/src/packages/socket/src/components/UnreachableServer.tsx index 9c9225c50..738864d5c 100644 --- a/src/packages/socket/src/components/UnreachableServer.tsx +++ b/src/packages/socket/src/components/UnreachableServer.tsx @@ -1,7 +1,7 @@ import { Button, Surface } from "@gryt/ui"; import { useState } from "react"; -import { PiArrowsClockwiseFill, PiPlugsFill, PiPulseBold } from "../../../../lib/icons"; +import { PiArrowsClockwiseFill, PiGlobeSimpleFill, PiPlugsFill, PiPulseBold } from "../../../../lib/icons"; import { ServerDoctor } from "./ServerDoctor"; /** @@ -14,12 +14,17 @@ export function UnreachableServer({ sfuHosts, stunHosts, onTryNow, + publicHost, + onUsePublic, }: { host: string; serverName: string; sfuHosts: string[]; stunHosts: string[]; onTryNow: () => void; + /** The address it answers on from anywhere, when this one is its LAN address (GRYT-2007). */ + publicHost?: string; + onUsePublic?: () => void; }) { const [doctorOpen, setDoctorOpen] = useState(false); @@ -73,6 +78,12 @@ export function UnreachableServer({ Check the connection + {publicHost && publicHost !== host && onUsePublic && ( + + )} {serverName !== host && {host}} diff --git a/src/packages/socket/src/components/serverView.tsx b/src/packages/socket/src/components/serverView.tsx index 92f808031..d080b648e 100644 --- a/src/packages/socket/src/components/serverView.tsx +++ b/src/packages/socket/src/components/serverView.tsx @@ -159,7 +159,14 @@ export const ServerView = ({ dmSpace = false }: { dmSpace?: boolean }) => { devFakeParticipants, devFakeMuted, devFakeScreenShare, devFakeDeafened, devFakeSpeaking, devFakeMembers, devFakeChatSeconds, } = useSettings(); - const { currentlyViewingServer, setShowRemoveServer, setLastSelectedChannelForServer, viewServerBehindDmSpace } = useServerManagement(); + const { currentlyViewingServer, setShowRemoveServer, setLastSelectedChannelForServer, viewServerBehindDmSpace, addServer, removeServer } = useServerManagement(); + /* Switches the saved server to the address it gave as public (GRYT-2007). A new address is + joined and proved like any server added by hand, so its identity check runs as normal. */ + const switchToPublicAddress = (server: { host: string; name?: string; publicHost?: string }) => { + if (!server.publicHost) return; + addServer({ host: server.publicHost, name: server.name || server.publicHost }, true); + removeServer(server.host); + }; const directory = useDirectory(); /* The conversations as the search panel's "channels", which is what it names a hit by and what `in:` offers. Only the fields it reads. */ @@ -1206,6 +1213,8 @@ export const ServerView = ({ dmSpace = false }: { dmSpace?: boolean }) => { sfuHosts={[]} stunHosts={[]} onTryNow={() => reconnectServer(currentlyViewingServer.host)} + publicHost={currentlyViewingServer.publicHost} + onUsePublic={() => switchToPublicAddress(currentlyViewingServer)} /> ); } @@ -1384,6 +1393,8 @@ forumTags={activeDm ? [] : activeChannelForumTags} sfuHosts={serverDetails.sfu_hosts ?? (serverDetails.sfu_host ? [serverDetails.sfu_host] : [])} stunHosts={serverDetails.stun_hosts ?? []} onTryNow={() => reconnectServer(host)} + publicHost={currentlyViewingServer.publicHost} + onUsePublic={() => switchToPublicAddress(currentlyViewingServer)} /> ); } diff --git a/src/packages/socket/src/hooks/registerServerSocketEvents.ts b/src/packages/socket/src/hooks/registerServerSocketEvents.ts index 6c91d8868..da12cf915 100644 --- a/src/packages/socket/src/hooks/registerServerSocketEvents.ts +++ b/src/packages/socket/src/hooks/registerServerSocketEvents.ts @@ -283,6 +283,13 @@ export function registerServerSocketEvents(socket: Socket, host: string, ctx: Se }); socket.on("server:details", (data: serverDetails) => { + const publicHost = data.server_info?.public_host; + const saved = serversRef.current[host]; + if (saved && typeof publicHost === "string" && publicHost !== host && saved.publicHost !== publicHost) { + const updated = { ...serversRef.current, [host]: { ...saved, publicHost } }; + serversRef.current = updated; + setServers(updated); + } // An older server says nothing, which reads as off. setExternalEmojisAllowed(host, data.server_info?.external_emojis === true); /* Where the sidebar arrives, and the layer deciding whether a message makes a