2021-06-18 17:47:25 -04:00
|
|
|
import { openDB } from 'idb';
|
2021-06-18 14:25:33 -04:00
|
|
|
import { Client } from "revolt.js";
|
2021-06-19 13:46:05 -04:00
|
|
|
import { takeError } from "./util";
|
2021-06-18 15:07:26 -04:00
|
|
|
import { createContext } from "preact";
|
|
|
|
import { Children } from "../../types/Preact";
|
|
|
|
import { Route } from "revolt.js/dist/api/routes";
|
2021-06-19 13:46:05 -04:00
|
|
|
import { useEffect, useMemo, useState } from "preact/hooks";
|
2021-06-18 15:07:26 -04:00
|
|
|
import { connectState } from "../../redux/connector";
|
2021-06-18 17:47:25 -04:00
|
|
|
import Preloader from "../../components/ui/Preloader";
|
2021-06-18 15:07:26 -04:00
|
|
|
import { WithDispatcher } from "../../redux/reducers";
|
|
|
|
import { AuthState } from "../../redux/reducers/auth";
|
|
|
|
import { SyncOptions } from "../../redux/reducers/sync";
|
2021-06-18 17:47:25 -04:00
|
|
|
import { registerEvents, setReconnectDisallowed } from "./events";
|
2021-06-18 12:57:08 -04:00
|
|
|
|
|
|
|
export enum ClientStatus {
|
2021-06-18 17:47:25 -04:00
|
|
|
INIT,
|
2021-06-18 12:57:08 -04:00
|
|
|
LOADING,
|
|
|
|
READY,
|
|
|
|
OFFLINE,
|
|
|
|
DISCONNECTED,
|
|
|
|
CONNECTING,
|
|
|
|
RECONNECTING,
|
2021-06-18 14:25:33 -04:00
|
|
|
ONLINE,
|
2021-06-18 12:57:08 -04:00
|
|
|
}
|
|
|
|
|
2021-06-18 15:07:26 -04:00
|
|
|
export interface ClientOperations {
|
|
|
|
login: (data: Route<"POST", "/auth/login">["data"]) => Promise<void>;
|
|
|
|
logout: (shouldRequest?: boolean) => Promise<void>;
|
|
|
|
loggedIn: () => boolean;
|
|
|
|
ready: () => boolean;
|
|
|
|
}
|
|
|
|
|
2021-06-19 13:46:05 -04:00
|
|
|
export const AppContext = createContext<Client>(undefined as any);
|
|
|
|
export const StatusContext = createContext<ClientStatus>(undefined as any);
|
|
|
|
export const OperationsContext = createContext<ClientOperations>(undefined as any);
|
2021-06-18 15:07:26 -04:00
|
|
|
|
|
|
|
type Props = WithDispatcher & {
|
|
|
|
auth: AuthState;
|
|
|
|
sync: SyncOptions;
|
|
|
|
children: Children;
|
|
|
|
};
|
|
|
|
|
|
|
|
function Context({ auth, sync, children, dispatcher }: Props) {
|
2021-06-18 17:47:25 -04:00
|
|
|
const [status, setStatus] = useState(ClientStatus.INIT);
|
|
|
|
const [client, setClient] = useState<Client>(undefined as unknown as Client);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
(async () => {
|
|
|
|
let db;
|
|
|
|
try {
|
|
|
|
db = await openDB('state', 3, {
|
|
|
|
upgrade(db) {
|
|
|
|
for (let store of [ "channels", "servers", "users", "members" ]) {
|
|
|
|
db.createObjectStore(store, {
|
|
|
|
keyPath: '_id'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
console.error('Failed to open IndexedDB store, continuing without.');
|
|
|
|
}
|
|
|
|
|
|
|
|
setClient(new Client({
|
|
|
|
autoReconnect: false,
|
|
|
|
apiURL: import.meta.env.VITE_API_URL,
|
|
|
|
debug: import.meta.env.DEV,
|
|
|
|
db
|
|
|
|
}));
|
|
|
|
|
|
|
|
setStatus(ClientStatus.LOADING);
|
|
|
|
})();
|
|
|
|
}, [ ]);
|
|
|
|
|
|
|
|
if (status === ClientStatus.INIT) return null;
|
2021-06-18 15:07:26 -04:00
|
|
|
|
2021-06-19 13:46:05 -04:00
|
|
|
const operations: ClientOperations = useMemo(() => {
|
|
|
|
return {
|
2021-06-18 17:47:25 -04:00
|
|
|
login: async data => {
|
|
|
|
setReconnectDisallowed(true);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const onboarding = await client.login(data);
|
|
|
|
setReconnectDisallowed(false);
|
|
|
|
const login = () =>
|
|
|
|
dispatcher({
|
|
|
|
type: "LOGIN",
|
|
|
|
session: client.session as any
|
|
|
|
});
|
|
|
|
|
|
|
|
if (onboarding) {
|
|
|
|
/*openScreen({
|
|
|
|
id: "onboarding",
|
|
|
|
callback: async (username: string) => {
|
|
|
|
await (onboarding as any)(username, true);
|
|
|
|
login();
|
|
|
|
}
|
|
|
|
});*/
|
|
|
|
} else {
|
|
|
|
login();
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
setReconnectDisallowed(false);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
logout: async shouldRequest => {
|
|
|
|
dispatcher({ type: "LOGOUT" });
|
|
|
|
|
|
|
|
delete client.user;
|
|
|
|
dispatcher({ 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: () => (
|
2021-06-19 13:46:05 -04:00
|
|
|
operations.loggedIn() &&
|
2021-06-18 17:47:25 -04:00
|
|
|
typeof client.user !== "undefined"
|
|
|
|
)
|
2021-06-18 15:07:26 -04:00
|
|
|
}
|
2021-06-19 13:46:05 -04:00
|
|
|
}, [ client, auth.active ]);
|
2021-06-18 15:07:26 -04:00
|
|
|
|
2021-06-18 17:47:25 -04:00
|
|
|
useEffect(
|
2021-06-19 13:46:05 -04:00
|
|
|
() => registerEvents({ operations, dispatcher }, setStatus, client),
|
2021-06-18 17:47:25 -04:00
|
|
|
[ client ]
|
|
|
|
);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
(async () => {
|
|
|
|
await client.restore();
|
|
|
|
|
|
|
|
if (auth.active) {
|
|
|
|
dispatcher({ type: "QUEUE_FAIL_ALL" });
|
|
|
|
|
|
|
|
const active = auth.accounts[auth.active];
|
|
|
|
client.user = client.users.get(active.session.user_id);
|
|
|
|
if (!navigator.onLine) {
|
|
|
|
return setStatus(ClientStatus.OFFLINE);
|
|
|
|
}
|
|
|
|
|
2021-06-19 13:46:05 -04:00
|
|
|
if (operations.ready())
|
2021-06-18 17:47:25 -04:00
|
|
|
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 });*/
|
|
|
|
//} else {
|
|
|
|
/*
|
|
|
|
// ! FIXME: all this code needs to be re-written
|
|
|
|
(async () => {
|
|
|
|
// ! FIXME: should be included in Ready payload
|
|
|
|
props.dispatcher({
|
|
|
|
type: 'SYNC_UPDATE',
|
|
|
|
// ! FIXME: write a procedure to resolve merge conflicts
|
|
|
|
update: mapSync(
|
|
|
|
await client.syncFetchSettings(DEFAULT_ENABLED_SYNC.filter(x => !props.sync?.disabled?.includes(x)))
|
|
|
|
)
|
|
|
|
});
|
|
|
|
})()
|
|
|
|
|
|
|
|
props.dispatcher({ type: 'UNREADS_SET', unreads: await client.syncFetchUnreads() });*/
|
|
|
|
//}
|
|
|
|
} catch (err) {
|
|
|
|
setStatus(ClientStatus.DISCONNECTED);
|
|
|
|
const error = takeError(err);
|
|
|
|
if (error === "Forbidden") {
|
2021-06-19 13:46:05 -04:00
|
|
|
operations.logout(true);
|
2021-06-18 17:47:25 -04:00
|
|
|
// openScreen({ id: "signed_out" });
|
|
|
|
} else {
|
|
|
|
// openScreen({ id: "error", error });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2021-06-19 07:34:53 -04:00
|
|
|
try {
|
|
|
|
await client.fetchConfiguration()
|
|
|
|
} catch (err) {
|
|
|
|
console.error("Failed to connect to API server.");
|
|
|
|
}
|
|
|
|
|
2021-06-18 17:47:25 -04:00
|
|
|
setStatus(ClientStatus.READY);
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
if (status === ClientStatus.LOADING) {
|
|
|
|
return <Preloader />;
|
|
|
|
}
|
|
|
|
|
2021-06-18 15:07:26 -04:00
|
|
|
return (
|
2021-06-19 13:46:05 -04:00
|
|
|
<AppContext.Provider value={client}>
|
|
|
|
<StatusContext.Provider value={status}>
|
|
|
|
<OperationsContext.Provider value={operations}>
|
|
|
|
{ children }
|
|
|
|
</OperationsContext.Provider>
|
|
|
|
</StatusContext.Provider>
|
2021-06-18 15:07:26 -04:00
|
|
|
</AppContext.Provider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default connectState<{ children: Children }>(
|
|
|
|
Context,
|
|
|
|
state => {
|
|
|
|
return {
|
|
|
|
auth: state.auth,
|
|
|
|
sync: state.sync
|
|
|
|
};
|
|
|
|
},
|
|
|
|
true
|
|
|
|
);
|