fix: enhance logic

This commit is contained in:
yassinedorbozgithub 2025-01-30 08:23:56 +01:00
parent b5d72761e4
commit 9cc2725674
7 changed files with 163 additions and 174 deletions

View File

@ -101,21 +101,18 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => {
setUser(user);
};
const isAuthenticated = !!user;
const { subscribe } = useBroadcastChannel();
useEffect(() => {
const search = location.search;
setSearch(search);
setIsReady(true);
}, []);
const { subscribe } = useBroadcastChannel();
subscribe((message) => {
if (message.data === "logout") {
subscribe("logout", () => {
router.reload();
}
});
}, []);
if (!isReady || isLoading) return <Progress />;

View File

@ -12,98 +12,123 @@ import {
ReactNode,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import { useTabUuid } from "@/hooks/useTabUuid";
import { generateId } from "@/utils/generateId";
export enum EBCEvent {
LOGOUT = "logout",
}
type BroadcastChannelPayload = {
event: `${EBCEvent}`;
data?: string | number | boolean | Record<string, unknown> | undefined | null;
};
export type BroadcastChannelData = {
tabId: string;
data: string | number | boolean | Record<string, unknown> | undefined | null;
payload: BroadcastChannelPayload;
};
export interface IBroadcastChannelProps {
channelName: string;
children?: ReactNode;
children: ReactNode;
}
export const BroadcastChannelContext = createContext<{
subscribers: ((message: BroadcastChannelData) => void)[];
subscribe: (callback: (message: BroadcastChannelData) => void) => () => void;
postMessage: (message: BroadcastChannelData) => void;
}>({
subscribers: [],
const getOrCreateTabId = () => {
let storedTabId = sessionStorage.getItem("tab_uuid");
if (storedTabId) {
return storedTabId;
}
storedTabId = generateId();
sessionStorage.setItem("tab_uuid", storedTabId);
return storedTabId;
};
interface IBroadcastChannelContext {
subscribe: (
event: `${EBCEvent}`,
callback: (message: BroadcastChannelData) => void,
) => () => void;
postMessage: (payload: BroadcastChannelPayload) => void;
}
export const BroadcastChannelContext = createContext<IBroadcastChannelContext>({
subscribe: () => () => {},
postMessage: () => {},
});
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
channelName,
children,
channelName,
}) => {
const channelRef = useRef<BroadcastChannel | null>(null);
const subscribersRef = useRef<Array<(message: BroadcastChannelData) => void>>(
[],
const channelRef = useRef<BroadcastChannel>(
new BroadcastChannel(channelName),
);
const tabUuidRef = useTabUuid();
const subscribersRef = useRef<
Record<string, Array<(message: BroadcastChannelData) => void>>
>({});
const tabUuid = getOrCreateTabId();
useEffect(() => {
const channel = new BroadcastChannel(channelName);
const handleMessage = ({ data }: MessageEvent<BroadcastChannelData>) => {
const { tabId, payload } = data;
channelRef.current = channel;
if (tabId === tabUuid) {
return;
}
const handleMessage = (event: MessageEvent) => {
const { tabId, data } = event.data;
if (tabId === tabUuidRef.current) return;
subscribersRef.current.forEach((callback) => callback(data));
subscribersRef.current[payload.event]?.forEach((callback) =>
callback(data),
);
};
channel.addEventListener("message", handleMessage);
channelRef.current.addEventListener("message", handleMessage);
return () => {
channel.removeEventListener("message", handleMessage);
channel.close();
channelRef.current.removeEventListener("message", handleMessage);
channelRef.current.close();
};
}, [channelName, tabUuidRef]);
}, []);
const postMessage = (message: BroadcastChannelData) => {
channelRef.current?.postMessage({
tabId: tabUuidRef.current,
data: message,
});
};
const subscribe = (callback: (message: BroadcastChannelData) => void) => {
subscribersRef.current.push(callback);
const subscribe: IBroadcastChannelContext["subscribe"] = (
event,
callback,
) => {
subscribersRef.current[event] ??= [];
subscribersRef.current[event]?.push(callback);
return () => {
const index = subscribersRef.current.indexOf(callback);
const index = subscribersRef.current[event]?.indexOf(callback) ?? -1;
if (index !== -1) {
subscribersRef.current.splice(index, 1);
subscribersRef.current[event]?.splice(index, 1);
}
};
};
const contextValue = useMemo(
() => ({
subscribers: subscribersRef.current,
subscribe,
postMessage,
}),
[],
);
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
channelRef.current?.postMessage({
tabId: tabUuid,
payload,
});
};
return (
<BroadcastChannelContext.Provider value={contextValue}>
<BroadcastChannelContext.Provider
value={{
subscribe,
postMessage,
}}
>
{children}
</BroadcastChannelContext.Provider>
);
};
export default BroadcastChannelProvider;
export const useBroadcastChannel = () => {
const context = useContext(BroadcastChannelContext);
@ -115,3 +140,5 @@ export const useBroadcastChannel = () => {
return context;
};
export default BroadcastChannelProvider;

View File

@ -23,7 +23,6 @@ import { useSocket } from "@/websocket/socket-hooks";
import { useFind } from "../crud/useFind";
import { useApiClient } from "../useApiClient";
import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth";
import { useTabUuid } from "../useTabUuid";
import { useToast } from "../useToast";
import { useTranslate } from "../useTranslate";
@ -61,7 +60,6 @@ export const useLogout = (
const { toast } = useToast();
const { t } = useTranslate();
const { postMessage } = useBroadcastChannel();
const uuid = useTabUuid();
return useMutation({
...options,
@ -72,7 +70,7 @@ export const useLogout = (
},
onSuccess: async () => {
queryClient.removeQueries([CURRENT_USER_KEY]);
postMessage({ data: "logout", tabId: uuid.current || "" });
postMessage({ event: "logout" });
await logoutRedirection();
toast.success(t("message.logout_success"));
},

View File

@ -1,30 +0,0 @@
/*
* 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 { useRef } from "react";
import { generateId } from "@/utils/generateId";
const getOrCreateTabId = () => {
let storedTabId = sessionStorage.getItem("tab_uuid");
if (storedTabId) {
return storedTabId;
}
storedTabId = generateId();
sessionStorage.setItem("tab_uuid", storedTabId);
return storedTabId;
};
export const useTabUuid = () => {
const tabUuidRef = useRef<string>(getOrCreateTabId());
return tabUuidRef;
};

View File

@ -1,30 +0,0 @@
/*
* 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 { useRef } from "react";
import { generateId } from "../utils/generateId";
const getOrCreateTabId = () => {
let storedTabId = sessionStorage.getItem("tab_uuid");
if (storedTabId) {
return storedTabId;
}
storedTabId = generateId();
sessionStorage.setItem("tab_uuid", storedTabId);
return storedTabId;
};
export const useTabUuid = () => {
const tabUuidRef = useRef<string>(getOrCreateTabId());
return tabUuidRef;
};

View File

@ -12,99 +12,125 @@ import {
ReactNode,
useContext,
useEffect,
useMemo,
useRef,
} from "react";
import { useTabUuid } from "../hooks/useTabUuid";
import { generateId } from "../utils/generateId";
export enum EBCEvent {
LOGOUT = "logout",
}
type BroadcastChannelPayload = {
event: `${EBCEvent}`;
data?: string | number | boolean | Record<string, unknown> | undefined | null;
};
export type BroadcastChannelData = {
tabId: string;
data: string | number | boolean | Record<string, unknown> | undefined | null;
payload: BroadcastChannelPayload;
};
export interface IBroadcastChannelProps {
channelName: string;
children?: ReactNode;
children: ReactNode;
}
export const BroadcastChannelContext = createContext<{
subscribers: ((message: BroadcastChannelData) => void)[];
subscribe: (callback: (message: BroadcastChannelData) => void) => () => void;
postMessage: (message: BroadcastChannelData) => void;
}>({
subscribers: [],
const getOrCreateTabId = () => {
let storedTabId = sessionStorage.getItem("tab_uuid");
if (storedTabId) {
return storedTabId;
}
storedTabId = generateId();
sessionStorage.setItem("tab_uuid", storedTabId);
return storedTabId;
};
interface IBroadcastChannelContext {
subscribe: (
event: `${EBCEvent}`,
callback: (message: BroadcastChannelData) => void,
) => () => void;
postMessage: (payload: BroadcastChannelPayload) => void;
}
export const BroadcastChannelContext = createContext<IBroadcastChannelContext>({
subscribe: () => () => {},
postMessage: () => {},
});
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
channelName,
children,
channelName,
}) => {
const channelRef = useRef<BroadcastChannel | null>(null);
const subscribersRef = useRef<Array<(message: BroadcastChannelData) => void>>(
[],
const channelRef = useRef<BroadcastChannel>(
new BroadcastChannel(channelName),
);
const tabUuidRef = useTabUuid();
const subscribersRef = useRef<
Record<string, Array<(message: BroadcastChannelData) => void>>
>({});
const tabUuid = getOrCreateTabId();
useEffect(() => {
const channel = new BroadcastChannel(channelName);
const handleMessage = ({ data }: MessageEvent<BroadcastChannelData>) => {
const { tabId, payload } = data;
channelRef.current = channel;
if (tabId === tabUuid) {
return;
}
const handleMessage = (event: MessageEvent) => {
const { tabId, data } = event.data;
if (tabId === tabUuidRef.current) return;
subscribersRef.current.forEach((callback) => callback(data));
subscribersRef.current[payload.event]?.forEach((callback) =>
callback(data),
);
};
channel.addEventListener("message", handleMessage);
channelRef.current.addEventListener("message", handleMessage);
return () => {
channel.removeEventListener("message", handleMessage);
channel.close();
channelRef.current.removeEventListener("message", handleMessage);
// eslint-disable-next-line react-hooks/exhaustive-deps
channelRef.current.close();
};
}, [channelName, tabUuidRef]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const postMessage = (message: BroadcastChannelData) => {
channelRef.current?.postMessage({
tabId: tabUuidRef.current,
data: message,
});
};
const subscribe = (callback: (message: BroadcastChannelData) => void) => {
subscribersRef.current.push(callback);
const subscribe: IBroadcastChannelContext["subscribe"] = (
event,
callback,
) => {
subscribersRef.current[event] ??= [];
subscribersRef.current[event]?.push(callback);
return () => {
const index = subscribersRef.current.indexOf(callback);
const index = subscribersRef.current[event]?.indexOf(callback) ?? -1;
if (index !== -1) {
subscribersRef.current.splice(index, 1);
subscribersRef.current[event]?.splice(index, 1);
}
};
};
const contextValue = useMemo(
() => ({
subscribers: subscribersRef.current,
subscribe,
postMessage,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
channelRef.current?.postMessage({
tabId: tabUuid,
payload,
});
};
return (
<BroadcastChannelContext.Provider value={contextValue}>
<BroadcastChannelContext.Provider
value={{
subscribe,
postMessage,
}}
>
{children}
</BroadcastChannelContext.Provider>
);
};
export default BroadcastChannelProvider;
export const useBroadcastChannel = () => {
const context = useContext(BroadcastChannelContext);
@ -116,3 +142,5 @@ export const useBroadcastChannel = () => {
return context;
};
export default BroadcastChannelProvider;

View File

@ -385,6 +385,8 @@ const ChatProvider: React.FC<{
}
}, [syncState, isOpen]);
const { subscribe } = useBroadcastChannel();
useEffect(() => {
if (screen === "chat" && connectionState === ConnectionState.connected) {
handleSubscription();
@ -404,6 +406,10 @@ const ChatProvider: React.FC<{
socketCtx.socket.io.on("reconnect", reSubscribe);
subscribe("logout", () => {
socketCtx.socket.disconnect();
});
return () => {
socketCtx.socket.io.off("reconnect", reSubscribe);
};
@ -452,13 +458,6 @@ const ChatProvider: React.FC<{
setMessage,
handleSubscription,
};
const { subscribe } = useBroadcastChannel();
subscribe(({ data }) => {
if (data === "logout") {
socketCtx.socket.disconnect();
}
});
return (
<ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>