Improve P2P stats peer locations and Kubo display (#1139)

* feat(p2p stats): plot peers by ip location

* fix(p2p stats): show own node map marker

* feat(p2p stats): support full node peers

* fix(p2p stats): clean up kubo stats

* fix(p2p stats): address review feedback
This commit is contained in:
Tommaso Casaburi
2026-05-24 17:32:05 +07:00
committed by GitHub
parent a6a1320381
commit 804c14fc35
9 changed files with 1051 additions and 81 deletions
@@ -45,6 +45,8 @@ const renderSettings = async (isElectron = false) => {
const getStatRows = () =>
new Map(Array.from(container.querySelectorAll('tr')).map((row) => [row.children.item(0)?.textContent ?? '', row.children.item(1)?.textContent ?? '']));
const getMarkerByTitle = (title: string) => Array.from(container.querySelectorAll('svg rect')).find((rect) => rect.querySelector('title')?.textContent === title);
describe('P2PStatsSettings', () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -183,6 +185,141 @@ describe('P2PStatsSettings', () => {
expect(container.textContent).not.toContain('topic subscribers');
});
it('places peer map markers from city-level IP locations', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => {
const requestUrl = String(url);
if (requestUrl === 'https://free.freeipapi.com/api/json/91.234.199.189') {
return {
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
ipAddress: '91.234.199.189',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
};
}
return {
ok: true,
json: async () => ({ country: 'US', ip: '147.75.84.175' }),
};
});
vi.stubGlobal('fetch', fetchMock);
testState.account = {
...testState.account,
pkcOptions: {
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
},
pkc: {
clients: {
libp2pJsClients: {
libp2pjs: {
key: 'libp2pjs',
_helia: {
libp2p: {
getConnections: () => [
{
remoteAddr: { toString: () => '/ip4/91.234.199.189/tcp/4001/ws/p2p/peer-geo' },
remotePeer: { toString: () => 'peer-geo' },
},
],
getMultiaddrs: () => ['/ip4/147.75.84.175/tcp/4001/ws'],
getPeers: () => ['peer-geo'],
peerId: { toString: () => 'self-peer' },
},
},
},
},
},
},
};
await renderSettings(false);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const marker = getMarkerByTitle('peer-geo - Haarlem, North Holland, NL');
expect(marker).not.toBeNull();
expect(marker?.getAttribute('height')).toBe('3');
expect(marker?.getAttribute('width')).toBe('3');
expect(Number(marker?.getAttribute('x'))).toBeCloseTo(183.15, 1);
expect(Number(marker?.getAttribute('y'))).toBeCloseTo(36.11, 1);
expect(fetchMock).toHaveBeenCalledWith('https://free.freeipapi.com/api/json/91.234.199.189', expect.objectContaining({ signal: expect.any(AbortSignal) }));
});
it('shows the own IP flag and a red precise map marker when leeching', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => {
const requestUrl = String(url);
if (requestUrl === 'https://api.country.is/117.2.120.113') {
return {
ok: false,
json: async () => ({}),
};
}
if (requestUrl === 'https://free.freeipapi.com/api/json/117.2.120.113') {
return {
ok: true,
json: async () => ({
cityName: 'Da Nang',
countryCode: 'VN',
ipAddress: '117.2.120.113',
latitude: 16.0678,
longitude: 108.221,
regionName: 'Da Nang City',
}),
};
}
return {
ok: true,
json: async () => ({ country: 'US', ip: '147.75.84.175' }),
};
});
vi.stubGlobal('fetch', fetchMock);
testState.account = {
...testState.account,
pkcOptions: {
libp2pJsClientsOptions: [{ key: 'libp2pjs' }],
},
pkc: {
clients: {
libp2pJsClients: {
libp2pjs: {
key: 'libp2pjs',
_helia: {
libp2p: {
getConnections: () => [],
getMultiaddrs: () => ['/ip4/117.2.120.113/tcp/4001/ws'],
getPeers: () => [],
peerId: { toString: () => 'self-peer' },
},
},
},
},
},
},
};
await renderSettings(false);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const rows = getStatRows();
const yourIpRow = Array.from(container.querySelectorAll('tr')).find((row) => row.textContent?.includes('Your IP'));
const marker = getMarkerByTitle('Your node - Da Nang, Da Nang City, VN');
expect(container.textContent).toContain('Leeching');
expect(rows.get('Your IP')).toContain('117.2.120.113');
expect(yourIpRow?.querySelector('[role="img"]')?.getAttribute('aria-label')).toBe('Vietnam');
expect(marker?.getAttribute('data-peer-role')).toBe('leecher');
expect(Number(marker?.getAttribute('x'))).toBeCloseTo(286.72, 1);
expect(Number(marker?.getAttribute('y'))).toBeCloseTo(72.43, 1);
});
it('reads browser transfer counters from Helia bitswap ledgers', async () => {
testState.account = {
...testState.account,
@@ -354,4 +491,266 @@ describe('P2PStatsSettings', () => {
expect(container.querySelector('a[href="https://github.com/bitsocialnet/bitsocial-seeder"]')).toBeNull();
expect(container.textContent).not.toContain('seed mode');
});
it('renders browser full-node RPC stats with seeding mode and leecher peer markers', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => {
const requestUrl = String(url);
if (requestUrl === 'https://free.freeipapi.com/api/json/147.75.84.175') {
return {
ok: true,
json: async () => ({
cityName: 'New York',
countryCode: 'US',
latitude: 40.7128,
longitude: -74.006,
regionName: 'New York',
}),
};
}
if (requestUrl === 'https://free.freeipapi.com/api/json/91.234.199.189') {
return {
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
};
}
if (requestUrl === 'https://free.freeipapi.com/api/json/117.2.120.113') {
return {
ok: true,
json: async () => ({
cityName: 'Da Nang',
countryCode: 'VN',
latitude: 16.0678,
longitude: 108.221,
regionName: 'Da Nang City',
}),
};
}
return {
ok: true,
json: async () => ({ country: 'US', ip: '147.75.84.175' }),
};
});
vi.stubGlobal('fetch', fetchMock);
testState.rpcSettings = { state: 'connected' };
testState.account = {
...testState.account,
pkcOptions: {
pkcRpcClientsOptions: ['ws://147.75.84.175:9138'],
},
pkc: {
clients: {
pkcRpcClients: {
'ws://147.75.84.175:9138': {
getPeers: vi.fn().mockResolvedValue({
peers: [
{
address: '/ip4/91.234.199.189/tcp/4001',
listenAddress: '/ip4/91.234.199.189/tcp/4001',
peerId: 'seed-peer',
},
{
address: '/ip4/117.2.120.113/tcp/4001',
listenAddress: '',
peerId: 'leech-peer',
},
],
}),
getStats: vi.fn().mockResolvedValue({
bandwidth: { TotalIn: 1024, TotalOut: 2048 },
identity: {
AgentVersion: 'kubo/full-node',
ID: 'full-node-peer',
Addresses: ['/ip4/147.75.84.175/tcp/4001'],
},
}),
state: 'connected',
},
},
},
},
};
await renderSettings(false);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const rows = getStatRows();
const markers = Array.from(container.querySelectorAll('svg rect'));
const ownMarker = markers.find((marker) => marker.querySelector('title')?.textContent?.startsWith('Your node'));
const leecherMarker = markers.find((marker) => marker.querySelector('title')?.textContent?.startsWith('leech-peer'));
expect(rows.get('Mode')).toBe('Seeding');
expect(rows.get('PKC RPC')).toBe('connected');
expect(rows.get('Peer ID')).toBe('full-node-peer');
expect(rows.get('Your IP')).toContain('147.75.84.175');
expect(rows.get('Data received')).toBe('1.00 KB');
expect(rows.get('Data sent')).toBe('2.00 KB');
expect(container.textContent).toContain('seed-peer');
expect(container.textContent).toContain('leech-peer');
expect(container.textContent).toContain('Leeching');
expect(container.querySelector('a[href="https://github.com/bitsocialnet/bitsocial-seeder"]')).toBeNull();
expect(ownMarker?.getAttribute('data-peer-role')).toBe('seeder');
expect(leecherMarker?.getAttribute('data-peer-role')).toBe('leecher');
});
it('does not resolve full-node RPC hostnames through external DNS', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
testState.rpcSettings = { state: 'connected' };
testState.account = {
...testState.account,
pkcOptions: {
pkcRpcClientsOptions: ['ws://node.example:9138'],
},
pkc: {
clients: {
pkcRpcClients: {
'ws://node.example:9138': {
getPeers: vi.fn().mockResolvedValue({ peers: [] }),
getStats: vi.fn().mockResolvedValue({
bandwidth: { TotalIn: 0, TotalOut: 0 },
identity: {
ID: 'hostname-rpc-node',
},
}),
state: 'connected',
},
},
},
},
};
await renderSettings(false);
await act(async () => Promise.resolve());
const rows = getStatRows();
expect(rows.get('Mode')).toBe('Seeding');
expect(rows.get('Peer ID')).toBe('hostname-rpc-node');
expect(rows.get('Your IP')).toBe('unavailable');
expect(fetchMock).not.toHaveBeenCalled();
});
it('renders Electron Kubo stats as seeding with the node location on the map', async () => {
const fetchMock = vi.fn(async (url: string | URL | Request) => {
const requestUrl = String(url);
if (requestUrl === 'http://localhost:50019/api/v0/id') {
return {
ok: true,
text: async () =>
JSON.stringify({
Addresses: [
'/dns4/77-168-54-121.example/tcp/4001/tls/ws/p2p/relay-peer/p2p-circuit/p2p/desktop-kubo-peer',
'/ip4/117.2.120.113/udp/4001/webrtc-direct/p2p/desktop-kubo-peer',
],
AgentVersion: 'kubo/0.41.0',
ID: 'desktop-kubo-peer',
}),
};
}
if (requestUrl === 'http://localhost:50019/api/v0/swarm/peers?direction=true&latency=true&streams=true') {
return {
ok: true,
text: async () =>
JSON.stringify({
Peers: [
{
Addr: '/ip4/91.234.199.189/tcp/4001',
Direction: 'outbound',
ListenAddress: '/ip4/91.234.199.189/tcp/4001',
Peer: 'kubo-seeder',
},
{
Addr: '/ip4/203.0.113.10/tcp/4001',
Direction: 'inbound',
ListenAddress: '',
Peer: 'kubo-leecher',
},
],
}),
};
}
if (requestUrl === 'http://localhost:50019/api/v0/stats/bw') {
return { ok: true, text: async () => JSON.stringify({ RateIn: 12, RateOut: 34, TotalIn: 4096, TotalOut: 8192 }) };
}
if (requestUrl === 'https://free.freeipapi.com/api/json/117.2.120.113') {
return {
ok: true,
json: async () => ({
cityName: 'Da Nang',
countryCode: 'VN',
latitude: 16.0678,
longitude: 108.221,
regionName: 'Da Nang City',
}),
};
}
if (requestUrl === 'https://free.freeipapi.com/api/json/91.234.199.189') {
return {
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
};
}
return {
ok: true,
json: async () => ({ country: 'VN', ip: '117.2.120.113' }),
};
});
vi.stubGlobal('fetch', fetchMock);
testState.rpcSettings = { state: 'connected' };
testState.account = {
...testState.account,
pkcOptions: {
pkcRpcClientsOptions: ['ws://localhost:9138'],
},
};
await renderSettings(true);
await act(async () => {
await Promise.resolve();
await Promise.resolve();
});
const rows = getStatRows();
const markers = Array.from(container.querySelectorAll('svg rect'));
const ownMarker = markers.find((marker) => marker.querySelector('title')?.textContent?.startsWith('Your node'));
const leecherMarker = markers.find((marker) => marker.querySelector('title')?.textContent?.startsWith('kubo-leecher'));
expect(rows.get('Mode')).toBe('Seeding');
expect(rows.get('Kubo RPC')).toBe('http://localhost:50019/api/v0');
expect(rows.get('Peer ID')).toBe('desktop-kubo-peer');
expect(rows.get('Your IP')).toContain('117.2.120.113');
expect(rows.get('Your IP')).not.toContain('77.168.54.121');
expect(rows.get('Data received')).toBe('4.00 KB');
expect(rows.get('Data sent')).toBe('8.00 KB');
expect(rows.has('PKC RPC')).toBe(false);
expect(rows.has('Agent')).toBe(false);
expect(rows.has('Repo size')).toBe(false);
expect(rows.has('Repo objects')).toBe(false);
expect(rows.has('Bitswap peers')).toBe(false);
expect(rows.has('Bitswap wantlist')).toBe(false);
expect(rows.has('Bandwidth in')).toBe(false);
expect(rows.has('Bandwidth out')).toBe(false);
expect(container.textContent).toContain('kubo-seeder');
expect(container.textContent).toContain('kubo-leecher');
expect(container.textContent).toContain('Leeching');
expect(container.textContent).not.toContain('Listen addresses');
expect(ownMarker?.getAttribute('data-peer-role')).toBe('seeder');
expect(leecherMarker?.getAttribute('data-peer-role')).toBe('leecher');
expect(fetchMock).not.toHaveBeenCalledWith('http://localhost:50019/api/v0/repo/stat', expect.anything());
expect(fetchMock).not.toHaveBeenCalledWith('http://localhost:50019/api/v0/bitswap/stat', expect.anything());
expect(fetchMock).not.toHaveBeenCalledWith('http://localhost:50019/api/v0/version', expect.anything());
expect(fetchMock).not.toHaveBeenCalledWith('http://localhost:50019/api/v0/swarm/addrs/local', expect.anything());
});
});