mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-25 23:22:06 -05:00
Merge pull request #158 from Snazzah/trusted-links
This commit is contained in:
commit
966daa6c78
5 changed files with 87 additions and 14 deletions
|
@ -18,13 +18,15 @@ import { useCallback, useContext } from "preact/hooks";
|
||||||
|
|
||||||
import { internalEmit } from "../../lib/eventEmitter";
|
import { internalEmit } from "../../lib/eventEmitter";
|
||||||
|
|
||||||
|
import { getState } from "../../redux";
|
||||||
|
|
||||||
|
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||||
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import { generateEmoji } from "../common/Emoji";
|
import { generateEmoji } from "../common/Emoji";
|
||||||
|
|
||||||
import { emojiDictionary } from "../../assets/emojis";
|
import { emojiDictionary } from "../../assets/emojis";
|
||||||
import { MarkdownProps } from "./Markdown";
|
import { MarkdownProps } from "./Markdown";
|
||||||
import {useIntermediate} from "../../context/intermediate/Intermediate";
|
|
||||||
|
|
||||||
// TODO: global.d.ts file for defining globals
|
// TODO: global.d.ts file for defining globals
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -35,9 +37,9 @@ declare global {
|
||||||
|
|
||||||
const ALLOWED_ORIGINS = [
|
const ALLOWED_ORIGINS = [
|
||||||
location.hostname,
|
location.hostname,
|
||||||
'app.revolt.chat',
|
"app.revolt.chat",
|
||||||
'nightly.revolt.chat',
|
"nightly.revolt.chat",
|
||||||
'local.revolt.chat',
|
"local.revolt.chat",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handler for code block copy.
|
// Handler for code block copy.
|
||||||
|
@ -176,13 +178,16 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
element.removeAttribute("data-type");
|
element.removeAttribute("data-type");
|
||||||
element.removeAttribute("target");
|
element.removeAttribute("target");
|
||||||
|
|
||||||
let internal;
|
let internal,
|
||||||
|
url: URL | null = null;
|
||||||
const href = element.href;
|
const href = element.href;
|
||||||
if (href) {
|
if (href) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(href, location.href);
|
url = new URL(href, location.href);
|
||||||
|
|
||||||
if (ALLOWED_ORIGINS.includes(url.hostname)) {
|
if (
|
||||||
|
ALLOWED_ORIGINS.includes(url.hostname)
|
||||||
|
) {
|
||||||
internal = true;
|
internal = true;
|
||||||
element.addEventListener(
|
element.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
|
@ -202,12 +207,20 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
|
||||||
if (!internal) {
|
if (!internal) {
|
||||||
element.setAttribute("target", "_blank");
|
element.setAttribute("target", "_blank");
|
||||||
element.onclick = (ev) => {
|
element.onclick = (ev) => {
|
||||||
ev.preventDefault();
|
const { trustedLinks } = getState();
|
||||||
openScreen({
|
if (
|
||||||
id: "external_link_prompt",
|
!url ||
|
||||||
link: href
|
!trustedLinks.domains?.includes(
|
||||||
})
|
url.hostname,
|
||||||
}
|
)
|
||||||
|
) {
|
||||||
|
ev.preventDefault();
|
||||||
|
openScreen({
|
||||||
|
id: "external_link_prompt",
|
||||||
|
link: href,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import Modal from "../../../components/ui/Modal";
|
import Modal from "../../../components/ui/Modal";
|
||||||
|
import { dispatch } from "../../../redux";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
@ -29,8 +30,23 @@ export function ExternalLinkModal({ onClose, link }: Props) {
|
||||||
confirmation: false,
|
confirmation: false,
|
||||||
children: "Cancel",
|
children: "Cancel",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
onClick: () => {
|
||||||
|
try {
|
||||||
|
const url = new URL(link);
|
||||||
|
dispatch({
|
||||||
|
type: "TRUSTED_LINKS_ADD_DOMAIN",
|
||||||
|
domain: url.hostname
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
window.open(link, "_blank");
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
plain: true,
|
||||||
|
children: <Text id="app.special.modals.external_links.trust_domain" />,
|
||||||
|
}
|
||||||
]}>
|
]}>
|
||||||
<Text id={"app.special.modals.external_links.short"} /> <br />
|
<Text id="app.special.modals.external_links.short" /> <br />
|
||||||
<a>{link}</a>
|
<a>{link}</a>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { QueuedMessage } from "./reducers/queue";
|
||||||
import { SectionToggle } from "./reducers/section_toggle";
|
import { SectionToggle } from "./reducers/section_toggle";
|
||||||
import { Settings } from "./reducers/settings";
|
import { Settings } from "./reducers/settings";
|
||||||
import { SyncOptions } from "./reducers/sync";
|
import { SyncOptions } from "./reducers/sync";
|
||||||
|
import { TrustedLinks } from "./reducers/trusted_links";
|
||||||
import { Unreads } from "./reducers/unreads";
|
import { Unreads } from "./reducers/unreads";
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
|
@ -29,6 +30,7 @@ export type State = {
|
||||||
lastOpened: LastOpened;
|
lastOpened: LastOpened;
|
||||||
notifications: Notifications;
|
notifications: Notifications;
|
||||||
sectionToggle: SectionToggle;
|
sectionToggle: SectionToggle;
|
||||||
|
trustedLinks: TrustedLinks;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -59,6 +61,7 @@ store.subscribe(() => {
|
||||||
lastOpened,
|
lastOpened,
|
||||||
notifications,
|
notifications,
|
||||||
sectionToggle,
|
sectionToggle,
|
||||||
|
trustedLinks,
|
||||||
} = store.getState() as State;
|
} = store.getState() as State;
|
||||||
|
|
||||||
localForage.setItem("state", {
|
localForage.setItem("state", {
|
||||||
|
@ -74,6 +77,7 @@ store.subscribe(() => {
|
||||||
lastOpened,
|
lastOpened,
|
||||||
notifications,
|
notifications,
|
||||||
sectionToggle,
|
sectionToggle,
|
||||||
|
trustedLinks,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { sectionToggle, SectionToggleAction } from "./section_toggle";
|
||||||
import { config, ConfigAction } from "./server_config";
|
import { config, ConfigAction } from "./server_config";
|
||||||
import { settings, SettingsAction } from "./settings";
|
import { settings, SettingsAction } from "./settings";
|
||||||
import { sync, SyncAction } from "./sync";
|
import { sync, SyncAction } from "./sync";
|
||||||
|
import { trustedLinks, TrustedLinksAction } from "./trusted_links";
|
||||||
import { unreads, UnreadsAction } from "./unreads";
|
import { unreads, UnreadsAction } from "./unreads";
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
@ -27,6 +28,7 @@ export default combineReducers({
|
||||||
lastOpened,
|
lastOpened,
|
||||||
notifications,
|
notifications,
|
||||||
sectionToggle,
|
sectionToggle,
|
||||||
|
trustedLinks,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Action =
|
export type Action =
|
||||||
|
@ -42,4 +44,5 @@ export type Action =
|
||||||
| LastOpenedAction
|
| LastOpenedAction
|
||||||
| NotificationsAction
|
| NotificationsAction
|
||||||
| SectionToggleAction
|
| SectionToggleAction
|
||||||
|
| TrustedLinksAction
|
||||||
| { type: "__INIT"; state: State };
|
| { type: "__INIT"; state: State };
|
||||||
|
|
37
src/redux/reducers/trusted_links.ts
Normal file
37
src/redux/reducers/trusted_links.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
export interface TrustedLinks {
|
||||||
|
domains?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TrustedLinksAction =
|
||||||
|
| { type: undefined }
|
||||||
|
| {
|
||||||
|
type: "TRUSTED_LINKS_ADD_DOMAIN";
|
||||||
|
domain: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "TRUSTED_LINKS_REMOVE_DOMAIN";
|
||||||
|
domain: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function trustedLinks(
|
||||||
|
state = {} as TrustedLinks,
|
||||||
|
action: TrustedLinksAction,
|
||||||
|
): TrustedLinks {
|
||||||
|
switch (action.type) {
|
||||||
|
case "TRUSTED_LINKS_ADD_DOMAIN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
domains: [
|
||||||
|
...(state.domains ?? []).filter((v) => v !== action.domain),
|
||||||
|
action.domain,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case "TRUSTED_LINKS_REMOVE_DOMAIN":
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
domains: state.domains?.filter((v) => v !== action.domain),
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue