feat: add event source provider with hook (#3298)

* feat: add event source provider with hook

* chore: jwt is updated

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-08-09 19:45:32 +03:00 committed by GitHub
parent f47c23032c
commit 900752b6e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 146 additions and 0 deletions

View File

@ -0,0 +1,22 @@
import { EventListener, EventSourceEventMap } from 'event-source-polyfill';
import { useEventSource } from 'providers/EventSource';
import { useEffect } from 'react';
export const useEventSourceEvent = (
eventName: keyof EventSourceEventMap,
listener: EventListener,
): void => {
const { eventSourceInstance } = useEventSource();
useEffect(() => {
if (eventSourceInstance) {
eventSourceInstance.addEventListener(eventName, listener);
}
return (): void => {
if (eventSourceInstance) {
eventSourceInstance.removeEventListener(eventName, listener);
}
};
}, [eventName, eventSourceInstance, listener]);
};

View File

@ -0,0 +1,124 @@
import { apiV3 } from 'api/apiV1';
import { ENVIRONMENT } from 'constants/env';
import { EventListener, EventSourcePolyfill } from 'event-source-polyfill';
import {
createContext,
PropsWithChildren,
useCallback,
useContext,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
interface IEventSourceContext {
eventSourceInstance: EventSourcePolyfill | null;
isConnectionOpen: boolean;
isConnectionLoading: boolean;
isConnectionError: string;
handleStartOpenConnection: (url?: string) => void;
handleCloseConnection: () => void;
}
const EventSourceContext = createContext<IEventSourceContext>({
eventSourceInstance: null,
isConnectionOpen: false,
isConnectionLoading: false,
isConnectionError: '',
handleStartOpenConnection: () => {},
handleCloseConnection: () => {},
});
export function EventSourceProvider({
children,
}: PropsWithChildren): JSX.Element {
const [isConnectionOpen, setIsConnectionOpen] = useState<boolean>(false);
const [isConnectionLoading, setIsConnectionLoading] = useState<boolean>(false);
const [isConnectionError, setIsConnectionError] = useState<string>('');
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
const eventSourceRef = useRef<EventSourcePolyfill | null>(null);
const handleCloseConnection = useCallback(() => {
if (!eventSourceRef.current) return;
eventSourceRef.current.close();
setIsConnectionOpen(false);
setIsConnectionLoading(false);
}, []);
const handleOpenConnection: EventListener = useCallback(() => {
setIsConnectionLoading(false);
setIsConnectionOpen(true);
}, []);
const handleErrorConnection: EventListener = useCallback(() => {
if (!eventSourceRef.current) return;
handleCloseConnection();
eventSourceRef.current.removeEventListener('error', handleErrorConnection);
eventSourceRef.current.removeEventListener('open', handleOpenConnection);
}, [handleCloseConnection, handleOpenConnection]);
const handleStartOpenConnection = useCallback(
(url?: string) => {
const eventSourceUrl = url || `${ENVIRONMENT.baseURL}${apiV3}logs/livetail`;
const TIMEOUT_IN_MS = 10 * 60 * 1000;
eventSourceRef.current = new EventSourcePolyfill(eventSourceUrl, {
headers: {
Authorization: `Bearer ${user?.accessJwt}`,
},
heartbeatTimeout: TIMEOUT_IN_MS,
});
setIsConnectionLoading(true);
setIsConnectionError('');
eventSourceRef.current.addEventListener('error', handleErrorConnection);
eventSourceRef.current.addEventListener('open', handleOpenConnection);
},
[handleErrorConnection, handleOpenConnection, user?.accessJwt],
);
const contextValue = useMemo(
() => ({
eventSourceInstance: eventSourceRef.current,
isConnectionError,
isConnectionLoading,
isConnectionOpen,
handleStartOpenConnection,
handleCloseConnection,
}),
[
isConnectionError,
isConnectionLoading,
isConnectionOpen,
handleStartOpenConnection,
handleCloseConnection,
],
);
return (
<EventSourceContext.Provider value={contextValue}>
{children}
</EventSourceContext.Provider>
);
}
export const useEventSource = (): IEventSourceContext => {
const context = useContext(EventSourceContext);
if (!context) {
throw new Error('Should be used inside the context');
}
return context;
};