// Copyright 2017-2026 @pezkuwi/apps-config authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { TFunction } from '../types.js'; import type { EndpointOption, LinkOption } from './types.js'; interface SortOption { isUnreachable?: boolean; } let dummyId = 0; function sortNoop (): number { return 0; } function sortLinks (a: SortOption, b: SortOption): number { return !!a.isUnreachable !== !!b.isUnreachable ? a.isUnreachable ? 1 : -1 : 0; } function expandLinked (input: LinkOption[]): LinkOption[] { const valueRelay = input.map(({ value }) => value); return input.reduce((result: LinkOption[], entry): LinkOption[] => { result.push(entry); return entry.linked ? result.concat( expandLinked(entry.linked).map((child): LinkOption => { child.genesisHashRelay = entry.genesisHash; child.isChild = true; child.textRelay = input.length ? input[0].text : undefined; child.valueRelay = valueRelay; if (entry.ui?.identityIcon && child.paraId && child.paraId < 2000) { if (!child.ui) { child.ui = { identityIcon: entry.ui.identityIcon }; } else if (!child.ui.identityIcon) { child.ui.identityIcon = entry.ui.identityIcon; } } return child; }) ) : result; }, []); } function expandEndpoint (t: TFunction, { dnslink, genesisHash, homepage, info, isChild, isDisabled, isPeople, isPeopleForIdentity, isUnreachable, linked, paraId, providers, relayName, teleport, text, ui }: EndpointOption, firstOnly: boolean, withSort: boolean): LinkOption[] { const hasProviders = Object.keys(providers).length !== 0; const base = { genesisHash, homepage, info, isChild, isDisabled, isPeople, isPeopleForIdentity, isUnreachable: isUnreachable || !hasProviders, paraId, providers: Object.keys(providers).map((k) => providers[k]), relayName, teleport, text, ui }; const result = Object .entries( hasProviders ? providers : { Placeholder: `wss://${++dummyId}` } ) .filter((_, index) => !firstOnly || index === 0) .map(([host, value], index): LinkOption => ({ ...base, dnslink: index === 0 ? dnslink : undefined, isLightClient: value.startsWith('light://'), isRelay: false, textBy: value.startsWith('light://') ? t('lightclient.experimental', 'light client (experimental)', { ns: 'apps-config' }) : t('rpc.hosted.via', 'via {{host}}', { ns: 'apps-config', replace: { host } }), value })) .sort((a, b) => a.isLightClient ? 1 : b.isLightClient ? -1 : a.textBy.toLocaleLowerCase().localeCompare(b.textBy.toLocaleLowerCase()) ); if (linked) { const last = result[result.length - 1]; const options: LinkOption[] = []; linked .sort(withSort ? sortLinks : sortNoop) .filter(({ paraId }) => paraId) .forEach((o) => options.push(...expandEndpoint(t, o, firstOnly, withSort)) ); last.isRelay = true; last.linked = options; } return expandLinked(result); } export function expandEndpoints (t: TFunction, input: EndpointOption[], firstOnly: boolean, withSort: boolean): LinkOption[] { return input .sort(withSort ? sortLinks : sortNoop) .reduce((all: LinkOption[], e) => all.concat(expandEndpoint(t, e, firstOnly, withSort)), []); } export function getTeleports (input: EndpointOption[]): number[] { return input .filter(({ teleport }) => !!teleport && teleport[0] === -1) .map(({ paraId }) => paraId) .filter((id): id is number => !!id); }