188 lines
4.9 KiB
TypeScript
188 lines
4.9 KiB
TypeScript
import type {
|
|
ConfiguredDefaultServerDefinition,
|
|
DefaultEndpointTemplate,
|
|
DefaultServerDefinition,
|
|
ServerEndpoint
|
|
} from './server-directory.models';
|
|
|
|
export function sanitiseServerBaseUrl(rawUrl: string): string {
|
|
let cleaned = rawUrl.trim().replace(/\/+$/, '');
|
|
|
|
if (cleaned.toLowerCase().endsWith('/api')) {
|
|
cleaned = cleaned.slice(0, -4);
|
|
}
|
|
|
|
return cleaned;
|
|
}
|
|
|
|
export function normaliseConfiguredServerUrl(
|
|
rawUrl: string,
|
|
defaultProtocol: 'http' | 'https'
|
|
): string {
|
|
let cleaned = rawUrl.trim();
|
|
|
|
if (!cleaned) {
|
|
return '';
|
|
}
|
|
|
|
if (cleaned.toLowerCase().startsWith('ws://')) {
|
|
cleaned = `http://${cleaned.slice(5)}`;
|
|
} else if (cleaned.toLowerCase().startsWith('wss://')) {
|
|
cleaned = `https://${cleaned.slice(6)}`;
|
|
} else if (cleaned.startsWith('//')) {
|
|
cleaned = `${defaultProtocol}:${cleaned}`;
|
|
} else if (!/^[a-z][a-z\d+.-]*:\/\//i.test(cleaned)) {
|
|
cleaned = `${defaultProtocol}://${cleaned}`;
|
|
}
|
|
|
|
return sanitiseServerBaseUrl(cleaned);
|
|
}
|
|
|
|
export function buildFallbackDefaultServerUrl(
|
|
configuredUrl: string | undefined,
|
|
defaultProtocol: 'http' | 'https'
|
|
): string {
|
|
if (configuredUrl?.trim()) {
|
|
return normaliseConfiguredServerUrl(configuredUrl, defaultProtocol);
|
|
}
|
|
|
|
return `${defaultProtocol}://localhost:3001`;
|
|
}
|
|
|
|
export function buildDefaultServerDefinitions(
|
|
configuredDefaults: ConfiguredDefaultServerDefinition[] | undefined,
|
|
configuredUrl: string | undefined,
|
|
defaultProtocol: 'http' | 'https'
|
|
): DefaultServerDefinition[] {
|
|
const seenKeys = new Set<string>();
|
|
const seenUrls = new Set<string>();
|
|
const definitions = (configuredDefaults ?? [])
|
|
.map((server, index) => {
|
|
const key = server.key?.trim() || `default-${index + 1}`;
|
|
const url = normaliseConfiguredServerUrl(server.url ?? '', defaultProtocol);
|
|
|
|
if (!key || !url || seenKeys.has(key) || seenUrls.has(url)) {
|
|
return null;
|
|
}
|
|
|
|
seenKeys.add(key);
|
|
seenUrls.add(url);
|
|
|
|
return {
|
|
key,
|
|
name: server.name?.trim() || (index === 0 ? 'Default Server' : `Default Server ${index + 1}`),
|
|
url
|
|
} satisfies DefaultServerDefinition;
|
|
})
|
|
.filter((definition): definition is DefaultServerDefinition => definition !== null);
|
|
|
|
if (definitions.length > 0) {
|
|
return definitions;
|
|
}
|
|
|
|
return [
|
|
{
|
|
key: 'default',
|
|
name: 'Default Server',
|
|
url: buildFallbackDefaultServerUrl(configuredUrl, defaultProtocol)
|
|
}
|
|
];
|
|
}
|
|
|
|
export function buildDefaultEndpointTemplates(
|
|
definitions: DefaultServerDefinition[]
|
|
): DefaultEndpointTemplate[] {
|
|
return definitions.map((definition) => ({
|
|
name: definition.name,
|
|
url: definition.url,
|
|
isActive: true,
|
|
isDefault: true,
|
|
defaultKey: definition.key,
|
|
status: 'unknown'
|
|
}));
|
|
}
|
|
|
|
export function hasEndpointForDefault(
|
|
endpoints: ServerEndpoint[],
|
|
defaultEndpoint: DefaultEndpointTemplate
|
|
): boolean {
|
|
return endpoints.some((endpoint) =>
|
|
endpoint.defaultKey === defaultEndpoint.defaultKey
|
|
|| sanitiseServerBaseUrl(endpoint.url) === defaultEndpoint.url
|
|
);
|
|
}
|
|
|
|
export function matchDefaultEndpointTemplate(
|
|
defaultEndpoints: DefaultEndpointTemplate[],
|
|
endpoint: ServerEndpoint,
|
|
sanitisedUrl: string,
|
|
claimedDefaultKeys: Set<string>
|
|
): DefaultEndpointTemplate | null {
|
|
if (endpoint.defaultKey) {
|
|
return defaultEndpoints.find(
|
|
(candidate) => candidate.defaultKey === endpoint.defaultKey && !claimedDefaultKeys.has(candidate.defaultKey)
|
|
) ?? null;
|
|
}
|
|
|
|
if (!endpoint.isDefault) {
|
|
return null;
|
|
}
|
|
|
|
const matchingCurrentDefault = defaultEndpoints.find(
|
|
(candidate) => candidate.url === sanitisedUrl && !claimedDefaultKeys.has(candidate.defaultKey)
|
|
);
|
|
|
|
if (matchingCurrentDefault) {
|
|
return matchingCurrentDefault;
|
|
}
|
|
|
|
return defaultEndpoints.find(
|
|
(candidate) => !claimedDefaultKeys.has(candidate.defaultKey)
|
|
) ?? null;
|
|
}
|
|
|
|
export function findDefaultEndpointKeyByUrl(
|
|
defaultEndpoints: DefaultEndpointTemplate[],
|
|
url: string
|
|
): string | null {
|
|
const sanitisedUrl = sanitiseServerBaseUrl(url);
|
|
|
|
return defaultEndpoints.find((endpoint) => endpoint.url === sanitisedUrl)?.defaultKey ?? null;
|
|
}
|
|
|
|
export function ensureAnyActiveEndpoint(endpoints: ServerEndpoint[]): ServerEndpoint[] {
|
|
if (endpoints.length === 0 || endpoints.some((endpoint) => endpoint.isActive)) {
|
|
return endpoints;
|
|
}
|
|
|
|
const nextEndpoints = [...endpoints];
|
|
|
|
nextEndpoints[0] = {
|
|
...nextEndpoints[0],
|
|
isActive: true
|
|
};
|
|
|
|
return nextEndpoints;
|
|
}
|
|
|
|
export function ensureCompatibleActiveEndpoint(endpoints: ServerEndpoint[]): ServerEndpoint[] {
|
|
if (endpoints.length === 0 || endpoints.some((endpoint) => endpoint.isActive)) {
|
|
return endpoints;
|
|
}
|
|
|
|
const fallbackIndex = endpoints.findIndex((endpoint) => endpoint.status !== 'incompatible');
|
|
|
|
if (fallbackIndex < 0) {
|
|
return endpoints;
|
|
}
|
|
|
|
const nextEndpoints = [...endpoints];
|
|
|
|
nextEndpoints[fallbackIndex] = {
|
|
...nextEndpoints[fallbackIndex],
|
|
isActive: true
|
|
};
|
|
|
|
return nextEndpoints;
|
|
}
|