v0.9.6: InfoNet hashchain, Wormhole gate encryption, mesh reputation, 16 community contributors

Gate messages now propagate via the Infonet hashchain as encrypted blobs — every node syncs them
through normal chain sync while only Gate members with MLS keys can decrypt. Added mesh reputation
system, peer push workers, voluntary Wormhole opt-in for node participation, fork recovery,
killwormhole scripts, obfuscated terminology, and hardened the self-updater to protect encryption
keys and chain state during updates.

New features: Shodan search, train tracking, Sentinel Hub imagery, 8 new intelligence layers,
CCTV expansion to 11,000+ cameras across 6 countries, Mesh Terminal CLI, prediction markets,
desktop-shell scaffold, and comprehensive mesh test suite (215 frontend + backend tests passing).

Community contributors: @wa1id, @AlborzNazari, @adust09, @Xpirix, @imqdcr, @csysp, @suranyami,
@chr0n1x, @johan-martensson, @singularfailure, @smithbh, @OrfeoTerkuci, @deuza, @tm-const,
@Elhard1, @ttulttul
This commit is contained in:
anoracleofra-code
2026-03-26 05:58:04 -06:00
parent d363013742
commit 668ce16dc7
363 changed files with 170430 additions and 23203 deletions
+105 -27
View File
@@ -1,25 +1,47 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useRef } from "react";
import { API_BASE } from "@/lib/api";
import { mergeData, setBackendStatus as setStoreBackendStatus } from "./useDataStore";
export type BackendStatus = 'connecting' | 'connected' | 'disconnected';
type FastDataProbe = {
commercial_flights?: unknown[];
military_flights?: unknown[];
tracked_flights?: unknown[];
ships?: unknown[];
sigint?: unknown[];
cctv?: unknown[];
};
function hasMeaningfulFastData(json: FastDataProbe): boolean {
return (
(json.commercial_flights?.length || 0) > 100 ||
(json.military_flights?.length || 0) > 25 ||
(json.tracked_flights?.length || 0) > 10 ||
(json.ships?.length || 0) > 100 ||
(json.sigint?.length || 0) > 100 ||
(json.cctv?.length || 0) > 100
);
}
/**
* Event name dispatched by page.tsx when a layer toggle changes.
* useDataPolling listens for this to immediately refetch slow-tier data
* so toggled layers (power plants, GDELT, etc.) appear without the usual
* 120-second wait.
*/
export const LAYER_TOGGLE_EVENT = 'sb:layer-toggle';
/**
* Polls the backend for fast and slow data tiers.
*
* Matches the proven GitHub polling pattern:
* - Empty useEffect dependency array (no restarts on viewport change)
* - No viewport bbox filtering (full data every poll)
* - Adaptive startup polling (3s retry → 15s/120s steady state)
* - ETag conditional requests for bandwidth savings
* - AbortController for clean unmount
* All data is fetched globally (no bbox filtering) — the backend returns its
* full in-memory cache and MapLibre culls off-screen entities on the GPU.
* This eliminates the "empty map when zooming out" lag.
*
* The AIS stream viewport POST (/api/viewport) is still handled separately
* by useViewportBounds to limit upstream AIS ingestion.
*/
export function useDataPolling() {
const dataRef = useRef<any>({});
const [dataVersion, setDataVersion] = useState(0);
const data = dataRef.current;
const [backendStatus, setBackendStatus] = useState<BackendStatus>('connecting');
const fastEtag = useRef<string | null>(null);
const slowEtag = useRef<string | null>(null);
@@ -27,43 +49,84 @@ export function useDataPolling() {
let hasData = false;
let fastTimerId: ReturnType<typeof setTimeout> | null = null;
let slowTimerId: ReturnType<typeof setTimeout> | null = null;
const fastAbortRef = { current: null as AbortController | null };
const slowAbortRef = { current: null as AbortController | null };
const fetchFastData = async () => {
if (fastTimerId) {
clearTimeout(fastTimerId);
fastTimerId = null;
}
if (fastAbortRef.current) return;
const controller = new AbortController();
fastAbortRef.current = controller;
try {
const headers: Record<string, string> = {};
if (fastEtag.current) headers['If-None-Match'] = fastEtag.current;
const res = await fetch(`${API_BASE}/api/live-data/fast`, { headers });
if (res.status === 304) { setBackendStatus('connected'); scheduleNext('fast'); return; }
const res = await fetch(`${API_BASE}/api/live-data/fast`, {
headers,
signal: controller.signal,
});
if (res.status === 304) {
setStoreBackendStatus('connected');
scheduleNext('fast');
return;
}
if (res.ok) {
setBackendStatus('connected');
setStoreBackendStatus('connected');
fastEtag.current = res.headers.get('etag') || null;
const json = await res.json();
dataRef.current = { ...dataRef.current, ...json };
setDataVersion(v => v + 1);
const flights = json.commercial_flights?.length || 0;
if (flights > 100) hasData = true;
mergeData(json);
if (hasMeaningfulFastData(json)) hasData = true;
}
} catch (e) {
console.error("Failed fetching fast live data", e);
setBackendStatus('disconnected');
const aborted =
typeof e === 'object' &&
e !== null &&
'name' in e &&
(e as { name?: string }).name === 'AbortError';
if (!aborted) {
console.error("Failed fetching fast live data", e);
setStoreBackendStatus('disconnected');
}
} finally {
if (fastAbortRef.current === controller) {
fastAbortRef.current = null;
}
}
scheduleNext('fast');
};
const fetchSlowData = async () => {
if (slowAbortRef.current) return;
const controller = new AbortController();
slowAbortRef.current = controller;
try {
const headers: Record<string, string> = {};
if (slowEtag.current) headers['If-None-Match'] = slowEtag.current;
const res = await fetch(`${API_BASE}/api/live-data/slow`, { headers });
const res = await fetch(`${API_BASE}/api/live-data/slow`, {
headers,
signal: controller.signal,
});
if (res.status === 304) { scheduleNext('slow'); return; }
if (res.ok) {
slowEtag.current = res.headers.get('etag') || null;
const json = await res.json();
dataRef.current = { ...dataRef.current, ...json };
setDataVersion(v => v + 1);
mergeData(json);
}
} catch (e) {
console.error("Failed fetching slow live data", e);
const aborted =
typeof e === 'object' &&
e !== null &&
'name' in e &&
(e as { name?: string }).name === 'AbortError';
if (!aborted) {
console.error("Failed fetching slow live data", e);
}
} finally {
if (slowAbortRef.current === controller) {
slowAbortRef.current = null;
}
}
scheduleNext('slow');
};
@@ -79,14 +142,29 @@ export function useDataPolling() {
}
};
// When a layer toggle fires, immediately refetch slow data so the user
// doesn't wait up to 120s for power plants / GDELT / etc. to appear.
const onLayerToggle = () => {
slowEtag.current = null; // invalidate ETag → guarantees fresh payload
if (slowTimerId) clearTimeout(slowTimerId);
slowTimerId = null;
fetchSlowData();
};
window.addEventListener(LAYER_TOGGLE_EVENT, onLayerToggle);
fetchFastData();
fetchSlowData();
return () => {
window.removeEventListener(LAYER_TOGGLE_EVENT, onLayerToggle);
if (fastTimerId) clearTimeout(fastTimerId);
if (slowTimerId) clearTimeout(slowTimerId);
if (fastAbortRef.current) fastAbortRef.current.abort();
if (slowAbortRef.current) slowAbortRef.current.abort();
};
}, []);
return { data, dataVersion, backendStatus };
// Data and backend status are now accessed via useDataStore hooks
// (useDataKey, useDataKeys, useDataSnapshot, useBackendStatus).
// This hook is a pure side-effect — it starts polling and writes to the store.
}