mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(p2p stats): show peer transfer counters
This commit is contained in:
+19
-4
@@ -113,8 +113,8 @@ describe('P2PStatsSettings', () => {
|
||||
getPeers: () => ['peer-1', 'peer-2'],
|
||||
metrics: {
|
||||
toJSON: () => ({
|
||||
helia_bitswap_data_received_bytes: { global: 2048, peer1: 2048 },
|
||||
helia_bitswap_sent_data_bytes_total: 1024,
|
||||
helia_bitswap_data_received_bytes: { global: 2048, 'peer-1': 1536 },
|
||||
helia_bitswap_sent_data_bytes: { global: 1024, 'peer-1': 512 },
|
||||
}),
|
||||
},
|
||||
peerId: { toString: () => 'self-peer' },
|
||||
@@ -161,6 +161,10 @@ describe('P2PStatsSettings', () => {
|
||||
expect(container.textContent).not.toContain('status');
|
||||
expect(connectedPeers?.textContent).toContain('2 peers, 1 connection');
|
||||
expect(connectedPeers?.textContent).toContain('WebSocket');
|
||||
expect(connectedPeers?.textContent).toContain('Received 1.50 KB');
|
||||
expect(connectedPeers?.textContent).toContain('Sent 512 B');
|
||||
expect(connectedPeers?.textContent).not.toContain('outbound');
|
||||
expect(connectedPeers?.textContent).not.toContain('open');
|
||||
expect(connectedPeers?.textContent).toContain('/ip4/127.0.0.1/tcp/4001/ws/p2p/peer-1');
|
||||
expect(rows.has('connections')).toBe(false);
|
||||
expect(rows.has('Listen addresses')).toBe(false);
|
||||
@@ -445,9 +449,14 @@ describe('P2PStatsSettings', () => {
|
||||
},
|
||||
},
|
||||
libp2p: {
|
||||
getConnections: () => [],
|
||||
getConnections: () => [
|
||||
{
|
||||
remoteAddr: { toString: () => '/ip4/91.234.199.189/tcp/4001/ws/p2p/peer-1' },
|
||||
remotePeer: { toString: () => 'peer-1' },
|
||||
},
|
||||
],
|
||||
getMultiaddrs: () => ['/ip4/147.75.84.175/tcp/4001/ws'],
|
||||
getPeers: () => [],
|
||||
getPeers: () => ['peer-1'],
|
||||
peerId: { toString: () => 'self-peer' },
|
||||
},
|
||||
},
|
||||
@@ -464,6 +473,8 @@ describe('P2PStatsSettings', () => {
|
||||
expect(container.textContent).toContain('4.00 KB');
|
||||
expect(container.textContent).toContain('Data sent');
|
||||
expect(container.textContent).toContain('2.00 KB');
|
||||
expect(container.querySelector('[data-testid="connected-peers"]')?.textContent).toContain('Received 4.00 KB');
|
||||
expect(container.querySelector('[data-testid="connected-peers"]')?.textContent).toContain('Sent 2.00 KB');
|
||||
});
|
||||
|
||||
it('starts browser transfer counters at zero when Helia exposes no byte totals yet', async () => {
|
||||
@@ -657,11 +668,13 @@ describe('P2PStatsSettings', () => {
|
||||
peers: [
|
||||
{
|
||||
address: '/ip4/91.234.199.189/tcp/4001',
|
||||
bandwidth: { TotalIn: 256, TotalOut: 512 },
|
||||
listenAddress: '/ip4/91.234.199.189/tcp/4001',
|
||||
peerId: 'seed-peer',
|
||||
},
|
||||
{
|
||||
address: '/ip4/117.2.120.113/tcp/4001',
|
||||
bandwidth: { TotalIn: 128, TotalOut: 64 },
|
||||
listenAddress: '',
|
||||
peerId: 'leech-peer',
|
||||
},
|
||||
@@ -700,6 +713,8 @@ describe('P2PStatsSettings', () => {
|
||||
expect(rows.get('Data sent')).toBe('2.00 KB');
|
||||
expect(container.textContent).toContain('seed-peer');
|
||||
expect(container.textContent).toContain('leech-peer');
|
||||
expect(container.querySelector('[data-testid="connected-peers"]')?.textContent).toContain('Received 256 B');
|
||||
expect(container.querySelector('[data-testid="connected-peers"]')?.textContent).toContain('Sent 64 B');
|
||||
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');
|
||||
|
||||
@@ -86,37 +86,16 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.connectionDirection,
|
||||
.connectionStatus,
|
||||
.connectionTransferStats,
|
||||
.connectionRole {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
text-transform: capitalize;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.connectionDirection::before {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.connectionDirection[data-direction*='out' i]::before {
|
||||
content: '↑';
|
||||
}
|
||||
|
||||
.connectionDirection[data-direction*='in' i]::before {
|
||||
content: '↓';
|
||||
}
|
||||
|
||||
.connectionStatus::before {
|
||||
content: '';
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: #9aa0a6;
|
||||
}
|
||||
|
||||
.connectionStatus[data-status*='open' i]::before {
|
||||
background: #3fb950;
|
||||
.connectionRole {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.connectionRole[data-peer-role='leecher'] {
|
||||
|
||||
@@ -36,6 +36,7 @@ type ConnectedPeerEntry = {
|
||||
peerId: string;
|
||||
role?: PeerConnectionRole;
|
||||
status?: string;
|
||||
transferStats?: TransferStats;
|
||||
transport: string;
|
||||
};
|
||||
|
||||
@@ -124,9 +125,15 @@ type TransferStats = {
|
||||
uploadedBytes?: number;
|
||||
};
|
||||
|
||||
type TransferStatsSnapshot = {
|
||||
peers: Map<string, TransferStats>;
|
||||
totals: TransferStats;
|
||||
};
|
||||
|
||||
type ObservedTransferStats = {
|
||||
connections: WeakSet<object>;
|
||||
downloadedBytes: number;
|
||||
peers: Map<string, TransferStats>;
|
||||
streams: WeakSet<object>;
|
||||
uploadedBytes: number;
|
||||
};
|
||||
@@ -190,6 +197,48 @@ const addTransferStats = (stats: TransferStats, direction: keyof TransferStats,
|
||||
stats[direction] = (stats[direction] ?? 0) + numericValue;
|
||||
};
|
||||
|
||||
const mergeTransferStats = (primary: TransferStats, fallback: TransferStats): TransferStats => ({
|
||||
downloadedBytes: primary.downloadedBytes ?? fallback.downloadedBytes,
|
||||
uploadedBytes: primary.uploadedBytes ?? fallback.uploadedBytes,
|
||||
});
|
||||
|
||||
const createTransferStatsSnapshot = (): TransferStatsSnapshot => ({ peers: new Map(), totals: {} });
|
||||
|
||||
const hasTransferStats = (stats?: TransferStats) => stats?.downloadedBytes !== undefined || stats?.uploadedBytes !== undefined;
|
||||
|
||||
const getPeerTransferKey = (value: unknown) => {
|
||||
const key = getStringValue(value, '').trim();
|
||||
if (!key) return undefined;
|
||||
const normalizedKey = key.toLowerCase();
|
||||
if (/^\d+$/.test(key) || ['global', 'value', 'total', 'sum', 'count'].includes(normalizedKey) || normalizedKey.includes('bytes') || normalizedKey.includes('rate')) {
|
||||
return undefined;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
const addPeerTransferStats = (peers: Map<string, TransferStats>, peerKey: unknown, direction: keyof TransferStats, value: unknown) => {
|
||||
const key = getPeerTransferKey(peerKey);
|
||||
if (!key) return;
|
||||
const stats = peers.get(key) ?? {};
|
||||
addTransferStats(stats, direction, value);
|
||||
if (hasTransferStats(stats)) peers.set(key, stats);
|
||||
};
|
||||
|
||||
const addTransferSnapshotStats = (snapshot: TransferStatsSnapshot, direction: keyof TransferStats, value: unknown, peerKey?: unknown) => {
|
||||
addTransferStats(snapshot.totals, direction, value);
|
||||
if (peerKey !== undefined) addPeerTransferStats(snapshot.peers, peerKey, direction, value);
|
||||
};
|
||||
|
||||
const mergeTransferSnapshots = (primary: TransferStatsSnapshot, fallback: TransferStatsSnapshot): TransferStatsSnapshot => {
|
||||
const peers = new Map<string, TransferStats>();
|
||||
for (const [peerKey, stats] of fallback.peers) peers.set(peerKey, stats);
|
||||
for (const [peerKey, stats] of primary.peers) peers.set(peerKey, mergeTransferStats(stats, peers.get(peerKey) ?? {}));
|
||||
return {
|
||||
peers,
|
||||
totals: mergeTransferStats(primary.totals, fallback.totals),
|
||||
};
|
||||
};
|
||||
|
||||
const getEntries = (value: unknown): [string, unknown][] => {
|
||||
try {
|
||||
if (value instanceof Map) return Array.from(value.entries()).map(([key, entry]) => [String(key), entry]);
|
||||
@@ -281,25 +330,27 @@ const getByteLength = (value: unknown): number | undefined => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getTransferStatsFromHeliaCounters = (helia: unknown): TransferStats => {
|
||||
const stats: TransferStats = {};
|
||||
const getTransferStatsFromHeliaCounters = (helia: unknown): TransferStatsSnapshot => {
|
||||
const snapshot = createTransferStatsSnapshot();
|
||||
const visited = new WeakSet<object>();
|
||||
let objectsVisited = 0;
|
||||
|
||||
const visit = (value: unknown, depth: number) => {
|
||||
const visit = (value: unknown, depth: number, peerKey?: string) => {
|
||||
try {
|
||||
if (!isRecord(value) || visited.has(value) || depth > MAX_TRANSFER_COUNTER_DEPTH || objectsVisited > MAX_TRANSFER_COUNTER_OBJECTS) return;
|
||||
visited.add(value);
|
||||
objectsVisited++;
|
||||
|
||||
if ('bytesReceived' in value || 'bytesSent' in value) {
|
||||
addTransferStats(stats, 'downloadedBytes', value.bytesReceived);
|
||||
addTransferStats(stats, 'uploadedBytes', value.bytesSent);
|
||||
const directPeerKey = getPeerTransferKey(getRecordField(value, ['peerId', 'peer', 'Peer', 'id', 'remotePeer'])) ?? peerKey;
|
||||
addTransferSnapshotStats(snapshot, 'downloadedBytes', value.bytesReceived, directPeerKey);
|
||||
addTransferSnapshotStats(snapshot, 'uploadedBytes', value.bytesSent, directPeerKey);
|
||||
}
|
||||
|
||||
for (const [key, entry] of getEntries(value)) {
|
||||
if (typeof entry === 'function' || key === 'logger' || key === 'log' || key === 'events' || key === 'datastore' || key === 'routing') continue;
|
||||
visit(entry, depth + 1);
|
||||
const nextPeerKey = peerKey ?? (isRecord(entry) && ('bytesReceived' in entry || 'bytesSent' in entry) ? getPeerTransferKey(key) : undefined);
|
||||
visit(entry, depth + 1, nextPeerKey);
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
@@ -307,7 +358,7 @@ const getTransferStatsFromHeliaCounters = (helia: unknown): TransferStats => {
|
||||
};
|
||||
|
||||
visit(helia, 0);
|
||||
return stats;
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
const classifyTransferMetricPath = (path: string[]) => {
|
||||
@@ -352,54 +403,73 @@ const getMetricSnapshot = async (source: unknown) => {
|
||||
return snapshots.find((snapshot) => snapshot !== undefined) ?? source;
|
||||
};
|
||||
|
||||
const getTransferStatsFromMetricSnapshot = (snapshot: unknown): TransferStats => {
|
||||
const stats: TransferStats = {};
|
||||
const getTransferStatsFromMetricSnapshot = (metricSnapshot: unknown): TransferStatsSnapshot => {
|
||||
const snapshot = createTransferStatsSnapshot();
|
||||
const visited = new WeakSet<object>();
|
||||
|
||||
const visit = (value: unknown, path: string[], depth: number) => {
|
||||
const direction = classifyTransferMetricPath(path);
|
||||
const numericValue = getFiniteNumber(value);
|
||||
if (direction && numericValue !== undefined) {
|
||||
addTransferStats(stats, direction, numericValue);
|
||||
addTransferStats(snapshot.totals, direction, numericValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isRecord(value) || visited.has(value) || depth > MAX_TRANSFER_COUNTER_DEPTH) return;
|
||||
visited.add(value);
|
||||
|
||||
if (direction && 'global' in value) {
|
||||
addTransferStats(stats, direction, value.global);
|
||||
return;
|
||||
}
|
||||
if (direction) {
|
||||
const totalValue = 'global' in value ? value.global : 'value' in value ? value.value : undefined;
|
||||
const hasTotalValue = getFiniteNumber(totalValue) !== undefined;
|
||||
if (hasTotalValue) addTransferStats(snapshot.totals, direction, totalValue);
|
||||
|
||||
if (direction && 'value' in value) {
|
||||
addTransferStats(stats, direction, value.value);
|
||||
return;
|
||||
let foundPeerValue = false;
|
||||
for (const [key, entry] of getEntries(value)) {
|
||||
const peerKey = getPeerTransferKey(key);
|
||||
const peerValue = getFiniteNumber(entry);
|
||||
if (!peerKey || peerValue === undefined) continue;
|
||||
addPeerTransferStats(snapshot.peers, peerKey, direction, peerValue);
|
||||
if (!hasTotalValue) addTransferStats(snapshot.totals, direction, peerValue);
|
||||
foundPeerValue = true;
|
||||
}
|
||||
|
||||
if (hasTotalValue || foundPeerValue) return;
|
||||
}
|
||||
|
||||
for (const [key, entry] of getEntries(value)) visit(entry, [...path, key], depth + 1);
|
||||
};
|
||||
|
||||
visit(snapshot, [], 0);
|
||||
return stats;
|
||||
visit(metricSnapshot, [], 0);
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
const mergeTransferStats = (primary: TransferStats, fallback: TransferStats): TransferStats => ({
|
||||
downloadedBytes: primary.downloadedBytes ?? fallback.downloadedBytes,
|
||||
uploadedBytes: primary.uploadedBytes ?? fallback.uploadedBytes,
|
||||
});
|
||||
|
||||
const getTransferStatsFromClientShape = (client?: Libp2pClientShape): TransferStats => {
|
||||
const clientRecord = isRecord(client) ? (client as Record<string, unknown>) : undefined;
|
||||
const statsSources = [clientRecord, clientRecord?.stats, clientRecord?.sessionStats].filter(Boolean);
|
||||
return statsSources.reduce<TransferStats>((stats, source) => {
|
||||
const getTransferStatsFromSources = (sources: unknown[]): TransferStats =>
|
||||
sources.reduce<TransferStats>((stats, source) => {
|
||||
if (!isRecord(source)) return stats;
|
||||
const downloadedBytes = source.totalIn ?? source.downloadedBytes ?? source.bytesReceived ?? source.receivedBytes;
|
||||
const uploadedBytes = source.totalOut ?? source.uploadedBytes ?? source.bytesSent ?? source.sentBytes;
|
||||
const downloadedBytes = source.totalIn ?? source.TotalIn ?? source.downloadedBytes ?? source.bytesReceived ?? source.receivedBytes ?? source.dataReceivedBytes;
|
||||
const uploadedBytes = source.totalOut ?? source.TotalOut ?? source.uploadedBytes ?? source.bytesSent ?? source.sentBytes ?? source.dataSentBytes;
|
||||
addTransferStats(stats, 'downloadedBytes', downloadedBytes);
|
||||
addTransferStats(stats, 'uploadedBytes', uploadedBytes);
|
||||
return stats;
|
||||
}, {});
|
||||
|
||||
const getNestedTransferStats = (source: unknown): TransferStats => {
|
||||
if (!isRecord(source)) return {};
|
||||
return getTransferStatsFromSources([
|
||||
source,
|
||||
source.stat,
|
||||
source.stats,
|
||||
source.sessionStats,
|
||||
source.bandwidth,
|
||||
source.Bandwidth,
|
||||
source.bandwidthStats,
|
||||
source.transferStats,
|
||||
]);
|
||||
};
|
||||
|
||||
const getTransferStatsFromClientShape = (client?: Libp2pClientShape): TransferStats => {
|
||||
const clientRecord = isRecord(client) ? (client as Record<string, unknown>) : undefined;
|
||||
return getNestedTransferStats(clientRecord);
|
||||
};
|
||||
|
||||
const getObservedTransferStats = (client?: Libp2pClientShape): ObservedTransferStats | undefined => {
|
||||
@@ -409,6 +479,7 @@ const getObservedTransferStats = (client?: Libp2pClientShape): ObservedTransferS
|
||||
stats = {
|
||||
connections: new WeakSet<object>(),
|
||||
downloadedBytes: 0,
|
||||
peers: new Map(),
|
||||
streams: new WeakSet<object>(),
|
||||
uploadedBytes: 0,
|
||||
};
|
||||
@@ -417,7 +488,23 @@ const getObservedTransferStats = (client?: Libp2pClientShape): ObservedTransferS
|
||||
return stats;
|
||||
};
|
||||
|
||||
const instrumentStreamTransferStats = (stream: unknown, stats: ObservedTransferStats) => {
|
||||
const addObservedTransferStats = (stats: ObservedTransferStats, direction: keyof TransferStats, value: unknown, peerKey?: unknown) => {
|
||||
addTransferStats(stats, direction, value);
|
||||
addPeerTransferStats(stats.peers, peerKey, direction, value);
|
||||
};
|
||||
|
||||
const getObservedTransferSnapshot = (stats?: ObservedTransferStats): TransferStatsSnapshot => {
|
||||
const snapshot = createTransferStatsSnapshot();
|
||||
if (!stats) return snapshot;
|
||||
snapshot.totals = {
|
||||
downloadedBytes: stats.downloadedBytes,
|
||||
uploadedBytes: stats.uploadedBytes,
|
||||
};
|
||||
for (const [peerKey, peerStats] of stats.peers) snapshot.peers.set(peerKey, peerStats);
|
||||
return snapshot;
|
||||
};
|
||||
|
||||
const instrumentStreamTransferStats = (stream: unknown, stats: ObservedTransferStats, peerKey?: unknown) => {
|
||||
if (!isRecord(stream) || stats.streams.has(stream)) return;
|
||||
stats.streams.add(stream);
|
||||
|
||||
@@ -425,7 +512,7 @@ const instrumentStreamTransferStats = (stream: unknown, stats: ObservedTransferS
|
||||
if (typeof send === 'function') {
|
||||
try {
|
||||
stream.send = function sendWithTransferStats(this: unknown, data: unknown, ...args: unknown[]) {
|
||||
addTransferStats(stats, 'uploadedBytes', getByteLength(data));
|
||||
addObservedTransferStats(stats, 'uploadedBytes', getByteLength(data), peerKey);
|
||||
return send.call(this, data, ...args);
|
||||
};
|
||||
} catch {
|
||||
@@ -438,7 +525,7 @@ const instrumentStreamTransferStats = (stream: unknown, stats: ObservedTransferS
|
||||
try {
|
||||
addEventListener.call(stream, 'message', (event: unknown) => {
|
||||
const data = isRecord(event) ? (event.data ?? event.detail) : undefined;
|
||||
addTransferStats(stats, 'downloadedBytes', getByteLength(data));
|
||||
addObservedTransferStats(stats, 'downloadedBytes', getByteLength(data), peerKey);
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
@@ -448,6 +535,7 @@ const instrumentStreamTransferStats = (stream: unknown, stats: ObservedTransferS
|
||||
|
||||
const instrumentConnectionTransferStats = (connection: unknown, stats: ObservedTransferStats) => {
|
||||
if (!isRecord(connection)) return;
|
||||
const peerKey = getStringValue(connection.remotePeer);
|
||||
|
||||
if (!stats.connections.has(connection)) {
|
||||
stats.connections.add(connection);
|
||||
@@ -456,7 +544,7 @@ const instrumentConnectionTransferStats = (connection: unknown, stats: ObservedT
|
||||
try {
|
||||
connection.newStream = async function newStreamWithTransferStats(this: unknown, ...args: unknown[]) {
|
||||
const stream = await newStream.apply(this, args);
|
||||
instrumentStreamTransferStats(stream, stats);
|
||||
instrumentStreamTransferStats(stream, stats, peerKey);
|
||||
return stream;
|
||||
};
|
||||
} catch {
|
||||
@@ -465,26 +553,26 @@ const instrumentConnectionTransferStats = (connection: unknown, stats: ObservedT
|
||||
}
|
||||
}
|
||||
|
||||
for (const stream of toArray(connection.streams)) instrumentStreamTransferStats(stream, stats);
|
||||
for (const stream of toArray(connection.streams)) instrumentStreamTransferStats(stream, stats, peerKey);
|
||||
};
|
||||
|
||||
const getBrowserTransferStats = async (client?: Libp2pClientShape, connections: unknown[] = []): Promise<TransferStats> => {
|
||||
const getBrowserTransferStats = async (client?: Libp2pClientShape, connections: unknown[] = []): Promise<TransferStatsSnapshot> => {
|
||||
try {
|
||||
const helia = client?._helia;
|
||||
const observedStats = getObservedTransferStats(client);
|
||||
if (observedStats) connections.forEach((connection) => instrumentConnectionTransferStats(connection, observedStats));
|
||||
|
||||
const clientStats = getTransferStatsFromClientShape(client);
|
||||
const clientStats = { peers: new Map(), totals: getTransferStatsFromClientShape(client) };
|
||||
const counterStats = getTransferStatsFromHeliaCounters(helia);
|
||||
const metricSources = [helia?.metrics, helia?.libp2p?.metrics].filter(Boolean);
|
||||
const metricSnapshots = await Promise.all(metricSources.map((source) => getMetricSnapshot(source)));
|
||||
const metricStats = metricSnapshots
|
||||
.map((snapshot) => getTransferStatsFromMetricSnapshot(snapshot))
|
||||
.reduce<TransferStats>((stats, nextStats) => mergeTransferStats(stats, nextStats), {});
|
||||
.reduce<TransferStatsSnapshot>((stats, nextStats) => mergeTransferSnapshots(stats, nextStats), createTransferStatsSnapshot());
|
||||
|
||||
return mergeTransferStats(mergeTransferStats(mergeTransferStats(clientStats, counterStats), metricStats), observedStats ?? {});
|
||||
return mergeTransferSnapshots(mergeTransferSnapshots(mergeTransferSnapshots(clientStats, counterStats), metricStats), getObservedTransferSnapshot(observedStats));
|
||||
} catch {
|
||||
return {};
|
||||
return createTransferStatsSnapshot();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -573,7 +661,25 @@ const getConnectionPeerId = (connection: unknown) => (isRecord(connection) ? get
|
||||
|
||||
const getConnectionAddress = (connection: unknown) => (isRecord(connection) ? getStringValue(connection.remoteAddr, 'address unavailable') : 'address unavailable');
|
||||
|
||||
const getBrowserConnectedPeersRow = (peers: unknown[], connections: unknown[]): ConnectedPeersStatRow => {
|
||||
const getPeerIdFromAddress = (address: string) => {
|
||||
const parts = address.split('/p2p/');
|
||||
return parts.length > 1 ? parts.at(-1)?.split('/')[0] : undefined;
|
||||
};
|
||||
|
||||
const getPeerTransferStats = (peerStats: Map<string, TransferStats>, peerId: string, address: string) => {
|
||||
const addressPeerId = getPeerIdFromAddress(address);
|
||||
return peerStats.get(peerId) ?? (addressPeerId ? peerStats.get(addressPeerId) : undefined) ?? peerStats.get(address);
|
||||
};
|
||||
|
||||
const getBrowserPeerTransferStats = (connection: unknown, peerId: string, address: string, peerStats: Map<string, TransferStats>) =>
|
||||
mergeTransferStats(
|
||||
getPeerTransferStats(peerStats, peerId, address) ?? {},
|
||||
mergeTransferStats(getNestedTransferStats(connection), { downloadedBytes: 0, uploadedBytes: 0 }),
|
||||
);
|
||||
|
||||
const formatPeerTransferBytes = (value: unknown) => (getFiniteNumber(value) === undefined ? 'unknown' : formatBytes(value));
|
||||
|
||||
const getBrowserConnectedPeersRow = (peers: unknown[], connections: unknown[], peerTransferStats: Map<string, TransferStats>): ConnectedPeersStatRow => {
|
||||
const entries = connections.map<ConnectedPeerEntry>((connection) => {
|
||||
const address = getConnectionAddress(connection);
|
||||
const peerId = getConnectionPeerId(connection);
|
||||
@@ -584,6 +690,7 @@ const getBrowserConnectedPeersRow = (peers: unknown[], connections: unknown[]):
|
||||
id: isRecord(connection) ? getStringValue(connection.id, fallbackId) : fallbackId,
|
||||
peerId,
|
||||
status: isRecord(connection) ? getStringValue(connection.status, '') : undefined,
|
||||
transferStats: getBrowserPeerTransferStats(connection, peerId, address, peerTransferStats),
|
||||
transport: getTransportLabel(address),
|
||||
};
|
||||
});
|
||||
@@ -655,6 +762,7 @@ const getConnectedPeersRowFromRecords = (peers: unknown): ConnectedPeersStatRow
|
||||
const direction = getStringField(peer, PEER_DIRECTION_FIELDS, '');
|
||||
const status = getStringField(peer, PEER_STATUS_FIELDS, '');
|
||||
const fallbackId = `${peerId}-${address}-${direction}-${index}`;
|
||||
const transferStats = getNestedTransferStats(peer);
|
||||
return {
|
||||
address,
|
||||
direction: direction || undefined,
|
||||
@@ -662,6 +770,7 @@ const getConnectedPeersRowFromRecords = (peers: unknown): ConnectedPeersStatRow
|
||||
peerId,
|
||||
role: getPeerConnectionRole(peer),
|
||||
status: status || undefined,
|
||||
transferStats: hasTransferStats(transferStats) ? transferStats : undefined,
|
||||
transport: getTransportLabel(address),
|
||||
};
|
||||
});
|
||||
@@ -762,13 +871,10 @@ const getBrowserLibp2pStats = async (account?: AccountShape, signal?: AbortSigna
|
||||
const client = getFirstObjectValue(account?.pkc?.clients?.libp2pJsClients) as Libp2pClientShape | undefined;
|
||||
const libp2p = client?._helia?.libp2p;
|
||||
const [peers, connections] = await Promise.all([getSafeArray(() => libp2p?.getPeers?.()), getSafeArray(() => libp2p?.getConnections?.())]);
|
||||
const connectedPeersRow = getBrowserConnectedPeersRow(peers, connections);
|
||||
const mode = getBrowserMode(client);
|
||||
const [transferStats, nodeEndpoint, connectedPeers] = await Promise.all([
|
||||
getBrowserTransferStats(client, connections),
|
||||
fetchOwnPublicEndpoint(signal),
|
||||
resolveConnectedPeerLocations(connectedPeersRow, signal),
|
||||
]);
|
||||
const [transferStats, nodeEndpoint] = await Promise.all([getBrowserTransferStats(client, connections), fetchOwnPublicEndpoint(signal)]);
|
||||
const connectedPeersRow = getBrowserConnectedPeersRow(peers, connections, transferStats.peers);
|
||||
const connectedPeers = await resolveConnectedPeerLocations(connectedPeersRow, signal);
|
||||
const connectedPeersWithMapEntries = {
|
||||
...connectedPeers,
|
||||
mapEntries: [...getOwnMapEntry(nodeEndpoint, mode), ...getPeerMapEntries(connectedPeers)],
|
||||
@@ -778,8 +884,8 @@ const getBrowserLibp2pStats = async (account?: AccountShape, signal?: AbortSigna
|
||||
{ name: 'Mode', value: mode },
|
||||
{ name: 'Peer ID', value: libp2p?.peerId?.toString() ?? 'unknown' },
|
||||
nodeEndpoint ? { countryCode: nodeEndpoint.countryCode, ip: nodeEndpoint.ip, name: 'Your IP', type: 'nodeEndpoint' } : { name: 'Your IP', value: 'unavailable' },
|
||||
{ name: 'Data received', value: transferStats.downloadedBytes === undefined ? 'unknown' : formatBytes(transferStats.downloadedBytes) },
|
||||
{ name: 'Data sent', value: transferStats.uploadedBytes === undefined ? 'unknown' : formatBytes(transferStats.uploadedBytes) },
|
||||
{ name: 'Data received', value: transferStats.totals.downloadedBytes === undefined ? 'unknown' : formatBytes(transferStats.totals.downloadedBytes) },
|
||||
{ name: 'Data sent', value: transferStats.totals.uploadedBytes === undefined ? 'unknown' : formatBytes(transferStats.totals.uploadedBytes) },
|
||||
connectedPeersWithMapEntries,
|
||||
];
|
||||
};
|
||||
@@ -965,14 +1071,9 @@ const ConnectedPeersValue = ({ row }: { row: ConnectedPeersStatRow }) => (
|
||||
<div className={styles.connectedPeer} key={entry.id}>
|
||||
<div className={styles.connectedPeerMeta}>
|
||||
<span className={styles.connectionTransport}>{entry.transport}</span>
|
||||
{entry.direction && (
|
||||
<span className={styles.connectionDirection} data-direction={entry.direction}>
|
||||
{entry.direction}
|
||||
</span>
|
||||
)}
|
||||
{entry.status && (
|
||||
<span className={styles.connectionStatus} data-status={entry.status}>
|
||||
{entry.status}
|
||||
{entry.transferStats && (
|
||||
<span className={styles.connectionTransferStats}>
|
||||
Received {formatPeerTransferBytes(entry.transferStats.downloadedBytes)} · Sent {formatPeerTransferBytes(entry.transferStats.uploadedBytes)}
|
||||
</span>
|
||||
)}
|
||||
{entry.role && (
|
||||
|
||||
Reference in New Issue
Block a user