mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-12-23 03:11:25 +00:00
Set up logic for handling errors
This commit is contained in:
parent
ab3fed3ddf
commit
bcee6aad5b
8 changed files with 163 additions and 56 deletions
|
@ -19,7 +19,14 @@ import {
|
|||
IsoData,
|
||||
} from "../shared/interfaces";
|
||||
import { routes } from "../shared/routes";
|
||||
import { favIconPngUrl, favIconUrl, initializeSite } from "../shared/utils";
|
||||
import {
|
||||
ErrorPageData,
|
||||
favIconPngUrl,
|
||||
favIconUrl,
|
||||
initializeSite,
|
||||
isAuthPath,
|
||||
} from "../shared/utils";
|
||||
import { VERSION } from "../shared/version";
|
||||
|
||||
const server = express();
|
||||
const [hostname, port] = process.env["LEMMY_UI_HOST"]
|
||||
|
@ -109,7 +116,6 @@ server.get("/css/themelist", async (_req, res) => {
|
|||
server.get("/*", async (req, res) => {
|
||||
try {
|
||||
const activeRoute = routes.find(route => matchPath(req.path, route));
|
||||
const context = {} as any;
|
||||
let auth: string | undefined = IsomorphicCookie.load("jwt", req);
|
||||
|
||||
const getSiteForm: GetSite = { auth };
|
||||
|
@ -119,6 +125,8 @@ server.get("/*", async (req, res) => {
|
|||
const headers = setForwardedHeaders(req.headers);
|
||||
const client = new LemmyHttp(getHttpBaseInternal(), headers);
|
||||
|
||||
const { path, url, query } = req;
|
||||
|
||||
// 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
|
||||
|
@ -131,14 +139,18 @@ server.get("/*", async (req, res) => {
|
|||
auth = undefined;
|
||||
try_site = await client.getSite(getSiteForm);
|
||||
}
|
||||
|
||||
if (!auth && isAuthPath(path)) {
|
||||
res.redirect("/");
|
||||
}
|
||||
const site: GetSiteResponse = try_site;
|
||||
initializeSite(site);
|
||||
|
||||
const initialFetchReq: InitialFetchRequest = {
|
||||
client,
|
||||
auth,
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
path,
|
||||
query,
|
||||
site,
|
||||
};
|
||||
|
||||
|
@ -146,7 +158,7 @@ server.get("/*", async (req, res) => {
|
|||
promises.push(...activeRoute.fetchInitialData(initialFetchReq));
|
||||
}
|
||||
|
||||
const routeData = await Promise.all(promises);
|
||||
let routeData = await Promise.all(promises);
|
||||
|
||||
// Redirect to the 404 if there's an API error
|
||||
if (routeData[0] && routeData[0].error) {
|
||||
|
@ -155,24 +167,36 @@ server.get("/*", async (req, res) => {
|
|||
if (error === "instance_is_private") {
|
||||
return res.redirect(`/signup`);
|
||||
} else {
|
||||
return res.send(`404: ${removeAuthParam(error)}`);
|
||||
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 (VERSION === "dev") {
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
const isoData: IsoData = {
|
||||
path: req.path,
|
||||
path,
|
||||
site_res: site,
|
||||
routeData,
|
||||
};
|
||||
|
||||
const wrapper = (
|
||||
<StaticRouter location={req.url} context={isoData}>
|
||||
<StaticRouter location={url} context={isoData}>
|
||||
<App />
|
||||
</StaticRouter>
|
||||
);
|
||||
if (context.url) {
|
||||
return res.redirect(context.url);
|
||||
}
|
||||
|
||||
const eruda = (
|
||||
<>
|
||||
|
@ -260,7 +284,8 @@ server.get("/*", async (req, res) => {
|
|||
`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.send(`404: ${removeAuthParam(err)}`);
|
||||
res.statusCode = 500;
|
||||
return res.send(VERSION === "dev" ? err.message : "Server error");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -292,16 +317,6 @@ process.on("SIGINT", () => {
|
|||
process.exit(0);
|
||||
});
|
||||
|
||||
function removeAuthParam(err: any): string {
|
||||
return removeParam(err.toString(), "auth");
|
||||
}
|
||||
|
||||
function removeParam(url: string, parameter: string): string {
|
||||
return url
|
||||
.replace(new RegExp("[?&]" + parameter + "=[^&#]*(#.*)?$"), "$1")
|
||||
.replace(new RegExp("([?&])" + parameter + "=[^&]*&"), "$1");
|
||||
}
|
||||
|
||||
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
|
||||
const defaultLogoPathDirectory = path.join(
|
||||
process.cwd(),
|
||||
|
|
|
@ -3,10 +3,12 @@ import { Provider } from "inferno-i18next-dess";
|
|||
import { Route, Switch } from "inferno-router";
|
||||
import { i18n } from "../../i18next";
|
||||
import { routes } from "../../routes";
|
||||
import { setIsoData } from "../../utils";
|
||||
import { isAuthPath, setIsoData } from "../../utils";
|
||||
import AuthGuard from "../common/auth-guard";
|
||||
import ErrorGuard from "../common/error-guard";
|
||||
import { ErrorPage } from "./error-page";
|
||||
import { Footer } from "./footer";
|
||||
import { Navbar } from "./navbar";
|
||||
import { NoMatch } from "./no-match";
|
||||
import "./styles.scss";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
|
@ -16,8 +18,8 @@ export class App extends Component<any, any> {
|
|||
super(props, context);
|
||||
}
|
||||
render() {
|
||||
let siteRes = this.isoData.site_res;
|
||||
let siteView = siteRes.site_view;
|
||||
const siteRes = this.isoData.site_res;
|
||||
const siteView = siteRes.site_view;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -27,10 +29,26 @@ export class App extends Component<any, any> {
|
|||
<Navbar siteRes={siteRes} />
|
||||
<div className="mt-4 p-0 fl-1">
|
||||
<Switch>
|
||||
{routes.map(({ path, component }) => (
|
||||
<Route key={path} path={path} exact component={component} />
|
||||
{routes.map(({ path, component: RouteComponent }) => (
|
||||
<Route
|
||||
key={path}
|
||||
path={path}
|
||||
exact
|
||||
component={routeProps => (
|
||||
<ErrorGuard>
|
||||
{RouteComponent &&
|
||||
(isAuthPath(path ?? "") ? (
|
||||
<AuthGuard>
|
||||
<RouteComponent {...routeProps} />
|
||||
</AuthGuard>
|
||||
) : (
|
||||
<RouteComponent {...routeProps} />
|
||||
))}
|
||||
</ErrorGuard>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<Route component={NoMatch} />
|
||||
<Route component={ErrorPage} />
|
||||
</Switch>
|
||||
</div>
|
||||
<Footer site={siteRes} />
|
||||
|
|
56
src/shared/components/app/error-page.tsx
Normal file
56
src/shared/components/app/error-page.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { Component } from "inferno";
|
||||
import { Link } from "inferno-router";
|
||||
import { ErrorPageData, setIsoData } from "../../utils";
|
||||
|
||||
export class ErrorPage extends Component<any, any> {
|
||||
private isoData = setIsoData(this.context);
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorPageData = this.getErrorPageData();
|
||||
|
||||
return (
|
||||
<div className="container-lg">
|
||||
<h1>{errorPageData ? "Error!" : "Page Not Found"}</h1>
|
||||
<p>
|
||||
{errorPageData
|
||||
? "There was an error on the server. Try refreshing your browser of coming back at a later time"
|
||||
: "The page you are looking for does not exist"}
|
||||
</p>
|
||||
{!errorPageData && (
|
||||
<Link to="/">Click here to return to your home page</Link>
|
||||
)}
|
||||
{errorPageData?.adminMatrixIds &&
|
||||
errorPageData.adminMatrixIds.length > 0 && (
|
||||
<div>
|
||||
<div>
|
||||
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:
|
||||
</div>
|
||||
<ul>
|
||||
{errorPageData.adminMatrixIds.map(matrixId => (
|
||||
<li key={matrixId}>{matrixId}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{errorPageData?.error && <code>{errorPageData.error.message}</code>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getErrorPageData() {
|
||||
const errorPageData = this.isoData.routeData[0] as
|
||||
| ErrorPageData
|
||||
| undefined;
|
||||
if (errorPageData?.type === "error") {
|
||||
return errorPageData;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { NoOptionI18nKeys } from "i18next";
|
||||
import { Component } from "inferno";
|
||||
import { i18n } from "../../i18next";
|
||||
|
||||
export class NoMatch extends Component<any, any> {
|
||||
private errCode = new URLSearchParams(this.props.location.search).get(
|
||||
"err"
|
||||
) as NoOptionI18nKeys;
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-lg">
|
||||
<h1>404</h1>
|
||||
{this.errCode && (
|
||||
<h3>
|
||||
{i18n.t("code")}: {i18n.t(this.errCode)}
|
||||
</h3>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
13
src/shared/components/common/auth-guard.tsx
Normal file
13
src/shared/components/common/auth-guard.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { InfernoNode } from "inferno";
|
||||
import { Redirect } from "inferno-router";
|
||||
import { UserService } from "../../services";
|
||||
|
||||
function AuthGuard(props: { children?: InfernoNode }) {
|
||||
if (!UserService.Instance.myUserInfo) {
|
||||
return <Redirect to="/" />;
|
||||
} else {
|
||||
return <>{props.children}</>;
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthGuard;
|
25
src/shared/components/common/error-guard.tsx
Normal file
25
src/shared/components/common/error-guard.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { Component } from "inferno";
|
||||
import { ErrorPageData, setIsoData } from "../../utils";
|
||||
import { ErrorPage } from "../app/error-page";
|
||||
|
||||
class ErrorGuard extends Component<any, any> {
|
||||
private isoData = setIsoData(this.context);
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
const errorPageData = this.isoData.routeData[0] as
|
||||
| ErrorPageData
|
||||
| undefined;
|
||||
|
||||
if (errorPageData?.type === "error") {
|
||||
return <ErrorPage />;
|
||||
} else {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorGuard;
|
|
@ -105,6 +105,12 @@ export type ThemeColor =
|
|||
| "gray"
|
||||
| "gray-dark";
|
||||
|
||||
export interface ErrorPageData {
|
||||
type: "error";
|
||||
error?: Error;
|
||||
adminMatrixIds?: string[];
|
||||
}
|
||||
|
||||
let customEmojis: EmojiMartCategory[] = [];
|
||||
export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
|
||||
string,
|
||||
|
|
|
@ -1 +1 @@
|
|||
export const VERSION = "unknown version";
|
||||
export const VERSION = "unknown version" as string;
|
||||
|
|
Loading…
Reference in a new issue