react-use-websocket
Version:
React Hook for WebSocket communication
163 lines (142 loc) • 5.14 kB
text/typescript
import { MutableRefObject } from 'react';
import { setUpSocketIOPing } from './socket-io';
import { DEFAULT_RECONNECT_LIMIT, DEFAULT_RECONNECT_INTERVAL_MS, ReadyState } from './constants';
import { Options, SendMessage, WebSocketLike } from './types';
export interface Setters {
setLastMessage: (message: WebSocketEventMap['message']) => void;
setReadyState: (readyState: ReadyState) => void;
}
const bindMessageHandler = (
webSocketInstance: WebSocketLike,
optionsRef: MutableRefObject<Options>,
setLastMessage: Setters['setLastMessage'],
) => {
webSocketInstance.onmessage = (message: WebSocketEventMap['message']) => {
optionsRef.current.onMessage && optionsRef.current.onMessage(message);
if (typeof optionsRef.current.filter === 'function' && optionsRef.current.filter(message) !== true) {
return;
}
setLastMessage(message);
};
};
const bindOpenHandler = (
webSocketInstance: WebSocketLike,
optionsRef: MutableRefObject<Options>,
setReadyState: Setters['setReadyState'],
reconnectCount: MutableRefObject<number>,
) => {
webSocketInstance.onopen = (event: WebSocketEventMap['open']) => {
optionsRef.current.onOpen && optionsRef.current.onOpen(event);
reconnectCount.current = 0;
setReadyState(ReadyState.OPEN);
};
};
const bindCloseHandler = (
webSocketInstance: WebSocketLike,
optionsRef: MutableRefObject<Options>,
setReadyState: Setters['setReadyState'],
reconnect: () => void,
reconnectCount: MutableRefObject<number>,
) => {
if (webSocketInstance instanceof EventSource) return () => {};
let reconnectTimeout: number;
webSocketInstance.onclose = (event: WebSocketEventMap['close']) => {
optionsRef.current.onClose && optionsRef.current.onClose(event);
setReadyState(ReadyState.CLOSED);
if (optionsRef.current.shouldReconnect && optionsRef.current.shouldReconnect(event)) {
const reconnectAttempts = optionsRef.current.reconnectAttempts ?? DEFAULT_RECONNECT_LIMIT;
if (reconnectCount.current < reconnectAttempts) {
reconnectTimeout = window.setTimeout(() => {
reconnectCount.current++;
reconnect();
}, optionsRef.current.reconnectInterval ?? DEFAULT_RECONNECT_INTERVAL_MS);
} else {
optionsRef.current.onReconnectStop && optionsRef.current.onReconnectStop(reconnectAttempts);
console.warn(`Max reconnect attempts of ${reconnectAttempts} exceeded`);
}
}
};
return () => reconnectTimeout && window.clearTimeout(reconnectTimeout);
};
const bindErrorHandler = (
webSocketInstance: WebSocketLike,
optionsRef: MutableRefObject<Options>,
setReadyState: Setters['setReadyState'],
reconnect: () => void,
reconnectCount: MutableRefObject<number>,
) => {
let reconnectTimeout: number;
webSocketInstance.onerror = (error: WebSocketEventMap['error']) => {
optionsRef.current.onError && optionsRef.current.onError(error);
if (webSocketInstance instanceof EventSource) {
optionsRef.current.onClose && optionsRef.current.onClose({
...error,
code: 1006,
reason: `An error occurred with the EventSource: ${error}`,
wasClean: false,
});
setReadyState(ReadyState.CLOSED);
webSocketInstance.close();
}
if (optionsRef.current.retryOnError) {
if (reconnectCount.current < (optionsRef.current.reconnectAttempts ?? DEFAULT_RECONNECT_LIMIT)) {
reconnectTimeout = window.setTimeout(() => {
reconnectCount.current++;
reconnect();
}, optionsRef.current.reconnectInterval ?? DEFAULT_RECONNECT_INTERVAL_MS);
} else {
optionsRef.current.onReconnectStop && optionsRef.current.onReconnectStop(optionsRef.current.reconnectAttempts as number);
console.warn(`Max reconnect attempts of ${optionsRef.current.reconnectAttempts} exceeded`);
}
}
};
return () => reconnectTimeout && window.clearTimeout(reconnectTimeout);
};
export const attachListeners = (
webSocketInstance: WebSocketLike,
setters: Setters,
optionsRef: MutableRefObject<Options>,
reconnect: () => void,
reconnectCount: MutableRefObject<number>,
sendMessage: SendMessage,
): (() => void) => {
const { setLastMessage, setReadyState } = setters;
let interval: number;
let cancelReconnectOnClose: () => void;
let cancelReconnectOnError: () => void;
if (optionsRef.current.fromSocketIO) {
interval = setUpSocketIOPing(sendMessage);
}
bindMessageHandler(
webSocketInstance,
optionsRef,
setLastMessage,
);
bindOpenHandler(
webSocketInstance,
optionsRef,
setReadyState,
reconnectCount,
);
cancelReconnectOnClose = bindCloseHandler(
webSocketInstance,
optionsRef,
setReadyState,
reconnect,
reconnectCount,
);
cancelReconnectOnError = bindErrorHandler(
webSocketInstance,
optionsRef,
setReadyState,
reconnect,
reconnectCount,
);
return () => {
setReadyState(ReadyState.CLOSING);
cancelReconnectOnClose();
cancelReconnectOnError();
webSocketInstance.close();
if (interval) clearInterval(interval);
};
};