Automatic account sync with react
parent
d666cab77a
commit
bb583eaa72
|
@ -1,22 +1,20 @@
|
||||||
import {
|
import {
|
||||||
|
accountAccessSingleUrl,
|
||||||
|
accountAccessUrl,
|
||||||
accountPasswordUrl,
|
accountPasswordUrl,
|
||||||
accountSettingsUrl,
|
accountSettingsUrl,
|
||||||
accountSubscriptionSingleUrl,
|
accountSubscriptionSingleUrl,
|
||||||
accountSubscriptionUrl,
|
accountSubscriptionUrl,
|
||||||
accountTokenUrl,
|
accountTokenUrl,
|
||||||
accountUrl,
|
accountUrl,
|
||||||
fetchLinesIterator,
|
|
||||||
withBasicAuth,
|
withBasicAuth,
|
||||||
withBearerAuth,
|
withBearerAuth
|
||||||
topicShortUrl,
|
|
||||||
topicUrl,
|
|
||||||
topicUrlAuth,
|
|
||||||
topicUrlJsonPoll,
|
|
||||||
topicUrlJsonPollWithSince, accountAccessUrl, accountAccessSingleUrl
|
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import userManager from "./UserManager";
|
|
||||||
import session from "./Session";
|
import session from "./Session";
|
||||||
import subscriptionManager from "./SubscriptionManager";
|
import subscriptionManager from "./SubscriptionManager";
|
||||||
|
import i18n from "i18next";
|
||||||
|
import prefs from "./Prefs";
|
||||||
|
import routes from "../components/routes";
|
||||||
|
|
||||||
const delayMillis = 45000; // 45 seconds
|
const delayMillis = 45000; // 45 seconds
|
||||||
const intervalMillis = 900000; // 15 minutes
|
const intervalMillis = 900000; // 15 minutes
|
||||||
|
@ -24,6 +22,15 @@ const intervalMillis = 900000; // 15 minutes
|
||||||
class AccountApi {
|
class AccountApi {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.timer = null;
|
this.timer = null;
|
||||||
|
this.listener = null; // Fired when account is fetched from remote
|
||||||
|
}
|
||||||
|
|
||||||
|
registerListener(listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetListener() {
|
||||||
|
this.listener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(user) {
|
async login(user) {
|
||||||
|
@ -92,6 +99,9 @@ class AccountApi {
|
||||||
}
|
}
|
||||||
const account = await response.json();
|
const account = await response.json();
|
||||||
console.log(`[AccountApi] Account`, account);
|
console.log(`[AccountApi] Account`, account);
|
||||||
|
if (this.listener) {
|
||||||
|
this.listener(account);
|
||||||
|
}
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,8 +250,37 @@ class AccountApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sync() {
|
async sync() {
|
||||||
// TODO
|
try {
|
||||||
|
if (!session.token()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log(`[AccountApi] Syncing account`);
|
||||||
|
const remoteAccount = await this.get();
|
||||||
|
if (remoteAccount.language) {
|
||||||
|
await i18n.changeLanguage(remoteAccount.language);
|
||||||
|
}
|
||||||
|
if (remoteAccount.notification) {
|
||||||
|
if (remoteAccount.notification.sound) {
|
||||||
|
await prefs.setSound(remoteAccount.notification.sound);
|
||||||
|
}
|
||||||
|
if (remoteAccount.notification.delete_after) {
|
||||||
|
await prefs.setDeleteAfter(remoteAccount.notification.delete_after);
|
||||||
|
}
|
||||||
|
if (remoteAccount.notification.min_priority) {
|
||||||
|
await prefs.setMinPriority(remoteAccount.notification.min_priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (remoteAccount.subscriptions) {
|
||||||
|
await subscriptionManager.syncFromRemote(remoteAccount.subscriptions);
|
||||||
|
}
|
||||||
|
return remoteAccount;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`[AccountApi] Error fetching account`, e);
|
||||||
|
if ((e instanceof UnauthorizedError)) {
|
||||||
|
session.resetAndRedirect(routes.login);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startWorker() {
|
startWorker() {
|
||||||
|
|
|
@ -17,21 +17,17 @@ import {BrowserRouter, Outlet, Route, Routes, useOutletContext, useParams} from
|
||||||
import {expandUrl} from "../app/utils";
|
import {expandUrl} from "../app/utils";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
import routes from "./routes";
|
import routes from "./routes";
|
||||||
import {useAutoSubscribe, useBackgroundProcesses, useConnectionListeners} from "./hooks";
|
import {useAccountListener, useAutoSubscribe, useBackgroundProcesses, useConnectionListeners} from "./hooks";
|
||||||
import PublishDialog from "./PublishDialog";
|
import PublishDialog from "./PublishDialog";
|
||||||
import Messaging from "./Messaging";
|
import Messaging from "./Messaging";
|
||||||
import "./i18n"; // Translations!
|
import "./i18n"; // Translations!
|
||||||
import {Backdrop, CircularProgress} from "@mui/material";
|
import {Backdrop, CircularProgress} from "@mui/material";
|
||||||
import Home from "./Home";
|
import Home from "./Home";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import i18n from "i18next";
|
|
||||||
import prefs from "../app/Prefs";
|
|
||||||
import session from "../app/Session";
|
|
||||||
import Pricing from "./Pricing";
|
import Pricing from "./Pricing";
|
||||||
import Signup from "./Signup";
|
import Signup from "./Signup";
|
||||||
import Account from "./Account";
|
import Account from "./Account";
|
||||||
import ResetPassword from "./ResetPassword";
|
import ResetPassword from "./ResetPassword";
|
||||||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -87,43 +83,10 @@ const Layout = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
useConnectionListeners(subscriptions, users);
|
useConnectionListeners(subscriptions, users);
|
||||||
|
useAccountListener(setAccount)
|
||||||
useBackgroundProcesses();
|
useBackgroundProcesses();
|
||||||
useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]);
|
useEffect(() => updateTitle(newNotificationsCount), [newNotificationsCount]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
// TODO this should not live here
|
|
||||||
try {
|
|
||||||
if (!session.token()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const remoteAccount = await accountApi.get();
|
|
||||||
setAccount(remoteAccount);
|
|
||||||
if (remoteAccount.language) {
|
|
||||||
await i18n.changeLanguage(remoteAccount.language);
|
|
||||||
}
|
|
||||||
if (remoteAccount.notification) {
|
|
||||||
if (remoteAccount.notification.sound) {
|
|
||||||
await prefs.setSound(remoteAccount.notification.sound);
|
|
||||||
}
|
|
||||||
if (remoteAccount.notification.delete_after) {
|
|
||||||
await prefs.setDeleteAfter(remoteAccount.notification.delete_after);
|
|
||||||
}
|
|
||||||
if (remoteAccount.notification.min_priority) {
|
|
||||||
await prefs.setMinPriority(remoteAccount.notification.min_priority);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (remoteAccount.subscriptions) {
|
|
||||||
await subscriptionManager.syncFromRemote(remoteAccount.subscriptions);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`[App] Error fetching account`, e);
|
|
||||||
if ((e instanceof UnauthorizedError)) {
|
|
||||||
session.resetAndRedirect(routes.login);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{display: 'flex'}}>
|
<Box sx={{display: 'flex'}}>
|
||||||
<CssBaseline/>
|
<CssBaseline/>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import config from "../app/config";
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
import {Trans, useTranslation} from "react-i18next";
|
import {Trans, useTranslation} from "react-i18next";
|
||||||
import session from "../app/Session";
|
import session from "../app/Session";
|
||||||
|
import accountApi from "../app/AccountApi";
|
||||||
|
|
||||||
const navWidth = 280;
|
const navWidth = 280;
|
||||||
|
|
||||||
|
@ -92,6 +93,11 @@ const NavList = (props) => {
|
||||||
notifier.maybeRequestPermission(granted => props.onNotificationGranted(granted))
|
notifier.maybeRequestPermission(granted => props.onNotificationGranted(granted))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAccountClick = () => {
|
||||||
|
accountApi.sync(); // Dangle!
|
||||||
|
navigate(routes.account);
|
||||||
|
};
|
||||||
|
|
||||||
const showSubscriptionsList = props.subscriptions?.length > 0;
|
const showSubscriptionsList = props.subscriptions?.length > 0;
|
||||||
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
|
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
|
||||||
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
||||||
|
@ -124,7 +130,7 @@ const NavList = (props) => {
|
||||||
<Divider sx={{my: 1}}/>
|
<Divider sx={{my: 1}}/>
|
||||||
</>}
|
</>}
|
||||||
{session.exists() &&
|
{session.exists() &&
|
||||||
<ListItemButton onClick={() => navigate(routes.account)} selected={location.pathname === routes.account}>
|
<ListItemButton onClick={handleAccountClick} selected={location.pathname === routes.account}>
|
||||||
<ListItemIcon><Person/></ListItemIcon>
|
<ListItemIcon><Person/></ListItemIcon>
|
||||||
<ListItemText primary={t("nav_button_account")}/>
|
<ListItemText primary={t("nav_button_account")}/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
CardContent,
|
CardContent,
|
||||||
FormControl,
|
FormControl,
|
||||||
Select,
|
Select,
|
||||||
Stack, styled,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
|
@ -482,6 +482,11 @@ const Reservations = () => {
|
||||||
const [dialogKey, setDialogKey] = useState(0);
|
const [dialogKey, setDialogKey] = useState(0);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
if (!session.exists() || !account) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
const reservations = account.reservations || [];
|
||||||
|
|
||||||
const handleAddClick = () => {
|
const handleAddClick = () => {
|
||||||
setDialogKey(prev => prev+1);
|
setDialogKey(prev => prev+1);
|
||||||
setDialogOpen(true);
|
setDialogOpen(true);
|
||||||
|
@ -495,6 +500,7 @@ const Reservations = () => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
try {
|
try {
|
||||||
await accountApi.upsertAccess(reservation.topic, reservation.everyone);
|
await accountApi.upsertAccess(reservation.topic, reservation.everyone);
|
||||||
|
await accountApi.sync();
|
||||||
console.debug(`[Preferences] Added topic reservation`, reservation);
|
console.debug(`[Preferences] Added topic reservation`, reservation);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Preferences] Error topic reservation.`, e);
|
console.log(`[Preferences] Error topic reservation.`, e);
|
||||||
|
@ -502,10 +508,6 @@ const Reservations = () => {
|
||||||
// FIXME handle 401/403
|
// FIXME handle 401/403
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!session.exists() || !account) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ padding: 1 }} aria-label={t("prefs_reservations_title")}>
|
<Card sx={{ padding: 1 }} aria-label={t("prefs_reservations_title")}>
|
||||||
<CardContent sx={{ paddingBottom: 1 }}>
|
<CardContent sx={{ paddingBottom: 1 }}>
|
||||||
|
@ -515,7 +517,7 @@ const Reservations = () => {
|
||||||
<Paragraph>
|
<Paragraph>
|
||||||
{t("prefs_reservations_description")}
|
{t("prefs_reservations_description")}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
{account.reservations.length > 0 && <ReservationsTable reservations={account.reservations}/>}
|
{reservations.length > 0 && <ReservationsTable reservations={reservations}/>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<Button onClick={handleAddClick}>{t("prefs_reservations_add_button")}</Button>
|
<Button onClick={handleAddClick}>{t("prefs_reservations_add_button")}</Button>
|
||||||
|
@ -523,7 +525,7 @@ const Reservations = () => {
|
||||||
key={`reservationAddDialog${dialogKey}`}
|
key={`reservationAddDialog${dialogKey}`}
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
reservation={null}
|
reservation={null}
|
||||||
reservations={account.reservations}
|
reservations={reservations}
|
||||||
onCancel={handleDialogCancel}
|
onCancel={handleDialogCancel}
|
||||||
onSubmit={handleDialogSubmit}
|
onSubmit={handleDialogSubmit}
|
||||||
/>
|
/>
|
||||||
|
@ -552,6 +554,7 @@ const ReservationsTable = (props) => {
|
||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
try {
|
try {
|
||||||
await accountApi.upsertAccess(reservation.topic, reservation.everyone);
|
await accountApi.upsertAccess(reservation.topic, reservation.everyone);
|
||||||
|
await accountApi.sync();
|
||||||
console.debug(`[Preferences] Added topic reservation`, reservation);
|
console.debug(`[Preferences] Added topic reservation`, reservation);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Preferences] Error topic reservation.`, e);
|
console.log(`[Preferences] Error topic reservation.`, e);
|
||||||
|
@ -562,6 +565,7 @@ const ReservationsTable = (props) => {
|
||||||
const handleDeleteClick = async (reservation) => {
|
const handleDeleteClick = async (reservation) => {
|
||||||
try {
|
try {
|
||||||
await accountApi.deleteAccess(reservation.topic);
|
await accountApi.deleteAccess(reservation.topic);
|
||||||
|
await accountApi.sync();
|
||||||
console.debug(`[Preferences] Deleted topic reservation`, reservation);
|
console.debug(`[Preferences] Deleted topic reservation`, reservation);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`[Preferences] Error topic reservation.`, e);
|
console.log(`[Preferences] Error topic reservation.`, e);
|
||||||
|
|
|
@ -96,3 +96,15 @@ export const useBackgroundProcesses = () => {
|
||||||
accountApi.startWorker();
|
accountApi.startWorker();
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAccountListener = (setAccount) => {
|
||||||
|
useEffect(() => {
|
||||||
|
accountApi.registerListener(setAccount);
|
||||||
|
(async () => {
|
||||||
|
await accountApi.sync();
|
||||||
|
})();
|
||||||
|
return () => {
|
||||||
|
accountApi.registerListener();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue