7.8 KiB
Server Directory Domain
Manages the list of server endpoints the client can connect to, health-checking them, resolving API URLs, and providing server CRUD, search, invites, and moderation. This is the central domain that other domains (auth, chat, attachment) depend on for knowing where the backend is.
Module map
server-directory/
├── application/
│ ├── server-directory.facade.ts High-level API: server CRUD, search, health, invites, moderation
│ └── server-endpoint-state.service.ts Signal-based endpoint list, reconciliation with defaults, localStorage persistence
│
├── domain/
│ ├── server-directory.models.ts ServerEndpoint, ServerInfo, ServerJoinAccessResponse, invite/ban/kick types
│ ├── server-directory.constants.ts CLIENT_UPDATE_REQUIRED_MESSAGE
│ └── server-endpoint-defaults.ts Default endpoint templates, URL sanitisation, reconciliation helpers
│
├── infrastructure/
│ ├── server-directory-api.service.ts HTTP client for all server API calls
│ ├── server-endpoint-health.service.ts Health probe (GET /api/health with 5 s timeout, fallback to /api/servers)
│ ├── server-endpoint-compatibility.service.ts Semantic version comparison for client/server compatibility
│ └── server-endpoint-storage.service.ts localStorage read/write for endpoint list and removed-default tracking
│
├── feature/
│ ├── invite/ Invite creation and resolution UI
│ ├── server-search/ Server search/browse panel
│ └── settings/ Server endpoint management settings
│
└── index.ts Barrel exports
Layer composition
The facade delegates HTTP work to the API service and endpoint state to the state service. Health probing combines the health service and compatibility service. Storage is accessed only through the state service.
graph TD
Facade[ServerDirectoryFacade]
State[ServerEndpointStateService]
API[ServerDirectoryApiService]
Health[ServerEndpointHealthService]
Compat[ServerEndpointCompatibilityService]
Storage[ServerEndpointStorageService]
Defaults[server-endpoint-defaults]
Models[server-directory.models]
Facade --> API
Facade --> State
Facade --> Health
Facade --> Compat
API --> State
State --> Storage
State --> Defaults
Health --> Compat
click Facade "application/server-directory.facade.ts" "High-level API" _blank
click State "application/server-endpoint-state.service.ts" "Signal-based endpoint state" _blank
click API "infrastructure/server-directory-api.service.ts" "HTTP client for server API" _blank
click Health "infrastructure/server-endpoint-health.service.ts" "Health probe" _blank
click Compat "infrastructure/server-endpoint-compatibility.service.ts" "Version compatibility" _blank
click Storage "infrastructure/server-endpoint-storage.service.ts" "localStorage persistence" _blank
click Defaults "domain/server-endpoint-defaults.ts" "Default endpoint templates" _blank
click Models "domain/server-directory.models.ts" "Domain types" _blank
Endpoint lifecycle
On startup, ServerEndpointStateService loads endpoints from localStorage, reconciles them with the configured defaults from the environment, and ensures at least one endpoint is active.
stateDiagram-v2
[*] --> Load: constructor
Load --> HasStored: localStorage has endpoints
Load --> InitDefaults: no stored endpoints
InitDefaults --> Ready: save default endpoints
HasStored --> Reconcile: compare stored vs defaults
Reconcile --> Ready: merge, ensure active
Ready --> HealthCheck: facade.testAllServers()
state HealthCheck {
[*] --> Probing
Probing --> Online: /api/health 200 OK
Probing --> Incompatible: version mismatch
Probing --> Offline: request failed
}
Health probing
The facade exposes testServer(endpointId) and testAllServers(). Both delegate to ServerEndpointHealthService.probeEndpoint(), which:
- Sends
GET /api/healthwith a 5-second timeout - On success, checks the response's
serverVersionagainst the client version viaServerEndpointCompatibilityService - If versions are incompatible, the endpoint is marked
incompatibleand deactivated - If
/api/healthfails, falls back toGET /api/serversas a basic liveness check - Updates the endpoint's status, latency, and version info in the state service
sequenceDiagram
participant Facade
participant Health as HealthService
participant Compat as CompatibilityService
participant API as Server
Facade->>Health: probeEndpoint(endpoint, clientVersion)
Health->>API: GET /api/health (5s timeout)
alt 200 OK
API-->>Health: { serverVersion }
Health->>Compat: evaluateServerVersion(serverVersion, clientVersion)
Compat-->>Health: { isCompatible, serverVersion }
Health-->>Facade: online / incompatible + latency + versions
else Request failed
Health->>API: GET /api/servers (fallback)
alt 200 OK
API-->>Health: servers list
Health-->>Facade: online + latency
else Also failed
Health-->>Facade: offline
end
end
Facade->>Facade: updateServerStatus(id, status, latency, versions)
Server search
The facade's searchServers(query) method supports two modes controlled by a searchAllServers flag:
- Single endpoint: searches only the active server's API
- All endpoints: fans out the query to every online active endpoint via
forkJoin, then deduplicates results by server ID
The API service normalises every ServerInfo response, filling in sourceId, sourceName, and sourceUrl so the UI knows which endpoint each server came from.
Default endpoint management
Default servers are configured in the environment file. The state service builds DefaultEndpointTemplate objects from the configuration and uses them during reconciliation:
- Stored endpoints are matched to defaults by
defaultKeyor URL - Missing defaults are added unless the user explicitly removed them (tracked in a separate localStorage key)
restoreDefaultServers()re-adds any removed defaults and clears the removal tracking- The primary default URL is used as a fallback when no endpoint is resolved
URL sanitisation strips trailing slashes and /api suffixes. Protocol-less URLs get http or https based on the current page protocol.
Server administration
The facade provides methods for server registration, updates, and unregistration. These map directly to the API service's HTTP calls:
| Method | HTTP | Endpoint |
|---|---|---|
registerServer |
POST | /api/servers |
updateServer |
PUT | /api/servers/:id |
unregisterServer |
DELETE | /api/servers/:id |
Invites and moderation
| Method | Purpose |
|---|---|
createInvite(serverId, request) |
Creates a time-limited invite link |
getInvite(inviteId) |
Resolves invite metadata |
requestServerAccess(request) |
Joins a server (via membership, password, invite, or public access) |
kickServerMember(serverId, request) |
Removes a user from the server |
banServerMember(serverId, request) |
Bans a user with optional reason and expiry |
unbanServerMember(serverId, request) |
Lifts a ban |
Persistence
All endpoint state is persisted to localStorage under two keys:
| Key | Contents |
|---|---|
metoyou_server_endpoints |
Full ServerEndpoint[] array |
metoyou_removed_default_server_keys |
Set of default endpoint keys the user explicitly removed |
The storage service handles JSON serialisation and defensive parsing. Invalid data falls back to empty state rather than throwing.