267 lines
7.4 KiB
JavaScript
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
|
|
};
|
|
})();
|