feat(mobx): migrate auth and config

This commit is contained in:
Paul
2021-12-11 21:04:12 +00:00
parent 2eb8801363
commit 763ac4eb98
22 changed files with 342 additions and 279 deletions

View File

@@ -205,7 +205,6 @@ export const Languages: { [key in Language]: LanguageEntry } = {
interface Props {
children: JSX.Element | JSX.Element[];
locale: Language;
}
export interface Dictionary {

View File

@@ -1,9 +1,9 @@
import { Redirect } from "react-router-dom";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../../mobx/State";
import { Children } from "../../types/Preact";
import { OperationsContext } from "./RevoltClient";
import { useClient } from "./RevoltClient";
interface Props {
auth?: boolean;
@@ -11,11 +11,13 @@ interface Props {
}
export const CheckAuth = (props: Props) => {
const operations = useContext(OperationsContext);
const auth = useApplicationState().auth;
const client = useClient();
const ready = auth.isLoggedIn() && typeof client?.user !== "undefined";
if (props.auth && !operations.ready()) {
if (props.auth && !ready) {
return <Redirect to="/login" />;
} else if (!props.auth && operations.ready()) {
} else if (!props.auth && ready) {
return <Redirect to="/" />;
}

View File

@@ -1,26 +1,22 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { Session } from "revolt-api/types/Auth";
import { observer } from "mobx-react-lite";
import { Client } from "revolt.js";
import { Route } from "revolt.js/dist/api/routes";
import { createContext } from "preact";
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { dispatch } from "../../redux";
import { connectState } from "../../redux/connector";
import { AuthState } from "../../redux/reducers/auth";
import { useApplicationState } from "../../mobx/State";
import Preloader from "../../components/ui/Preloader";
import { Children } from "../../types/Preact";
import { useIntermediate } from "../intermediate/Intermediate";
import { registerEvents, setReconnectDisallowed } from "./events";
import { registerEvents } from "./events";
import { takeError } from "./util";
export enum ClientStatus {
INIT,
LOADING,
READY,
LOADING,
OFFLINE,
DISCONNECTED,
CONNECTING,
@@ -29,179 +25,75 @@ export enum ClientStatus {
}
export interface ClientOperations {
login: (
data: Route<"POST", "/auth/session/login">["data"],
) => Promise<void>;
logout: (shouldRequest?: boolean) => Promise<void>;
loggedIn: () => boolean;
ready: () => boolean;
}
// By the time they are used, they should all be initialized.
// Currently the app does not render until a client is built and the other two are always initialized on first render.
// - insert's words
export const AppContext = createContext<Client>(null!);
export const StatusContext = createContext<ClientStatus>(null!);
export const OperationsContext = createContext<ClientOperations>(null!);
export const LogOutContext = createContext(() => {});
type Props = {
auth: AuthState;
children: Children;
};
function Context({ auth, children }: Props) {
export default observer(({ children }: Props) => {
const state = useApplicationState();
const { openScreen } = useIntermediate();
const [status, setStatus] = useState(ClientStatus.INIT);
const [client, setClient] = useState<Client>(
undefined as unknown as Client,
);
const [client, setClient] = useState<Client>(null!);
const [status, setStatus] = useState(ClientStatus.LOADING);
const [loaded, setLoaded] = useState(false);
function logout() {
setLoaded(false);
client.logout(false);
}
useEffect(() => {
(async () => {
const client = new Client({
autoReconnect: false,
apiURL: import.meta.env.VITE_API_URL,
debug: import.meta.env.DEV,
});
setClient(client);
setStatus(ClientStatus.LOADING);
})();
if (navigator.onLine) {
new Client().req("GET", "/").then(state.config.set);
}
}, []);
if (status === ClientStatus.INIT) return null;
const operations: ClientOperations = useMemo(() => {
return {
login: async (data) => {
setReconnectDisallowed(true);
try {
const onboarding = await client.login(data);
setReconnectDisallowed(false);
const login = () =>
dispatch({
type: "LOGIN",
session: client.session as Session,
});
if (onboarding) {
openScreen({
id: "onboarding",
callback: async (username: string) =>
onboarding(username, true).then(login),
});
} else {
login();
}
} catch (err) {
setReconnectDisallowed(false);
throw err;
}
},
logout: async (shouldRequest) => {
dispatch({ type: "LOGOUT" });
client.reset();
dispatch({ type: "RESET" });
openScreen({ id: "none" });
setStatus(ClientStatus.READY);
client.websocket.disconnect();
if (shouldRequest) {
try {
await client.logout();
} catch (err) {
console.error(err);
}
}
},
loggedIn: () => typeof auth.active !== "undefined",
ready: () =>
operations.loggedIn() && typeof client.user !== "undefined",
};
}, [client, auth.active, openScreen]);
useEffect(
() => registerEvents({ operations }, setStatus, client),
[client, operations],
);
useEffect(() => {
(async () => {
if (auth.active) {
dispatch({ type: "QUEUE_FAIL_ALL" });
if (state.auth.isLoggedIn()) {
const client = state.config.createClient();
setClient(client);
const active = auth.accounts[auth.active];
client.user = client.users.get(active.session.user_id);
if (!navigator.onLine) {
return setStatus(ClientStatus.OFFLINE);
}
if (operations.ready()) setStatus(ClientStatus.CONNECTING);
if (navigator.onLine) {
await client
.fetchConfiguration()
.catch(() =>
console.error("Failed to connect to API server."),
);
}
try {
await client.fetchConfiguration();
const callback = await client.useExistingSession(
active.session,
);
if (callback) {
openScreen({ id: "onboarding", callback });
}
} catch (err) {
setStatus(ClientStatus.DISCONNECTED);
client
.useExistingSession(state.auth.getSession()!)
.then(() => setLoaded(true))
.catch((err) => {
const error = takeError(err);
if (error === "Forbidden" || error === "Unauthorized") {
operations.logout(true);
client.logout(true);
openScreen({ id: "signed_out" });
} else {
setStatus(ClientStatus.DISCONNECTED);
openScreen({ id: "error", error });
}
}
} else {
try {
await client.fetchConfiguration();
} catch (err) {
console.error("Failed to connect to API server.");
}
});
} else {
setStatus(ClientStatus.READY);
setLoaded(true);
}
}, [state.auth.getSession()]);
setStatus(ClientStatus.READY);
}
})();
// eslint-disable-next-line
}, []);
useEffect(() => registerEvents(state.auth, setStatus, client), [client]);
if (status === ClientStatus.LOADING) {
if (!loaded || status === ClientStatus.LOADING) {
return <Preloader type="spinner" />;
}
return (
<AppContext.Provider value={client}>
<StatusContext.Provider value={status}>
<OperationsContext.Provider value={operations}>
<LogOutContext.Provider value={logout}>
{children}
</OperationsContext.Provider>
</LogOutContext.Provider>
</StatusContext.Provider>
</AppContext.Provider>
);
}
export default connectState<{ children: Children }>(Context, (state) => {
return {
auth: state.auth,
sync: state.sync,
};
});
export const useClient = () => useContext(AppContext);

View File

@@ -21,7 +21,7 @@ import {
import { Language } from "../Locale";
import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";
type Props = {
/*type Props = {
settings: Settings;
locale: Language;
sync: SyncOptions;
@@ -150,4 +150,8 @@ export default connectState(SyncManager, (state) => {
sync: state.sync,
notifications: state.notifications,
};
});
});*/
function SyncManager() {
return <></>;
}

View File

@@ -4,9 +4,10 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"
import { StateUpdater } from "preact/hooks";
import Auth from "../../mobx/stores/Auth";
import { dispatch } from "../../redux";
import { ClientOperations, ClientStatus } from "./RevoltClient";
import { ClientStatus } from "./RevoltClient";
export let preventReconnect = false;
let preventUntil = 0;
@@ -16,10 +17,12 @@ export function setReconnectDisallowed(allowed: boolean) {
}
export function registerEvents(
{ operations }: { operations: ClientOperations },
auth: Auth,
setStatus: StateUpdater<ClientStatus>,
client: Client,
) {
if (!client) return;
function attemptReconnect() {
if (preventReconnect) return;
function reconnect() {
@@ -36,14 +39,11 @@ export function registerEvents(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let listeners: Record<string, (...args: any[]) => void> = {
connecting: () =>
operations.ready() && setStatus(ClientStatus.CONNECTING),
connecting: () => setStatus(ClientStatus.CONNECTING),
dropped: () => {
if (operations.ready()) {
setStatus(ClientStatus.DISCONNECTED);
attemptReconnect();
}
setStatus(ClientStatus.DISCONNECTED);
attemptReconnect();
},
packet: (packet: ClientboundNotification) => {
@@ -70,6 +70,11 @@ export function registerEvents(
},
ready: () => setStatus(ClientStatus.ONLINE),
logout: () => {
auth.logout();
setStatus(ClientStatus.READY);
},
};
if (import.meta.env.DEV) {
@@ -89,19 +94,15 @@ export function registerEvents(
}
const online = () => {
if (operations.ready()) {
setStatus(ClientStatus.RECONNECTING);
setReconnectDisallowed(false);
attemptReconnect();
}
setStatus(ClientStatus.RECONNECTING);
setReconnectDisallowed(false);
attemptReconnect();
};
const offline = () => {
if (operations.ready()) {
setReconnectDisallowed(true);
client.websocket.disconnect();
setStatus(ClientStatus.OFFLINE);
}
setReconnectDisallowed(true);
client.websocket.disconnect();
setStatus(ClientStatus.OFFLINE);
};
window.addEventListener("online", online);