From 42489d5acc1ab827e499cb5e858cade3cd6f6966 Mon Sep 17 00:00:00 2001 From: Yongjun Park Date: Wed, 12 Jul 2023 15:58:24 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20apollo=20req=20&=20res=20mi?= =?UTF-8?q?ddleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/providers/ApolloProvider.tsx | 51 ++++++++++++++++++++-- app/src/services/auth/getNewAccessToken.ts | 36 +++++++++++++++ app/src/utils/storage/clearStorage.ts | 4 ++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 app/src/services/auth/getNewAccessToken.ts create mode 100644 app/src/utils/storage/clearStorage.ts diff --git a/app/src/providers/ApolloProvider.tsx b/app/src/providers/ApolloProvider.tsx index 66984262..4b850fe8 100644 --- a/app/src/providers/ApolloProvider.tsx +++ b/app/src/providers/ApolloProvider.tsx @@ -1,30 +1,72 @@ +import { getNewAccessToken } from '@/services/auth/getNewAccessToken'; import { ApolloClient, ApolloLink, ApolloProvider, HttpLink, InMemoryCache, - concat, + from, + fromPromise, } from '@apollo/client'; +import { onError } from '@apollo/client/link/error'; +import { ROUTES } from '@routes/ROUTES'; import { getAccessToken } from '@utils/storage/accessToken'; +import { clearStorage } from '@utils/storage/clearStorage'; +import { getRefreshToken } from '@utils/storage/refreshToken'; const httpLink = new HttpLink({ uri: import.meta.env.VITE_BACKEND_GRAPHQL_ENDPOINT, }); const authMiddleWare = new ApolloLink((operation, forward) => { + if (operation.operationName === 'getLanding') { + return forward(operation); + } const accessToken = getAccessToken(); if (accessToken === null) { return forward(operation); } operation.setContext({ headers: { - authorization: `Bearer ${getAccessToken()}`, + ...operation.getContext().headers, + authorization: `Bearer ${accessToken}`, }, }); return forward(operation); }); +const refreshLink = onError(({ graphQLErrors, operation, forward }) => { + if (graphQLErrors) { + // forEach 내부에서 async await 사용하면 안됨 -> for ~ of + for (const { extensions } of graphQLErrors) { + if (extensions?.status === 401) { + return fromPromise(getNewAccessToken(getRefreshToken() ?? '')).flatMap( + (accessToken) => { + operation.setContext({ + headers: { + ...operation.getContext().headers, + authorization: `Bearer ${accessToken}`, + }, + }); + return forward(operation); + }, + ); + } + } + } +}); + +const resetLink = onError(({ graphQLErrors }) => { + if (graphQLErrors) { + graphQLErrors.forEach(({ extensions }) => { + if (extensions?.status === 400) { + clearStorage(); + window.location.href = ROUTES.ROOT; + } + }); + } +}); + /** * @description * 쿼리 바뀌었을때 이부분 안건드리면 기존에 있던캐시정보에 바뀐 쿼리정보 병합하려고해서 에러발생 @@ -34,8 +76,9 @@ const authMiddleWare = new ApolloLink((operation, forward) => { * https://go.apollo.dev/c/merging-non-normalized-objects * https://go.apollo.dev/c/generating-unique-identifiers */ -const client = new ApolloClient({ - link: concat(authMiddleWare, httpLink), + +export const client = new ApolloClient({ + link: from([authMiddleWare, refreshLink, resetLink, httpLink]), cache: new InMemoryCache({ typePolicies: { Query: { diff --git a/app/src/services/auth/getNewAccessToken.ts b/app/src/services/auth/getNewAccessToken.ts new file mode 100644 index 00000000..56fe8498 --- /dev/null +++ b/app/src/services/auth/getNewAccessToken.ts @@ -0,0 +1,36 @@ +import { gql } from '@/__generated__'; +import { client } from '@providers/ApolloProvider'; +import { setAccessToken } from '@utils/storage/accessToken'; +import { setRefreshToken } from '@utils/storage/refreshToken'; + +const GET_NEW_ACCESS_TOKEN = gql(/* GraphQL */ ` + mutation GetNewAccessToken($refreshToken: String!) { + refreshToken(refreshToken: $refreshToken) { + __typename + ... on Success { + message + accessToken + refreshToken + userId + } + ... on NoAssociated { + message + } + } + } +`); + +export const getNewAccessToken = async (refreshToken: string) => { + const { data } = await client.mutate({ + mutation: GET_NEW_ACCESS_TOKEN, + variables: { refreshToken }, + }); + if (!data || data.refreshToken.__typename !== 'Success') { + return null; + } + const { accessToken: newAccessToken, refreshToken: newRefreshToken } = + data.refreshToken; + setAccessToken(newAccessToken); + setRefreshToken(newRefreshToken); + return newAccessToken; +}; diff --git a/app/src/utils/storage/clearStorage.ts b/app/src/utils/storage/clearStorage.ts new file mode 100644 index 00000000..d8426841 --- /dev/null +++ b/app/src/utils/storage/clearStorage.ts @@ -0,0 +1,4 @@ +export const clearStorage = () => { + localStorage.clear(); + sessionStorage.clear(); +};