Release validation: tests green, USB pack, fleet UX and API hardening.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
This commit is contained in:
AetherForge
2026-06-06 16:57:39 -07:00
parent 5229854f00
commit 415b5dc6a3
119 changed files with 7005 additions and 3082 deletions

View File

@@ -1,4 +1,5 @@
import React, { useEffect, useRef, useCallback, useState } from 'react';
import React, { useEffect, useRef, useCallback, useState, useMemo } from 'react';
import { agentStatsUnchanged, WS_LATEST_MESSAGE_TYPES } from '../help/wsStatsCoalesce';
import type {
WSDashboardInit,
WSAgentOffline,
@@ -22,6 +23,12 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
const wsRef = useRef<WebSocket | null>(null);
const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const unmounted = useRef(false);
// Guard: true while an openSocket() invocation is in-flight (awaiting ticket fetch).
// A second connect() call while one is already opening is a no-op — prevents duplicate
// sockets when 'aetherforge-auth' fires during the async fetch.
const openingRef = useRef(false);
// AbortController for the current in-flight ws-ticket fetch; replaced on each open attempt.
const ticketAbortRef = useRef<AbortController | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [agents, setAgents] = useState<Agent[]>([]);
const [recentShares, setRecentShares] = useState<Share[]>([]);
@@ -69,12 +76,23 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
}
const openSocket = async () => {
if (openingRef.current) return;
openingRef.current = true;
// Cancel any previously in-flight ticket fetch before starting a new one.
if (ticketAbortRef.current) {
ticketAbortRef.current.abort();
}
const abortCtrl = new AbortController();
ticketAbortRef.current = abortCtrl;
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
let wsQuery = `token=${encodeURIComponent(token)}`;
try {
const resp = await fetch('/api/v1/auth/ws-ticket', {
method: 'POST',
headers: authHeaders(),
signal: abortCtrl.signal,
});
if (resp.ok) {
const data = (await resp.json()) as { ticket?: string };
@@ -83,9 +101,17 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
}
}
} catch {
/* fall back to legacy token query param */
/* fall back to legacy token query param — also catches AbortError */
} finally {
if (ticketAbortRef.current === abortCtrl) {
ticketAbortRef.current = null;
}
openingRef.current = false;
}
if (unmounted.current) return;
// If this invocation was aborted by a newer call, bail out — the newer
// call will (or already has) opened its own socket.
if (abortCtrl.signal.aborted) return;
const wsUrl = `${protocol}//${window.location.host}/ws/dashboard?${wsQuery}`;
const ws = new WebSocket(wsUrl);
@@ -106,7 +132,9 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data) as WSMessage;
setLatestMessage(msg);
if (WS_LATEST_MESSAGE_TYPES.has(msg.type)) {
setLatestMessage(msg);
}
switch (msg.type) {
case 'init': {
@@ -144,8 +172,11 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
}
case 'stats_update': {
const update = msg.payload as WSStatsUpdate;
setAgents((prev) =>
prev.map((a) =>
setAgents((prev) => {
const idx = prev.findIndex((a) => a.id === update.agent_id);
if (idx < 0) return prev;
if (agentStatsUnchanged(prev[idx], update)) return prev;
return prev.map((a) =>
a.id === update.agent_id
? {
...a,
@@ -196,9 +227,9 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
...(update.services !== undefined ? { services: update.services } : {}),
...(update.latency_ms !== undefined ? { latency_ms: update.latency_ms } : {}),
}
: a
)
);
: a,
);
});
break;
}
case 'new_share': {
@@ -294,11 +325,37 @@ export function WebSocketProvider({ children }: { children: React.ReactNode }) {
};
}, [connect]);
const value = useMemo(
() => ({
isConnected,
agents,
recentShares,
fleetAlerts,
poolStatus,
aiActivity,
agentLogs,
commandResults,
policyAcks,
latestMessage,
sendDashboardMessage,
}),
[
isConnected,
agents,
recentShares,
fleetAlerts,
poolStatus,
aiActivity,
agentLogs,
commandResults,
policyAcks,
latestMessage,
sendDashboardMessage,
],
);
return (
<WebSocketContext.Provider value={{
isConnected, agents, recentShares, fleetAlerts, poolStatus,
aiActivity, agentLogs, commandResults, policyAcks, latestMessage, sendDashboardMessage,
}}>
<WebSocketContext.Provider value={value}>
{children}
</WebSocketContext.Provider>
);