인프런 커뮤니티 질문&답변

ju han님의 프로필 이미지
ju han

작성한 질문수

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

여러가지 쿼리방식 및 Refresh Token 받아오기 실습

따로 분리한 getAccessToken() 함수 result의 타입

해결된 질문

작성

·

638

1

강의를 보고 똑같이 따라했지만 저는 result에 타입이 unknown이라고 뜨네요.

이때문인지는 모르겠지만 로그인 후 넘어간 페이지에서 버튼 누르는 과정에서 오류가 뜹니다.ㅜ

아래는 아폴로 세팅입니다. 문제가 무엇일까요?

import {
  ApolloClient,
  ApolloLink,
  ApolloProvider,
  fromPromise,
  InMemoryCache,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import { createUploadLink } from "apollo-upload-client";
import { useEffect } from "react";
import { useRecoilState } from "recoil";
import { getAccessToken } from "../../../commons/libraries/getAccessToken";
import { accessTokenState } from "../../../commons/store";

const GLOBAL_STATE = new InMemoryCache();


interface IApolloSettingProps {
  children: JSX.Element;
}

export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  

  // 3. 프리랜더링 무시 - useEffect
  useEffect(() => {
    console.log("지금은 브라우저다");
    const result = localStorage.getItem("accessToken");
    console.log(result);
    if (result) setAccessToken(result);
  }, []);

  // 에러를 캐치하고 캐치한 에러가 토큰만료면 재발급 받은 후, 기존의 쿼리를 포워드해서 다시 날려준다.
  const errorLink = onError(({ graphQLErrors, operation, forward }) => {
    // 1-1. 에러를 캐치
    if (graphQLErrors) {
      for (const err of graphQLErrors) {
        // 1-2. 해당 에러가 토큰만료 에러인지 체크(UNAUTHENTICATED)
        if (err.extensions.code === "UNAUTHENTICATED") {
          return fromPromise(
            // 2-1. refreshToken으로 accessToken을 재발급
      
            getAccessToken().then((newAccessToken) => {
              // 2-2. 재발급 받은 accessToken 저장하기
              setAccessToken(newAccessToken);

              // 3-1. 재발급 받은 accessToken으로 방금 실패한 쿼리의 정보 수정하기
              if (typeof newAccessToken !== "string") return;
              operation.setContext({
                headers: {
                  ...operation.getContext().headers, // 만료된 토큰이 추가되어 있는 상태
                  Authorization: `Bearer ${newAccessToken}`, // 토큰만 새것으로 바꿔치기
                },
              });
            })
          ).flatMap(() => forward(operation)); // 3-2. 방금 수정한 쿼리 재요청하기
        }
      }
    }
  });

  const uploadLink = createUploadLink({
    uri: "https://backendonline.codebootcamp.co.kr/graphql", // https 로 변경(토큰 정보를 쿠키에 담을 수 있게)
    headers: { Authorization: `Bearer ${accessToken}` },
    credentials: "include", // https 변경으로 추가된 조건
  });

  const client = new ApolloClient({
    link: ApolloLink.from([errorLink, uploadLink as unknown as ApolloLink]),
    // cache: new InMemoryCache(),
    cache: GLOBAL_STATE,
  });

  // prettier-ignore
  // 주석으로 prettier-ignore 해주면 한줄로 바뀌는걸 막아준다
  return <ApolloProvider client={client}>
    {props.children}
    </ApolloProvider>;
}

혹시 이부분때문일까요??

link: ApolloLink.from([errorLink, uploadLink as unknown as ApolloLink])

답변 1

0

안녕하세요!

해당 오류는 백엔드 서버에서 refreshToken이 새로운 토큰을 재발급해주는 로직이 잘못되어 토큰바꿔치기가 안되어 발생하는 오류입니다.
따라서 해당 부분을 보완한 서버로 바꿔주셔야 합니다.

uploadLink와 getAccessToken() 부분에 주소를 아래 주소로 바꿔주세요!
https://backend-practice.codebootcamp.co.kr/graphql

감사합니다.😁

ju han님의 프로필 이미지
ju han
질문자

감사합니다!! 수정하니까 되네요!!

그런데 또 궁금한점이 있습니다.

수업노트에 보면 아래처럼 예시를 보여주시는데,

// src/apollo/index.tsx 파일

export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
  const [userInfo, setUserInfo] = useRecoilState(userInfoState);
	if (!accessToken || !userInfo) return;
	    setUserInfo(JSON.parse(userInfo));
	  }, []);
	
	const errorLink = onError(({ graphQLErrors, operation, forward })=>{
		// 1-1. 에러를 캐치

		// 1-2. 해당에러가 토큰만료 에러인지 체크(UNAUTHENTICATED)		

    // 2-1. refreshToken으로 accessToken을 재발급 받기
				
		// 2-2. 재발급 받은 accessToken 저장하기

		// 3-1. 재발급 받은 accessToken으로 방금 실패한 쿼리정보 수정하기

		// 3-2. 재발급 받은 accessToken으로 방금 수정한 쿼리 재요청하기

	})

	  const uploadLink = createUploadLink({
	    uri: "http://backendonline.codebootcamp.co.kr/graphql",
	    headers: { Authorization: `Bearer ${accessToken}` },
		  credentials: "include",
	  });
	
	  const client = new ApolloClient({
	    link: ApolloLink.from([uploadLink]),
	    cache: APOLLO_CACHE,
	    connectToDevTools: true,
	  });
	

	  return (
	    <ApolloProvider client={client}>
	        {props.children}
	    </ApolloProvider>
	  )
	}

 

아래 두 부분은 수업에서 진행하지 않았던 것 같은데

  1. userInfo 글로벌스테이트에 저장하는 부분

  2. client 설정에 connectToDevTools 부분

혹시 어떻게 추가가 된건지 알 수 있을까요?

 const [userInfo, setUserInfo] = useRecoilState(userInfoState);
  const client = new ApolloClient({
	    link: ApolloLink.from([uploadLink]),
	    cache: APOLLO_CACHE,
	    connectToDevTools: true,
	  });
ju han님의 프로필 이미지
ju han

작성한 질문수

질문하기