mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-15 04:40:26 +02:00
feat: real-time gate messages via SSE + faster push/pull intervals
- Add Server-Sent Events endpoint at GET /api/mesh/gate/stream that broadcasts ALL gate events to connected frontends (privacy: no per-gate subscriptions, clients filter locally) - Hook SSE broadcast into all gate event entry points: local append, peer push receiver, and pull loop - Reduce push/pull intervals from 30s to 10s for faster relay sync - Add useGateSSE hook for frontend EventSource integration - GateView + MeshChat use SSE for instant refresh, polling demoted to 30s fallback Latency: same-node instant, cross-node ~10s avg (was ~34s)
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/mesh/wormholeIdentityClient';
|
||||
import { gateEnvelopeDisplayText, gateEnvelopeState, isEncryptedGateEnvelope } from '@/mesh/gateEnvelope';
|
||||
import { validateEventPayload } from '@/mesh/meshSchema';
|
||||
import { useGateSSE } from '@/hooks/useGateSSE';
|
||||
|
||||
const GATE_INTROS: Record<string, string> = {
|
||||
infonet:
|
||||
@@ -357,11 +358,21 @@ export default function GateView({
|
||||
}
|
||||
}, [gateId, hydrateMessages]);
|
||||
|
||||
// SSE: instant delivery when new gate events arrive
|
||||
const handleSSEEvent = useCallback(
|
||||
(eventGateId: string) => {
|
||||
if (eventGateId === gateId) void refreshGate();
|
||||
},
|
||||
[gateId, refreshGate],
|
||||
);
|
||||
useGateSSE(handleSSEEvent);
|
||||
|
||||
// Fallback poll (30s) in case SSE disconnects
|
||||
useEffect(() => {
|
||||
void refreshGate();
|
||||
const timer = window.setInterval(() => {
|
||||
void refreshGate();
|
||||
}, 8000);
|
||||
}, 30_000);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { API_BASE } from '@/lib/api';
|
||||
import { controlPlaneJson } from '@/lib/controlPlane';
|
||||
import { useGateSSE } from '@/hooks/useGateSSE';
|
||||
import { requestSecureMeshTerminalLauncherOpen } from '@/lib/meshTerminalLauncher';
|
||||
import {
|
||||
loadIdentityBoundSensitiveValue,
|
||||
@@ -1111,6 +1112,17 @@ const MeshChat = React.memo(function MeshChat({
|
||||
const [reps, setReps] = useState<Record<string, number>>({});
|
||||
const repsRef = useRef(reps);
|
||||
const [votedOn, setVotedOn] = useState<Record<string, 1 | -1>>({});
|
||||
|
||||
// SSE: bump tick counter to trigger immediate re-poll on gate events
|
||||
const [sseGateTick, setSseGateTick] = useState(0);
|
||||
const selectedGateRef = useRef(selectedGate);
|
||||
selectedGateRef.current = selectedGate;
|
||||
const handleSSEGateEvent = useCallback((eventGateId: string) => {
|
||||
if (eventGateId === selectedGateRef.current.trim().toLowerCase()) {
|
||||
setSseGateTick((t) => t + 1);
|
||||
}
|
||||
}, []);
|
||||
useGateSSE(handleSSEGateEvent);
|
||||
const [gateReplyContext, setGateReplyContext] = useState<GateReplyContext | null>(null);
|
||||
const [showCreateGate, setShowCreateGate] = useState(false);
|
||||
const [newGateId, setNewGateId] = useState('');
|
||||
@@ -1699,7 +1711,7 @@ const MeshChat = React.memo(function MeshChat({
|
||||
}
|
||||
};
|
||||
poll();
|
||||
const iv = setInterval(poll, 10000);
|
||||
const iv = setInterval(poll, 30_000); // SSE handles fast path; this is fallback
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(iv);
|
||||
@@ -1712,6 +1724,7 @@ const MeshChat = React.memo(function MeshChat({
|
||||
gatePersonaBusy,
|
||||
gatePersonaPromptOpen,
|
||||
hydrateInfonetMessages,
|
||||
sseGateTick, // SSE event triggers immediate re-poll
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user