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
+9 -2
View File
@@ -49,11 +49,11 @@ describe('p2p-runtime', () => {
expect(getP2PRuntimeMode({ pkc: { clients: { libp2pJsClients: { libp2pjs: {} } } } }, browserWindow)).toBe('browser-libp2p');
});
it('detects electron Kubo RPC accounts only in electron runtime', () => {
it('detects full-node RPC accounts in browser and electron runtimes', () => {
const account = { pkcOptions: { pkcRpcClientsOptions: ['ws://localhost:9138'] } };
expect(getP2PRuntimeMode(account, electronWindow)).toBe('electron-kubo-rpc');
expect(getP2PRuntimeMode(account, browserWindow)).toBeNull();
expect(getP2PRuntimeMode(account, browserWindow)).toBe('full-node-rpc');
});
it('shows p2p settings in browsers when pure p2p is enabled by default', () => {
@@ -70,6 +70,13 @@ describe('p2p-runtime', () => {
expect(isBrowserPureP2PEnabled(gatewayAccount, p2pBrowserWindowWithDisabledPureP2P)).toBe(false);
});
it('still shows browser full-node RPC stats when browser pure p2p was toggled off', () => {
const account = { pkcOptions: { pkcRpcClientsOptions: ['ws://node.example'] } };
expect(isBrowserPureP2PEnabled(account, browserWindowWithDisabledPureP2P)).toBe(false);
expect(shouldShowP2PSettingsSection(account, browserWindowWithDisabledPureP2P)).toBe(true);
});
it('builds browser p2p and gateway account options without a direct pkc-js import', () => {
const account = {
pkcOptions: {
+85 -6
View File
@@ -6,6 +6,7 @@ import {
extractIpv6FromAddress,
fetchOwnIpCountryCode,
fetchOwnPublicEndpoint,
fetchPeerMapLocation,
getApproximateCountryCode,
getApproximateLatLon,
getFirstPublicIpFromAddresses,
@@ -98,15 +99,34 @@ describe('getFirstPublicIpFromAddresses', () => {
describe('fetchOwnPublicEndpoint', () => {
it('caches the fetched public endpoint', async () => {
const fetchMock = vi.fn().mockResolvedValue({
const fetchMock = vi.fn(async (url: string | URL | Request) => ({
ok: true,
json: async () => ({ country: 'US', ip: '2001:4860:4860::8888' }),
});
json: async () =>
String(url).startsWith('https://free.freeipapi.com/api/json/')
? {
cityName: 'Mountain View',
countryCode: 'US',
latitude: 37.422,
longitude: -122.085,
regionName: 'California',
}
: { country: 'US', ip: '2001:4860:4860::8888' },
}));
vi.stubGlobal('fetch', fetchMock);
await expect(fetchOwnPublicEndpoint()).resolves.toEqual({ countryCode: 'us', ip: '2001:4860:4860::8888' });
await expect(fetchOwnPublicEndpoint()).resolves.toEqual({ countryCode: 'us', ip: '2001:4860:4860::8888' });
expect(fetchMock).toHaveBeenCalledTimes(1);
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({
countryCode: 'us',
ip: '2001:4860:4860::8888',
location: {
countryCode: 'us',
label: 'Mountain View, California, US',
lat: 37.422,
lon: -122.085,
source: 'geoip',
},
});
await expect(fetchOwnPublicEndpoint()).resolves.toMatchObject({ countryCode: 'us', ip: '2001:4860:4860::8888' });
expect(fetchMock).toHaveBeenCalledTimes(2);
vi.unstubAllGlobals();
});
@@ -147,6 +167,65 @@ describe('fetchOwnIpCountryCode', () => {
});
});
describe('fetchPeerMapLocation', () => {
it('resolves and caches a city-level peer location', async () => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
cityName: 'Haarlem',
countryCode: 'NL',
ipAddress: '91.234.199.189',
latitude: 52.3874,
longitude: 4.64622,
regionName: 'North Holland',
}),
});
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
countryCode: 'nl',
label: 'Haarlem, North Holland, NL',
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
await expect(fetchPeerMapLocation('/ip4/91.234.199.189/tcp/4001')).resolves.toMatchObject({
lat: 52.3874,
lon: 4.64622,
source: 'geoip',
});
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0]).toBe('https://free.freeipapi.com/api/json/91.234.199.189');
vi.unstubAllGlobals();
});
it('falls back to the offline country estimate when lookup fails', async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: false });
vi.stubGlobal('fetch', fetchMock);
const location = await fetchPeerMapLocation(addr('80.80.80.80'));
const centroid = COUNTRY_CENTROIDS[location!.countryCode!];
expect(location).toMatchObject({ source: 'coarse' });
expect(centroid).toBeDefined();
expect(Math.abs(location!.lat - centroid.lat)).toBeLessThanOrEqual(1);
expect(Math.abs(location!.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
vi.unstubAllGlobals();
});
it('does not call GeoIP for private peer addresses', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
await expect(fetchPeerMapLocation('/ip4/10.0.0.1/tcp/4001')).resolves.toBeUndefined();
expect(fetchMock).not.toHaveBeenCalled();
vi.unstubAllGlobals();
});
});
describe('getApproximateCountryCode', () => {
it('returns undefined when the peer cannot be placed offline', () => {
expect(getApproximateCountryCode('/ip4/10.0.0.1/tcp/4001')).toBeUndefined();