From 6f6acbb0a5e4f6389e9821e229d055541d92d8b0 Mon Sep 17 00:00:00 2001 From: SleeplessOne1917 Date: Fri, 21 Jul 2023 18:42:32 -0400 Subject: [PATCH] Add external interaction page --- src/server/handlers/catch-all-handler.tsx | 4 +- src/shared/components/app/app.tsx | 4 +- src/shared/components/common/auth-guard.tsx | 17 ++- src/shared/components/home/login.tsx | 20 ++- src/shared/components/remote_fetch.tsx | 135 ++++++++++++++++++++ src/shared/routes.ts | 6 + src/shared/utils/app/is-auth-path.ts | 2 +- 7 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 src/shared/components/remote_fetch.tsx diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx index 06d38f31..d64f136f 100644 --- a/src/server/handlers/catch-all-handler.tsx +++ b/src/server/handlers/catch-all-handler.tsx @@ -48,7 +48,7 @@ export default async (req: Request, res: Response) => { let errorPageData: ErrorPageData | undefined = undefined; let try_site = await client.getSite(getSiteForm); - if (try_site.state === "failed" && try_site.msg == "not_logged_in") { + if (try_site.state === "failed" && try_site.msg === "not_logged_in") { console.error( "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" ); @@ -58,7 +58,7 @@ export default async (req: Request, res: Response) => { } if (!auth && isAuthPath(path)) { - return res.redirect("/login"); + return res.redirect(`/login?prev=${encodeURIComponent(url)}`); } if (try_site.state === "success") { diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 2000bec2..f4c414cb 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -72,10 +72,10 @@ export class App extends Component { return ( -
+
{RouteComponent && (isAuthPath(path ?? "") ? ( - + ) : ( diff --git a/src/shared/components/common/auth-guard.tsx b/src/shared/components/common/auth-guard.tsx index 13f89139..b8a651f5 100644 --- a/src/shared/components/common/auth-guard.tsx +++ b/src/shared/components/common/auth-guard.tsx @@ -1,4 +1,5 @@ -import { Component, InfernoNode } from "inferno"; +import { Component } from "inferno"; +import { RouteComponentProps } from "inferno-router/dist/Route"; import { UserService } from "../../services"; import { Spinner } from "./icon"; @@ -6,20 +7,26 @@ interface AuthGuardState { hasRedirected: boolean; } -class AuthGuard extends Component<{ children?: InfernoNode }, AuthGuardState> { +class AuthGuard extends Component< + RouteComponentProps>, + AuthGuardState +> { state = { hasRedirected: false, } as AuthGuardState; - constructor(props: any, context: any) { + constructor( + props: RouteComponentProps>, + context: any + ) { super(props, context); } componentDidMount() { if (!UserService.Instance.myUserInfo) { + const { pathname, search } = this.props.location; this.context.router.history.replace( - "/login", - this.context.router.history.location + `/login?prev=${encodeURIComponent(pathname + search)}` ); } else { this.setState({ hasRedirected: true }); diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 53a09096..6b6a0f0b 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -1,6 +1,6 @@ import { myAuth, setIsoData } from "@utils/app"; import { isBrowser } from "@utils/browser"; -import { Location } from "history"; +import { getQueryParams } from "@utils/helpers"; import { Component, linkEvent } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; @@ -11,6 +11,17 @@ import { HtmlTags } from "../common/html-tags"; import { Spinner } from "../common/icon"; import PasswordInput from "../common/password-input"; +interface LoginProps { + prev?: string; +} + +const getLoginQueryParams = () => + getQueryParams({ + prev(param) { + return param ? decodeURIComponent(param) : undefined; + }, + }); + interface State { loginRes: RequestState; form: { @@ -57,11 +68,10 @@ async function handleLoginSubmit(i: Login, event: any) { UserService.Instance.myUserInfo = site.data.my_user; } - const { hash, pathname, search } = (i.props.history.location.state ?? - {}) as Location; + const { prev } = getLoginQueryParams(); - i.props.history.location.state - ? i.props.history.replace({ hash, pathname, search }) + prev + ? i.props.history.replace(prev) : i.props.history.action === "PUSH" ? i.props.history.back() : i.props.history.replace("/"); diff --git a/src/shared/components/remote_fetch.tsx b/src/shared/components/remote_fetch.tsx new file mode 100644 index 00000000..e87cbfc4 --- /dev/null +++ b/src/shared/components/remote_fetch.tsx @@ -0,0 +1,135 @@ +import { myAuthRequired, setIsoData } from "@utils/app"; +import { getQueryParams } from "@utils/helpers"; +import { QueryParams, RouteDataResponse } from "@utils/types"; +import { Component } from "inferno"; +import { Link } from "inferno-router"; +import { ResolveObjectResponse } from "lemmy-js-client"; +import { InitialFetchRequest } from "../interfaces"; +import { FirstLoadService, HttpService, I18NextService } from "../services"; +import { RequestState } from "../services/HttpService"; +import { HtmlTags } from "./common/html-tags"; + +interface RemoteFetchProps { + uri?: string; +} + +type RemoteFetchData = RouteDataResponse<{ + resolveObjectRes: ResolveObjectResponse; +}>; + +interface RemoteFetchState { + resolveObjectRes: RequestState; + isIsomorphic: boolean; +} + +const getUriFromQuery = (uri?: string): string | undefined => + uri ? decodeURIComponent(uri) : undefined; + +const getRemoteFetchQueryParams = () => + getQueryParams({ + uri: getUriFromQuery, + }); + +function uriToQuery(uri: string) { + const match = decodeURIComponent(uri).match(/https?:\/\/(.+)\/c\/(.+)/); + + return match ? `!${match[2]}@${match[1]}` : ""; +} + +export class RemoteFetch extends Component { + private isoData = setIsoData(this.context); + state: RemoteFetchState = { + resolveObjectRes: { state: "empty" }, + isIsomorphic: false, + }; + + constructor(props: any, context: any) { + super(props, context); + + if (FirstLoadService.isFirstLoad) { + // const { resolveObjectRes } = this.isoData.routeData; + + this.state = { + ...this.state, + isIsomorphic: true, + // resolveObjectRes, + }; + } + } + + async componentDidMount() { + if (!this.state.isIsomorphic) { + const { uri } = getRemoteFetchQueryParams(); + + if (uri) { + this.setState({ resolveObjectRes: { state: "loading" } }); + this.setState({ + resolveObjectRes: await HttpService.client.resolveObject({ + auth: myAuthRequired(), + q: uriToQuery(uri), + }), + }); + } + } + } + + render() { + return ( +
+ +
+
+ {this.content} +
+
+
+ ); + } + + get content() { + const status: "success" | "loading" | "empty" = "success"; + + switch (status) { + case "success": { + return ( + <> +

Community Federated!

+ + Click to visit com + + + ); + } + } + } + + get documentTitle(): string { + const { uri } = getRemoteFetchQueryParams(); + const name = this.isoData.site_res.site_view.site.name; + return `${I18NextService.i18n.t("search")} - ${ + uri ? `${uri} - ` : "" + }${name}`; + } + + static async fetchInitialData({ + client, + auth, + query: { uri }, + }: InitialFetchRequest< + QueryParams + >): Promise { + const data: RemoteFetchData = { resolveObjectRes: { state: "empty" } }; + + if (uri && auth) { + data.resolveObjectRes = await client.resolveObject({ + auth, + q: uriToQuery(uri), + }); + } + + return data; + } +} diff --git a/src/shared/routes.ts b/src/shared/routes.ts index 01325afa..19e0d31e 100644 --- a/src/shared/routes.ts +++ b/src/shared/routes.ts @@ -21,6 +21,7 @@ import { VerifyEmail } from "./components/person/verify-email"; import { CreatePost } from "./components/post/create-post"; import { Post } from "./components/post/post"; import { CreatePrivateMessage } from "./components/private_message/create-private-message"; +import { RemoteFetch } from "./components/remote_fetch"; import { Search } from "./components/search"; import { InitialFetchRequest, RouteData } from "./interfaces"; @@ -140,4 +141,9 @@ export const routes: IRoutePropsWithFetch>[] = [ fetchInitialData: Instances.fetchInitialData, }, { path: `/legal`, component: Legal }, + { + path: "/activitypub/externalInteraction", + component: RemoteFetch, + fetchInitialData: RemoteFetch.fetchInitialData, + }, ]; diff --git a/src/shared/utils/app/is-auth-path.ts b/src/shared/utils/app/is-auth-path.ts index fc65e0c2..adacff66 100644 --- a/src/shared/utils/app/is-auth-path.ts +++ b/src/shared/utils/app/is-auth-path.ts @@ -1,5 +1,5 @@ export default function isAuthPath(pathname: string) { - return /^\/(create_.*?|inbox|settings|admin|reports|registration_applications)\b/g.test( + return /^\/(create_.*?|inbox|settings|admin|reports|registration_applications|activitypub.*?)\b/g.test( pathname ); }