11export const API_BASE_URL = "https://panda-market-api.vercel.app" ;
22
3+ // 기본 함수
34async function fetchApi ( url : string , options = { } ) {
45 try {
56 const response = await fetch ( url , options ) ;
@@ -15,6 +16,7 @@ async function fetchApi(url: string, options = {}) {
1516 }
1617}
1718
19+ // 상품 가져오기 함수
1820export async function getProducts ( {
1921 page = "" ,
2022 pageSize = "" ,
@@ -24,29 +26,32 @@ export async function getProducts({
2426 const params = new URLSearchParams ( { page, pageSize, orderBy, keyword } ) ;
2527 const url = `${ API_BASE_URL } /products?${ params } ` ;
2628
27- return fetchApi ( url ) ;
29+ return fetchWithAuth ( url ) ;
2830}
2931
32+ // 상품 id별 가져오기 함수
3033export async function getProductsById ( productId : string | undefined ) {
3134 const url = `${ API_BASE_URL } /products/${ productId } ` ;
3235
33- return fetchApi ( url ) ;
36+ return fetchWithAuth ( url ) ;
3437}
3538
39+ // 댓글 가져오기 함수
3640export async function getCommentsById (
3741 productId : string | undefined ,
3842 { limit = "" }
3943) {
4044 const params = new URLSearchParams ( { limit } ) ;
4145 const url = `${ API_BASE_URL } /products/${ productId } /comments?${ params } ` ;
4246
43- return fetchApi ( url ) ;
47+ return fetchWithAuth ( url ) ;
4448}
4549
4650interface UpdateCommentParams {
4751 content : string ;
4852}
4953
54+ // 댓글 수정 함수
5055export async function updateCommentsById (
5156 commentId : number ,
5257 { content } : UpdateCommentParams
@@ -60,7 +65,7 @@ export async function updateCommentsById(
6065 body : JSON . stringify ( { content } ) ,
6166 } ;
6267
63- return fetchApi ( url , options ) ;
68+ return fetchWithAuth ( url , options ) ;
6469}
6570
6671interface SignupParams {
@@ -70,6 +75,7 @@ interface SignupParams {
7075 passwordConfirmation : string ;
7176}
7277
78+ // 회원가입 함수
7379export async function signup ( data : SignupParams ) {
7480 const url = `${ API_BASE_URL } /auth/signUp` ;
7581 const options = {
@@ -92,6 +98,7 @@ interface LoginParams {
9298 password : string ;
9399}
94100
101+ // 로그인 함수
95102export async function login ( data : LoginParams ) {
96103 const url = `${ API_BASE_URL } /auth/signIn` ;
97104 const options = {
@@ -106,3 +113,90 @@ export async function login(data: LoginParams) {
106113 } ;
107114 return fetchApi ( url , options ) ;
108115}
116+
117+ // 토큰을 디코딩하는 함수
118+ const parseJwt = ( token : string ) => {
119+ try {
120+ const base64Url = token . split ( "." ) [ 1 ] ;
121+ const base64 = base64Url . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) ;
122+ const decodedPayload = JSON . parse ( atob ( base64 ) ) ;
123+ return decodedPayload ;
124+ } catch ( error ) {
125+ console . error ( "JWT 파싱 오류 : " , error ) ;
126+ return null ;
127+ }
128+ } ;
129+
130+ // 토큰이 만료되었는지 비교하는 함수
131+ const isTokenExpired = ( token : string ) => {
132+ if ( ! token ) return true ;
133+
134+ const decodedToken = parseJwt ( token ) ;
135+ if ( ! decodedToken ) return true ;
136+
137+ const expirationTime = decodedToken . exp * 1000 ;
138+ return Date . now ( ) > expirationTime ;
139+ } ;
140+
141+ // 리프레시 토큰을 통해 새로운 엑세스 토큰을 받아오는 함수
142+ const refreshAccessToken = async ( refreshToken : string ) => {
143+ const url = `${ API_BASE_URL } /auth/refresh-token` ;
144+ const options = {
145+ method : "POST" ,
146+ headers : {
147+ "Content-Type" : "application/json" ,
148+ } ,
149+ body : JSON . stringify ( { refreshToken } ) ,
150+ } ;
151+
152+ try {
153+ const response = await fetch ( url , options ) ;
154+ if ( ! response . ok ) {
155+ throw new Error ( "리프레시 토큰 요청 실패" ) ;
156+ }
157+
158+ const data = await response . json ( ) ;
159+ const newAccessToken = data . accessToken ;
160+ if ( newAccessToken ) {
161+ localStorage . setItem ( "access_token" , newAccessToken ) ;
162+ }
163+ return newAccessToken ;
164+ } catch ( error ) {
165+ console . error ( "엑세스 토큰 갱신 오류:" , error ) ;
166+ throw new Error ( "엑세스 토큰을 갱신할 수 없습니다." ) ;
167+ }
168+ } ;
169+
170+ // 로컬 스토리지에서 엑세스 토큰을 가져오고 만료되었으면 리프레시 토큰으로 갱신하는 함수
171+ export const getAccessToken = async ( ) => {
172+ let accessToken = localStorage . getItem ( "access_token" ) ;
173+ const refreshToken = localStorage . getItem ( "refresh_token" ) ;
174+
175+ if ( accessToken && ! isTokenExpired ( accessToken ) ) {
176+ return accessToken ;
177+ }
178+
179+ if ( refreshToken ) {
180+ return await refreshAccessToken ( refreshToken ) ;
181+ }
182+
183+ return null ;
184+ } ;
185+
186+ // 모든 API 보낼때 토큰 담아서 보내는 함수
187+ async function fetchWithAuth ( url : string , options : RequestInit = { } ) {
188+ const accessToken = await getAccessToken ( ) ;
189+
190+ if ( ! accessToken ) {
191+ throw new Error ( "로그인이 필요합니다." ) ;
192+ }
193+
194+ const authOptions = {
195+ ...options ,
196+ headers : {
197+ ...( options . headers || { } ) ,
198+ Authorization : `Bearer ${ accessToken } ` ,
199+ } ,
200+ } ;
201+ return fetchApi ( url , authOptions ) ;
202+ }
0 commit comments