From 337157327c8aa055e20603b96410759b4540ac8e Mon Sep 17 00:00:00 2001 From: ae Date: Mon, 21 Apr 2025 13:53:15 +0300 Subject: [PATCH] style: undefined -> null return types & log source prefix --- web/src/lib/client.ts | 123 +++++++++++++++++++----------------------- web/src/lib/const.ts | 2 +- 2 files changed, 57 insertions(+), 68 deletions(-) diff --git a/web/src/lib/client.ts b/web/src/lib/client.ts index c40863c..a0b33b6 100644 --- a/web/src/lib/client.ts +++ b/web/src/lib/client.ts @@ -107,7 +107,7 @@ export const cError: Writable = writable(null) class ApiClient { private viewCookieName: string private baseUrl: string - private lastAtUpdate = new Date(0) + private lastAtUpdate = new Date(0) // Refreshing the page wipes access and CSRF tokens from memory -> Rotation needed private lastCsrfUpdate = new Date(0) private refreshInProgress = false private activeVersion = -1 @@ -123,7 +123,7 @@ class ApiClient { private async handleRequest( fn: () => Promise, options: { useBearerAuth: boolean } - ): Promise { + ): Promise { isPending.set(true) cError.set(null) @@ -137,18 +137,21 @@ class ApiClient { try { await this.checkAndRefreshAccessToken() } catch (err) { - console.log("refresh attempt not successful") + console.log("[REQ] Refresh attempt not successful") await this.handleLocalLogout() throw new Error("Session expired, please authenticate again.") } } + return await fn() } catch (err) { cError.set(err instanceof Error ? err.message : "Unknown error") - console.log(`error: ${get(cError)}`) + console.log(`[ERR] ${get(cError)}`) } finally { isPending.set(false) } + + return null } // Should be attached to routes that handle authentication with the bearer token (access token) @@ -161,12 +164,13 @@ class ApiClient { // This should never happen due to the token expiration checks we make client-side, // but it's still good to have as a fallback try { - console.log("unexpected 401 caught, attempting refresh") + console.log("[RES] Unexpected 401 caught, attempting to refresh...") await this.checkAndRefreshAccessToken() } catch (err) { - console.log("refresh attempt not successful") + const errMsg = err instanceof Error ? err.message : "Unknown error" + console.log(`[ERR] ${errMsg}`) await this.handleLocalLogout() - throw new Error("Session expired, please authenticate again.") + throw new Error(errMsg) } } @@ -192,20 +196,16 @@ class ApiClient { const timeSinceUpdate = Date.now() - this.lastAtUpdate.getTime() const needsRefresh = timeSinceUpdate > AT_EXP_MS - REFRESH_BUF - console.log(`timeSinceUpdate: ${timeSinceUpdate}`) - if (needsRefresh) { - console.log("running token refresh attempt") + console.log(`[AUTH] Running token refresh attempt (timeSinceUpdate=${timeSinceUpdate})`) this.refreshInProgress = true await this.refreshAccessToken() this.refreshInProgress = false - } else { - console.log("no need to rotate tokens") } } - private async refreshAccessToken(): Promise { + private async refreshAccessToken(): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/cookie/refresh`, { @@ -235,7 +235,7 @@ class ApiClient { const needsRefresh = timeSinceUpdate > CSRF_EXP_MS - REFRESH_BUF if (!token || needsRefresh) { - console.log("refreshing csrf token") + console.log("[AUTH] Refreshing CSRF token") await this.refreshCsrfToken() token = get(csrfToken) } @@ -243,7 +243,7 @@ class ApiClient { return { "X-Csrf-Token": token || "" } } - private async refreshCsrfToken(): Promise { + private async refreshCsrfToken(): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/cookie/csrf`, { @@ -263,7 +263,7 @@ class ApiClient { ) } - private async handleLocalLogout(): Promise { + private async handleLocalLogout(): Promise { this.lastAtUpdate = new Date(0) this.lastCsrfUpdate = new Date(0) @@ -286,7 +286,7 @@ class ApiClient { const viewCookie = this.getCookieValue(this.viewCookieName) if (!viewCookie) { - console.log("view cookie not found") + console.log("[AUTH] View cookie not found") return false } @@ -294,14 +294,11 @@ class ApiClient { const expirationTimestamp = parseInt(value, 10) if (isNaN(expirationTimestamp)) { - console.log(`invalid expiration timestamp: ${value}`) + console.log(`[ERR] Invalid view cookie expiration timestamp: ${value}`) return false } - const expirationDate = new Date(expirationTimestamp * 1000) - console.log(`auth cookie expiration: ${expirationDate}`) - - return now < expirationDate + return now < new Date(expirationTimestamp * 1000) } private deleteViewCookie() { @@ -378,12 +375,12 @@ class ApiClient { private joinDeserializedVersion( noteID: string, apiResponse: ApiFullVersionResponse - ): FullNote | undefined { + ): FullNote | null { // Cache lookups are safe here due to this always being called *after* fetching the actual `FullNote` const cachedNote = this.loadedNotesCache.get(noteID) if (!cachedNote) { - return + return null } return { @@ -399,7 +396,7 @@ class ApiClient { } } - public async register(username: string, password: string): Promise { + public async register(username: string, password: string): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/signup`, { @@ -408,20 +405,21 @@ class ApiClient { body: JSON.stringify({ username, password }) }) - // Can't overwrite `username` parameter + // Can't overwrite the function parameter const data = await this.handleResponse<{ id: string username: string }>(response, { useBearerAuth: false }) - console.log(`${data.username} -> ${data.id}`) + console.log(`[USER] Registration of user '${data.username}' successful`) + await goto("/login") }, { useBearerAuth: false } ) } - public async login(username: string, password: string): Promise { + public async login(username: string, password: string): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/login`, { @@ -444,7 +442,7 @@ class ApiClient { ) } - public async logout(): Promise { + public async logout(): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/logout`, { @@ -456,7 +454,7 @@ class ApiClient { }) if (response.status === 204) { - console.log("logout successful") + console.log("[USER] Logout successful") await this.handleLocalLogout() return } @@ -468,7 +466,7 @@ class ApiClient { ) } - public async getCurrentUser(): Promise { + public async getCurrentUser(): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/me`, { @@ -479,15 +477,16 @@ class ApiClient { const data = await this.handleResponse(response, { useBearerAuth: false }) const user = this.deserializeUser(data) - console.log(user) - currentUser.set(user) }, { useBearerAuth: true } ) } - public async updateCurrentUserPassword(oldPassword: string, newPassword: string): Promise { + public async updateCurrentUserPassword( + oldPassword: string, + newPassword: string + ): Promise { return this.handleRequest( async () => { const data = { @@ -502,21 +501,21 @@ class ApiClient { }, body: JSON.stringify(data) }) + const { accessToken: token, user } = await this.handleResponse<{ accessToken: string user: User }>(response, { useBearerAuth: false }) + accessToken.set(token) currentUser.set(user || null) this.lastAtUpdate = new Date() - - console.log(user) }, { useBearerAuth: true } ) } - public async deleteCurrentUser(password: string): Promise { + public async deleteCurrentUser(password: string): Promise { return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/auth/owner`, { @@ -529,7 +528,7 @@ class ApiClient { }) if (response.status === 204) { - console.log("deletion successful") + console.log("[USER] Deletion successful") await this.handleLocalLogout() return } @@ -541,7 +540,7 @@ class ApiClient { ) } - public async adminListAll(): Promise { + public async adminListAll(): Promise { const user = get(currentUser) if (!user || !user.isAdmin) { throw new Error("Admin privileges required.") @@ -559,7 +558,7 @@ class ApiClient { }) const users = await this.handleResponse(response, { useBearerAuth: false }) - console.log(`admin: got ${users.length} user results`) + console.log(`[ADMIN] Got ${users.length} user results`) return users }, @@ -567,7 +566,7 @@ class ApiClient { ) } - public async adminDeleteUser(userID: string): Promise { + public async adminDeleteUser(userID: string): Promise { const user = get(currentUser) if (!user || !user.isAdmin) { throw new Error("Admin privileges required.") @@ -587,7 +586,7 @@ class ApiClient { }) if (response.status === 204) { - console.log("admin: deletion successful") + console.log(`[ADMIN] Deletion of user '${userID}' successful`) return } @@ -597,7 +596,7 @@ class ApiClient { ) } - public async listNotes(): Promise { + public async listNotes(): Promise { return this.handleRequest( async () => { const params = new URLSearchParams() @@ -618,7 +617,7 @@ class ApiClient { notes = this.deserializeNoteMetadatas(data) } - console.log(`got ${notes.length} note metadata results`) + console.log(`[NOTE] Got ${notes.length} note metadata results`) return notes }, @@ -626,7 +625,7 @@ class ApiClient { ) } - public async createNote(): Promise { + public async createNote(): Promise { // NOTE: The initial note version doesn't allow any user input, the first user-made modification // is applied through the version creation endpoint @@ -644,10 +643,7 @@ class ApiClient { ) } - public async getActiveFullNote( - noteID: string, - fetchRemote: boolean - ): Promise { + public async getActiveFullNote(noteID: string, fetchRemote: boolean): Promise { if (!UUID_REGEX.test(noteID)) { throw new Error("Invalid note ID format.") } @@ -656,7 +652,6 @@ class ApiClient { if (!fetchRemote) { const cachedNote = this.loadedNotesCache.get(noteID) if (cachedNote != null) { - // console.log(`full note cache hit ${noteID}`) this.activeVersion = cachedNote.versionNumber return cachedNote } @@ -675,7 +670,7 @@ class ApiClient { }) const note = this.deserializeFullNote(data) - console.log(`caching ${noteID}`) + console.log(`[CACHE] Storing ${noteID}`) this.loadedNotesCache.set(noteID, note) this.activeVersion = note.versionNumber @@ -685,7 +680,7 @@ class ApiClient { ) } - public async deleteNote(noteID: string): Promise { + public async deleteNote(noteID: string): Promise { if (!UUID_REGEX.test(noteID)) { throw new Error("Invalid note ID format.") } @@ -700,7 +695,7 @@ class ApiClient { }) if (response.status === 204) { - console.log("deletion successful") + console.log("[NOTE] Deletion successful") return } @@ -713,7 +708,7 @@ class ApiClient { public async getNoteHistory( noteID: string, fetchRemote: boolean - ): Promise { + ): Promise { if (!UUID_REGEX.test(noteID)) { throw new Error("Invalid note ID format.") } @@ -721,7 +716,6 @@ class ApiClient { if (!fetchRemote) { const cachedVersions = this.loadedHistoryCache.get(noteID) if (cachedVersions != null) { - // console.log(`full version cache hit ${noteID}`) return cachedVersions } } @@ -739,8 +733,8 @@ class ApiClient { }) const versions = this.deserializeVersionMetadatas(data) - console.log(`got ${versions.length} version metadata results, caching ${noteID}`) this.loadedHistoryCache.set(noteID, versions) + console.log(`[VER] Got and cached ${versions.length} version metadata results`) return versions }, @@ -748,7 +742,7 @@ class ApiClient { ) } - public async createVersion(noteID: string, title: string, content: string): Promise { + public async createVersion(noteID: string, title: string, content: string): Promise { if (!UUID_REGEX.test(noteID)) { throw new Error("Invalid note ID format.") } @@ -767,7 +761,7 @@ class ApiClient { }) if (response.status === 204) { - console.log("creation successful") + console.log("[VER] Creation successful") return } @@ -777,7 +771,7 @@ class ApiClient { ) } - public async getFullVersion(noteID: string, versionID: string): Promise { + public async getFullVersion(noteID: string, versionID: string): Promise { if (!UUID_REGEX.test(noteID)) { throw new Error("Invalid note ID format.") } @@ -786,18 +780,13 @@ class ApiClient { throw new Error("Invalid version ID format.") } - // NOTE: Versions aren't editable so we don't need to prevent the system from attempting - // to locate each request's contents first from the cache + // NOTE: No need to explicitly prevent attempting a cache hit as versions aren't editable const cachedVersion = this.loadedVersionsCache.get(noteID + versionID) if (cachedVersion != null) { - // console.log(`full version cache hit [${noteID}, ${versionID}]`) return cachedVersion } - // TODO: check if the requested version is the current version -> use `loadedNotesCache` - // (we probably have to modify the caching mechanism so we can look the regular note items up using versionID) - return this.handleRequest( async () => { const response = await fetch(`${this.baseUrl}/notes/${noteID}/${versionID}`, { @@ -812,10 +801,10 @@ class ApiClient { const version = this.joinDeserializedVersion(noteID, data) if (!version) { - return + return null } - console.log(`caching [${noteID}, ${versionID}]`) + console.log(`[CACHE] Storing [${noteID}, ${versionID}]`) this.loadedVersionsCache.set(noteID + versionID, version) return version diff --git a/web/src/lib/const.ts b/web/src/lib/const.ts index 7c083b5..51a1dee 100644 --- a/web/src/lib/const.ts +++ b/web/src/lib/const.ts @@ -2,7 +2,7 @@ // will automatically be proxied to the correct destination export const API_BASE_ADDR = import.meta.env.PROD ? "/api" : "http://localhost:8080/api" -// Lifetimes of *in-memory* authentication tokens +// Lifetimes of *in-memory* authentication tokens in milliseconds export const AT_EXP_MS = 15 * 60 * 1000 // 15 min. export const CSRF_EXP_MS = 12 * 60 * 60 * 1000 // 12 h. export const REFRESH_BUF = 30 * 1000 // 30 s.