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.
This commit is contained in:
		
							parent
							
								
									4e44b034bd
								
							
						
					
					
						commit
						8ccfa5c3fb
					
				
					 15 changed files with 43 additions and 78 deletions
				
			
		|  | @ -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; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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; |  | ||||||
|  | @ -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); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -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); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue