mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-06 16:53:38 +02:00
Pass 1 on PATH integration
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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(',');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user