mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-05 18:28:39 +02:00
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:
@@ -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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user