mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-05 17:08:26 +00:00
fix(security): harden browser handoff boundaries
This commit is contained in:
+19
-9
@@ -137,7 +137,7 @@ function App() {
|
||||
const maxConcurrentDownloads = useSettingsStore(state => state.maxConcurrentDownloads);
|
||||
const preventsSleepWhileDownloading = useSettingsStore(state => state.preventsSleepWhileDownloading);
|
||||
const activeTransferCount = downloads.filter(download => isTransferActiveStatus(download.status)).length;
|
||||
const { addToast } = useToast();
|
||||
const { addToast, removeToast } = useToast();
|
||||
const isMacUserAgent = navigator.userAgent.includes('Mac');
|
||||
const usesCustomWindowControls = !isMacUserAgent && platform.os !== 'macos';
|
||||
// Keep dialogs out of the titlebar area while platform detection is still
|
||||
@@ -179,19 +179,20 @@ function App() {
|
||||
|
||||
const actionLabel = action === 'shutdown' ? 'Shut down' : action === 'restart' ? 'Restart' : 'Sleep';
|
||||
let timerId: number | null = null;
|
||||
let toastId: string | null = null;
|
||||
const cancel = () => {
|
||||
if (timerId !== null) {
|
||||
window.clearTimeout(timerId);
|
||||
if (pendingPostActionTimer.current === timerId) {
|
||||
pendingPostActionTimer.current = null;
|
||||
}
|
||||
timerId = null;
|
||||
clearPendingPostActionTimer();
|
||||
timerId = null;
|
||||
if (toastId !== null) {
|
||||
removeToast(toastId);
|
||||
toastId = null;
|
||||
}
|
||||
};
|
||||
|
||||
addToast({
|
||||
toastId = addToast({
|
||||
variant: 'warning',
|
||||
isActionable: true,
|
||||
onDismiss: clearPendingPostActionTimer,
|
||||
message: (
|
||||
<div className="flex items-center gap-3">
|
||||
<span>{actionLabel} in 10 seconds.</span>
|
||||
@@ -207,6 +208,10 @@ function App() {
|
||||
});
|
||||
|
||||
timerId = window.setTimeout(() => {
|
||||
if (toastId !== null) {
|
||||
removeToast(toastId);
|
||||
toastId = null;
|
||||
}
|
||||
if (pendingPostActionTimer.current === timerId) {
|
||||
pendingPostActionTimer.current = null;
|
||||
}
|
||||
@@ -233,7 +238,7 @@ function App() {
|
||||
});
|
||||
}, 10_000);
|
||||
pendingPostActionTimer.current = timerId;
|
||||
}, [addToast, clearPendingPostActionTimer]);
|
||||
}, [addToast, clearPendingPostActionTimer, removeToast]);
|
||||
|
||||
const startSidebarResize = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
@@ -326,6 +331,11 @@ function App() {
|
||||
}
|
||||
});
|
||||
unlistenExtension = await listen('extension-add-download', (event) => {
|
||||
if (event.payload.request_id) {
|
||||
void invoke('ack_extension_download', { requestId: event.payload.request_id }).catch(error => {
|
||||
console.error('Failed to acknowledge browser extension download:', error);
|
||||
});
|
||||
}
|
||||
if (!startupInputReady.current || useSettingsStore.getState().showKeychainModal) {
|
||||
pendingStartupInputs.current.push({ type: 'extension', payload: event.payload });
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ExtensionCookieScope } from "./ExtensionCookieScope";
|
||||
|
||||
export type ExtensionDownload = { urls: Array<string>, referer: string | null, silent: boolean, filename: string | null, headers: string | null, cookies: string | null, cookie_scopes: Array<ExtensionCookieScope> | null, media: boolean, };
|
||||
export type ExtensionDownload = { request_id?: string, urls: Array<string>, referer: string | null, silent: boolean, filename: string | null, headers: string | null, cookies: string | null, cookie_scopes: Array<ExtensionCookieScope> | null, media: boolean, };
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -65,6 +65,7 @@ type CommandMap = {
|
||||
grant_keychain_access: { args: undefined; result: PairingTokenHydration };
|
||||
acknowledge_pairing_token_change: { args: undefined; result: void };
|
||||
set_extension_frontend_ready: { args: { ready: boolean }; result: void };
|
||||
ack_extension_download: { args: { requestId: string }; result: void };
|
||||
get_system_proxy: { args: undefined; result: string | null };
|
||||
get_file_category: { args: { filename: string }; result: DownloadCategory };
|
||||
check_for_updates: { args: undefined; result: ReleaseCheckOutcome };
|
||||
|
||||
Reference in New Issue
Block a user