mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(p2p stats): improve own-IP geolocation and world map accuracy (#1138)
* fix(p2p stats): improve own-IP geolocation and world map accuracy Resolve the user's public endpoint when libp2p only advertises private listen addresses, look up an accurate country flag for "Your IP", snap peer markers to country centroids, and add leeching seeder link plus panel layout tweaks. * fix(p2p stats): skip caching own-IP lookups cancelled by abort When the P2P stats panel unmounts mid-request, its AbortSignal cancels the in-flight fetchOwnPublicEndpoint / fetchOwnIpCountryCode calls. Those empty results were still cached for 30-60s, so reopening the panel within that window showed "Your IP" as unavailable or without a country flag even though nothing had actually failed. Skip caching when the signal aborted so a later open retries. Addresses Cursor Bugbot finding.
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { extractIpv4FromAddress, getApproximateCountryCode, getApproximateLatLon, isPrivateOrReservedIpv4 } from '../peer-geo';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { COUNTRY_CENTROIDS } from '../../data/country-centroids';
|
||||
import {
|
||||
extractIpFromAddress,
|
||||
extractIpv4FromAddress,
|
||||
extractIpv6FromAddress,
|
||||
fetchOwnIpCountryCode,
|
||||
fetchOwnPublicEndpoint,
|
||||
getApproximateCountryCode,
|
||||
getApproximateLatLon,
|
||||
getFirstPublicIpFromAddresses,
|
||||
isPrivateOrReservedIpv4,
|
||||
} from '../peer-geo';
|
||||
|
||||
const addr = (ip: string) => `/ip4/${ip}/tcp/4001/ws/p2p/12D3KooWExample`;
|
||||
|
||||
@@ -19,6 +30,13 @@ describe('extractIpv4FromAddress', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractIpv6FromAddress', () => {
|
||||
it('extracts the IPv6 from a multiaddr', () => {
|
||||
expect(extractIpv6FromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
|
||||
expect(extractIpFromAddress('/ip6/2001:4860:4860::8888/tcp/4001/ws')).toBe('2001:4860:4860::8888');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPrivateOrReservedIpv4', () => {
|
||||
it('flags private and reserved ranges', () => {
|
||||
for (const ip of ['10.0.0.1', '172.16.5.4', '192.168.1.10', '127.0.0.1', '169.254.1.1', '100.64.0.1', '0.0.0.0', '239.255.0.1']) {
|
||||
@@ -44,27 +62,17 @@ describe('getApproximateLatLon', () => {
|
||||
expect(getApproximateLatLon(addr('8.8.8.8'))).toEqual(getApproximateLatLon(addr('8.8.8.8')));
|
||||
});
|
||||
|
||||
it('places addresses in the expected continental region', () => {
|
||||
const na = getApproximateLatLon(addr('8.8.8.8'));
|
||||
expect(na?.lon).toBeLessThan(-80); // North America
|
||||
expect(na?.lat).toBeGreaterThan(30);
|
||||
|
||||
const eu = getApproximateLatLon(addr('80.80.80.80'));
|
||||
expect(eu?.lon).toBeGreaterThan(0);
|
||||
expect(eu?.lon).toBeLessThan(30);
|
||||
expect(eu?.lat).toBeGreaterThan(40);
|
||||
|
||||
const as = getApproximateLatLon(addr('1.1.1.1'));
|
||||
expect(as?.lon).toBeGreaterThan(90);
|
||||
|
||||
const af = getApproximateLatLon(addr('41.0.0.1'));
|
||||
expect(af?.lon).toBeGreaterThan(8);
|
||||
expect(af?.lon).toBeLessThan(34);
|
||||
expect(af?.lat).toBeLessThan(12);
|
||||
|
||||
const sa = getApproximateLatLon(addr('200.0.0.1'));
|
||||
expect(sa?.lon).toBeLessThan(-45);
|
||||
expect(sa?.lat).toBeLessThan(-5);
|
||||
it('snaps a peer to the centroid of its flag country', () => {
|
||||
for (const ip of ['8.8.8.8', '80.80.80.80', '1.1.1.1', '41.0.0.1', '200.0.0.1', '194.110.247.146', '91.234.199.189']) {
|
||||
const country = getApproximateCountryCode(addr(ip));
|
||||
expect(country).toBeDefined();
|
||||
const centroid = COUNTRY_CENTROIDS[country!];
|
||||
expect(centroid).toBeDefined();
|
||||
const loc = getApproximateLatLon(addr(ip))!;
|
||||
// Marker sits at the country centroid, within the small placement jitter.
|
||||
expect(Math.abs(loc.lat - centroid.lat)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(loc.lon - centroid.lon)).toBeLessThanOrEqual(1.3);
|
||||
}
|
||||
});
|
||||
|
||||
it('stays within valid coordinate bounds', () => {
|
||||
@@ -77,6 +85,68 @@ describe('getApproximateLatLon', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFirstPublicIpFromAddresses', () => {
|
||||
it('returns the first public IP from multiaddrs', () => {
|
||||
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/147.75.84.175/tcp/4001/ws'])).toBe('147.75.84.175');
|
||||
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip6/2001:4860:4860::8888/tcp/443'])).toBe('2001:4860:4860::8888');
|
||||
});
|
||||
|
||||
it('returns undefined when only private addresses are present', () => {
|
||||
expect(getFirstPublicIpFromAddresses(['/ip4/127.0.0.1/tcp/4001', '/ip4/10.0.0.5/tcp/4001', '/ip6/fd00::1/tcp/4001'])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchOwnPublicEndpoint', () => {
|
||||
it('caches the fetched public endpoint', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ 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);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchOwnIpCountryCode', () => {
|
||||
it("resolves and caches the accurate country for the node's own ip", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ country: 'VN', ip: '172.225.56.8' }),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
|
||||
await expect(fetchOwnIpCountryCode('172.225.56.8')).resolves.toBe('vn');
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock.mock.calls[0][0]).toBe('https://api.country.is/172.225.56.8');
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('does not cache a result from an aborted lookup', async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ country: 'VN', ip: '203.0.113.50' }),
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
// An aborted request is cancellation, not a real result, so it must not be cached.
|
||||
await fetchOwnIpCountryCode('203.0.113.50', controller.signal);
|
||||
// A later non-aborted call must perform a fresh lookup instead of a cached blank.
|
||||
await expect(fetchOwnIpCountryCode('203.0.113.50')).resolves.toBe('vn');
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApproximateCountryCode', () => {
|
||||
it('returns undefined when the peer cannot be placed offline', () => {
|
||||
expect(getApproximateCountryCode('/ip4/10.0.0.1/tcp/4001')).toBeUndefined();
|
||||
|
||||
Reference in New Issue
Block a user