mirror of
https://github.com/hexastack/hexabot
synced 2025-04-02 20:31:33 +00:00
Merge pull request #653 from Hexastack/652-bug---broadcastchannel-duplicate-tab
fix: resolve broadcastChannel duplication tab bug
This commit is contained in:
commit
1b5b759549
@ -15,23 +15,16 @@ import {
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
import { generateId } from "@/utils/generateId";
|
||||
|
||||
export enum EBCEvent {
|
||||
LOGIN = "login",
|
||||
LOGOUT = "logout",
|
||||
}
|
||||
|
||||
type BroadcastChannelPayload = {
|
||||
type BroadcastChannelMessage = {
|
||||
event: `${EBCEvent}`;
|
||||
data?: string | number | boolean | Record<string, unknown> | undefined | null;
|
||||
};
|
||||
|
||||
type BroadcastChannelData = {
|
||||
tabId: string;
|
||||
payload: BroadcastChannelPayload;
|
||||
};
|
||||
|
||||
interface IBroadcastChannelProps {
|
||||
channelName: string;
|
||||
children: ReactNode;
|
||||
@ -40,24 +33,11 @@ interface IBroadcastChannelProps {
|
||||
interface IBroadcastChannelContext {
|
||||
subscribe: (
|
||||
event: `${EBCEvent}`,
|
||||
callback: (message: BroadcastChannelData) => void,
|
||||
callback: (message: BroadcastChannelMessage) => void,
|
||||
) => void;
|
||||
postMessage: (payload: BroadcastChannelPayload) => void;
|
||||
postMessage: (message: BroadcastChannelMessage) => void;
|
||||
}
|
||||
|
||||
const getOrCreateTabId = () => {
|
||||
let storedTabId = sessionStorage.getItem("tab_uuid");
|
||||
|
||||
if (storedTabId) {
|
||||
return storedTabId;
|
||||
}
|
||||
|
||||
storedTabId = generateId();
|
||||
sessionStorage.setItem("tab_uuid", storedTabId);
|
||||
|
||||
return storedTabId;
|
||||
};
|
||||
|
||||
export const BroadcastChannelContext = createContext<
|
||||
IBroadcastChannelContext | undefined
|
||||
>(undefined);
|
||||
@ -75,19 +55,10 @@ export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||
Array<Parameters<IBroadcastChannelContext["subscribe"]>["1"]>
|
||||
>
|
||||
>({});
|
||||
const tabUuid = getOrCreateTabId();
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = ({ data }: MessageEvent<BroadcastChannelData>) => {
|
||||
const { tabId, payload } = data;
|
||||
|
||||
if (tabId === tabUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
subscribersRef.current[payload.event].forEach((callback) =>
|
||||
callback(data),
|
||||
);
|
||||
const handleMessage = ({ data }: MessageEvent<BroadcastChannelMessage>) => {
|
||||
subscribersRef.current[data.event].forEach((callback) => callback(data));
|
||||
};
|
||||
|
||||
channelRef.current.addEventListener("message", handleMessage);
|
||||
@ -113,11 +84,8 @@ export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||
}
|
||||
};
|
||||
};
|
||||
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
|
||||
channelRef.current.postMessage({
|
||||
tabId: tabUuid,
|
||||
payload,
|
||||
});
|
||||
const postMessage: IBroadcastChannelContext["postMessage"] = (message) => {
|
||||
channelRef.current.postMessage(message);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -15,22 +15,15 @@ import {
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
import { generateId } from "../utils/generateId";
|
||||
|
||||
export enum EBCEvent {
|
||||
LOGOUT = "logout",
|
||||
}
|
||||
|
||||
type BroadcastChannelPayload = {
|
||||
type BroadcastChannelMessage = {
|
||||
event: `${EBCEvent}`;
|
||||
data?: string | number | boolean | Record<string, unknown> | undefined | null;
|
||||
};
|
||||
|
||||
type BroadcastChannelData = {
|
||||
tabId: string;
|
||||
payload: BroadcastChannelPayload;
|
||||
};
|
||||
|
||||
interface IBroadcastChannelProps {
|
||||
channelName: string;
|
||||
children: ReactNode;
|
||||
@ -39,24 +32,11 @@ interface IBroadcastChannelProps {
|
||||
interface IBroadcastChannelContext {
|
||||
subscribe: (
|
||||
event: `${EBCEvent}`,
|
||||
callback: (message: BroadcastChannelData) => void,
|
||||
callback: (message: BroadcastChannelMessage) => void,
|
||||
) => void;
|
||||
postMessage: (payload: BroadcastChannelPayload) => void;
|
||||
postMessage: (message: BroadcastChannelMessage) => void;
|
||||
}
|
||||
|
||||
const getOrCreateTabId = () => {
|
||||
let storedTabId = sessionStorage.getItem("tab_uuid");
|
||||
|
||||
if (storedTabId) {
|
||||
return storedTabId;
|
||||
}
|
||||
|
||||
storedTabId = generateId();
|
||||
sessionStorage.setItem("tab_uuid", storedTabId);
|
||||
|
||||
return storedTabId;
|
||||
};
|
||||
|
||||
export const BroadcastChannelContext = createContext<
|
||||
IBroadcastChannelContext | undefined
|
||||
>(undefined);
|
||||
@ -74,19 +54,10 @@ export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||
Array<Parameters<IBroadcastChannelContext["subscribe"]>["1"]>
|
||||
>
|
||||
>({});
|
||||
const tabUuid = getOrCreateTabId();
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = ({ data }: MessageEvent<BroadcastChannelData>) => {
|
||||
const { tabId, payload } = data;
|
||||
|
||||
if (tabId === tabUuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
subscribersRef.current[payload.event].forEach((callback) =>
|
||||
callback(data),
|
||||
);
|
||||
const handleMessage = ({ data }: MessageEvent<BroadcastChannelMessage>) => {
|
||||
subscribersRef.current[data.event].forEach((callback) => callback(data));
|
||||
};
|
||||
|
||||
channelRef.current.addEventListener("message", handleMessage);
|
||||
@ -96,7 +67,6 @@ export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
channelRef.current.close();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const subscribe: IBroadcastChannelContext["subscribe"] = (
|
||||
@ -114,11 +84,8 @@ export const BroadcastChannelProvider: FC<IBroadcastChannelProps> = ({
|
||||
}
|
||||
};
|
||||
};
|
||||
const postMessage: IBroadcastChannelContext["postMessage"] = (payload) => {
|
||||
channelRef.current.postMessage({
|
||||
tabId: tabUuid,
|
||||
payload,
|
||||
});
|
||||
const postMessage: IBroadcastChannelContext["postMessage"] = (message) => {
|
||||
channelRef.current.postMessage(message);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,21 +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 { getRandom } from "./safeRandom";
|
||||
|
||||
export const generateId = () => {
|
||||
const d =
|
||||
typeof performance === "undefined" ? Date.now() : performance.now() * 1000;
|
||||
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (getRandom() * 16 + d) % 16 | 0;
|
||||
|
||||
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||
});
|
||||
};
|
@ -1,16 +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).
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Return a cryptographically secure random value between 0 and 1
|
||||
*
|
||||
* @returns A cryptographically secure random value between 0 and 1
|
||||
*/
|
||||
export const getRandom = (): number =>
|
||||
window.crypto.getRandomValues(new Uint32Array(1))[0] * Math.pow(2, -32);
|
Loading…
Reference in New Issue
Block a user