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);
|
setUser(user);
|
||||||
};
|
};
|
||||||
const isAuthenticated = !!user;
|
const isAuthenticated = !!user;
|
||||||
|
const { subscribe } = useBroadcastChannel();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const search = location.search;
|
const search = location.search;
|
||||||
|
|
||||||
setSearch(search);
|
setSearch(search);
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const { subscribe } = useBroadcastChannel();
|
subscribe("logout", () => {
|
||||||
|
|
||||||
subscribe((message) => {
|
|
||||||
if (message.data === "logout") {
|
|
||||||
router.reload();
|
router.reload();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!isReady || isLoading) return <Progress />;
|
if (!isReady || isLoading) return <Progress />;
|
||||||
|
|
||||||
|
|||||||
@ -12,98 +12,123 @@ import {
|
|||||||
ReactNode,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
|
||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} 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 = {
|
export type BroadcastChannelData = {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
data: string | number | boolean | Record<string, unknown> | undefined | null;
|
payload: BroadcastChannelPayload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IBroadcastChannelProps {
|
export interface IBroadcastChannelProps {
|
||||||
channelName: string;
|
channelName: string;
|
||||||
children?: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BroadcastChannelContext = createContext<{
|
const getOrCreateTabId = () => {
|
||||||
subscribers: ((message: BroadcastChannelData) => void)[];
|
let storedTabId = sessionStorage.getItem("tab_uuid");
|
||||||
subscribe: (callback: (message: BroadcastChannelData) => void) => () => void;
|
|
||||||
postMessage: (message: BroadcastChannelData) => void;
|
if (storedTabId) {
|
||||||
}>({
|
return storedTabId;
|
||||||
subscribers: [],
|
}
|
||||||
|
|
||||||
|
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: () => () => {},
|
subscribe: () => () => {},
|
||||||
postMessage: () => {},
|
postMessage: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||||
channelName,
|
|
||||||
children,
|
children,
|
||||||
|
channelName,
|
||||||
}) => {
|
}) => {
|
||||||
const channelRef = useRef<BroadcastChannel | null>(null);
|
const channelRef = useRef<BroadcastChannel>(
|
||||||
const subscribersRef = useRef<Array<(message: BroadcastChannelData) => void>>(
|
new BroadcastChannel(channelName),
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
const tabUuidRef = useTabUuid();
|
const subscribersRef = useRef<
|
||||||
|
Record<string, Array<(message: BroadcastChannelData) => void>>
|
||||||
|
>({});
|
||||||
|
const tabUuid = getOrCreateTabId();
|
||||||
|
|
||||||
useEffect(() => {
|
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) => {
|
subscribersRef.current[payload.event]?.forEach((callback) =>
|
||||||
const { tabId, data } = event.data;
|
callback(data),
|
||||||
|
);
|
||||||
if (tabId === tabUuidRef.current) return;
|
|
||||||
|
|
||||||
subscribersRef.current.forEach((callback) => callback(data));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
channel.addEventListener("message", handleMessage);
|
channelRef.current.addEventListener("message", handleMessage);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
channel.removeEventListener("message", handleMessage);
|
channelRef.current.removeEventListener("message", handleMessage);
|
||||||
channel.close();
|
channelRef.current.close();
|
||||||
};
|
};
|
||||||
}, [channelName, tabUuidRef]);
|
}, []);
|
||||||
|
|
||||||
const postMessage = (message: BroadcastChannelData) => {
|
const subscribe: IBroadcastChannelContext["subscribe"] = (
|
||||||
channelRef.current?.postMessage({
|
event,
|
||||||
tabId: tabUuidRef.current,
|
callback,
|
||||||
data: message,
|
) => {
|
||||||
});
|
subscribersRef.current[event] ??= [];
|
||||||
};
|
subscribersRef.current[event]?.push(callback);
|
||||||
const subscribe = (callback: (message: BroadcastChannelData) => void) => {
|
|
||||||
subscribersRef.current.push(callback);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const index = subscribersRef.current.indexOf(callback);
|
const index = subscribersRef.current[event]?.indexOf(callback) ?? -1;
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
subscribersRef.current.splice(index, 1);
|
subscribersRef.current[event]?.splice(index, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const contextValue = useMemo(
|
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
|
||||||
() => ({
|
channelRef.current?.postMessage({
|
||||||
subscribers: subscribersRef.current,
|
tabId: tabUuid,
|
||||||
subscribe,
|
payload,
|
||||||
postMessage,
|
});
|
||||||
}),
|
};
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BroadcastChannelContext.Provider value={contextValue}>
|
<BroadcastChannelContext.Provider
|
||||||
|
value={{
|
||||||
|
subscribe,
|
||||||
|
postMessage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</BroadcastChannelContext.Provider>
|
</BroadcastChannelContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BroadcastChannelProvider;
|
|
||||||
|
|
||||||
export const useBroadcastChannel = () => {
|
export const useBroadcastChannel = () => {
|
||||||
const context = useContext(BroadcastChannelContext);
|
const context = useContext(BroadcastChannelContext);
|
||||||
|
|
||||||
@ -115,3 +140,5 @@ export const useBroadcastChannel = () => {
|
|||||||
|
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default BroadcastChannelProvider;
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import { useSocket } from "@/websocket/socket-hooks";
|
|||||||
import { useFind } from "../crud/useFind";
|
import { useFind } from "../crud/useFind";
|
||||||
import { useApiClient } from "../useApiClient";
|
import { useApiClient } from "../useApiClient";
|
||||||
import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth";
|
import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth";
|
||||||
import { useTabUuid } from "../useTabUuid";
|
|
||||||
import { useToast } from "../useToast";
|
import { useToast } from "../useToast";
|
||||||
import { useTranslate } from "../useTranslate";
|
import { useTranslate } from "../useTranslate";
|
||||||
|
|
||||||
@ -61,7 +60,6 @@ export const useLogout = (
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { t } = useTranslate();
|
const { t } = useTranslate();
|
||||||
const { postMessage } = useBroadcastChannel();
|
const { postMessage } = useBroadcastChannel();
|
||||||
const uuid = useTabUuid();
|
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
...options,
|
...options,
|
||||||
@ -72,7 +70,7 @@ export const useLogout = (
|
|||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
queryClient.removeQueries([CURRENT_USER_KEY]);
|
queryClient.removeQueries([CURRENT_USER_KEY]);
|
||||||
postMessage({ data: "logout", tabId: uuid.current || "" });
|
postMessage({ event: "logout" });
|
||||||
await logoutRedirection();
|
await logoutRedirection();
|
||||||
toast.success(t("message.logout_success"));
|
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,
|
ReactNode,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
|
||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} 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 = {
|
export type BroadcastChannelData = {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
data: string | number | boolean | Record<string, unknown> | undefined | null;
|
payload: BroadcastChannelPayload;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IBroadcastChannelProps {
|
export interface IBroadcastChannelProps {
|
||||||
channelName: string;
|
channelName: string;
|
||||||
children?: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BroadcastChannelContext = createContext<{
|
const getOrCreateTabId = () => {
|
||||||
subscribers: ((message: BroadcastChannelData) => void)[];
|
let storedTabId = sessionStorage.getItem("tab_uuid");
|
||||||
subscribe: (callback: (message: BroadcastChannelData) => void) => () => void;
|
|
||||||
postMessage: (message: BroadcastChannelData) => void;
|
if (storedTabId) {
|
||||||
}>({
|
return storedTabId;
|
||||||
subscribers: [],
|
}
|
||||||
|
|
||||||
|
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: () => () => {},
|
subscribe: () => () => {},
|
||||||
postMessage: () => {},
|
postMessage: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||||
channelName,
|
|
||||||
children,
|
children,
|
||||||
|
channelName,
|
||||||
}) => {
|
}) => {
|
||||||
const channelRef = useRef<BroadcastChannel | null>(null);
|
const channelRef = useRef<BroadcastChannel>(
|
||||||
const subscribersRef = useRef<Array<(message: BroadcastChannelData) => void>>(
|
new BroadcastChannel(channelName),
|
||||||
[],
|
|
||||||
);
|
);
|
||||||
const tabUuidRef = useTabUuid();
|
const subscribersRef = useRef<
|
||||||
|
Record<string, Array<(message: BroadcastChannelData) => void>>
|
||||||
|
>({});
|
||||||
|
const tabUuid = getOrCreateTabId();
|
||||||
|
|
||||||
useEffect(() => {
|
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) => {
|
subscribersRef.current[payload.event]?.forEach((callback) =>
|
||||||
const { tabId, data } = event.data;
|
callback(data),
|
||||||
|
);
|
||||||
if (tabId === tabUuidRef.current) return;
|
|
||||||
|
|
||||||
subscribersRef.current.forEach((callback) => callback(data));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
channel.addEventListener("message", handleMessage);
|
channelRef.current.addEventListener("message", handleMessage);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
channel.removeEventListener("message", handleMessage);
|
channelRef.current.removeEventListener("message", handleMessage);
|
||||||
channel.close();
|
// 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) => {
|
const subscribe: IBroadcastChannelContext["subscribe"] = (
|
||||||
channelRef.current?.postMessage({
|
event,
|
||||||
tabId: tabUuidRef.current,
|
callback,
|
||||||
data: message,
|
) => {
|
||||||
});
|
subscribersRef.current[event] ??= [];
|
||||||
};
|
subscribersRef.current[event]?.push(callback);
|
||||||
const subscribe = (callback: (message: BroadcastChannelData) => void) => {
|
|
||||||
subscribersRef.current.push(callback);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const index = subscribersRef.current.indexOf(callback);
|
const index = subscribersRef.current[event]?.indexOf(callback) ?? -1;
|
||||||
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
subscribersRef.current.splice(index, 1);
|
subscribersRef.current[event]?.splice(index, 1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const contextValue = useMemo(
|
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
|
||||||
() => ({
|
channelRef.current?.postMessage({
|
||||||
subscribers: subscribersRef.current,
|
tabId: tabUuid,
|
||||||
subscribe,
|
payload,
|
||||||
postMessage,
|
});
|
||||||
}),
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BroadcastChannelContext.Provider value={contextValue}>
|
<BroadcastChannelContext.Provider
|
||||||
|
value={{
|
||||||
|
subscribe,
|
||||||
|
postMessage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</BroadcastChannelContext.Provider>
|
</BroadcastChannelContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BroadcastChannelProvider;
|
|
||||||
|
|
||||||
export const useBroadcastChannel = () => {
|
export const useBroadcastChannel = () => {
|
||||||
const context = useContext(BroadcastChannelContext);
|
const context = useContext(BroadcastChannelContext);
|
||||||
|
|
||||||
@ -116,3 +142,5 @@ export const useBroadcastChannel = () => {
|
|||||||
|
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default BroadcastChannelProvider;
|
||||||
|
|||||||
@ -385,6 +385,8 @@ const ChatProvider: React.FC<{
|
|||||||
}
|
}
|
||||||
}, [syncState, isOpen]);
|
}, [syncState, isOpen]);
|
||||||
|
|
||||||
|
const { subscribe } = useBroadcastChannel();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (screen === "chat" && connectionState === ConnectionState.connected) {
|
if (screen === "chat" && connectionState === ConnectionState.connected) {
|
||||||
handleSubscription();
|
handleSubscription();
|
||||||
@ -404,6 +406,10 @@ const ChatProvider: React.FC<{
|
|||||||
|
|
||||||
socketCtx.socket.io.on("reconnect", reSubscribe);
|
socketCtx.socket.io.on("reconnect", reSubscribe);
|
||||||
|
|
||||||
|
subscribe("logout", () => {
|
||||||
|
socketCtx.socket.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
socketCtx.socket.io.off("reconnect", reSubscribe);
|
socketCtx.socket.io.off("reconnect", reSubscribe);
|
||||||
};
|
};
|
||||||
@ -452,13 +458,6 @@ const ChatProvider: React.FC<{
|
|||||||
setMessage,
|
setMessage,
|
||||||
handleSubscription,
|
handleSubscription,
|
||||||
};
|
};
|
||||||
const { subscribe } = useBroadcastChannel();
|
|
||||||
|
|
||||||
subscribe(({ data }) => {
|
|
||||||
if (data === "logout") {
|
|
||||||
socketCtx.socket.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>
|
<ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user