mosaicmap/app/ws-context.tsx
2025-08-24 23:56:56 +08:00

164 lines
4.3 KiB
TypeScript

'use client'
import React, { createContext, useContext, useRef, useState, ReactNode, useEffect } from 'react'
import { Map } from 'maplibre-gl';
import { gql, useSubscription } from '@apollo/client';
import { subscribeToConnectionState, reconnectWebSocket, WSConnectionState } from '@/lib/apollo-client';
import * as Sentry from '@sentry/nextjs';
// 定义WSContext的类型
interface WSContextType {
wsStatus: WsStatus
connectionState: WSConnectionState
data: any
loading: boolean
error: any
restart: () => void
forceReconnect: () => void
isOnline: boolean
}
enum WsStatus {
CONNECTING = 'connecting',
CONNECTED = 'connected',
DISCONNECTED = 'disconnected',
RECONNECTING = 'reconnecting'
}
// 创建Context
const WSContext = createContext<WSContextType | undefined>(undefined)
// Provider组件的Props类型
interface MapProviderProps {
children: ReactNode
}
const SUBSCRIPTION_QUERY = gql`
subscription {
statusUpdates {
id
message
status
timestamp
newestDt
}
}
`
// Provider组件
export function WSProvider({ children }: MapProviderProps) {
const [wsStatus, setWsStatus] = useState<WsStatus>(WsStatus.DISCONNECTED)
const [connectionState, setConnectionState] = useState<WSConnectionState>({
status: 'disconnected',
reconnectAttempts: 0
});
const [isOnline, setIsOnline] = useState(typeof window !== 'undefined' ? navigator.onLine : true);
const { data, loading, error, restart } = useSubscription(SUBSCRIPTION_QUERY, {
errorPolicy: 'all',
onError: (error) => {
Sentry.captureException(error);
}
});
// 监听网络状态变化
useEffect(() => {
if (typeof window === 'undefined') return;
const handleOnline = () => {
setIsOnline(true);
if (wsStatus === WsStatus.DISCONNECTED) {
restart();
}
};
const handleOffline = () => {
setIsOnline(false);
setWsStatus(WsStatus.DISCONNECTED);
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, [wsStatus, restart]);
// 监听 WebSocket 连接状态
useEffect(() => {
const unsubscribe = subscribeToConnectionState((state) => {
setConnectionState(state);
switch (state.status) {
case 'connecting':
setWsStatus(WsStatus.CONNECTING);
break;
case 'connected':
setWsStatus(WsStatus.CONNECTED);
break;
case 'disconnected':
setWsStatus(WsStatus.DISCONNECTED);
break;
case 'reconnecting':
setWsStatus(WsStatus.RECONNECTING);
break;
}
});
return unsubscribe;
}, []);
// 监听订阅状态变化
useEffect(() => {
if (loading && wsStatus !== WsStatus.CONNECTING) {
setWsStatus(WsStatus.CONNECTING);
} else if (error && !loading) {
if (isOnline) {
setTimeout(() => {
restart();
}, 5000);
}
}
}, [loading, error, isOnline, restart, wsStatus]);
const forceReconnect = () => {
Sentry.startSpan({
op: 'websocket.manual-reconnect',
name: 'Manual WebSocket Reconnection'
}, () => {
reconnectWebSocket();
restart();
});
};
const value: WSContextType = {
wsStatus,
connectionState,
data,
loading,
error,
restart,
forceReconnect,
isOnline
}
return (
<WSContext.Provider value={value}>
{children}
</WSContext.Provider>
)
}
// 自定义Hook用于使用MapContext
export function useWS() {
const context = useContext(WSContext)
if (context === undefined) {
throw new Error('useWS must be used within a WSProvider')
}
return context
}