Pass 1 on PATH integration

This commit is contained in:
Jack Kingsman
2026-03-18 19:15:02 -07:00
parent b832239e22
commit 69e09378f5
48 changed files with 1353 additions and 412 deletions
+4 -2
View File
@@ -12,6 +12,7 @@ import {
calculateDistance,
formatDistance,
formatRouteLabel,
getDirectContactRoute,
getEffectiveContactRoute,
hasRoutingOverride,
parsePathHops,
@@ -134,11 +135,12 @@ export function ContactInfoPane({
? calculateDistance(config.lat, config.lon, contact.lat, contact.lon)
: null;
const effectiveRoute = contact ? getEffectiveContactRoute(contact) : null;
const directRoute = contact ? getDirectContactRoute(contact) : null;
const pathHashModeLabel =
effectiveRoute && effectiveRoute.pathLen >= 0
? formatPathHashMode(effectiveRoute.pathHashMode)
: null;
const learnedRouteLabel = contact ? formatRouteLabel(contact.last_path_len, true) : null;
const learnedRouteLabel = directRoute ? formatRouteLabel(directRoute.path_len, true) : null;
const isPrefixOnlyResolvedContact = contact ? isPrefixOnlyContact(contact.public_key) : false;
const isUnknownFullKeyResolvedContact =
contact !== null &&
@@ -330,7 +332,7 @@ export function ContactInfoPane({
}
/>
)}
{contact && hasRoutingOverride(contact) && learnedRouteLabel && (
{hasRoutingOverride(contact) && learnedRouteLabel && (
<InfoItem label="Learned Route" value={learnedRouteLabel} />
)}
{pathHashModeLabel && <InfoItem label="Hop Width" value={pathHashModeLabel} />}
@@ -4,6 +4,7 @@ import type { Contact, PathDiscoveryResponse, PathDiscoveryRoute } from '../type
import {
findContactsByPrefix,
formatRouteLabel,
getDirectContactRoute,
getEffectiveContactRoute,
hasRoutingOverride,
parsePathHops,
@@ -99,16 +100,17 @@ export function ContactPathDiscoveryModal({
const [result, setResult] = useState<PathDiscoveryResponse | null>(null);
const effectiveRoute = useMemo(() => getEffectiveContactRoute(contact), [contact]);
const directRoute = useMemo(() => getDirectContactRoute(contact), [contact]);
const hasForcedRoute = hasRoutingOverride(contact);
const learnedRouteSummary = useMemo(() => {
if (contact.last_path_len === -1) {
if (!directRoute) {
return 'Flood';
}
const hops = parsePathHops(contact.last_path, contact.last_path_len);
const hops = parsePathHops(directRoute.path, directRoute.path_len);
return hops.length > 0
? `${formatRouteLabel(contact.last_path_len, true)} (${hops.join(' -> ')})`
: formatRouteLabel(contact.last_path_len, true);
}, [contact.last_path, contact.last_path_len]);
? `${formatRouteLabel(directRoute.path_len, true)} (${hops.join(' -> ')})`
: formatRouteLabel(directRoute.path_len, true);
}, [directRoute]);
const forcedRouteSummary = useMemo(() => {
if (!hasForcedRoute) {
return null;
@@ -5,6 +5,7 @@ import type { Contact } from '../types';
import {
formatRouteLabel,
formatRoutingOverrideInput,
getDirectContactRoute,
hasRoutingOverride,
} from '../utils/pathUtils';
import { Button } from './ui/button';
@@ -28,7 +29,7 @@ interface ContactRoutingOverrideModalProps {
}
function summarizeLearnedRoute(contact: Contact): string {
return formatRouteLabel(contact.last_path_len, true);
return formatRouteLabel(getDirectContactRoute(contact)?.path_len ?? -1, true);
}
function summarizeForcedRoute(contact: Contact): string | null {
+7 -3
View File
@@ -12,6 +12,7 @@ import type { Contact, Message, MessagePath, RadioConfig } from '../types';
import { CONTACT_TYPE_REPEATER } from '../types';
import { formatTime, parseSenderFromText } from '../utils/messageParser';
import { formatHopCounts, type SenderInfo } from '../utils/pathUtils';
import { getDirectContactRoute } from '../utils/pathUtils';
import { ContactAvatar } from './ContactAvatar';
import { PathModal } from './PathModal';
import { handleKeyboardActivate } from '../utils/a11y';
@@ -500,12 +501,13 @@ export function MessageList({
parsedSender: string | null
): SenderInfo => {
if (msg.type === 'PRIV' && contact) {
const directRoute = getDirectContactRoute(contact);
return {
name: contact.name || contact.public_key.slice(0, 12),
publicKeyOrPrefix: contact.public_key,
lat: contact.lat,
lon: contact.lon,
pathHashMode: contact.out_path_hash_mode,
pathHashMode: directRoute?.path_hash_mode ?? null,
};
}
if (msg.type === 'CHAN') {
@@ -515,12 +517,13 @@ export function MessageList({
? contacts.find((candidate) => candidate.public_key === msg.sender_key)
: null) || (senderName ? getContactByName(senderName) : null);
if (senderContact) {
const directRoute = getDirectContactRoute(senderContact);
return {
name: senderContact.name || senderName || senderContact.public_key.slice(0, 12),
publicKeyOrPrefix: senderContact.public_key,
lat: senderContact.lat,
lon: senderContact.lon,
pathHashMode: senderContact.out_path_hash_mode,
pathHashMode: directRoute?.path_hash_mode ?? null,
};
}
if (senderName || msg.sender_key) {
@@ -538,12 +541,13 @@ export function MessageList({
if (parsedSender) {
const senderContact = getContactByName(parsedSender);
if (senderContact) {
const directRoute = getDirectContactRoute(senderContact);
return {
name: parsedSender,
publicKeyOrPrefix: senderContact.public_key,
lat: senderContact.lat,
lon: senderContact.lon,
pathHashMode: senderContact.out_path_hash_mode,
pathHashMode: directRoute?.path_hash_mode ?? null,
};
}
}
+2 -2
View File
@@ -293,8 +293,8 @@ describe('App startup hash resolution', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
direct_path: null,
direct_path_len: -1,
last_advert: null,
lat: null,
lon: null,
@@ -196,9 +196,9 @@ describe('ChatHeader key visibility', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: 'AA',
last_path_len: 1,
out_path_hash_mode: 0,
direct_path: 'AA',
direct_path_len: 1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -242,9 +242,9 @@ describe('ChatHeader key visibility', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: 'AA',
last_path_len: 1,
out_path_hash_mode: 0,
direct_path: 'AA',
direct_path_len: 1,
direct_path_hash_mode: 0,
route_override_path: 'BBDD',
route_override_len: 2,
route_override_hash_mode: 0,
+7 -7
View File
@@ -39,9 +39,9 @@ function createContact(overrides: Partial<Contact> = {}): Contact {
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -104,7 +104,7 @@ describe('ContactInfoPane', () => {
});
it('shows hop width when contact has a stored path hash mode', async () => {
const contact = createContact({ out_path_hash_mode: 1 });
const contact = createContact({ direct_path_hash_mode: 1, direct_path_len: 1 });
getContactAnalytics.mockResolvedValue(createAnalytics(contact));
render(<ContactInfoPane {...baseProps} contactKey={contact.public_key} />);
@@ -117,7 +117,7 @@ describe('ContactInfoPane', () => {
});
it('does not show hop width for flood-routed contacts', async () => {
const contact = createContact({ last_path_len: -1, out_path_hash_mode: -1 });
const contact = createContact({ direct_path_len: -1, direct_path_hash_mode: -1 });
getContactAnalytics.mockResolvedValue(createAnalytics(contact));
render(<ContactInfoPane {...baseProps} contactKey={contact.public_key} />);
@@ -131,8 +131,8 @@ describe('ContactInfoPane', () => {
it('shows forced routing override and learned route separately', async () => {
const contact = createContact({
last_path_len: 1,
out_path_hash_mode: 0,
direct_path_len: 1,
direct_path_hash_mode: 0,
route_override_path: 'ae92f13e',
route_override_len: 2,
route_override_hash_mode: 1,
+9 -9
View File
@@ -166,9 +166,9 @@ describe('ConversationPane', () => {
name: 'Repeater',
type: 2,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -268,9 +268,9 @@ describe('ConversationPane', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
@@ -304,9 +304,9 @@ describe('ConversationPane', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
+3 -3
View File
@@ -274,8 +274,8 @@ function makeContact(overrides: Partial<Contact> = {}): Contact {
name: 'TestNode',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
direct_path: null,
direct_path_len: 0,
last_advert: null,
lat: null,
lon: null,
@@ -285,7 +285,7 @@ function makeContact(overrides: Partial<Contact> = {}): Contact {
last_read_at: null,
first_seen: null,
...overrides,
out_path_hash_mode: overrides.out_path_hash_mode ?? 0,
direct_path_hash_mode: overrides.direct_path_hash_mode ?? 0,
};
}
+6 -6
View File
@@ -29,9 +29,9 @@ describe('MapView', () => {
name: 'Mystery Node',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
@@ -63,9 +63,9 @@ describe('MapView', () => {
name: 'Almost Stale',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
+3 -3
View File
@@ -23,9 +23,9 @@ const mockContact: Contact = {
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
+3 -3
View File
@@ -51,9 +51,9 @@ function createContact(publicKey: string, name: string, type = 1): Contact {
name,
type,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
+6 -6
View File
@@ -22,8 +22,9 @@ function createContact(overrides: Partial<Contact> = {}): Contact {
name: 'Test Contact',
type: CONTACT_TYPE_REPEATER,
flags: 0,
last_path: null,
last_path_len: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
@@ -33,7 +34,6 @@ function createContact(overrides: Partial<Contact> = {}): Contact {
last_read_at: null,
first_seen: null,
...overrides,
out_path_hash_mode: overrides.out_path_hash_mode ?? 0,
};
}
@@ -139,9 +139,9 @@ describe('contact routing helpers', () => {
it('prefers routing override over learned route', () => {
const effective = getEffectiveContactRoute(
createContact({
last_path: 'AABB',
last_path_len: 1,
out_path_hash_mode: 0,
direct_path: 'AABB',
direct_path_len: 1,
direct_path_hash_mode: 0,
route_override_path: 'AE92F13E',
route_override_len: 2,
route_override_hash_mode: 1,
+19 -19
View File
@@ -82,9 +82,9 @@ const contacts: Contact[] = [
name: 'TestRepeater',
type: 2,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -305,9 +305,9 @@ describe('RepeaterDashboard', () => {
name: 'Neighbor',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
@@ -365,9 +365,9 @@ describe('RepeaterDashboard', () => {
name: 'Neighbor',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
@@ -520,15 +520,15 @@ describe('RepeaterDashboard', () => {
});
describe('path type display and reset', () => {
it('shows flood when last_path_len is -1', () => {
it('shows flood when direct_path_len is -1', () => {
render(<RepeaterDashboard {...defaultProps} />);
expect(screen.getByText('flood')).toBeInTheDocument();
});
it('shows direct when last_path_len is 0', () => {
it('shows direct when direct_path_len is 0', () => {
const directContacts: Contact[] = [
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 0, last_seen: 1700000000 },
];
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
@@ -536,9 +536,9 @@ describe('RepeaterDashboard', () => {
expect(screen.getByText('direct')).toBeInTheDocument();
});
it('shows N hops when last_path_len > 0', () => {
it('shows N hops when direct_path_len > 0', () => {
const hoppedContacts: Contact[] = [
{ ...contacts[0], last_path_len: 3, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 3, last_seen: 1700000000 },
];
render(<RepeaterDashboard {...defaultProps} contacts={hoppedContacts} />);
@@ -548,7 +548,7 @@ describe('RepeaterDashboard', () => {
it('shows 1 hop (singular) for single hop', () => {
const oneHopContacts: Contact[] = [
{ ...contacts[0], last_path_len: 1, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 1, last_seen: 1700000000 },
];
render(<RepeaterDashboard {...defaultProps} contacts={oneHopContacts} />);
@@ -558,7 +558,7 @@ describe('RepeaterDashboard', () => {
it('direct path is clickable, underlined, and marked as editable', () => {
const directContacts: Contact[] = [
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 0, last_seen: 1700000000 },
];
render(<RepeaterDashboard {...defaultProps} contacts={directContacts} />);
@@ -573,7 +573,7 @@ describe('RepeaterDashboard', () => {
const forcedContacts: Contact[] = [
{
...contacts[0],
last_path_len: 1,
direct_path_len: 1,
last_seen: 1700000000,
route_override_path: 'ae92f13e',
route_override_len: 2,
@@ -589,7 +589,7 @@ describe('RepeaterDashboard', () => {
it('clicking direct path opens modal and can force direct routing', async () => {
const directContacts: Contact[] = [
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 0, last_seen: 1700000000 },
];
const { api } = await import('../api');
@@ -613,7 +613,7 @@ describe('RepeaterDashboard', () => {
it('closing the routing override modal does not call the API', async () => {
const directContacts: Contact[] = [
{ ...contacts[0], last_path_len: 0, last_seen: 1700000000 },
{ ...contacts[0], direct_path_len: 0, last_seen: 1700000000 },
];
const { api } = await import('../api');
+3 -3
View File
@@ -231,9 +231,9 @@ describe('SearchView', () => {
name: 'Bob',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
+3 -3
View File
@@ -27,9 +27,9 @@ function makeContact(
name,
type,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
+9 -9
View File
@@ -225,9 +225,9 @@ describe('resolveContactFromHashToken', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -242,9 +242,9 @@ describe('resolveContactFromHashToken', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -259,9 +259,9 @@ describe('resolveContactFromHashToken', () => {
name: null,
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -42,9 +42,9 @@ function makeContact(suffix: string): Contact {
name: `Contact-${suffix}`,
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -200,9 +200,9 @@ describe('useConversationActions', () => {
name: 'Alice',
type: 1,
flags: 0,
last_path: 'AABB',
last_path_len: 2,
out_path_hash_mode: 0,
direct_path: 'AABB',
direct_path_len: 2,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -100,9 +100,9 @@ describe('useRealtimeAppState', () => {
name: 'Bob',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -142,9 +142,9 @@ describe('useRealtimeAppState', () => {
name: 'Bob',
type: 1,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -232,9 +232,9 @@ describe('useRealtimeAppState', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
+3 -3
View File
@@ -44,9 +44,9 @@ function makeContact(pubkey: string): Contact {
name: `Contact-${pubkey.slice(0, 6)}`,
type: 1,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: 0,
last_advert: null,
lat: null,
lon: null,
@@ -44,9 +44,9 @@ function createContact(publicKey: string, name: string, type = 1): Contact {
name,
type,
flags: 0,
last_path: null,
last_path_len: 0,
out_path_hash_mode: 0,
direct_path: null,
direct_path_len: 0,
direct_path_hash_mode: 0,
route_override_path: null,
route_override_len: null,
route_override_hash_mode: null,
@@ -112,9 +112,9 @@ describe('useWebSocket dispatch', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
+6 -6
View File
@@ -25,9 +25,9 @@ describe('wsEvents', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
@@ -50,9 +50,9 @@ describe('wsEvents', () => {
name: null,
type: 0,
flags: 0,
last_path: null,
last_path_len: -1,
out_path_hash_mode: -1,
direct_path: null,
direct_path_len: -1,
direct_path_hash_mode: -1,
last_advert: null,
lat: null,
lon: null,
+14 -3
View File
@@ -99,12 +99,17 @@ export interface Contact {
name: string | null;
type: number;
flags: number;
last_path: string | null;
last_path_len: number;
out_path_hash_mode: number;
direct_path: string | null;
direct_path_len: number;
direct_path_hash_mode: number;
direct_path_updated_at?: number | null;
route_override_path?: string | null;
route_override_len?: number | null;
route_override_hash_mode?: number | null;
effective_route?: ContactRoute | null;
effective_route_source?: 'override' | 'direct' | 'flood';
direct_route?: ContactRoute | null;
route_override?: ContactRoute | null;
last_advert: number | null;
lat: number | null;
lon: number | null;
@@ -115,6 +120,12 @@ export interface Contact {
first_seen: number | null;
}
export interface ContactRoute {
path: string;
path_len: number;
path_hash_mode: number;
}
export interface ContactAdvertPath {
path: string;
path_len: number;
+74 -15
View File
@@ -1,4 +1,4 @@
import type { Contact, RadioConfig, MessagePath } from '../types';
import type { Contact, ContactRoute, RadioConfig, MessagePath } from '../types';
import { CONTACT_TYPE_REPEATER } from '../types';
const MAX_PATH_BYTES = 64;
@@ -37,6 +37,7 @@ export interface EffectiveContactRoute {
pathLen: number;
pathHashMode: number;
forced: boolean;
source: 'override' | 'direct' | 'flood';
}
function normalizePathHashMode(mode: number | null | undefined): number | null {
@@ -114,29 +115,86 @@ export function parsePathHops(path: string | null | undefined, hopCount?: number
}
export function hasRoutingOverride(contact: Contact): boolean {
return contact.route_override_len !== null && contact.route_override_len !== undefined;
return (
(contact.route_override !== null && contact.route_override !== undefined) ||
(contact.route_override_len !== null && contact.route_override_len !== undefined)
);
}
export function getDirectContactRoute(contact: Contact): ContactRoute | null {
if (contact.direct_route) {
return contact.direct_route;
}
if (contact.direct_path_len < 0) {
return null;
}
return {
path: contact.direct_path ?? '',
path_len: contact.direct_path_len,
path_hash_mode:
normalizePathHashMode(contact.direct_path_hash_mode) ??
inferPathHashMode(contact.direct_path, contact.direct_path_len) ??
0,
};
}
function getRouteOverride(contact: Contact): ContactRoute | null {
if (contact.route_override) {
return contact.route_override;
}
if (!hasRoutingOverride(contact)) {
return null;
}
const pathLen = contact.route_override_len ?? -1;
let pathHashMode = normalizePathHashMode(contact.route_override_hash_mode);
if (pathLen === -1) {
pathHashMode = -1;
} else if (pathHashMode == null) {
pathHashMode = inferPathHashMode(contact.route_override_path, pathLen) ?? 0;
}
return {
path: contact.route_override_path ?? '',
path_len: pathLen,
path_hash_mode: pathHashMode,
};
}
export function getEffectiveContactRoute(contact: Contact): EffectiveContactRoute {
const forced = hasRoutingOverride(contact);
const pathLen = forced ? (contact.route_override_len ?? -1) : contact.last_path_len;
const path = forced ? (contact.route_override_path ?? '') : (contact.last_path ?? '');
const route = contact.effective_route;
if (route) {
return {
path: route.path || null,
pathLen: route.path_len,
pathHashMode: route.path_hash_mode,
forced: contact.effective_route_source === 'override',
source: contact.effective_route_source ?? 'flood',
};
}
let pathHashMode = forced
? (contact.route_override_hash_mode ?? null)
: (contact.out_path_hash_mode ?? null);
const directRoute = getDirectContactRoute(contact);
const overrideRoute = getRouteOverride(contact);
const resolvedRoute = overrideRoute ?? directRoute;
const source = overrideRoute ? 'override' : directRoute ? 'direct' : 'flood';
const pathLen = resolvedRoute?.path_len ?? -1;
let pathHashMode = resolvedRoute?.path_hash_mode ?? null;
if (pathLen === -1) {
pathHashMode = -1;
} else if (pathHashMode == null || pathHashMode < 0 || pathHashMode > 2) {
pathHashMode = inferPathHashMode(path, pathLen) ?? 0;
pathHashMode = inferPathHashMode(resolvedRoute?.path, pathLen) ?? 0;
}
return {
path: path || null,
path: resolvedRoute?.path || null,
pathLen,
pathHashMode,
forced,
forced: source === 'override',
source,
};
}
@@ -151,16 +209,17 @@ export function formatRouteLabel(pathLen: number, capitalize: boolean = false):
}
export function formatRoutingOverrideInput(contact: Contact): string {
if (!hasRoutingOverride(contact)) {
const routeOverride = getRouteOverride(contact);
if (!routeOverride) {
return '';
}
if (contact.route_override_len === -1) {
if (routeOverride.path_len === -1) {
return '-1';
}
if (contact.route_override_len === 0) {
if (routeOverride.path_len === 0) {
return '0';
}
return parsePathHops(contact.route_override_path, contact.route_override_len)
return parsePathHops(routeOverride.path, routeOverride.path_len)
.map((hop) => hop.toLowerCase())
.join(',');
}