Files
Shadowbroker/frontend/src/hooks/useReverseGeocode.ts
T
anoracleofra-code 668ce16dc7 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
2026-03-26 05:58:04 -06:00

120 lines
4.6 KiB
TypeScript

import { useCallback, useEffect, useState, useRef } from 'react';
import {
GEOCODE_THROTTLE_MS,
GEOCODE_DISTANCE_THRESHOLD,
GEOCODE_CACHE_SIZE,
} from '@/lib/constants';
import { API_BASE } from '@/lib/api';
const REVERSE_GEOCODE_TIMEOUT_MS = 1200;
const REVERSE_GEOCODE_MIN_INTERVAL_MS = 2500;
const REVERSE_GEOCODE_GRID_DECIMALS = 1;
const MOUSE_COORDS_UI_INTERVAL_MS = 80;
const MOUSE_COORDS_DISPLAY_DECIMALS = 4;
async function fetchJsonWithTimeout(url: string, timeoutMs: number, signal?: AbortSignal) {
const controller = new AbortController();
const timeout = window.setTimeout(() => controller.abort(), timeoutMs);
const onAbort = () => controller.abort();
if (signal) signal.addEventListener('abort', onAbort, { once: true });
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} finally {
window.clearTimeout(timeout);
if (signal) signal.removeEventListener('abort', onAbort);
}
}
export function useReverseGeocode() {
const [mouseCoords, setMouseCoords] = useState<{ lat: number; lng: number } | null>(null);
const [locationLabel, setLocationLabel] = useState('');
const geocodeCache = useRef<Map<string, string>>(new Map());
const geocodeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const coordsUiTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const lastGeocodedPos = useRef<{ lat: number; lng: number } | null>(null);
const geocodeAbort = useRef<AbortController | null>(null);
const lastRequestAt = useRef(0);
const lastUiCoordsKey = useRef('');
const pendingUiCoords = useRef<{ lat: number; lng: number } | null>(null);
useEffect(() => {
return () => {
if (geocodeTimer.current) clearTimeout(geocodeTimer.current);
if (coordsUiTimer.current) clearTimeout(coordsUiTimer.current);
if (geocodeAbort.current) geocodeAbort.current.abort();
};
}, []);
const handleMouseCoords = useCallback((coords: { lat: number; lng: number }) => {
pendingUiCoords.current = coords;
if (!coordsUiTimer.current) {
coordsUiTimer.current = setTimeout(() => {
coordsUiTimer.current = null;
const next = pendingUiCoords.current;
if (!next) return;
const uiKey = `${next.lat.toFixed(MOUSE_COORDS_DISPLAY_DECIMALS)},${next.lng.toFixed(MOUSE_COORDS_DISPLAY_DECIMALS)}`;
if (uiKey === lastUiCoordsKey.current) return;
lastUiCoordsKey.current = uiKey;
setMouseCoords(next);
}, MOUSE_COORDS_UI_INTERVAL_MS);
}
if (geocodeTimer.current) clearTimeout(geocodeTimer.current);
geocodeTimer.current = setTimeout(async () => {
if (lastGeocodedPos.current) {
const dLat = Math.abs(coords.lat - lastGeocodedPos.current.lat);
const dLng = Math.abs(coords.lng - lastGeocodedPos.current.lng);
if (dLat < GEOCODE_DISTANCE_THRESHOLD && dLng < GEOCODE_DISTANCE_THRESHOLD) return;
}
const gridKey = `${coords.lat.toFixed(REVERSE_GEOCODE_GRID_DECIMALS)},${coords.lng.toFixed(REVERSE_GEOCODE_GRID_DECIMALS)}`;
const cached = geocodeCache.current.get(gridKey);
if (cached) {
setLocationLabel(cached);
lastGeocodedPos.current = coords;
return;
}
const now = Date.now();
if (now - lastRequestAt.current < REVERSE_GEOCODE_MIN_INTERVAL_MS) return;
lastRequestAt.current = now;
if (geocodeAbort.current) geocodeAbort.current.abort();
geocodeAbort.current = new AbortController();
try {
const data = await fetchJsonWithTimeout(
`${API_BASE}/api/geocode/reverse?lat=${coords.lat}&lng=${coords.lng}&local_only=1`,
REVERSE_GEOCODE_TIMEOUT_MS,
geocodeAbort.current.signal,
);
const label = data?.label || 'Unknown';
if (geocodeCache.current.size > GEOCODE_CACHE_SIZE) {
const iter = geocodeCache.current.keys();
for (let i = 0; i < 100; i++) {
const key = iter.next().value;
if (key !== undefined) geocodeCache.current.delete(key);
}
}
geocodeCache.current.set(gridKey, label);
setLocationLabel(label);
lastGeocodedPos.current = coords;
} catch (err) {
const isAbort =
typeof err === 'object' &&
err !== null &&
'name' in err &&
(err as { name?: string }).name === 'AbortError';
if (!isAbort) {
/* Silently fail - keep last label */
}
}
}, GEOCODE_THROTTLE_MS);
}, []);
return { mouseCoords, locationLabel, handleMouseCoords };
}