diff --git a/package.json b/package.json
index 62141717..21f82367 100644
--- a/package.json
+++ b/package.json
@@ -107,6 +107,7 @@
"eslint-config-preact": "^1.1.4",
"eslint-plugin-jsdoc": "^39.3.2",
"eventemitter3": "^4.0.7",
+ "history": "4",
"json-stringify-deterministic": "^1.0.2",
"localforage": "^1.9.0",
"lodash.defaultsdeep": "^4.6.1",
diff --git a/src/context/history.ts b/src/context/history.ts
new file mode 100644
index 00000000..5e816997
--- /dev/null
+++ b/src/context/history.ts
@@ -0,0 +1,5 @@
+import { createBrowserHistory } from "history";
+
+export const history = createBrowserHistory({
+ basename: import.meta.env.BASE_URL,
+});
diff --git a/src/context/index.tsx b/src/context/index.tsx
index f30f885c..663fdfaa 100644
--- a/src/context/index.tsx
+++ b/src/context/index.tsx
@@ -1,4 +1,4 @@
-import { BrowserRouter as Router, Link } from "react-router-dom";
+import { Router, Link } from "react-router-dom";
import { ContextMenuTrigger } from "preact-context-menu";
import { Text } from "preact-i18n";
@@ -10,6 +10,7 @@ import { hydrateState } from "../mobx/State";
import Locale from "./Locale";
import Theme from "./Theme";
+import { history } from "./history";
import Intermediate from "./intermediate/Intermediate";
import ModalRenderer from "./modals/ModalRenderer";
import Client from "./revoltjs/RevoltClient";
@@ -36,7 +37,7 @@ export default function Context({ children }: { children: Children }) {
if (!ready) return ;
return (
-
+
diff --git a/src/context/intermediate/Intermediate.tsx b/src/context/intermediate/Intermediate.tsx
index b58c37de..906e4c95 100644
--- a/src/context/intermediate/Intermediate.tsx
+++ b/src/context/intermediate/Intermediate.tsx
@@ -141,35 +141,7 @@ export default function Intermediate(props: Props) {
const actions = useMemo(() => {
return {
openLink: (href?: string, trusted?: boolean) => {
- const link = determineLink(href);
-
- switch (link.type) {
- case "profile": {
- openScreen({ id: "profile", user_id: link.id });
- return true;
- }
- case "navigate": {
- history.push(link.path);
- return true;
- }
- case "external": {
- if (
- !trusted &&
- !settings.security.isTrustedOrigin(
- link.url.hostname,
- )
- ) {
- openScreen({
- id: "external_link_prompt",
- link: link.href,
- });
- } else {
- window.open(link.href, "_blank", "noreferrer");
- }
- }
- }
-
- return true;
+ return modalController.openLink(href, trusted);
},
openScreen: (screen: Screen) => openScreen(screen),
writeClipboard: (a: string) => modalController.writeText(a),
diff --git a/src/context/intermediate/Modals.tsx b/src/context/intermediate/Modals.tsx
index a2a91507..4f59c974 100644
--- a/src/context/intermediate/Modals.tsx
+++ b/src/context/intermediate/Modals.tsx
@@ -1,6 +1,5 @@
//import { isModalClosing } from "../../components/ui/Modal";
import { Screen } from "./Intermediate";
-import { ExternalLinkModal } from "./modals/ExternalLinkPrompt";
import { InputModal } from "./modals/Input";
import { OnboardingModal } from "./modals/Onboarding";
import { PromptModal } from "./modals/Prompt";
@@ -23,8 +22,6 @@ export default function Modals({ screen, openScreen }: Props) {
return ;
case "onboarding":
return ;
- case "external_link_prompt":
- return ;
}
return null;
diff --git a/src/context/intermediate/modals/ExternalLinkPrompt.tsx b/src/context/modals/components/LinkWarning.tsx
similarity index 70%
rename from src/context/intermediate/modals/ExternalLinkPrompt.tsx
rename to src/context/modals/components/LinkWarning.tsx
index 48e336ac..04a9e876 100644
--- a/src/context/intermediate/modals/ExternalLinkPrompt.tsx
+++ b/src/context/modals/components/LinkWarning.tsx
@@ -2,35 +2,32 @@ import { Text } from "preact-i18n";
import { Modal } from "@revoltchat/ui";
+import { noopTrue } from "../../../lib/js";
+
import { useApplicationState } from "../../../mobx/State";
-import { useIntermediate } from "../Intermediate";
+import { ModalProps } from "../types";
-interface Props {
- onClose: () => void;
- link: string;
-}
-
-export function ExternalLinkModal({ onClose, link }: Props) {
- const { openLink } = useIntermediate();
+export default function LinkWarning({
+ link,
+ callback,
+ ...props
+}: ModalProps<"link_warning">) {
const settings = useApplicationState().settings;
return (
}
actions={[
{
- onClick: () => {
- openLink(link, true);
- onClose();
- },
+ onClick: callback,
confirmation: true,
palette: "accent",
children: "Continue",
},
{
- onClick: onClose,
+ onClick: noopTrue,
confirmation: false,
children: "Cancel",
},
@@ -41,8 +38,7 @@ export function ExternalLinkModal({ onClose, link }: Props) {
settings.security.addTrustedOrigin(url.hostname);
} catch (e) {}
- openLink(link, true);
- onClose();
+ return callback();
},
palette: "plain",
children: (
diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx
index 1b0670fd..6a2485d0 100644
--- a/src/context/modals/index.tsx
+++ b/src/context/modals/index.tsx
@@ -8,9 +8,16 @@ import {
import type { Client, API } from "revolt.js";
import { ulid } from "ulid";
+import { determineLink } from "../../lib/links";
+
+import { getApplicationState, useApplicationState } from "../../mobx/State";
+
+import { history } from "../history";
+// import { determineLink } from "../../lib/links";
import Changelog from "./components/Changelog";
import Clipboard from "./components/Clipboard";
import Error from "./components/Error";
+import LinkWarning from "./components/LinkWarning";
import MFAEnableTOTP from "./components/MFAEnableTOTP";
import MFAFlow from "./components/MFAFlow";
import MFARecovery from "./components/MFARecovery";
@@ -156,12 +163,46 @@ class ModalControllerExtended extends ModalController {
});
}
}
+
+ openLink(href?: string, trusted?: boolean) {
+ const link = determineLink(href);
+ const settings = getApplicationState().settings;
+
+ switch (link.type) {
+ case "profile": {
+ // TODO: port Profile
+ // openScreen({ id: "profile", user_id: link.id });
+ break;
+ }
+ case "navigate": {
+ history.push(link.path);
+ break;
+ }
+ case "external": {
+ if (
+ !trusted &&
+ !settings.security.isTrustedOrigin(link.url.hostname)
+ ) {
+ modalController.push({
+ type: "link_warning",
+ link: link.href,
+ callback: () => this.openLink(href, true) as true,
+ });
+ } else {
+ window.open(link.href, "_blank", "noreferrer");
+ }
+ }
+ }
+
+ return true;
+ }
}
export const modalController = new ModalControllerExtended({
changelog: Changelog,
clipboard: Clipboard,
error: Error,
+ link_warning: LinkWarning,
mfa_flow: MFAFlow,
mfa_recovery: MFARecovery,
mfa_enable_totp: MFAEnableTOTP,
diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts
index 1b5a36e7..d6bb5883 100644
--- a/src/context/modals/types.ts
+++ b/src/context/modals/types.ts
@@ -51,6 +51,11 @@ export type Modal = {
type: "clipboard";
text: string;
}
+ | {
+ type: "link_warning";
+ link: string;
+ callback: () => true;
+ }
| {
type: "signed_out";
}
diff --git a/src/mobx/State.ts b/src/mobx/State.ts
index ffc2ebbb..b67f03a8 100644
--- a/src/mobx/State.ts
+++ b/src/mobx/State.ts
@@ -311,3 +311,11 @@ export async function hydrateState() {
export function useApplicationState() {
return state;
}
+
+/**
+ * Get the application state
+ * @returns Application state
+ */
+export function getApplicationState() {
+ return state;
+}
diff --git a/src/mobx/stores/helpers/SSecurity.ts b/src/mobx/stores/helpers/SSecurity.ts
index a57d8d1f..0fce7f93 100644
--- a/src/mobx/stores/helpers/SSecurity.ts
+++ b/src/mobx/stores/helpers/SSecurity.ts
@@ -2,6 +2,8 @@ import { makeAutoObservable, computed, action } from "mobx";
import Settings from "../Settings";
+const TRUSTED_DOMAINS = ["revolt.chat", "revolt.wtf", "gifbox.me", "rvlt.gg"];
+
/**
* Helper class for changing security options.
*/
@@ -27,6 +29,10 @@ export default class SSecurity {
}
@computed isTrustedOrigin(origin: string) {
+ if (TRUSTED_DOMAINS.find((x) => origin.endsWith(x))) {
+ return true;
+ }
+
return this.settings.get("security:trustedOrigins")?.includes(origin);
}
}
diff --git a/yarn.lock b/yarn.lock
index 88c78fb3..5a43c7b5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3574,6 +3574,7 @@ __metadata:
eslint-plugin-jsdoc: ^39.3.2
eventemitter3: ^4.0.7
fs-extra: ^10.0.0
+ history: 4
json-stringify-deterministic: ^1.0.2
klaw: ^3.0.0
localforage: ^1.9.0
@@ -5043,7 +5044,7 @@ __metadata:
languageName: node
linkType: hard
-"history@npm:^4.9.0":
+"history@npm:4, history@npm:^4.9.0":
version: 4.10.1
resolution: "history@npm:4.10.1"
dependencies: