Add external interaction page

This commit is contained in:
SleeplessOne1917 2023-07-21 18:42:32 -04:00
parent dcc17375b4
commit 6f6acbb0a5
7 changed files with 173 additions and 15 deletions

View file

@ -48,7 +48,7 @@ export default async (req: Request, res: Response) => {
let errorPageData: ErrorPageData | undefined = undefined; let errorPageData: ErrorPageData | undefined = undefined;
let try_site = await client.getSite(getSiteForm); 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( console.error(
"Incorrect JWT token, skipping auth so frontend can remove jwt cookie" "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)) { if (!auth && isAuthPath(path)) {
return res.redirect("/login"); return res.redirect(`/login?prev=${encodeURIComponent(url)}`);
} }
if (try_site.state === "success") { if (try_site.state === "success") {

View file

@ -72,10 +72,10 @@ export class App extends Component<AppProps, any> {
return ( return (
<ErrorGuard> <ErrorGuard>
<div tabIndex={-1}> <div tabIndex={-1} className="h-100">
{RouteComponent && {RouteComponent &&
(isAuthPath(path ?? "") ? ( (isAuthPath(path ?? "") ? (
<AuthGuard> <AuthGuard {...routeProps}>
<RouteComponent {...routeProps} /> <RouteComponent {...routeProps} />
</AuthGuard> </AuthGuard>
) : ( ) : (

View file

@ -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 { UserService } from "../../services";
import { Spinner } from "./icon"; import { Spinner } from "./icon";
@ -6,20 +7,26 @@ interface AuthGuardState {
hasRedirected: boolean; hasRedirected: boolean;
} }
class AuthGuard extends Component<{ children?: InfernoNode }, AuthGuardState> { class AuthGuard extends Component<
RouteComponentProps<Record<string, string>>,
AuthGuardState
> {
state = { state = {
hasRedirected: false, hasRedirected: false,
} as AuthGuardState; } as AuthGuardState;
constructor(props: any, context: any) { constructor(
props: RouteComponentProps<Record<string, string>>,
context: any
) {
super(props, context); super(props, context);
} }
componentDidMount() { componentDidMount() {
if (!UserService.Instance.myUserInfo) { if (!UserService.Instance.myUserInfo) {
const { pathname, search } = this.props.location;
this.context.router.history.replace( this.context.router.history.replace(
"/login", `/login?prev=${encodeURIComponent(pathname + search)}`
this.context.router.history.location
); );
} else { } else {
this.setState({ hasRedirected: true }); this.setState({ hasRedirected: true });

View file

@ -1,6 +1,6 @@
import { myAuth, setIsoData } from "@utils/app"; import { myAuth, setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser"; import { isBrowser } from "@utils/browser";
import { Location } from "history"; import { getQueryParams } from "@utils/helpers";
import { Component, linkEvent } from "inferno"; import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route"; import { RouteComponentProps } from "inferno-router/dist/Route";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
@ -11,6 +11,17 @@ import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon"; import { Spinner } from "../common/icon";
import PasswordInput from "../common/password-input"; import PasswordInput from "../common/password-input";
interface LoginProps {
prev?: string;
}
const getLoginQueryParams = () =>
getQueryParams<LoginProps>({
prev(param) {
return param ? decodeURIComponent(param) : undefined;
},
});
interface State { interface State {
loginRes: RequestState<LoginResponse>; loginRes: RequestState<LoginResponse>;
form: { form: {
@ -57,11 +68,10 @@ async function handleLoginSubmit(i: Login, event: any) {
UserService.Instance.myUserInfo = site.data.my_user; UserService.Instance.myUserInfo = site.data.my_user;
} }
const { hash, pathname, search } = (i.props.history.location.state ?? const { prev } = getLoginQueryParams();
{}) as Location;
i.props.history.location.state prev
? i.props.history.replace({ hash, pathname, search }) ? i.props.history.replace(prev)
: i.props.history.action === "PUSH" : i.props.history.action === "PUSH"
? i.props.history.back() ? i.props.history.back()
: i.props.history.replace("/"); : i.props.history.replace("/");

View file

@ -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<ResolveObjectResponse>;
isIsomorphic: boolean;
}
const getUriFromQuery = (uri?: string): string | undefined =>
uri ? decodeURIComponent(uri) : undefined;
const getRemoteFetchQueryParams = () =>
getQueryParams<RemoteFetchProps>({
uri: getUriFromQuery,
});
function uriToQuery(uri: string) {
const match = decodeURIComponent(uri).match(/https?:\/\/(.+)\/c\/(.+)/);
return match ? `!${match[2]}@${match[1]}` : "";
}
export class RemoteFetch extends Component<any, RemoteFetchState> {
private isoData = setIsoData<RemoteFetchData>(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 (
<div className="remote-fetch container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
<div className="row">
<div className="col-12 col-lg-6 offset-lg-3 text-center">
{this.content}
</div>
</div>
</div>
);
}
get content() {
const status: "success" | "loading" | "empty" = "success";
switch (status) {
case "success": {
return (
<>
<h1>Community Federated!</h1>
<Link href="/" className="btn btn-lg bt-link btn-primary mt-auto">
Click to visit com
</Link>
</>
);
}
}
}
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<RemoteFetchProps>
>): Promise<RemoteFetchData> {
const data: RemoteFetchData = { resolveObjectRes: { state: "empty" } };
if (uri && auth) {
data.resolveObjectRes = await client.resolveObject({
auth,
q: uriToQuery(uri),
});
}
return data;
}
}

View file

@ -21,6 +21,7 @@ import { VerifyEmail } from "./components/person/verify-email";
import { CreatePost } from "./components/post/create-post"; import { CreatePost } from "./components/post/create-post";
import { Post } from "./components/post/post"; import { Post } from "./components/post/post";
import { CreatePrivateMessage } from "./components/private_message/create-private-message"; import { CreatePrivateMessage } from "./components/private_message/create-private-message";
import { RemoteFetch } from "./components/remote_fetch";
import { Search } from "./components/search"; import { Search } from "./components/search";
import { InitialFetchRequest, RouteData } from "./interfaces"; import { InitialFetchRequest, RouteData } from "./interfaces";
@ -140,4 +141,9 @@ export const routes: IRoutePropsWithFetch<Record<string, any>>[] = [
fetchInitialData: Instances.fetchInitialData, fetchInitialData: Instances.fetchInitialData,
}, },
{ path: `/legal`, component: Legal }, { path: `/legal`, component: Legal },
{
path: "/activitypub/externalInteraction",
component: RemoteFetch,
fetchInitialData: RemoteFetch.fetchInitialData,
},
]; ];

View file

@ -1,5 +1,5 @@
export default function isAuthPath(pathname: string) { 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 pathname
); );
} }