signoz/frontend/src/providers/EventSource.tsx
Vikrant Gupta f525647b40
chore(auth): refactor the client handlers in preparation for multi tenant login (#7902)
* chore: update auth

* chore: password changes

* chore: make changes in oss code

* chore: login

* chore: get to a running state

* fix: migration inital commit

* fix: signoz cloud intgtn tests

* fix: minor fixes

* chore: sso code fixed with org domain

* fix: tests

* fix: ee auth api's

* fix: changes in name

* fix: return user in login api

* fix: address comments

* fix: validate password

* fix: handle get domain by email properly

* fix: move authomain to usermodule

* fix: use displayname instead of hname

* fix: rename back endpoints

* fix: update telemetry

* fix: correct errors

* fix: test and fix the invite endpoints

* fix: delete all things related to user in store

* fix: address issues

* fix: ee delete invite

* fix: rename func

* fix: update user and update role

* fix: update role

* chore(api): update the api folder structure according to rest principles

* fix: login and invite changes

* chore(api): update the api folder structure according to rest principles

* chore(login): update the frontend according to the new APIs

* fix: return org name in users response

* chore(login): update the frontend according to the new APIs

* fix: update user role

* fix: nil check

* chore(login): update the frontend according to the new API

* fix: getinvite and update role

* fix: sso

* fix: getinvite use sso ctx

* fix: use correct sourceurl

* fix: getsourceurl from req payload

* chore(login): update the frontend according to the new API

* fix: update created_at

* fix: fix reset password

* chore(login): fixed reset password and bulk invites

* fix: sso signup and token password change

* fix: don't delete last admin

* fix: reset password and migration

* fix: migration

* chore(login): fix the unwanted throw statement and tsconfig

* fix: reset password for sso users

* fix: clean up invite

* chore(login): delete last admin user and reset password

* fix: migration

* fix: update claims and store code

* fix: use correct error

* fix: proper nil checks

* fix: make migration multitenant

* fix: address comments

* fix: minor fixes

* fix: test

* fix: rename reset password

* fix: set self restration only when sso endabled

* chore(auth): update the invite user API

* fix: integration tests

* fix: integration tests

* fix: integration tests

* fix: integration tests

* fix: integration tests

* fix: integration tests

* fix: integration tests

* chore(auth): update integration test

* fix: telemetry

---------

Co-authored-by: nityanandagohain <nityanandagohain@gmail.com>
2025-05-14 18:23:41 +00:00

197 lines
5.5 KiB
TypeScript

import { apiV3 } from 'api/apiV1';
import getLocalStorageApi from 'api/browser/localstorage/get';
import { Logout } from 'api/utils';
import loginApi from 'api/v1/login/login';
import afterLogin from 'AppRoutes/utils';
import { ENVIRONMENT } from 'constants/env';
import { LIVE_TAIL_HEARTBEAT_TIMEOUT } from 'constants/liveTail';
import { LOCALSTORAGE } from 'constants/localStorage';
import { EventListener, EventSourcePolyfill } from 'event-source-polyfill';
import { useNotifications } from 'hooks/useNotifications';
import {
createContext,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import APIError from 'types/api/error';
interface IEventSourceContext {
eventSourceInstance: EventSourcePolyfill | null;
isConnectionOpen: boolean;
isConnectionLoading: boolean;
isConnectionError: boolean;
initialLoading: boolean;
reconnectDueToError: boolean;
handleStartOpenConnection: (urlProps: {
url?: string;
queryString: string;
}) => void;
handleCloseConnection: () => void;
handleSetInitialLoading: (value: boolean) => void;
}
const EventSourceContext = createContext<IEventSourceContext>({
eventSourceInstance: null,
isConnectionOpen: false,
isConnectionLoading: false,
initialLoading: true,
isConnectionError: false,
reconnectDueToError: false,
handleStartOpenConnection: () => {},
handleCloseConnection: () => {},
handleSetInitialLoading: () => {},
});
export function EventSourceProvider({
children,
}: PropsWithChildren): JSX.Element {
const [isConnectionOpen, setIsConnectionOpen] = useState<boolean>(false);
const [isConnectionLoading, setIsConnectionLoading] = useState<boolean>(false);
const [isConnectionError, setIsConnectionError] = useState<boolean>(false);
const [reconnectDueToError, setReconnectDueToError] = useState<boolean>(false);
const [initialLoading, setInitialLoading] = useState<boolean>(true);
const eventSourceRef = useRef<EventSourcePolyfill | null>(null);
const { notifications } = useNotifications();
const handleSetInitialLoading = useCallback((value: boolean) => {
setInitialLoading(value);
}, []);
const handleOpenConnection: EventListener = useCallback(() => {
setIsConnectionLoading(false);
setIsConnectionOpen(true);
setInitialLoading(false);
}, []);
const handleErrorConnection: EventListener = useCallback(async () => {
setIsConnectionOpen(false);
setIsConnectionLoading(true);
setInitialLoading(false);
try {
const response = await loginApi({
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
});
afterLogin(
response.data.userId,
response.data.accessJwt,
response.data.refreshJwt,
true,
);
// If token refresh was successful, we'll let the component
// handle reconnection through the reconnectDueToError state
setReconnectDueToError(true);
setIsConnectionError(true);
return;
} catch (error) {
// If there was an error during token refresh, we'll just
// let the component handle the error state
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
setIsConnectionError(true);
if (!eventSourceRef.current) return;
eventSourceRef.current.close();
Logout();
}
}, [notifications]);
const destroyEventSourceSession = useCallback(() => {
if (!eventSourceRef.current) return;
eventSourceRef.current.close();
eventSourceRef.current.removeEventListener('error', handleErrorConnection);
eventSourceRef.current.removeEventListener('open', handleOpenConnection);
}, [handleErrorConnection, handleOpenConnection]);
const handleCloseConnection = useCallback(() => {
setIsConnectionOpen(false);
setIsConnectionLoading(false);
setIsConnectionError(false);
destroyEventSourceSession();
}, [destroyEventSourceSession]);
const handleStartOpenConnection = useCallback(
(urlProps: { url?: string; queryString: string }): void => {
const { url, queryString } = urlProps;
const eventSourceUrl = url
? `${url}/?${queryString}`
: `${ENVIRONMENT.baseURL}${apiV3}logs/livetail?${queryString}`;
eventSourceRef.current = new EventSourcePolyfill(eventSourceUrl, {
headers: {
Authorization: `Bearer ${getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN)}`,
},
heartbeatTimeout: LIVE_TAIL_HEARTBEAT_TIMEOUT,
});
setIsConnectionLoading(true);
setIsConnectionError(false);
setReconnectDueToError(false);
eventSourceRef.current.addEventListener('error', handleErrorConnection);
eventSourceRef.current.addEventListener('open', handleOpenConnection);
},
[handleErrorConnection, handleOpenConnection],
);
useEffect(
() => (): void => {
handleCloseConnection();
},
[handleCloseConnection],
);
const contextValue: IEventSourceContext = useMemo(
() => ({
eventSourceInstance: eventSourceRef.current,
isConnectionError,
isConnectionLoading,
isConnectionOpen,
initialLoading,
reconnectDueToError,
handleStartOpenConnection,
handleCloseConnection,
handleSetInitialLoading,
}),
[
isConnectionError,
isConnectionLoading,
isConnectionOpen,
initialLoading,
reconnectDueToError,
handleStartOpenConnection,
handleCloseConnection,
handleSetInitialLoading,
],
);
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;
};