From 4bc783ccbe6c11affe3087f222ba4e7291c91b73 Mon Sep 17 00:00:00 2001 From: Aditya Date: Fri, 24 Jan 2025 20:06:50 +0530 Subject: [PATCH 1/3] fix: update return type of findOneByForeignId to include null --- api/src/chat/repositories/subscriber.repository.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/chat/repositories/subscriber.repository.ts b/api/src/chat/repositories/subscriber.repository.ts index 3950e656..c58fe7e1 100644 --- a/api/src/chat/repositories/subscriber.repository.ts +++ b/api/src/chat/repositories/subscriber.repository.ts @@ -125,10 +125,10 @@ export class SubscriberRepository extends BaseRepository< * * @returns The found subscriber entity. */ - async findOneByForeignId(id: string): Promise { + async findOneByForeignId(id: string): Promise { const query = this.findByForeignIdQuery(id); const [result] = await this.execute(query, Subscriber); - return result; + return result || null; } /** From 29c0912de61544e21c55b609e031593f26af7b73 Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 27 Jan 2025 17:47:26 +0530 Subject: [PATCH 2/3] fix: update return type of findOneByForeignId to include null --- api/src/chat/repositories/subscriber.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/chat/repositories/subscriber.repository.ts b/api/src/chat/repositories/subscriber.repository.ts index c58fe7e1..fa59ee70 100644 --- a/api/src/chat/repositories/subscriber.repository.ts +++ b/api/src/chat/repositories/subscriber.repository.ts @@ -123,7 +123,7 @@ export class SubscriberRepository extends BaseRepository< * * @param id - The foreign ID of the subscriber. * - * @returns The found subscriber entity. + * @returns The found subscriber entity, or `null` if no subscriber is found. */ async findOneByForeignId(id: string): Promise { const query = this.findByForeignIdQuery(id); From 7145404dbce2749132cca2b0477e1386bfae0a1c Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Tue, 28 Jan 2025 11:49:46 +0100 Subject: [PATCH 3/3] feat(frontend): add BroadcastChannel hook --- frontend/src/contexts/auth.context.tsx | 12 +++++ frontend/src/hooks/entities/auth-hooks.ts | 3 ++ frontend/src/hooks/useBroadcastChannel.ts | 57 +++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 frontend/src/hooks/useBroadcastChannel.ts diff --git a/frontend/src/contexts/auth.context.tsx b/frontend/src/contexts/auth.context.tsx index a2faf186..a68651a4 100644 --- a/frontend/src/contexts/auth.context.tsx +++ b/frontend/src/contexts/auth.context.tsx @@ -21,6 +21,11 @@ import { Progress } from "@/app-components/displays/Progress"; import { useLogout } from "@/hooks/entities/auth-hooks"; import { useApiClient } from "@/hooks/useApiClient"; import { CURRENT_USER_KEY, PUBLIC_PATHS } from "@/hooks/useAuth"; +import { + EBCEvent, + ETabMode, + useBroadcastChannel, +} from "@/hooks/useBroadcastChannel"; import { useTranslate } from "@/hooks/useTranslate"; import { RouterType } from "@/services/types"; import { IUser } from "@/types/user.types"; @@ -59,6 +64,7 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { i18n.changeLanguage(lang); }; const { mutate: logoutSession } = useLogout(); + const { mode, value } = useBroadcastChannel(); const logout = async () => { updateLanguage(publicRuntimeConfig.lang.default); logoutSession(); @@ -107,6 +113,12 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { setIsReady(true); }, []); + useEffect(() => { + if (value === EBCEvent.LOGOUT_END_SESSION && mode === ETabMode.SECONDARY) { + router.reload(); + } + }, [value, mode, router]); + if (!isReady || isLoading) return ; return ( diff --git a/frontend/src/hooks/entities/auth-hooks.ts b/frontend/src/hooks/entities/auth-hooks.ts index e3a1b07b..fb78fed4 100755 --- a/frontend/src/hooks/entities/auth-hooks.ts +++ b/frontend/src/hooks/entities/auth-hooks.ts @@ -22,6 +22,7 @@ import { useSocket } from "@/websocket/socket-hooks"; import { useFind } from "../crud/useFind"; import { useApiClient } from "../useApiClient"; import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth"; +import { EBCEvent, useBroadcastChannel } from "../useBroadcastChannel"; import { useToast } from "../useToast"; import { useTranslate } from "../useTranslate"; @@ -58,6 +59,7 @@ export const useLogout = ( const { logoutRedirection } = useLogoutRedirection(); const { toast } = useToast(); const { t } = useTranslate(); + const { send } = useBroadcastChannel(); return useMutation({ ...options, @@ -68,6 +70,7 @@ export const useLogout = ( }, onSuccess: async () => { queryClient.removeQueries([CURRENT_USER_KEY]); + send(EBCEvent.LOGOUT_END_SESSION); await logoutRedirection(); toast.success(t("message.logout_success")); }, diff --git a/frontend/src/hooks/useBroadcastChannel.ts b/frontend/src/hooks/useBroadcastChannel.ts new file mode 100644 index 00000000..beba882d --- /dev/null +++ b/frontend/src/hooks/useBroadcastChannel.ts @@ -0,0 +1,57 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +import { useEffect, useRef, useState } from "react"; + +export enum ETabMode { + PRIMARY = "primary", + SECONDARY = "secondary", +} + +export enum EBCEvent { + LOGOUT_END_SESSION = "logout-end-session", +} + +export const useBroadcastChannel = ( + channelName: string = "main-broadcast-channel", + initialValue?: EBCEvent, +) => { + const channelRef = useRef(null); + const [value, setValue] = useState(initialValue); + const [mode, setMode] = useState(ETabMode.PRIMARY); + + useEffect(() => { + channelRef.current = new BroadcastChannel(channelName); + }, [channelName]); + + useEffect(() => { + if (channelRef.current) { + channelRef.current.addEventListener("message", (event) => { + if (mode === ETabMode.PRIMARY) { + setValue(event.data); + setMode(ETabMode.SECONDARY); + } + }); + channelRef.current.postMessage(initialValue); + } + + return () => { + if (channelRef.current?.onmessage) { + channelRef.current.onmessage = null; + } + channelRef.current?.close(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [channelName, initialValue]); + + const send = (data: EBCEvent) => { + channelRef.current?.postMessage(data); + }; + + return { mode, value, send }; +};