mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: enhance logic
This commit is contained in:
parent
b5d72761e4
commit
9cc2725674
@ -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 />;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"));
|
||||
},
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user