fix(security): harden browser handoff boundaries

This commit is contained in:
NimBold
2026-07-17 02:05:41 +03:30
parent 50c3da2f5d
commit a8dc4fb447
9 changed files with 240 additions and 36 deletions
+16 -2
View File
@@ -14,6 +14,7 @@ export interface ToastMessage {
variant?: ToastVariant;
duration?: number;
isActionable?: boolean;
onDismiss?: () => void;
}
interface ToastState extends ToastMessage {
@@ -21,7 +22,7 @@ interface ToastState extends ToastMessage {
}
interface ToastContextType {
addToast: (toast: Omit<ToastMessage, 'id'>) => void;
addToast: (toast: Omit<ToastMessage, 'id'>) => string;
removeToast: (id: string) => void;
}
@@ -33,10 +34,12 @@ export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) =
const addToast = useCallback((toast: Omit<ToastMessage, 'id'>) => {
nextToastId.current += 1;
const id = `toast-${nextToastId.current}`;
setToasts(prev => {
const next = [...prev, { ...toast, id: `toast-${nextToastId.current}` }];
const next = [...prev, { ...toast, id }];
return next.slice(-MAX_VISIBLE_TOASTS);
});
return id;
}, []);
const removeToast = useCallback((id: string) => {
@@ -70,6 +73,7 @@ const ToastItem: React.FC<{ toast: ToastState; removeToast: (id: string) => void
const [isHovered, setIsHovered] = useState(false);
const timerStartedAt = useRef<number | null>(null);
const remainingDuration = useRef<number | null>(null);
const onDismissCalled = useRef(false);
useLayoutEffect(() => {
const frame = requestAnimationFrame(() => setIsMounted(true));
@@ -99,6 +103,16 @@ const ToastItem: React.FC<{ toast: ToastState; removeToast: (id: string) => void
};
}, [toast, isHovered, removeToast]);
useEffect(() => {
const dismiss = () => {
if (onDismissCalled.current) return;
onDismissCalled.current = true;
toast.onDismiss?.();
};
if (toast.exiting) dismiss();
return dismiss;
}, [toast.exiting, toast.onDismiss]);
useEffect(() => {
if (toast.exiting) {
const fallbackTimer = setTimeout(() => {