mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
fix(links): warn on unhandled middle clicks and desktop links (#3311)
This commit is contained in:
1 parent
ebf91ccc76
commit
bef20cba85
9 files changed
+71
-69
No files matched your search
@@ -31,11 +31,9 @@ import {
|
||||
type RendererProps,
|
||||
} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
||||
import {ExternalLinkWarningModal} from '@app/features/messaging/components/modals/ExternalLinkWarningModal';
|
||||
import {
|
||||
MIDDLE_MOUSE_BUTTON,
|
||||
useOpenInBrowserOnMiddleClick,
|
||||
} from '@app/features/messaging/hooks/useOpenInBrowserOnMiddleClick';
|
||||
import {useOpenInBrowserOnMiddleClick} from '@app/features/messaging/hooks/useOpenInBrowserOnMiddleClick';
|
||||
import AttachmentUrlRefresher from '@app/features/messaging/state/AttachmentUrlRefresher';
|
||||
import {MIDDLE_MOUSE_BUTTON} from '@app/features/messaging/utils/ExternalLinkUtils';
|
||||
import {goToMessage} from '@app/features/messaging/utils/MessageNavigator';
|
||||
import type {LinkNode} from '@app/features/messaging/utils/markdown/parser/Nodes';
|
||||
import * as NavigationCommands from '@app/features/navigation/commands/NavigationCommands';
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils';
|
||||
import {handleExternalLinkAuxClick} from '@app/features/messaging/utils/ExternalLinkUtils';
|
||||
import type React from 'react';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
export const MIDDLE_MOUSE_BUTTON = 1;
|
||||
|
||||
interface MiddleClickOpenHandlers {
|
||||
onMouseDown: (event: React.MouseEvent) => void;
|
||||
onAuxClick: (event: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
export function useOpenInBrowserOnMiddleClick(url: string | null | undefined, enabled = true): MiddleClickOpenHandlers {
|
||||
return useMemo<MiddleClickOpenHandlers>(
|
||||
() => ({
|
||||
onMouseDown: (event) => {
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON || !enabled || !url) return;
|
||||
},
|
||||
onAuxClick: (event) => {
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON || !enabled || !url) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openExternalUrlWithWarning(url);
|
||||
if (enabled) handleExternalLinkAuxClick(event, url);
|
||||
},
|
||||
}),
|
||||
[url, enabled],
|
||||
|
||||
@@ -2,31 +2,79 @@
|
||||
|
||||
import {openOAuthAuthorizeModalFromUrl} from '@app/features/auth/commands/OAuthAuthorizeModalCommands';
|
||||
import {ExternalLinkWarningModal} from '@app/features/messaging/components/modals/ExternalLinkWarningModal';
|
||||
import {unwrapDesktopLocalResourceURL} from '@app/features/messaging/utils/DesktopLocalResourceTarget';
|
||||
import TrustedDomain from '@app/features/trusted_domain/state/TrustedDomain';
|
||||
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||
import {modal} from '@app/features/ui/commands/ModalCommands';
|
||||
import {openExternalUrl} from '@app/features/ui/utils/NativeUtils';
|
||||
import {getSafeExternalUrl, isDesktop, openExternalUrl} from '@app/features/ui/utils/NativeUtils';
|
||||
import type React from 'react';
|
||||
|
||||
export function openExternalUrlWithWarning(url: string): void {
|
||||
if (openOAuthAuthorizeModalFromUrl(url)) {
|
||||
return;
|
||||
}
|
||||
let hostname: string | null = null;
|
||||
export const MIDDLE_MOUSE_BUTTON = 1;
|
||||
|
||||
let externalLinkInterceptorAttached = false;
|
||||
|
||||
function isTrustedExternalUrl(url: string): boolean {
|
||||
try {
|
||||
hostname = new URL(url).hostname;
|
||||
return TrustedDomain.isTrustedDomain(new URL(url).hostname);
|
||||
} catch {
|
||||
hostname = null;
|
||||
return false;
|
||||
}
|
||||
if (hostname && TrustedDomain.isTrustedDomain(hostname)) {
|
||||
void openExternalUrl(url);
|
||||
}
|
||||
|
||||
export function openUrlInBrowserWithWarning(url: string): void {
|
||||
const target = unwrapDesktopLocalResourceURL(url);
|
||||
if (isTrustedExternalUrl(target)) {
|
||||
void openExternalUrl(target);
|
||||
return;
|
||||
}
|
||||
ModalCommands.push(
|
||||
modal(() => (
|
||||
<ExternalLinkWarningModal
|
||||
url={url}
|
||||
url={target}
|
||||
data-flx="messaging.external-link-utils.open-external-url-with-warning.external-link-warning-modal"
|
||||
/>
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
export function openExternalUrlWithWarning(url: string): void {
|
||||
if (openOAuthAuthorizeModalFromUrl(url)) {
|
||||
return;
|
||||
}
|
||||
openUrlInBrowserWithWarning(url);
|
||||
}
|
||||
|
||||
export function handleExternalLinkAuxClick(event: React.MouseEvent, url: string | null | undefined): void {
|
||||
if (event.button !== MIDDLE_MOUSE_BUTTON || !url) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
openUrlInBrowserWithWarning(url);
|
||||
}
|
||||
|
||||
function findUnhandledExternalAnchorHref(event: MouseEvent): string | null {
|
||||
if (event.defaultPrevented) return null;
|
||||
const target = event.target as Element | null;
|
||||
const anchor = target?.closest?.('a[target="_blank"]');
|
||||
const href = anchor?.getAttribute('href') ?? null;
|
||||
return getSafeExternalUrl(href) ? href : null;
|
||||
}
|
||||
|
||||
export function attachExternalLinkInterceptor(): () => void {
|
||||
if (!isDesktop() || externalLinkInterceptorAttached) return () => undefined;
|
||||
const handler = (event: MouseEvent) => {
|
||||
const expectedButton = event.type === 'click' ? 0 : MIDDLE_MOUSE_BUTTON;
|
||||
if (event.button !== expectedButton) return;
|
||||
const href = findUnhandledExternalAnchorHref(event);
|
||||
if (href == null) return;
|
||||
event.preventDefault();
|
||||
openUrlInBrowserWithWarning(href);
|
||||
};
|
||||
document.addEventListener('click', handler);
|
||||
document.addEventListener('auxclick', handler);
|
||||
externalLinkInterceptorAttached = true;
|
||||
return () => {
|
||||
document.removeEventListener('click', handler);
|
||||
document.removeEventListener('auxclick', handler);
|
||||
externalLinkInterceptorAttached = false;
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user