Local MCP Server
在Termux中运行本地MCP服务器,支持Ollama模型的文件读取与命令执行。
下载 11
提供 SSE、WebSocket 和 SWR 集成的 React 实时数据钩子,支持连接管理与重连逻辑。
openclaw skills install @wpank/realtime-react-hooks命令、参数、文件名以原文为准
使用 SSE、WebSocket 和 SWR 在 React 应用中实现实时数据的生产级模式。
npx clawhub@latest install realtime-react-hooksimport { useEffect, useRef, useState, useCallback } from 'react';
interface UseSSEOptions<T> {
url: string;
onMessage?: (data: T) => void;
onError?: (error: Event) => void;
enabled?: boolean;
}
export function useSSE<T>({
url,
onMessage,
onError,
enabled = true,
}: UseSSEOptions<T>) {
const [data, setData] = useState<T | null>(null);
const [isConnected, setIsConnected] = useState(false);
const eventSourceRef = useRef<EventSource | null>(null);
useEffect(() => {
if (!enabled) return;
const eventSource = new EventSource(url);
eventSourceRef.current = eventSource;
eventSource.onopen = () => {
setIsConnected(true);
};
eventSource.onmessage = (event) => {
try {
const parsed = JSON.parse(event.data) as T;
setData(parsed);
onMessage?.(parsed);
} catch (e) {
console.error('SSE 解析错误:', e);
}
};
eventSource.onerror = (error) => {
setIsConnected(false);
onError?.(error);
};
return () => {
eventSource.close();
eventSourceRef.current = null;
};
}, [url, enabled]);
const close = useCallback(() => {
eventSourceRef.current?.close();
setIsConnected(false);
}, []);
return { data, isConnected, close };
}interface UseWebSocketOptions {
url: string;
onMessage?: (data: unknown) => void;
reconnect?: boolean;
maxRetries?: number;
}
export function useWebSocket({
url,
onMessage,
reconnect = true,
maxRetries = 5,
}: UseWebSocketOptions) {
const [isConnected, setIsConnected] = useState(false);
const wsRef = useRef<WebSocket | null>(null);
const retriesRef = useRef(0);
const connect = useCallback(() => {
const ws = new WebSocket(url);
wsRef.current = ws;
ws.onopen = () => {
setIsConnected(true);
retriesRef.current = 0;
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
onMessage?.(data);
} catch {
onMessage?.(event.data);
}
};
ws.onclose = () => {
setIsConnected(false);
if (reconnect && retriesRef.current < maxRetries) {
retriesRef.current++;
const delay = Math.min(1000 * 2 ** retriesRef.current, 30000);
setTimeout(connect, delay);
}
};
ws.onerror = () => {
ws.close();
};
}, [url, onMessage, reconnect, maxRetries]);
useEffect(() => {
connect();
return () => wsRef.current?.close();
}, [connect]);
const send = useCallback((data: unknown) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
wsRef.current.send(JSON.stringify(data));
}
}, []);
return { isConnected, send };
}import useSWR from 'swr';
import { useEffect } from 'react';
export function useRealtimeData<T>(
key: string,
fetcher: () => Promise<T>
) {
const { data, mutate, ...rest } = useSWR(key, fetcher);
// 订阅实时更新
useEffect(() => {
const eventSource = new EventSource(`/api/events/${key}`);
eventSource.onmessage = (event) => {
const update = JSON.parse(event.data);
// 乐观更新缓存
mutate((current) => {
if (!current) return update;
return { ...current, ...update };
}, false); // false = 不重新验证
};
return () => eventSource.close();
}, [key, mutate]);
return { data, mutate, ...rest };
}interface UseSubscriptionOptions {
channels: string[];
onEvent: (channel: string, data: unknown) => void;
}
export function useSubscription({ channels, onEvent }: UseSubscriptionOptions) {
const { send, isConnected } = useWebSocket({
url: '/api/ws',
onMessage: (msg: any) => {
if (msg.type === 'event') {
onEvent(msg.channel, msg.data);
}
},
});
useEffect(() => {
if (!isConnected) return;
// 订阅频道
channels.forEach((channel) => {
send({ type: 'subscribe', channel });
});
return () => {
channels.forEach((channel) => {
send({ type: 'unsubscribe', channel });
});
};
}, [channels, isConnected, send]);
return { isConnected };
}export function ConnectionStatus({ isConnected }: { isConnected: boolean }) {
return (
<div className="flex items-center gap-2">
<span
className={cn(
'size-2 rounded-full',
isConnected ? 'bg-success animate-pulse' : 'bg-destructive'
)}
/>
<span className="text-xs text-muted-foreground">
{isConnected ? '已连接' : '已断开'}
</span>
</div>
);
}mutate(data, false) 实现乐观更新// SSE
const { data, isConnected } = useSSE({ url: '/api/events' });
// WebSocket
const { isConnected, send } = useWebSocket({
url: 'wss://api.example.com/ws',
onMessage: (data) => console.log(data),
});
// SWR + 实时数据
const { data } = useRealtimeData('metrics', fetchMetrics);
// 订阅
useSubscription({
channels: ['user:123', 'global'],
onEvent: (channel, data) => updateState(channel, data),
});已收录 3 个 Skill