feat(mobx): migrate auth and config

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

View file

@ -1,10 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" background="#191919">
<head>
<meta charset="UTF-8" />
<!--App Title-->
<title>Revolt</title>
<meta name="apple-mobile-web-app-title" content="Revolt" />
<!--App Scaling-->
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
@ -74,9 +77,4 @@
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
<style>
html {
background-color: #191919;
}
</style>
</html>

View file

@ -49,7 +49,10 @@
"FunctionExpression": false
},
"ignore": {
"MethodDefinition": ["toJSON", "hydrate"]
"MethodDefinition": [
"toJSON",
"hydrate"
]
}
}
]
@ -140,7 +143,7 @@
"react-virtuoso": "^1.10.4",
"redux": "^4.1.0",
"revolt-api": "0.5.3-alpha.10",
"revolt.js": "^5.1.0-alpha.10",
"revolt.js": "5.1.0-alpha.15",
"rimraf": "^3.0.2",
"sass": "^1.35.1",
"shade-blend-color": "^1.0.0",

View file

@ -276,13 +276,13 @@ export const ServerListSidebar = observer(({ unreads }: Props) => {
onClick={() =>
homeActive && history.push("/settings")
}>
<UserHover user={client.user}>
<UserHover user={client.user ?? undefined}>
<Icon
size={42}
unread={homeUnread}
count={alertCount}>
<UserIcon
target={client.user}
target={client.user ?? undefined}
size={32}
status
hover

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.");
}
setStatus(ClientStatus.READY);
setLoaded(true);
}
})();
// eslint-disable-next-line
}, []);
}, [state.auth.getSession()]);
if (status === ClientStatus.LOADING) {
useEffect(() => registerEvents(state.auth, setStatus, client), [client]);
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();
}
},
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();
}
};
const offline = () => {
if (operations.ready()) {
setReconnectDisallowed(true);
client.websocket.disconnect();
setStatus(ClientStatus.OFFLINE);
}
};
window.addEventListener("online", online);

View file

