Fix path modal support for multibyte

This commit is contained in:
Jack Kingsman
2026-03-08 13:21:37 -07:00
parent 806252ec7e
commit 69a6922827
3 changed files with 81 additions and 3 deletions

View File

@@ -380,8 +380,9 @@ export function MessageList({
publicKeyOrPrefix: config?.public_key || '',
lat: config?.lat ?? null,
lon: config?.lon ?? null,
pathHashMode: config?.path_hash_mode ?? null,
}),
[config?.name, config?.public_key, config?.lat, config?.lon]
[config?.name, config?.public_key, config?.lat, config?.lon, config?.path_hash_mode]
);
// Derive live so the byte-perfect button disables if the 30s window expires while modal is open
@@ -411,6 +412,7 @@ export function MessageList({
publicKeyOrPrefix: contact.public_key,
lat: contact.lat,
lon: contact.lon,
pathHashMode: contact.out_path_hash_mode,
};
}
// For channel messages, try to find contact by parsed sender name
@@ -422,6 +424,7 @@ export function MessageList({
publicKeyOrPrefix: senderContact.public_key,
lat: senderContact.lat,
lon: senderContact.lon,
pathHashMode: senderContact.out_path_hash_mode,
};
}
}
@@ -431,6 +434,7 @@ export function MessageList({
publicKeyOrPrefix: msg.conversation_key || '',
lat: null,
lon: null,
pathHashMode: null,
};
};

View File

@@ -324,6 +324,33 @@ describe('resolvePath', () => {
expect(result.receiver.name).toBe('MyRadio');
});
it('uses explicit sender and receiver multibyte modes for endpoint prefixes', () => {
const result = resolvePath(
'',
{ ...sender, pathHashMode: 1 },
contacts,
createConfig({
public_key: 'ABCDEF' + 'F'.repeat(58),
path_hash_mode: 2,
})
);
expect(result.sender.prefix).toBe('5EEE');
expect(result.receiver.prefix).toBe('ABCDEF');
});
it('derives sender multibyte width from path metadata when sender mode is unknown', () => {
const result = resolvePath(
'1A2B3C4D',
{ ...sender, publicKeyOrPrefix: 'AABBCCDDEEFF' + '0'.repeat(52), pathHashMode: null },
contacts,
config,
2
);
expect(result.sender.prefix).toBe('AABB');
});
it('handles null config gracefully', () => {
const result = resolvePath('1A', sender, contacts, null);

View File

@@ -29,6 +29,46 @@ export interface SenderInfo {
publicKeyOrPrefix: string;
lat: number | null;
lon: number | null;
pathHashMode?: number | null;
}
function normalizePathHashMode(mode: number | null | undefined): number | null {
if (mode == null || !Number.isInteger(mode) || mode < 0 || mode > 2) {
return null;
}
return mode;
}
function inferPathHashMode(
path: string | null | undefined,
hopCount?: number | null
): number | null {
if (!path || path.length === 0 || hopCount == null || hopCount <= 0) {
return null;
}
const charsPerHop = path.length / hopCount;
if (
charsPerHop < 2 ||
charsPerHop > 6 ||
charsPerHop % 2 !== 0 ||
charsPerHop * hopCount !== path.length
) {
return null;
}
return charsPerHop / 2 - 1;
}
function formatEndpointPrefix(key: string | null | undefined, pathHashMode: number | null): string {
if (!key) {
return '??';
}
const normalized = key.toUpperCase();
const hashMode = normalizePathHashMode(pathHashMode) ?? 0;
const chars = (hashMode + 1) * 2;
return normalized.slice(0, Math.min(chars, normalized.length));
}
/**
@@ -269,9 +309,13 @@ export function resolvePath(
hopCount?: number | null
): ResolvedPath {
const hopPrefixes = parsePathHops(path, hopCount);
const inferredPathHashMode = inferPathHashMode(path, hopCount);
// Build sender info
const senderPrefix = sender.publicKeyOrPrefix.toUpperCase().slice(0, 2);
const senderPrefix = formatEndpointPrefix(
sender.publicKeyOrPrefix,
normalizePathHashMode(sender.pathHashMode) ?? inferredPathHashMode
);
const resolvedSender = {
name: sender.name,
prefix: senderPrefix,
@@ -280,7 +324,10 @@ export function resolvePath(
};
// Build receiver info from radio config
const receiverPrefix = config?.public_key?.toUpperCase().slice(0, 2) || '??';
const receiverPrefix = formatEndpointPrefix(
config?.public_key,
normalizePathHashMode(config?.path_hash_mode) ?? inferredPathHashMode
);
const resolvedReceiver = {
name: config?.name || 'Unknown',
prefix: receiverPrefix,