From 1c57e35ba5bebef720c384fa921304e583d4581e Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Thu, 12 Mar 2026 20:13:45 -0700 Subject: [PATCH] Don't collapse ambiguous senders to imply an indirect link betwen repeaters --- .../src/networkGraph/packetNetworkGraph.ts | 23 +++++--- frontend/src/test/packetNetworkGraph.test.ts | 58 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/frontend/src/networkGraph/packetNetworkGraph.ts b/frontend/src/networkGraph/packetNetworkGraph.ts index 2a96264..24068d9 100644 --- a/frontend/src/networkGraph/packetNetworkGraph.ts +++ b/frontend/src/networkGraph/packetNetworkGraph.ts @@ -710,13 +710,22 @@ function projectCanonicalPathWithAliases( repeaterAliases: Map ): ProjectedPacketNetworkPath { const projected = compactPathSteps( - canonicalPath.map((nodeId) => ({ - nodeId: isPacketNetworkNodeVisible(state.nodes.get(nodeId), visibility) - ? (repeaterAliases.get(nodeId) ?? nodeId) - : null, - markHiddenLinkWhenOmitted: true, - hiddenLabel: null, - })) + canonicalPath.map((nodeId, index) => { + const node = state.nodes.get(nodeId); + const visible = isPacketNetworkNodeVisible(node, visibility); + return { + nodeId: visible ? (repeaterAliases.get(nodeId) ?? nodeId) : null, + // Only hidden repeater hops should imply a bridged dashed segment. + // Hidden sender/recipient endpoints should disappear with their own edge. + markHiddenLinkWhenOmitted: + !visible && + !!node && + node.type === 'repeater' && + index > 0 && + index < canonicalPath.length - 1, + hiddenLabel: null, + }; + }) ); return { diff --git a/frontend/src/test/packetNetworkGraph.test.ts b/frontend/src/test/packetNetworkGraph.test.ts index fd61827..cb6825d 100644 --- a/frontend/src/test/packetNetworkGraph.test.ts +++ b/frontend/src/test/packetNetworkGraph.test.ts @@ -9,6 +9,7 @@ import { projectPacketNetwork, snapshotNeighborIds, } from '../networkGraph/packetNetworkGraph'; +import { buildLinkKey } from '../utils/visualizerUtils'; import type { Contact, RadioConfig, RawPacket } from '../types'; import { CONTACT_TYPE_REPEATER } from '../types'; @@ -181,6 +182,63 @@ describe('packetNetworkGraph', () => { expect(projection.links.get('565656565656->self')?.hasDirectObservation).toBe(true); }); + it('does not bridge across hidden ambiguous sender endpoints', () => { + const selfKey = 'ffffffffffff0000000000000000000000000000000000000000000000000000'; + const repeaterOneKey = '1111111111110000000000000000000000000000000000000000000000000000'; + const repeaterTwoKey = '2222222222220000000000000000000000000000000000000000000000000000'; + const repeaterThreeKey = '3333333333330000000000000000000000000000000000000000000000000000'; + const repeaterFourKey = '4444444444440000000000000000000000000000000000000000000000000000'; + + packetFixtures.set('dm-hidden-ambiguous-sender-a', { + payloadType: PayloadType.TextMessage, + messageHash: 'dm-hidden-ambiguous-sender-a', + pathBytes: ['111111111111', '222222222222'], + srcHash: '32', + dstHash: 'ffffffffffff', + advertPubkey: null, + groupTextSender: null, + anonRequestPubkey: null, + }); + packetFixtures.set('dm-hidden-ambiguous-sender-b', { + payloadType: PayloadType.TextMessage, + messageHash: 'dm-hidden-ambiguous-sender-b', + pathBytes: ['333333333333', '444444444444'], + srcHash: '32', + dstHash: 'ffffffffffff', + advertPubkey: null, + groupTextSender: null, + anonRequestPubkey: null, + }); + + const state = createPacketNetworkState('Me'); + const context = buildPacketNetworkContext({ + contacts: [ + createContact(repeaterOneKey, 'Relay 1', CONTACT_TYPE_REPEATER), + createContact(repeaterTwoKey, 'Relay 2', CONTACT_TYPE_REPEATER), + createContact(repeaterThreeKey, 'Relay 3', CONTACT_TYPE_REPEATER), + createContact(repeaterFourKey, 'Relay 4', CONTACT_TYPE_REPEATER), + ], + config: createConfig(selfKey), + repeaterAdvertPaths: [], + splitAmbiguousByTraffic: false, + useAdvertPathHints: false, + }); + + ingestPacketIntoPacketNetwork(state, context, createPacket('dm-hidden-ambiguous-sender-a')); + ingestPacketIntoPacketNetwork(state, context, createPacket('dm-hidden-ambiguous-sender-b')); + + const projection = projectPacketNetwork(state, { + showAmbiguousNodes: false, + showAmbiguousPaths: true, + collapseLikelyKnownSiblingRepeaters: true, + }); + + expect(projection.links.has(buildLinkKey('111111111111', '222222222222'))).toBe(true); + expect(projection.links.has(buildLinkKey('333333333333', '444444444444'))).toBe(true); + expect(projection.links.has(buildLinkKey('111111111111', '333333333333'))).toBe(false); + expect(projection.links.has(buildLinkKey('222222222222', '444444444444'))).toBe(false); + }); + it('does not add a DM recipient node from destination metadata alone', () => { const selfKey = 'ffffffffffff0000000000000000000000000000000000000000000000000000'; const aliceKey = 'aaaaaaaaaaaa0000000000000000000000000000000000000000000000000000';