From 3996cdaae310f2a8e8777a7fe05597847b5ce5f9 Mon Sep 17 00:00:00 2001 From: abias Date: Sun, 14 May 2023 19:49:55 -0400 Subject: [PATCH] Handle error when site not returned --- src/server/index.tsx | 291 ++++++++++--------- src/shared/components/app/app.tsx | 10 +- src/shared/components/app/error-page.tsx | 7 +- src/shared/components/app/footer.tsx | 10 +- src/shared/components/app/navbar.tsx | 12 +- src/shared/components/common/error-guard.tsx | 3 +- src/shared/components/person/settings.tsx | 3 - src/shared/interfaces.ts | 3 + src/shared/utils.ts | 8 +- 9 files changed, 190 insertions(+), 157 deletions(-) diff --git a/src/server/index.tsx b/src/server/index.tsx index e406e6fe..26f5cf90 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -16,7 +16,7 @@ import { getHttpBase, getHttpBaseInternal } from "../shared/env"; import { ILemmyConfig, InitialFetchRequest, - IsoData, + IsoDataOptionalSite, } from "../shared/interfaces"; import { routes } from "../shared/routes"; import { @@ -78,6 +78,7 @@ server.get("/css/themes/:name", async (req, res) => { res.contentType("text/css"); const theme = req.params.name; if (!theme.endsWith(".css")) { + res.statusCode = 400; res.send("Theme must be a css file"); } @@ -121,8 +122,6 @@ server.get("/*", async (req, res) => { const getSiteForm: GetSite = { auth }; - const promises: Promise[] = []; - const headers = setForwardedHeaders(req.headers); const client = new LemmyHttp(getHttpBaseInternal(), headers); @@ -131,39 +130,46 @@ server.get("/*", async (req, res) => { // Get site data first // This bypasses errors, so that the client can hit the error on its own, // in order to remove the jwt on the browser. Necessary for wrong jwts - let try_site: any = await client.getSite(getSiteForm); - if (try_site.error == "not_logged_in") { - console.error( - "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" - ); - getSiteForm.auth = undefined; - auth = undefined; - try_site = await client.getSite(getSiteForm); + let site: GetSiteResponse | undefined = undefined; + let routeData: any[] = []; + try { + let try_site: any = await client.getSite(getSiteForm); + if (try_site.error == "not_logged_in") { + console.error( + "Incorrect JWT token, skipping auth so frontend can remove jwt cookie" + ); + getSiteForm.auth = undefined; + auth = undefined; + try_site = await client.getSite(getSiteForm); + } + + if (!auth && isAuthPath(path)) { + res.redirect("/login"); + return; + } + + site = try_site; + initializeSite(site); + + if (site) { + const initialFetchReq: InitialFetchRequest = { + client, + auth, + path, + query, + site, + }; + + if (activeRoute?.fetchInitialData) { + routeData = await Promise.all([ + ...activeRoute.fetchInitialData(initialFetchReq), + ]); + } + } + } catch (error) { + routeData = [getErrorRouteData(error)]; } - if (!auth && isAuthPath(path)) { - res.redirect("/login"); - return; - } - - const site: GetSiteResponse = try_site; - initializeSite(site); - - const initialFetchReq: InitialFetchRequest = { - client, - auth, - path, - query, - site, - }; - - if (activeRoute?.fetchInitialData) { - promises.push(...activeRoute.fetchInitialData(initialFetchReq)); - } - - let routeData = await Promise.all(promises); - // let routeData = [{ error: "I am an error, hear me roar!" } as any]; - // Redirect to the 404 if there's an API error if (routeData[0] && routeData[0].error) { const error = routeData[0].error; @@ -171,28 +177,14 @@ server.get("/*", async (req, res) => { if (error === "instance_is_private") { return res.redirect(`/signup`); } else { - const errorPageData: ErrorPageData = { type: "error" }; - - // Exact error should only be seen in a development environment. Users - // in production will get a more generic message. - if (NODE_ENV === "development") { - errorPageData.error = error; - } - - const adminMatrixIds = site.admins - .map(({ person: { matrix_user_id } }) => matrix_user_id) - .filter(id => id) as string[]; - if (adminMatrixIds.length > 0) { - errorPageData.adminMatrixIds = adminMatrixIds; - } - - routeData = [errorPageData]; + routeData = [getErrorRouteData(error, site)]; } } + console.log("Route Data"); console.log(routeData); - const isoData: IsoData = { + const isoData: IsoDataOptionalSite = { path, site_res: site, routeData, @@ -204,91 +196,11 @@ server.get("/*", async (req, res) => { ); - const eruda = ( - <> - - - - ); - - const erudaStr = process.env["LEMMY_UI_DEBUG"] ? renderToString(eruda) : ""; const root = renderToString(wrapper); - const helmet = Helmet.renderStatic(); - const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST }; - - const appleTouchIcon = site.site_view.site.icon - ? `data:image/png;base64,${sharp( - await fetchIconPng(site.site_view.site.icon) - ) - .resize(180, 180) - .extend({ - bottom: 20, - top: 20, - left: 20, - right: 20, - background: "#222222", - }) - .png() - .toBuffer() - .then(buf => buf.toString("base64"))}` - : favIconPngUrl; - - res.send(` - - - - - - - - ${erudaStr} - - - ${customHtmlHeader} - - ${helmet.title.toString()} - ${helmet.meta.toString()} - - - - - - - - - - - - - - - - - ${helmet.link.toString()} - - - - - - -
${root}
- - - -`); + res.send(await createSsrHtml(root, isoData)); } catch (err) { + // If an error is caught here, the error page couldn't even be rendered console.error(err); res.statusCode = 500; return res.send(NODE_ENV === "development" ? err.message : "Server error"); @@ -382,3 +294,116 @@ async function fetchIconPng(iconUrl: string) { .then(res => res.blob()) .then(blob => blob.arrayBuffer()); } + +function getErrorRouteData(error: string, site?: GetSiteResponse) { + const errorPageData: ErrorPageData = { type: "error" }; + + // Exact error should only be seen in a development environment. Users + // in production will get a more generic message. + if (NODE_ENV === "development") { + errorPageData.error = error; + } + + const adminMatrixIds = site?.admins + .map(({ person: { matrix_user_id } }) => matrix_user_id) + .filter(id => id) as string[] | undefined; + if (adminMatrixIds && adminMatrixIds.length > 0) { + errorPageData.adminMatrixIds = adminMatrixIds; + } + + return [errorPageData]; +} + +async function createSsrHtml(root: string, isoData: IsoDataOptionalSite) { + const site = isoData.site_res; + const appleTouchIcon = site?.site_view.site.icon + ? `data:image/png;base64,${sharp( + await fetchIconPng(site.site_view.site.icon) + ) + .resize(180, 180) + .extend({ + bottom: 20, + top: 20, + left: 20, + right: 20, + background: "#222222", + }) + .png() + .toBuffer() + .then(buf => buf.toString("base64"))}` + : favIconPngUrl; + + const eruda = ( + <> + + + + ); + + const erudaStr = process.env["LEMMY_UI_DEBUG"] ? renderToString(eruda) : ""; + + const helmet = Helmet.renderStatic(); + + const config: ILemmyConfig = { wsHost: process.env.LEMMY_UI_LEMMY_WS_HOST }; + + return ` + + + + + + + + ${erudaStr} + + + ${customHtmlHeader} + + ${helmet.title.toString()} + ${helmet.meta.toString()} + + + + + + + + + ${ + site && + `` + } + + + + + + + + ${helmet.link.toString()} + + + + + + +
${root}
+ + + +`; +} diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 1251a5e4..e7e51bef 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -1,6 +1,7 @@ import { Component } from "inferno"; import { Provider } from "inferno-i18next-dess"; import { Route, Switch } from "inferno-router"; +import { IsoDataOptionalSite } from "shared/interfaces"; import { i18n } from "../../i18next"; import { routes } from "../../routes"; import { isAuthPath, setIsoData } from "../../utils"; @@ -13,19 +14,22 @@ import "./styles.scss"; import { Theme } from "./theme"; export class App extends Component { - private isoData = setIsoData(this.context); + private isoData: IsoDataOptionalSite = setIsoData(this.context); constructor(props: any, context: any) { super(props, context); } render() { const siteRes = this.isoData.site_res; - const siteView = siteRes.site_view; + const siteView = siteRes?.site_view; + console.log(`Path: ${this.isoData.path}`); return ( <>
- + {siteView && ( + + )}
diff --git a/src/shared/components/app/error-page.tsx b/src/shared/components/app/error-page.tsx index 5c08dd52..e093463b 100644 --- a/src/shared/components/app/error-page.tsx +++ b/src/shared/components/app/error-page.tsx @@ -1,9 +1,10 @@ import { Component } from "inferno"; import { Link } from "inferno-router"; +import { IsoDataOptionalSite } from "shared/interfaces"; import { ErrorPageData, setIsoData } from "../../utils"; export class ErrorPage extends Component { - private isoData = setIsoData(this.context); + private isoData: IsoDataOptionalSite = setIsoData(this.context); constructor(props: any, context: any) { super(props, context); @@ -28,8 +29,8 @@ export class ErrorPage extends Component {
If you would like to reach out to one of{" "} - {this.isoData.site_res.site_view.site.name}'s admins for - support, try the following Matrix addresses: + {this.isoData.site_res?.site_view.site.name ?? "this instance"} + 's admins for support, try the following Matrix addresses:
    {errorPageData.adminMatrixIds.map(matrixId => ( diff --git a/src/shared/components/app/footer.tsx b/src/shared/components/app/footer.tsx index bbe49982..bd66165e 100644 --- a/src/shared/components/app/footer.tsx +++ b/src/shared/components/app/footer.tsx @@ -6,7 +6,7 @@ import { docsUrl, joinLemmyUrl, repoUrl } from "../../utils"; import { VERSION } from "../../version"; interface FooterProps { - site: GetSiteResponse; + site?: GetSiteResponse; } export class Footer extends Component { @@ -19,27 +19,27 @@ export class Footer extends Component {