Files
BackOne/app-shell/dist/peer-directory.js
T
2026-06-30 13:10:35 +07:00

267 lines
7.4 KiB
JavaScript

window.BackOnePeerDirectory = (function () {
const NETWORK_KEY = 'backone.lastNetworkId';
const API = {
peers: ['/api/backone/peers', '/app/api/peers', '/peer'],
networks: ['/api/backone/networks', '/app/api/networks', '/network'],
status: ['/api/backone/status', '/app/api/status', '/status'],
network: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
members: (id) => [
`/api/backone/network/${id}/members`,
`/app/api/network/${id}/members`,
`/unstable/controller/network/${id}/member`
],
join: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
],
leave: (id) => [
`/api/backone/network/${id}`,
`/app/api/network/${id}`,
`/network/${id}`
]
};
function apiGet(path) {
return BackOneAuth.apiGet(path);
}
/** Same data as `backone-cli -j peers` (GET /peer). */
async function loadCliPeers() {
try {
const peers = await BackOneAuth.apiGetFirst(API.peers);
return Array.isArray(peers) ? peers : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j listnetworks` (GET /network). */
async function loadNetworks() {
try {
const nets = await BackOneAuth.apiGetFirst(API.networks);
return Array.isArray(nets) ? nets : [];
} catch (_) {
return [];
}
}
/** Same data as `backone-cli -j info` (GET /status). */
async function loadCoreStatus() {
try {
return await BackOneAuth.apiGetFirst(API.status);
} catch (_) {
try {
const res = await fetch('/health', { cache: 'no-store' });
return { online: res.ok };
} catch (_2) {
return { online: false };
}
}
}
function countLeafPeers(cliPeers) {
if (!Array.isArray(cliPeers)) return 0;
return cliPeers.filter(p => !p.role || p.role === 'LEAF').length;
}
async function loadNetwork(nwid) {
if (!nwid) return null;
const networks = await loadNetworks();
const found = networks.find(n => n.id === nwid);
if (found) return found;
try {
return await BackOneAuth.apiGetFirst(API.network(nwid));
} catch (_) {
return null;
}
}
function getLastNetworkId() {
return localStorage.getItem(NETWORK_KEY) || '';
}
function setLastNetworkId(nwid) {
if (nwid) localStorage.setItem(NETWORK_KEY, nwid);
}
function peerIsOnline(p) {
if (!p) return false;
const paths = p.paths || [];
return paths.some(x => x.active && !x.expired);
}
function peerLinkLabel(p) {
if (!p) return '';
if (p.tunneled) return 'RELAY';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.active && !path.expired) {
return 'DIRECT';
}
}
for (const path of paths) {
if (path.active && !path.expired) {
return 'DIRECT';
}
}
return 'RELAY';
}
function peerPreferredPath(p) {
if (!p) return '';
const paths = p.paths || [];
for (const path of paths) {
if (path.preferred && path.address) return path.address;
}
for (const path of paths) {
if (path.active && path.address) return path.address;
}
return '';
}
function cliPeerMap(cliPeers) {
const map = {};
for (const p of cliPeers) {
const id = (p.address || '').toLowerCase();
if (id) map[id] = p;
}
return map;
}
async function loadMemberMap(nwid) {
if (!nwid) return {};
try {
const data = await BackOneAuth.apiGetFirst(API.members(nwid));
const map = {};
if (data && Array.isArray(data.data)) {
for (const m of data.data) {
const id = (m.id || '').toLowerCase();
if (id) map[id] = m;
}
}
return map;
} catch (_) {
return {};
}
}
function buildPeerEntry(peerId, member, cliPeer, networkId, networkName) {
const controllerName = member ? (member.name || '') : '';
const ips = member && member.ipAssignments ? member.ipAssignments : [];
return {
peerId,
networkId: networkId || '',
networkName: networkName || '',
displayName: BackOneContacts.getDisplayName(peerId, controllerName),
controllerName,
nickname: BackOneContacts.getNickname(peerId),
online: peerIsOnline(cliPeer),
latency: cliPeer && typeof cliPeer.latency === 'number' ? cliPeer.latency : null,
role: (cliPeer && cliPeer.role) || (member ? 'LEAF' : ''),
version: (cliPeer && cliPeer.version) || '',
link: cliPeer ? peerLinkLabel(cliPeer) : '',
path: cliPeer ? peerPreferredPath(cliPeer) : '',
ip: ips.length ? ips[0] : '',
onNetwork: !!member
};
}
async function loadPeersForNetwork(nwid) {
if (!nwid) return [];
const [cliPeers, members, selfStatus, network] = await Promise.all([
loadCliPeers(),
loadMemberMap(nwid),
loadCoreStatus().catch(() => ({})),
loadNetwork(nwid)
]);
await BackOneContacts.load();
const selfId = ((selfStatus && selfStatus.address) || '').toLowerCase();
const networkId = nwid;
const networkName = network ? (network.name || network.id || nwid) : nwid;
const cliById = cliPeerMap(cliPeers);
const out = [];
if (Object.keys(members).length > 0) {
for (const peerId of Object.keys(members)) {
const member = members[peerId];
if (!member || member.authorized === false) continue;
if (peerId === selfId) continue;
out.push(buildPeerEntry(peerId, member, cliById[peerId], networkId, networkName));
}
} else {
for (const peerId of Object.keys(cliById)) {
const p = cliById[peerId];
if (!peerId || peerId.length !== 10 || peerId === selfId) continue;
if (p.role && p.role !== 'LEAF') continue;
out.push(buildPeerEntry(peerId, null, p, networkId, networkName));
}
}
out.sort((a, b) => {
if (a.online !== b.online) return a.online ? -1 : 1;
return a.displayName.localeCompare(b.displayName);
});
return out;
}
function searchPeers(query, list) {
const q = (query || '').trim().toLowerCase();
if (!q) return list;
return list.filter(p =>
p.displayName.toLowerCase().includes(q) ||
p.peerId.includes(q) ||
(p.networkName || '').toLowerCase().includes(q) ||
(p.networkId || '').includes(q) ||
(p.controllerName || '').toLowerCase().includes(q) ||
(p.role || '').toLowerCase().includes(q) ||
(p.path || '').toLowerCase().includes(q) ||
(p.ip || '').toLowerCase().includes(q)
);
}
function normalizeNetworkId(input) {
const id = String(input || '').replace(/[^0-9a-fA-F]/g, '').toLowerCase();
return id.length === 16 ? id : '';
}
async function joinNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.join(id), 'POST', {});
}
async function leaveNetwork(nwid) {
const id = normalizeNetworkId(nwid);
if (!id) {
throw new Error('Network ID must be 16 hexadecimal characters');
}
return BackOneAuth.apiSendFirst(API.leave(id), 'DELETE');
}
return {
loadCliPeers,
loadNetworks,
loadNetwork,
loadCoreStatus,
countLeafPeers,
loadPeersForNetwork,
getLastNetworkId,
setLastNetworkId,
joinNetwork,
leaveNetwork,
normalizeNetworkId,
searchPeers,
peerIsOnline,
peerLinkLabel
};
})();