Fix session replica behaviour (merge with session)

The harder-to-refactor parts are the places where exists/username/token
are called within a React component. However, `resetAndRedirect` and
`store` are already called from async contexts, so adding an `await`
is simple.

This thus merges the logic, keeping localStorage for the components to
call, but making sure reset/store behaviour works correctly for the
replica.
pull/751/head
nimbleghost 2023-06-13 14:00:51 +02:00
parent 4e44b034bd
commit 8ccfa5c3fb
15 changed files with 43 additions and 78 deletions

View File

@ -367,7 +367,7 @@ class AccountApi {
} catch (e) { } catch (e) {
console.log(`[AccountApi] Error fetching account`, e); console.log(`[AccountApi] Error fetching account`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
return undefined; return undefined;
} }

View File

@ -1,29 +1,36 @@
import sessionReplica from "./SessionReplica"; import Dexie from "dexie";
/** /**
* Manages the logged-in user's session and access token. * Manages the logged-in user's session and access token.
* The session replica is stored in IndexedDB so that the service worker can access it. * The session replica is stored in IndexedDB so that the service worker can access it.
*/ */
class Session { class Session {
constructor(replica) { constructor() {
this.replica = replica; const db = new Dexie("session-replica");
db.version(1).stores({
kv: "&key",
});
this.db = db;
} }
store(username, token) { async store(username, token) {
await this.db.kv.bulkPut([
{ key: "user", value: username },
{ key: "token", value: token },
]);
localStorage.setItem("user", username); localStorage.setItem("user", username);
localStorage.setItem("token", token); localStorage.setItem("token", token);
this.replica.store(username, token);
} }
reset() { async resetAndRedirect(url) {
await this.db.delete();
localStorage.removeItem("user"); localStorage.removeItem("user");
localStorage.removeItem("token"); localStorage.removeItem("token");
this.replica.reset(); window.location.href = url;
} }
resetAndRedirect(url) { async usernameAsync() {
this.reset(); return (await this.db.kv.get({ key: "user" }))?.value;
window.location.href = url;
} }
exists() { exists() {
@ -39,5 +46,5 @@ class Session {
} }
} }
const session = new Session(sessionReplica); const session = new Session();
export default session; export default session;

View File

@ -1,41 +0,0 @@
import Dexie from "dexie";
/**
* Replica of the session in IndexedDB. This is used by the service
* worker to access the session. This is a bit of a hack.
*/
class SessionReplica {
constructor() {
const db = new Dexie("session-replica");
db.version(1).stores({
kv: "&key",
});
this.db = db;
}
async store(username, token) {
try {
await this.db.kv.bulkPut([
{ key: "user", value: username },
{ key: "token", value: token },
]);
} catch (e) {
console.error("[Session] Error replicating session to IndexedDB", e);
}
}
async reset() {
try {
await this.db.delete();
} catch (e) {
console.error("[Session] Error resetting session on IndexedDB", e);
}
}
async username() {
return (await this.db.kv.get({ key: "user" }))?.value;
}
}
const sessionReplica = new SessionReplica();
export default sessionReplica;

View File

@ -1,6 +1,5 @@
import Dexie from "dexie"; import Dexie from "dexie";
import session from "./Session"; import session from "./Session";
import sessionReplica from "./SessionReplica";
// Uses Dexie.js // Uses Dexie.js
// https://dexie.org/docs/API-Reference#quick-reference // https://dexie.org/docs/API-Reference#quick-reference
@ -23,7 +22,7 @@ const createDatabase = (username) => {
}; };
export const dbAsync = async () => { export const dbAsync = async () => {
const username = await sessionReplica.username(); const username = await session.usernameAsync();
return createDatabase(username); return createDatabase(username);
}; };

View File

@ -164,7 +164,7 @@ const ChangePasswordDialog = (props) => {
if (e instanceof IncorrectPasswordError) { if (e instanceof IncorrectPasswordError) {
setError(t("account_basics_password_dialog_current_password_incorrect")); setError(t("account_basics_password_dialog_current_password_incorrect"));
} else if (e instanceof UnauthorizedError) { } else if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }
@ -245,7 +245,7 @@ const AccountType = () => {
} catch (e) { } catch (e) {
console.log(`[Account] Error opening billing portal`, e); console.log(`[Account] Error opening billing portal`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setShowPortalError(true); setShowPortalError(true);
} }
@ -371,7 +371,7 @@ const PhoneNumbers = () => {
} catch (e) { } catch (e) {
console.log(`[Account] Error deleting phone number`, e); console.log(`[Account] Error deleting phone number`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
} }
}; };
@ -447,7 +447,7 @@ const AddPhoneNumberDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[Account] Error sending verification`, e); console.log(`[Account] Error sending verification`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }
@ -464,7 +464,7 @@ const AddPhoneNumberDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[Account] Error confirming verification`, e); console.log(`[Account] Error confirming verification`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }
@ -946,7 +946,7 @@ const TokenDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[Account] Error creating token`, e); console.log(`[Account] Error creating token`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }
@ -1003,7 +1003,7 @@ const TokenDeleteDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[Account] Error deleting token`, e); console.log(`[Account] Error deleting token`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }
@ -1080,13 +1080,13 @@ const DeleteAccountDialog = (props) => {
await accountApi.delete(password); await accountApi.delete(password);
await db().delete(); await db().delete();
console.debug(`[Account] Account deleted`); console.debug(`[Account] Account deleted`);
session.resetAndRedirect(routes.app); await session.resetAndRedirect(routes.app);
} catch (e) { } catch (e) {
console.log(`[Account] Error deleting account`, e); console.log(`[Account] Error deleting account`, e);
if (e instanceof IncorrectPasswordError) { if (e instanceof IncorrectPasswordError) {
setError(t("account_basics_password_dialog_current_password_incorrect")); setError(t("account_basics_password_dialog_current_password_incorrect"));
} else if (e instanceof UnauthorizedError) { } else if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }

View File

@ -123,7 +123,7 @@ const ProfileIcon = () => {
await accountApi.logout(); await accountApi.logout();
await db().delete(); await db().delete();
} finally { } finally {
session.resetAndRedirect(routes.app); await session.resetAndRedirect(routes.app);
} }
}; };

