mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
The FE connected realtime via Phoenix Channels (wss .../socket/v2), but the
Go backend serves no Phoenix endpoint — its realtime is SSE at
/v1/base/realtime (envelope {event,chain,data}). Result: repeating ws 404 ->
'Live updates temporarily delayed'.
lib/socket/sse.ts reimplements the slice of the Phoenix Socket/Channel API the
27 realtime consumers use, backed by one EventSource, behind the existing
SocketProvider/useSocketChannel/useSocketMessage seam — no consumer changes.
Envelope routes via an optional topic field or a hub-channel translation table
(blocks -> blocks:new_block/new_block today). CT harness becomes a tiny SSE
server with the same signatures. 12 vitest cases cover it.
Frontend-only; chain/indexer untouched. Data freshness remains gated on the
separate C-Chain-rollback/indexer-reorg owner items.
72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
|
|
import type { Channel, SocketOptions } from './sse';
|
|
import { Socket } from './sse';
|
|
|
|
type ChannelRegistry = Record<string, { channel: Channel; subscribers: number }>;
|
|
|
|
const socketContexts = new Map<string, React.Context<{
|
|
socket: Socket | null;
|
|
channelRegistry: React.MutableRefObject<ChannelRegistry>;
|
|
} | null>>();
|
|
|
|
function getSocketContext(name: string) {
|
|
if (!socketContexts.has(name)) {
|
|
socketContexts.set(name, React.createContext<{
|
|
socket: Socket | null;
|
|
channelRegistry: React.MutableRefObject<ChannelRegistry>;
|
|
} | null>(null));
|
|
}
|
|
return socketContexts.get(name)!;
|
|
}
|
|
|
|
interface SocketProviderProps {
|
|
children: React.ReactNode;
|
|
url?: string;
|
|
options?: SocketOptions;
|
|
name?: string;
|
|
}
|
|
|
|
export function SocketProvider({ children, options, url, name = 'default' }: SocketProviderProps) {
|
|
const [ socket, setSocket ] = useState<Socket | null>(null);
|
|
const channelRegistry = React.useRef<ChannelRegistry>({});
|
|
|
|
useEffect(() => {
|
|
if (!url) {
|
|
return;
|
|
}
|
|
|
|
const socketInstance = new Socket(url, options);
|
|
socketInstance.connect();
|
|
setSocket(socketInstance);
|
|
|
|
return () => {
|
|
socketInstance.disconnect();
|
|
setSocket(null);
|
|
};
|
|
}, [ options, url ]);
|
|
|
|
const value = React.useMemo(() => ({
|
|
socket,
|
|
channelRegistry,
|
|
}), [ socket, channelRegistry ]);
|
|
|
|
const SocketContext = getSocketContext(name);
|
|
|
|
return (
|
|
<SocketContext.Provider value={ value }>
|
|
{ children }
|
|
</SocketContext.Provider>
|
|
);
|
|
}
|
|
|
|
// Hook to use a specific named socket
|
|
export function useSocket(name: string = 'default') {
|
|
const SocketContext = getSocketContext(name);
|
|
const context = React.useContext(SocketContext);
|
|
if (context === undefined) {
|
|
throw new Error(`useSocket must be used within a SocketProvider with name "${ name }"`);
|
|
}
|
|
return context;
|
|
}
|