Files
twenty/packages/twenty-front-component-renderer/src/index.ts
T
Raphaël Bosi 2899058b5f Warn users before front components navigate to an external site (#23270)
https://github.com/user-attachments/assets/af3fb042-d066-4e0c-9348-f86ea92a6fcd



Front component anchors render a real host `<a>`, so clicking a link to
another domain performed an uncontrolled full-page navigation. This adds
a phishing-resistant "you're leaving Twenty" confirmation modal before
navigating to an external origin (Fixes
[#23260](https://github.com/twentyhq/twenty/issues/23260)).

The renderer intercepts external anchor clicks in
`createHtmlHostWrapper` and hands the destination to a host callback via
context; twenty-front owns the modal (reuses `ConfirmationModal`) and a
per-application list of trusted origins persisted in localStorage. A
"Don't ask again for this site" checkbox (checked by default) skips the
modal next time for that app.

Scope is external cross-origin http(s) links only; same-origin links
keep native behavior. External links always open in a new tab, so a
component can never navigate the Twenty tab away, even once its origin
is trusted. The modal is rendered by the trusted host, so components
cannot style or suppress it.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23270?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-27 13:21:38 +00:00

143 lines
3.8 KiB
TypeScript

export { FrontComponentRenderer } from './host/components/FrontComponentRenderer';
export { FrontComponentExternalNavigationContext } from './host/contexts/FrontComponentExternalNavigationContext';
export {
FrontComponentInputFocusContext,
type SetEditableFocused,
} from './host/contexts/FrontComponentInputFocusContext';
export { type RequestExternalNavigation } from './host/types/RequestExternalNavigation';
export { componentRegistry } from './host/generated/host-component-registry';
export { FrontComponentConfirmationModalResultEffect } from './remote/components/FrontComponentConfirmationModalResultEffect';
export { FrontComponentErrorEffect } from './remote/components/FrontComponentErrorEffect';
export { FrontComponentInitializeHostCommunicationApiEffect } from './remote/components/FrontComponentInitializeHostCommunicationApiEffect';
export { FrontComponentUpdateContextEffect } from './remote/components/FrontComponentUpdateContextEffect';
export { FrontComponentUpdateHostCommunicationApiEffect } from './remote/components/FrontComponentUpdateHostCommunicationApiEffect';
export { FrontComponentWorkerEffect } from './remote/components/FrontComponentWorkerEffect';
export {
HtmlA,
HtmlArticle,
HtmlAside,
HtmlBlockquote,
HtmlBr,
HtmlButton,
HtmlCode,
HtmlDiv,
HtmlEm,
HtmlFooter,
HtmlForm,
HtmlH1,
HtmlH2,
HtmlH3,
HtmlH4,
HtmlH5,
HtmlH6,
HtmlHeader,
HtmlHr,
HtmlIframe,
HtmlAudio,
HtmlImg,
HtmlSource,
HtmlVideo,
HtmlInput,
HtmlLabel,
HtmlLi,
HtmlMain,
HtmlNav,
HtmlOl,
HtmlOption,
HtmlP,
HtmlPre,
HtmlSection,
HtmlSelect,
HtmlSmall,
HtmlSpan,
HtmlStrong,
HtmlTable,
HtmlTbody,
HtmlTd,
HtmlTextarea,
HtmlTfoot,
HtmlTh,
HtmlThead,
HtmlTr,
HtmlUl,
} from './remote/generated/remote-components';
export {
HtmlAElement,
HtmlArticleElement,
HtmlAsideElement,
HtmlBlockquoteElement,
HtmlBrElement,
HtmlButtonElement,
HtmlCodeElement,
HtmlDivElement,
HtmlEmElement,
HtmlFooterElement,
HtmlFormElement,
HtmlH1Element,
HtmlH2Element,
HtmlH3Element,
HtmlH4Element,
HtmlH5Element,
HtmlH6Element,
HtmlHeaderElement,
HtmlHrElement,
HtmlIframeElement,
HtmlAudioElement,
HtmlImgElement,
HtmlSourceElement,
HtmlVideoElement,
HtmlInputElement,
HtmlLabelElement,
HtmlLiElement,
HtmlMainElement,
HtmlNavElement,
HtmlOlElement,
HtmlOptionElement,
HtmlPElement,
HtmlPreElement,
HtmlSectionElement,
HtmlSelectElement,
HtmlSmallElement,
HtmlSpanElement,
HtmlStrongElement,
HtmlTableElement,
HtmlTbodyElement,
HtmlTdElement,
HtmlTextareaElement,
HtmlTfootElement,
HtmlTheadElement,
HtmlThElement,
HtmlTrElement,
HtmlUlElement,
RemoteFragmentElement,
RemoteRootElement,
} from './remote/generated/remote-elements';
export type {
HtmlAProperties,
HtmlButtonProperties,
HtmlCommonEvents,
HtmlCommonProperties,
HtmlFormProperties,
HtmlIframeProperties,
HtmlAudioProperties,
HtmlImgProperties,
HtmlSourceProperties,
HtmlVideoProperties,
HtmlInputProperties,
HtmlLabelProperties,
HtmlOptionProperties,
HtmlSelectProperties,
HtmlTdProperties,
HtmlTextareaProperties,
HtmlThProperties,
} from './remote/generated/remote-elements';
export { createFrontComponentRemoteWorker } from './remote/worker/utils/createFrontComponentRemoteWorker';
export { installStyleBridge } from './polyfills/installStyleBridge';
export { exposeGlobals } from './remote/utils/exposeGlobals';
export type { FrontComponentExecutionContext } from 'twenty-sdk/front-component';
export type { FrontComponentHostCommunicationApi } from './types/FrontComponentHostCommunicationApi';
export type { HostToWorkerRenderContext } from './types/HostToWorkerRenderContext';
export type { SdkClientUrls } from './types/SdkClientUrls';
export type { PropertySchema } from './constants/PropertySchema';
export type { WorkerExports } from './types/WorkerExports';