View File

@ -24,7 +24,7 @@ const Login = () => {
try { try {
const token = await accountApi.login(user); const token = await accountApi.login(user);
console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`); console.log(`[Login] User auth for user ${user.username} successful, token is ${token}`);
session.store(user.username, token); await session.store(user.username, token);
window.location.href = routes.app; window.location.href = routes.app;
} catch (e) { } catch (e) {
console.log(`[Login] User auth for user ${user.username} failed`, e); console.log(`[Login] User auth for user ${user.username} failed`, e);

View File

@ -59,7 +59,7 @@ const maybeUpdateAccountSettings = async (payload) => {
} catch (e) { } catch (e) {
console.log(`[Preferences] Error updating account settings`, e); console.log(`[Preferences] Error updating account settings`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
} }
}; };

View File

@ -211,7 +211,7 @@ const PublishDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[PublishDialog] Retrieving attachment limits failed`, e); console.log(`[PublishDialog] Retrieving attachment limits failed`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setAttachFileError(""); // Reset error (rely on server-side checking) setAttachFileError(""); // Reset error (rely on server-side checking)
} }

View File

@ -43,7 +43,7 @@ export const ReserveAddDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[ReserveAddDialog] Error adding topic reservation.`, e); console.log(`[ReserveAddDialog] Error adding topic reservation.`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else if (e instanceof TopicReservedError) { } else if (e instanceof TopicReservedError) {
setError(t("subscribe_dialog_error_topic_already_reserved")); setError(t("subscribe_dialog_error_topic_already_reserved"));
return; return;
@ -99,7 +99,7 @@ export const ReserveEditDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[ReserveEditDialog] Error updating topic reservation.`, e); console.log(`[ReserveEditDialog] Error updating topic reservation.`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
return; return;
@ -136,7 +136,7 @@ export const ReserveDeleteDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[ReserveDeleteDialog] Error deleting topic reservation.`, e); console.log(`[ReserveDeleteDialog] Error deleting topic reservation.`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
return; return;

View File

@ -27,7 +27,7 @@ const Signup = () => {
await accountApi.create(user.username, user.password); await accountApi.create(user.username, user.password);
const token = await accountApi.login(user); const token = await accountApi.login(user);
console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`); console.log(`[Signup] User signup for user ${user.username} successful, token is ${token}`);
session.store(user.username, token); await session.store(user.username, token);
window.location.href = routes.app; window.location.href = routes.app;
} catch (e) { } catch (e) {
console.log(`[Signup] Signup for user ${user.username} failed`, e); console.log(`[Signup] Signup for user ${user.username} failed`, e);

View File

@ -39,7 +39,7 @@ export const subscribeTopic = async (baseUrl, topic, opts) => {
} catch (e) { } catch (e) {
console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e); console.log(`[SubscribeDialog] Subscribing to topic ${topic} failed`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
} }
} }
@ -124,7 +124,7 @@ const SubscribePage = (props) => {
} catch (e) { } catch (e) {
console.log(`[SubscribeDialog] Error reserving topic`, e); console.log(`[SubscribeDialog] Error reserving topic`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else if (e instanceof TopicReservedError) { } else if (e instanceof TopicReservedError) {
setError(t("subscribe_dialog_error_topic_already_reserved")); setError(t("subscribe_dialog_error_topic_already_reserved"));
return; return;

View File

@ -155,7 +155,7 @@ export const SubscriptionPopup = (props) => {
} catch (e) { } catch (e) {
console.log(`[SubscriptionPopup] Error unsubscribing`, e); console.log(`[SubscriptionPopup] Error unsubscribing`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
} }
} }
@ -298,7 +298,7 @@ const DisplayNameDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[SubscriptionSettingsDialog] Error updating subscription`, e); console.log(`[SubscriptionSettingsDialog] Error updating subscription`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
return; return;

View File

@ -140,7 +140,7 @@ const UpgradeDialog = (props) => {
} catch (e) { } catch (e) {
console.log(`[UpgradeDialog] Error changing billing subscription`, e); console.log(`[UpgradeDialog] Error changing billing subscription`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} else { } else {
setError(e.message); setError(e.message);
} }

View File

@ -114,7 +114,7 @@ export const useAutoSubscribe = (subscriptions, selected) => {
} catch (e) { } catch (e) {
console.log(`[Hooks] Auto-subscribing failed`, e); console.log(`[Hooks] Auto-subscribing failed`, e);
if (e instanceof UnauthorizedError) { if (e instanceof UnauthorizedError) {
session.resetAndRedirect(routes.login); await session.resetAndRedirect(routes.login);
} }
} }
} }