@ -488,7 +488,8 @@ function ContextMenus(props: Props) {
elements.push(
<MenuItem data={action} disabled={disabled}>
<Text
id={`app.context_menu.${locale ?? action.action
id={`app.context_menu.${
locale ?? action.action
}`}
/>
{tip && <div className="tip">{tip}</div>}
@ -705,7 +706,8 @@ function ContextMenus(props: Props) {
if (message && !queued) {
const sendPermission =
message.channel &&
message.channel.permission & ChannelPermission.SendMessage
message.channel.permission &
ChannelPermission.SendMessage;
if (sendPermission) {
generateAction({

View file

@ -10,6 +10,7 @@ import Draft from "./stores/Draft";
import Experiments from "./stores/Experiments";
import Layout from "./stores/Layout";
import LocaleOptions from "./stores/LocaleOptions";
import ServerConfig from "./stores/ServerConfig";
/**
* Handles global application state.
@ -20,6 +21,7 @@ export default class State {
locale: LocaleOptions;
experiments: Experiments;
layout: Layout;
config: ServerConfig;
private persistent: [string, Persistent<unknown>][] = [];
@ -32,12 +34,16 @@ export default class State {
this.locale = new LocaleOptions();
this.experiments = new Experiments();
this.layout = new Layout();
this.config = new ServerConfig();
makeAutoObservable(this);
this.registerListeners = this.registerListeners.bind(this);
this.register();
}
/**
* Categorise and register stores referenced on this object.
*/
private register() {
for (const key of Object.keys(this)) {
const obj = (
@ -65,12 +71,22 @@ export default class State {
}
}
/**
* Register reaction listeners for persistent data stores.
* @returns Function to dispose of listeners
*/
registerListeners() {
const listeners = this.persistent.map(([id, store]) => {
return reaction(
() => store.toJSON(),
(value) => {
localforage.setItem(id, value);
async (value) => {
try {
await localforage.setItem(id, value);
} catch (err) {
console.error("Failed to serialise!");
console.error(err);
console.error(value);
}
},
);
});
@ -78,6 +94,9 @@ export default class State {
return () => listeners.forEach((x) => x());
}
/**
* Load data stores from local storage.
*/
async hydrate() {
for (const [id, store] of this.persistent) {
const data = await localforage.getItem(id);

View file

@ -1,12 +1,18 @@
import { makeAutoObservable, ObservableMap } from "mobx";
import { action, computed, makeAutoObservable, ObservableMap } from "mobx";
import { Session } from "revolt-api/types/Auth";
import { Nullable } from "revolt.js/dist/util/null";
import { mapToRecord } from "../../lib/conversion";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
interface Account {
session: Session;
}
interface Data {
sessions: Record<string, Session>;
sessions: Record<string, Account> | [string, Account][];
current?: string;
}
@ -15,7 +21,7 @@ interface Data {
* accounts and their sessions.
*/
export default class Auth implements Store, Persistent<Data> {
private sessions: ObservableMap<string, Session>;
private sessions: ObservableMap<string, Account>;
private current: Nullable<string>;
/**
@ -31,17 +37,27 @@ export default class Auth implements Store, Persistent<Data> {
return "auth";
}
toJSON() {
@action toJSON() {
return {
sessions: [...this.sessions],
sessions: JSON.parse(JSON.stringify(this.sessions)),
current: this.current ?? undefined,
};
}
hydrate(data: Data) {
Object.keys(data.sessions).forEach((id) =>
this.sessions.set(id, data.sessions[id]),
@action hydrate(data: Data) {
if (Array.isArray(data.sessions)) {
data.sessions.forEach(([key, value]) =>
this.sessions.set(key, value),
);
} else if (
typeof data.sessions === "object" &&
data.sessions !== null
) {
let v = data.sessions;
Object.keys(data.sessions).forEach((id) =>
this.sessions.set(id, v[id]),
);
}
if (data.current && this.sessions.has(data.current)) {
this.current = data.current;
@ -52,8 +68,8 @@ export default class Auth implements Store, Persistent<Data> {
* Add a new session to the auth manager.
* @param session Session
*/
setSession(session: Session) {
this.sessions.set(session.user_id, session);
@action setSession(session: Session) {
this.sessions.set(session.user_id, { session });
this.current = session.user_id;
}
@ -61,11 +77,28 @@ export default class Auth implements Store, Persistent<Data> {
* Remove existing session by user ID.
* @param user_id User ID tied to session
*/
removeSession(user_id: string) {
this.sessions.delete(user_id);
@action removeSession(user_id: string) {
if (user_id == this.current) {
this.current = null;
}
this.sessions.delete(user_id);
}
@action logout() {
this.current && this.removeSession(this.current);
}
@computed getSession() {
if (!this.current) return;
return this.sessions.get(this.current)!.session;
}
/**
* Check whether we are currently logged in.
* @returns Whether we are logged in
*/
@computed isLoggedIn() {
return this.current !== null;
}
}

View file

@ -0,0 +1,75 @@
import { action, computed, makeAutoObservable } from "mobx";
import { RevoltConfiguration } from "revolt-api/types/Core";
import { Client } from "revolt.js";
import { Nullable } from "revolt.js/dist/util/null";
import Persistent from "../interfaces/Persistent";
import Store from "../interfaces/Store";
interface Data {
config?: RevoltConfiguration;
}
/**
* Stores server configuration data.
*/
export default class ServerConfig
implements Store, Persistent<RevoltConfiguration>
{
private config: Nullable<RevoltConfiguration>;
/**
* Construct new ServerConfig store.
*/
constructor() {
this.config = null;
makeAutoObservable(this);
this.set = this.set.bind(this);
}
get id() {
return "server_conf";
}
toJSON() {
return JSON.parse(JSON.stringify(this.config));
}
@action hydrate(data: RevoltConfiguration) {
this.config = data;
}
/**
* Create a new Revolt client.
* @returns Revolt client
*/
createClient() {
const client = new Client({
autoReconnect: false,
apiURL: import.meta.env.VITE_API_URL,
debug: import.meta.env.DEV,
});
if (this.config !== null) {
client.configuration = this.config;
}
return client;
}
/**
* Get server configuration.
* @returns Server configuration
*/
@computed get() {
return this.config;
}
/**
* Set server configuration.
* @param config Server configuration
*/
@action set(config: RevoltConfiguration) {
this.config = config;
}
}

View file

@ -1,3 +1,4 @@
import { observer } from "mobx-react-lite";
import { Helmet } from "react-helmet";
import { Route, Switch } from "react-router-dom";
import { LIBRARY_VERSION } from "revolt.js";
@ -6,22 +7,24 @@ import styles from "./Login.module.scss";
import { Text } from "preact-i18n";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../../mobx/State";
import { ThemeContext } from "../../context/Theme";
import { AppContext } from "../../context/revoltjs/RevoltClient";
import LocaleSelector from "../../components/common/LocaleSelector";
import background from "./background.jpg";
import { Titlebar } from "../../components/native/Titlebar";
import { APP_VERSION } from "../../version";
import background from "./background.jpg";
import { FormCreate } from "./forms/FormCreate";
import { FormLogin } from "./forms/FormLogin";
import { FormReset, FormSendReset } from "./forms/FormReset";
import { FormResend, FormVerify } from "./forms/FormVerify";
export default function Login() {
export default observer(() => {
const theme = useContext(ThemeContext);
const client = useContext(AppContext);
const configuration = useApplicationState().config.get();
return (
<>
@ -35,8 +38,7 @@ export default function Login() {
<div className={styles.content}>
<div className={styles.attribution}>
<span>
API:{" "}
<code>{client.configuration?.revolt ?? "???"}</code>{" "}
API: <code>{configuration?.revolt ?? "???"}</code>{" "}
&middot; revolt.js: <code>{LIBRARY_VERSION}</code>{" "}
&middot; App: <code>{APP_VERSION}</code>
</span>
@ -80,4 +82,4 @@ export default function Login() {
</div>
</>
);
}
});

View file

@ -1,10 +1,11 @@
import HCaptcha from "@hcaptcha/react-hcaptcha";
import { observer } from "mobx-react-lite";
import styles from "../Login.module.scss";
import { Text } from "preact-i18n";
import { useContext, useEffect } from "preact/hooks";
import { useEffect } from "preact/hooks";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { useApplicationState } from "../../../mobx/State";
import Preloader from "../../../components/ui/Preloader";
@ -13,22 +14,22 @@ export interface CaptchaProps {
onCancel: () => void;
}
export function CaptchaBlock(props: CaptchaProps) {
const client = useContext(AppContext);
export const CaptchaBlock = observer((props: CaptchaProps) => {
const configuration = useApplicationState().config.get();
useEffect(() => {
if (!client.configuration?.features.captcha.enabled) {
if (!configuration?.features.captcha.enabled) {
props.onSuccess();
}
}, [client.configuration?.features.captcha.enabled, props]);
}, [configuration?.features.captcha.enabled, props]);
if (!client.configuration?.features.captcha.enabled)
if (!configuration?.features.captcha.enabled)
return <Preloader type="spinner" />;
return (
<div>
<HCaptcha
sitekey={client.configuration.features.captcha.key}
sitekey={configuration.features.captcha.key}
onVerify={(token) => props.onSuccess(token)}
/>
<div className={styles.footer}>
@ -38,4 +39,4 @@ export function CaptchaBlock(props: CaptchaProps) {
</div>
</div>
);
}
});

View file

@ -6,6 +6,8 @@ import styles from "../Login.module.scss";
import { Text } from "preact-i18n";
import { useContext, useState } from "preact/hooks";
import { useApplicationState } from "../../../mobx/State";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { takeError } from "../../../context/revoltjs/util";
@ -44,7 +46,7 @@ interface FormInputs {
}
export function Form({ page, callback }: Props) {
const client = useContext(AppContext);
const configuration = useApplicationState().config.get();
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState<string | undefined>(undefined);
@ -80,10 +82,7 @@ export function Form({ page, callback }: Props) {
}
try {
if (
client.configuration?.features.captcha.enabled &&
page !== "reset"
) {
if (configuration?.features.captcha.enabled && page !== "reset") {
setCaptcha({
onSuccess: async (captcha) => {
setCaptcha(undefined);
@ -111,7 +110,7 @@ export function Form({ page, callback }: Props) {
if (typeof success !== "undefined") {
return (
<div className={styles.success}>
{client.configuration?.features.email ? (
{configuration?.features.email ? (
<>
<Envelope size={72} />
<h2>
@ -172,8 +171,7 @@ export function Form({ page, callback }: Props) {
error={errors.password?.message}
/>
)}
{client.configuration?.features.invite_only &&
page === "create" && (
{configuration?.features.invite_only && page === "create" && (
<FormField
type="invite"
register={register}

View file

@ -1,10 +1,9 @@
import { useContext } from "preact/hooks";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { useApplicationState } from "../../../mobx/State";
import { Form } from "./Form";
export function FormCreate() {
const client = useContext(AppContext);
const config = useApplicationState().config;
const client = config.createClient();
return <Form page="create" callback={(data) => client.register(data)} />;
}

View file

@ -1,15 +1,16 @@
import { detect } from "detect-browser";
import { useHistory } from "react-router-dom";
import { Session } from "revolt-api/types/Auth";
import { Client } from "revolt.js";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../../../mobx/State";
import { OperationsContext } from "../../../context/revoltjs/RevoltClient";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { Form } from "./Form";
export function FormLogin() {
const { login } = useContext(OperationsContext);
const history = useHistory();
const auth = useApplicationState().auth;
const { openScreen } = useIntermediate();
return (
<Form
@ -34,8 +35,40 @@ export function FormLogin() {
friendly_name = "Unknown Device";
}
await login({ ...data, friendly_name });
history.push("/");
// ! FIXME: temporary login flow code
// This should be replaced in the future.
const client = new Client();
await client.fetchConfiguration();
const session = (await client.req(
"POST",
"/auth/session/login",
{ ...data, friendly_name },
)) as unknown as Session;
client.session = session;
(client as any).Axios.defaults.headers = {
"x-session-token": session?.token,
};
function login() {
auth.setSession(session);
}
const { onboarding } = await client.req(
"GET",
"/onboard/hello",
);
if (onboarding) {
openScreen({
id: "onboarding",
callback: async (username: string) =>
client
.completeOnboarding({ username }, false)
.then(login),
});
} else {
login();
}
}}
/>
);

View file

@ -2,12 +2,15 @@ import { useHistory, useParams } from "react-router-dom";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../../../mobx/State";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { Form } from "./Form";
export function FormSendReset() {
const client = useContext(AppContext);
const config = useApplicationState().config;
const client = config.createClient();
return (
<Form

View file

@ -2,6 +2,8 @@ import { useHistory, useParams } from "react-router-dom";
import { useContext, useEffect, useState } from "preact/hooks";
import { useApplicationState } from "../../../mobx/State";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import { takeError } from "../../../context/revoltjs/util";
@ -11,7 +13,8 @@ import Preloader from "../../../components/ui/Preloader";
import { Form } from "./Form";
export function FormResend() {
const client = useContext(AppContext);
const config = useApplicationState().config;
const client = config.createClient();
return (
<Form

View file

@ -32,7 +32,6 @@ function mapMailProvider(email?: string): [string, string] | undefined {
case "outlook.com.br":
case "outlook.cl":
case "outlook.cz":
case "outlook.dk":
case "outlook.com.gr":
case "outlook.co.il":
case "outlook.in":

View file

@ -29,10 +29,7 @@ import { useContext } from "preact/hooks";
import { useApplicationState } from "../../mobx/State";
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
import {
AppContext,
OperationsContext,
} from "../../context/revoltjs/RevoltClient";
import { AppContext, LogOutContext } from "../../context/revoltjs/RevoltClient";
import LineDivider from "../../components/ui/LineDivider";
@ -57,7 +54,7 @@ import { ThemeShop } from "./panes/ThemeShop";
export default observer(() => {
const history = useHistory();
const client = useContext(AppContext);
const operations = useContext(OperationsContext);
const logout = useContext(LogOutContext);
const experiments = useApplicationState().experiments;
function switchPage(to?: string) {
@ -220,7 +217,7 @@ export default observer(() => {
</a>
<LineDivider />
<ButtonItem
onClick={() => operations.logout()}
onClick={logout}
className={styles.logOut}
compact>
<LogOut size={20} />

View file

@ -3765,10 +3765,10 @@ revolt-api@^0.5.3-alpha.9:
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
revolt.js@^5.1.0-alpha.10:
version "5.1.0-alpha.10"
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.1.0-alpha.10.tgz#e393ac8524e629d3359135651b23b044c0cc9b7b"
integrity sha512-wEmBMJkZE/oWy6mzVZg1qw5QC9CE+Gb7sTFlJl+C4pbXfTJWAtY311Tjbd2tX8w3ohYDmN338bVfCW4cOQ8GXQ==
revolt.js@5.1.0-alpha.15:
version "5.1.0-alpha.15"
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.1.0-alpha.15.tgz#a2be1f29de93f1ec18f0e502ecb65ade55c0070d"
integrity sha512-1gGcGDv1+J5NlmnX099XafKugCebACg9ke0NA754I4hLTNMMwkZyphyvYWWWkI394qn2mA3NG7WgEmrIoZUtgw==
dependencies:
axios "^0.21.4"
eventemitter3 "^4.0.7"