feat: signal server tag

This commit is contained in:
2026-06-05 06:16:02 +02:00
parent 6865147e8f
commit bf4e6891d1
69 changed files with 2808 additions and 1269 deletions

View File

@@ -93,9 +93,10 @@ export class ServerDirectoryService {
endpointId: string,
status: ServerEndpoint['status'],
latency?: number,
versions?: ServerEndpointVersions
versions?: ServerEndpointVersions,
serverTag?: string
): void {
this.endpointState.updateServerStatus(endpointId, status, latency, versions);
this.endpointState.updateServerStatus(endpointId, status, latency, versions, serverTag);
}
async ensureEndpointVersionCompatibility(selector?: ServerSourceSelector): Promise<boolean> {
@@ -212,7 +213,8 @@ export class ServerDirectoryService {
endpointId,
healthResult.status,
healthResult.latency,
healthResult.versions
healthResult.versions,
healthResult.serverTag
);
return healthResult.status === 'online';

View File

@@ -228,7 +228,8 @@ export class ServerEndpointStateService {
endpointId: string,
status: ServerEndpoint['status'],
latency?: number,
versions?: ServerEndpointVersions
versions?: ServerEndpointVersions,
serverTag?: string
): void {
this._servers.update((endpoints) => ensureCompatibleActiveEndpoint(endpoints.map((endpoint) => {
if (endpoint.id !== endpointId) {
@@ -240,6 +241,7 @@ export class ServerEndpointStateService {
instanceId: versions?.serverInstanceId ?? endpoint.instanceId,
status,
latency,
serverTag: serverTag ?? endpoint.serverTag,
isActive: status === 'incompatible' && !endpoint.isDefault ? false : endpoint.isActive,
serverVersion: versions?.serverVersion ?? endpoint.serverVersion,
clientVersion: versions?.clientVersion ?? endpoint.clientVersion

View File

@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest';
import {
isSignalServerTagUrl,
presentSignalServerTag,
resolveEndpointSignalServerTag,
resolveUserHomeSignalServerTag
} from './signal-server-tag.rules';
import type { ServerEndpoint } from '../models/server-directory.model';
describe('signal-server-tag.rules', () => {
const endpoints: ServerEndpoint[] = [
{
id: 'endpoint-1',
name: 'Primary',
url: 'http://signal.example.com:3001',
isActive: true,
isDefault: true,
status: 'online',
serverTag: 'EU'
},
{
id: 'endpoint-2',
name: 'Fallback',
url: 'https://signal-backup.example.com',
isActive: true,
isDefault: false,
status: 'online'
}
];
it('uses configured serverTag when present on an endpoint', () => {
expect(resolveEndpointSignalServerTag(endpoints[0])).toBe('EU');
});
it('falls back to endpoint url when serverTag is missing', () => {
expect(resolveEndpointSignalServerTag(endpoints[1])).toBe('https://signal-backup.example.com');
});
it('resolves a user home signal server tag from known endpoints', () => {
expect(resolveUserHomeSignalServerTag('http://signal.example.com:3001/', endpoints)).toBe('EU');
});
it('falls back to the home signal server url when the endpoint is unknown', () => {
expect(resolveUserHomeSignalServerTag('http://unknown.example.com:3001', endpoints))
.toBe('http://unknown.example.com:3001');
});
it('returns undefined when the user has no home signal server url', () => {
expect(resolveUserHomeSignalServerTag(undefined, endpoints)).toBeUndefined();
});
it('detects http and https values as urls', () => {
expect(isSignalServerTagUrl('http://signal.example.com:3001')).toBe(true);
expect(isSignalServerTagUrl('https://signal.example.com')).toBe(true);
expect(isSignalServerTagUrl('EU')).toBe(false);
});
it('prefixes non-url tags with #', () => {
expect(presentSignalServerTag('EU')).toEqual({
kind: 'label',
label: 'EU',
display: '#EU'
});
});
it('strips an existing # before re-prefixing label tags', () => {
expect(presentSignalServerTag('#sweden')).toEqual({
kind: 'label',
label: 'sweden',
display: '#sweden'
});
});
it('presents url tags as globe tooltip targets', () => {
expect(presentSignalServerTag('https://signal.example.com')).toEqual({
kind: 'url',
url: 'https://signal.example.com'
});
});
});

View File

@@ -0,0 +1,52 @@
import type { ServerEndpoint } from '../models/server-directory.model';
import { sanitiseServerBaseUrl } from './server-endpoint-defaults.logic';
export type SignalServerTagPresentation =
| { kind: 'url'; url: string }
| { kind: 'label'; label: string; display: string };
export function isSignalServerTagUrl(value: string): boolean {
return /^https?:\/\//i.test(value.trim());
}
export function presentSignalServerTag(tag: string): SignalServerTagPresentation {
const normalized = tag.trim();
if (isSignalServerTagUrl(normalized)) {
return { kind: 'url', url: normalized };
}
const label = normalized.replace(/^#+/, '');
return { kind: 'label', label, display: `#${label}` };
}
export function resolveEndpointSignalServerTag(
endpoint: Pick<ServerEndpoint, 'serverTag' | 'url'>
): string {
const configuredTag = endpoint.serverTag?.trim();
return configuredTag || endpoint.url;
}
export function resolveUserHomeSignalServerTag(
homeSignalServerUrl: string | undefined,
endpoints: ServerEndpoint[]
): string | undefined {
const normalizedHomeUrl = homeSignalServerUrl?.trim();
if (!normalizedHomeUrl) {
return undefined;
}
const sanitizedHomeUrl = sanitiseServerBaseUrl(normalizedHomeUrl);
const matchingEndpoint = endpoints.find(
(endpoint) => sanitiseServerBaseUrl(endpoint.url) === sanitizedHomeUrl
);
if (matchingEndpoint) {
return resolveEndpointSignalServerTag(matchingEndpoint);
}
return sanitizedHomeUrl;
}

View File

@@ -57,6 +57,8 @@ export interface ServerEndpoint {
instanceId?: string;
name: string;
url: string;
/** Display tag advertised by the signal server health endpoint. */
serverTag?: string;
isActive: boolean;
isDefault: boolean;
defaultKey?: string;
@@ -141,10 +143,12 @@ export interface ServerVersionCompatibilityResult {
export interface ServerHealthCheckPayload {
serverInstanceId?: unknown;
serverVersion?: unknown;
serverTag?: unknown;
}
export interface ServerEndpointHealthResult {
status: ServerEndpointStatus;
latency?: number;
serverTag?: string;
versions?: ServerEndpointVersions;
}

View File

@@ -91,7 +91,9 @@
<div class="relative shrink-0">
@if (isJoinedServer(server)) {
<div class="flex items-center overflow-hidden rounded-md border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-500">
<div
class="flex items-center overflow-hidden rounded-md border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-500"
>
<span class="px-2.5 py-1.5">Joined</span>
<button
type="button"

View File

@@ -585,7 +585,8 @@ export class ServerBrowserComponent implements OnInit {
await firstValueFrom(this.webrtc.connectToSignalingServer(wsUrl));
this.webrtc.identify(currentUser.oderId || currentUser.id, currentUser.displayName || 'User', wsUrl, {
description: currentUser.description,
profileUpdatedAt: currentUser.profileUpdatedAt
profileUpdatedAt: currentUser.profileUpdatedAt,
homeSignalServerUrl: currentUser.homeSignalServerUrl
});
this.webrtc.sendRawMessageToSignalUrl(wsUrl, {

View File

@@ -33,11 +33,15 @@ export class ServerEndpointHealthService {
const serverInstanceId = typeof payload.serverInstanceId === 'string' && payload.serverInstanceId.trim().length > 0
? payload.serverInstanceId.trim()
: undefined;
const serverTag = typeof payload.serverTag === 'string' && payload.serverTag.trim().length > 0
? payload.serverTag.trim()
: undefined;
if (!versionCompatibility.isCompatible) {
return {
status: 'incompatible',
latency,
serverTag,
versions: {
serverInstanceId,
serverVersion: versionCompatibility.serverVersion,
@@ -49,6 +53,7 @@ export class ServerEndpointHealthService {
return {
status: 'online',
latency,
serverTag,
versions: {
serverInstanceId,
serverVersion: versionCompatibility.serverVersion,