From 1f1d57e0442b2933ff327ed10b399059e90f2828 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 17:31:12 +0530 Subject: [PATCH 1/6] refactor(web): optimize-session-invalidation --- web/src/context/AtlasProvider.tsx | 57 +++++++++++++++++++++++------ web/src/utils/atlas/addUser.ts | 11 ++++-- web/src/utils/atlas/updateEmail.ts | 11 ++++-- web/src/utils/atlas/uploadToIpfs.ts | 14 +++++++ 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index b34dbe3f2..b527f3c1c 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -22,9 +22,11 @@ import { type ConfirmEmailResponse, Roles, Products, + AuthorizationError, } from "utils/atlas"; import { isUndefined } from "src/utils"; +import { GraphQLError } from "graphql"; interface IAtlasProvider { isVerified: boolean; @@ -94,16 +96,32 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = }, [authToken, address]); useEffect(() => { - // initial verfiy check - setIsVerified(verifySession()); + let timeoutId: NodeJS.Timeout; - // verify session every 5 sec - const intervalId = setInterval(() => { - setIsVerified(verifySession()); - }, 5000); + const verifyAndSchedule = () => { + console.log("checking"); + + // initial verfiy check + const isValid = verifySession(); + setIsVerified(isValid); + + if (isValid && authToken) { + try { + const payload = decodeJwt(authToken); + const expiresIn = (payload.exp as number) * 1000 - Date.now(); + + timeoutId = setTimeout(verifyAndSchedule, Math.max(0, expiresIn)); + } catch (err) { + console.error("Error decoding JWT:", err); + setIsVerified(false); + } + } + }; + + verifyAndSchedule(); return () => { - clearInterval(intervalId); + clearTimeout(timeoutId); }; }, [authToken, verifySession, address]); @@ -140,6 +158,21 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = return !isUndefined(user.email); }, [user]); + async function fetchWithAuthErrorHandling(request: () => Promise): Promise { + try { + return await request(); + } catch (error) { + if ( + error instanceof AuthorizationError || + (error instanceof GraphQLError && error.extensions["code"] === "UNAUTHENTICATED") + ) { + setIsVerified(false); + throw error; + } + throw error; + } + } + /** * @description authorise user and enable authorised calls */ @@ -173,7 +206,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = if (!address || !isVerified) return false; setIsAddingUser(true); - const userAdded = await addUserToAtlas(atlasGqlClient, userSettings); + const userAdded = await fetchWithAuthErrorHandling(() => addUserToAtlas(atlasGqlClient, userSettings)); refetchUser(); return userAdded; @@ -199,7 +232,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = if (!address || !isVerified) return false; setIsUpdatingUser(true); - const emailUpdated = await updateEmailInAtlas(atlasGqlClient, userSettings); + // const emailUpdated = await updateEmailInAtlas(atlasGqlClient, userSettings); + const emailUpdated = await fetchWithAuthErrorHandling(() => updateEmailInAtlas(atlasGqlClient, userSettings)); refetchUser(); return emailUpdated; @@ -227,9 +261,8 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = if (!address || !isVerified || !atlasUri || !authToken) return null; setIsUploadingFile(true); - const hash = await uploadToIpfs( - { baseUrl: atlasUri, authToken }, - { file, name: file.name, role, product: Products.CourtV2 } + const hash = await fetchWithAuthErrorHandling(() => + uploadToIpfs({ baseUrl: atlasUri, authToken }, { file, name: file.name, role, product: Products.CourtV2 }) ); return hash ? `/ipfs/${hash}` : null; } catch (err: any) { diff --git a/web/src/utils/atlas/addUser.ts b/web/src/utils/atlas/addUser.ts index 96302a29c..dba5e48f7 100644 --- a/web/src/utils/atlas/addUser.ts +++ b/web/src/utils/atlas/addUser.ts @@ -1,3 +1,4 @@ +import { GraphQLError } from "graphql"; import { gql, type GraphQLClient } from "graphql-request"; import { toast } from "react-toastify"; @@ -30,10 +31,12 @@ export function addUser(client: GraphQLClient, userData: AddUserData): Promise { if (!response.ok) { const error = await response.json().catch(() => ({ message: "Error uploading to IPFS" })); + + if (response.status === 401) throw new AuthorizationError(error.message); + throw new Error(error.message); } @@ -59,3 +62,14 @@ export async function uploadToIpfs(config: Config, payload: IpfsUploadPayload): OPTIONS ); } + +export class AuthorizationError extends Error { + constructor(message: string) { + super(message); + this.name = "AuthorizationError"; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} From 24ac2efdc498cc0d6595dfc348947cd5eaab1596 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 17:37:10 +0530 Subject: [PATCH 2/6] refactor(web): remove-code-smells --- web/src/context/AtlasProvider.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index b527f3c1c..f2b219d42 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -99,8 +99,6 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = let timeoutId: NodeJS.Timeout; const verifyAndSchedule = () => { - console.log("checking"); - // initial verfiy check const isValid = verifySession(); setIsVerified(isValid); @@ -167,7 +165,6 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = (error instanceof GraphQLError && error.extensions["code"] === "UNAUTHENTICATED") ) { setIsVerified(false); - throw error; } throw error; } From b1144a16cfa396ed17dad3fb412aa043fbc905dc Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 17:53:08 +0530 Subject: [PATCH 3/6] refactor(web): address-code-rabbit-feedback --- web/src/context/AtlasProvider.tsx | 4 ++-- web/src/utils/atlas/uploadToIpfs.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index f2b219d42..c3801ca46 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -96,7 +96,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = }, [authToken, address]); useEffect(() => { - let timeoutId: NodeJS.Timeout; + let timeoutId: ReturnType; const verifyAndSchedule = () => { // initial verfiy check @@ -162,7 +162,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = } catch (error) { if ( error instanceof AuthorizationError || - (error instanceof GraphQLError && error.extensions["code"] === "UNAUTHENTICATED") + (error instanceof GraphQLError && error?.extensions["code"] === "UNAUTHENTICATED") ) { setIsVerified(false); } diff --git a/web/src/utils/atlas/uploadToIpfs.ts b/web/src/utils/atlas/uploadToIpfs.ts index 2d7d02509..cd95c5a51 100644 --- a/web/src/utils/atlas/uploadToIpfs.ts +++ b/web/src/utils/atlas/uploadToIpfs.ts @@ -44,7 +44,6 @@ export async function uploadToIpfs(config: Config, payload: IpfsUploadPayload): const error = await response.json().catch(() => ({ message: "Error uploading to IPFS" })); if (response.status === 401) throw new AuthorizationError(error.message); - throw new Error(error.message); } @@ -64,9 +63,9 @@ export async function uploadToIpfs(config: Config, payload: IpfsUploadPayload): } export class AuthorizationError extends Error { + readonly name = "AuthorizationError" as const; constructor(message: string) { super(message); - this.name = "AuthorizationError"; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); From aabad1e3e6e3352ab23066cdcb1d0dacc41f560a Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 18:04:02 +0530 Subject: [PATCH 4/6] refactor(web): remove-irrelevant-comment --- web/src/context/AtlasProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index c3801ca46..37514e735 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -229,7 +229,6 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = if (!address || !isVerified) return false; setIsUpdatingUser(true); - // const emailUpdated = await updateEmailInAtlas(atlasGqlClient, userSettings); const emailUpdated = await fetchWithAuthErrorHandling(() => updateEmailInAtlas(atlasGqlClient, userSettings)); refetchUser(); From 02eb9b28bcf6e7d3649922746796e7addc4bc66e Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 18:59:19 +0530 Subject: [PATCH 5/6] refactor(web): address-feedback --- web/src/context/AtlasProvider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index 37514e735..e1644130f 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -99,7 +99,7 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = let timeoutId: ReturnType; const verifyAndSchedule = () => { - // initial verfiy check + // initial verify check const isValid = verifySession(); setIsVerified(isValid); @@ -156,13 +156,13 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = return !isUndefined(user.email); }, [user]); - async function fetchWithAuthErrorHandling(request: () => Promise): Promise { + function fetchWithAuthErrorHandling(request: () => Promise): Promise { try { - return await request(); + return request(); } catch (error) { if ( error instanceof AuthorizationError || - (error instanceof GraphQLError && error?.extensions["code"] === "UNAUTHENTICATED") + (error instanceof GraphQLError && error?.extensions?.["code"] === "UNAUTHENTICATED") ) { setIsVerified(false); } From b72d24152e5479d49793f3858bce26176146826d Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Mon, 28 Oct 2024 19:08:30 +0530 Subject: [PATCH 6/6] refactor(web): revert-fetch-with-auth-function-change --- web/src/context/AtlasProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/context/AtlasProvider.tsx b/web/src/context/AtlasProvider.tsx index e1644130f..cdc6f1ac4 100644 --- a/web/src/context/AtlasProvider.tsx +++ b/web/src/context/AtlasProvider.tsx @@ -156,9 +156,9 @@ const AtlasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) = return !isUndefined(user.email); }, [user]); - function fetchWithAuthErrorHandling(request: () => Promise): Promise { + async function fetchWithAuthErrorHandling(request: () => Promise): Promise { try { - return request(); + return await request(); } catch (error) { if ( error instanceof AuthorizationError ||