11import { assertOk } from "@/shared/utils/apiError" ;
22import type { UserOnboardingRequest , UserUser } from "@nycu-sdc/core-system-sdk" ;
3- import { authLogout , authRefreshToken , userGetMe , userUpdateUsername } from "@nycu-sdc/core-system-sdk" ;
3+ import { authLogout , userGetMe , userUpdateUsername } from "@nycu-sdc/core-system-sdk" ;
44
55export type OAuthProvider = "google" | "nycu" ;
66
@@ -10,105 +10,11 @@ const defaultRequestOptions: RequestInit = {
1010 credentials : "include"
1111} ;
1212
13- const REFRESH_TOKEN_STORAGE_KEY = "core-system.refresh-token" ;
14- const ACCESS_TOKEN_EXPIRY_STORAGE_KEY = "core-system.access-token-exp-ms" ;
15- let refreshInFlight : Promise < boolean > | null = null ;
16-
17- const parseJwtExpirationMs = ( jwt : string ) : number | null => {
18- try {
19- const [ , payloadBase64Url ] = jwt . split ( "." ) ;
20- if ( ! payloadBase64Url ) return null ;
21- const payloadBase64 = payloadBase64Url . replace ( / - / g, "+" ) . replace ( / _ / g, "/" ) ;
22- const padded = payloadBase64 + "=" . repeat ( ( 4 - ( payloadBase64 . length % 4 ) ) % 4 ) ;
23- const payloadJson = atob ( padded ) ;
24- const payload = JSON . parse ( payloadJson ) as { exp ?: number } ;
25- if ( ! payload . exp || typeof payload . exp !== "number" ) return null ;
26- return payload . exp * 1000 ;
27- } catch {
28- return null ;
29- }
30- } ;
31-
32- export const withAuthRefreshRetry = async < T extends { status : number } > ( request : ( ) => Promise < T > ) : Promise < T > => {
33- const firstResponse = await request ( ) ;
34- if ( firstResponse . status !== 401 ) return firstResponse ;
35-
36- if ( ! refreshInFlight ) {
37- refreshInFlight = authService
38- . refreshAccessToken ( )
39- . catch ( ( ) => false )
40- . finally ( ( ) => {
41- refreshInFlight = null ;
42- } ) ;
43- }
44-
45- const refreshed = await refreshInFlight ;
46- if ( ! refreshed ) return firstResponse ;
47-
48- return request ( ) ;
49- } ;
50-
5113const normalizeProvider = ( provider : OAuthProvider ) : string => {
5214 return provider . toLowerCase ( ) ;
5315} ;
5416
5517export const authService = {
56- getStoredRefreshToken ( ) : string | null {
57- if ( typeof window === "undefined" ) return null ;
58- return window . localStorage . getItem ( REFRESH_TOKEN_STORAGE_KEY ) ;
59- } ,
60-
61- setStoredRefreshToken ( refreshToken : string ) {
62- if ( typeof window === "undefined" ) return ;
63- window . localStorage . setItem ( REFRESH_TOKEN_STORAGE_KEY , refreshToken ) ;
64- } ,
65-
66- getStoredAccessTokenExpiryMs ( ) : number | null {
67- if ( typeof window === "undefined" ) return null ;
68- const raw = window . localStorage . getItem ( ACCESS_TOKEN_EXPIRY_STORAGE_KEY ) ;
69- if ( ! raw ) return null ;
70- const value = Number ( raw ) ;
71- return Number . isFinite ( value ) ? value : null ;
72- } ,
73-
74- setStoredAccessTokenExpiryMs ( expirationMs : number ) {
75- if ( typeof window === "undefined" ) return ;
76- window . localStorage . setItem ( ACCESS_TOKEN_EXPIRY_STORAGE_KEY , String ( expirationMs ) ) ;
77- } ,
78-
79- clearStoredAccessTokenExpiryMs ( ) {
80- if ( typeof window === "undefined" ) return ;
81- window . localStorage . removeItem ( ACCESS_TOKEN_EXPIRY_STORAGE_KEY ) ;
82- } ,
83-
84- clearStoredRefreshToken ( ) {
85- if ( typeof window === "undefined" ) return ;
86- window . localStorage . removeItem ( REFRESH_TOKEN_STORAGE_KEY ) ;
87- this . clearStoredAccessTokenExpiryMs ( ) ;
88- } ,
89-
90- async refreshAccessToken ( ) : Promise < boolean > {
91- const refreshToken = this . getStoredRefreshToken ( ) ;
92- if ( ! refreshToken ) return false ;
93-
94- const res = await authRefreshToken ( refreshToken , defaultRequestOptions ) ;
95- if ( res . status === 404 ) {
96- this . clearStoredRefreshToken ( ) ;
97- throw new Error ( "Refresh token expired" ) ;
98- }
99-
100- assertOk ( res . status , "Failed to refresh access token" , res . data ) ;
101- if ( res . data ?. refreshToken ) {
102- this . setStoredRefreshToken ( res . data . refreshToken ) ;
103- }
104- const accessTokenExpMs = parseJwtExpirationMs ( res . data . accessToken ) ;
105- if ( accessTokenExpMs ) {
106- this . setStoredAccessTokenExpiryMs ( accessTokenExpMs ) ;
107- }
108-
109- return true ;
110- } ,
111-
11218 redirectToOAuthLogin (
11319 provider : OAuthProvider ,
11420 options : {
@@ -132,19 +38,18 @@ export const authService = {
13238 } ,
13339
13440 async logout ( ) : Promise < void > {
135- const res = await withAuthRefreshRetry ( ( ) => authLogout ( defaultRequestOptions ) ) ;
41+ const res = await authLogout ( defaultRequestOptions ) ;
13642 assertOk ( res . status , "Failed to logout" , res . data ) ;
137- this . clearStoredRefreshToken ( ) ;
13843 } ,
13944
14045 async getCurrentUser < T extends UserUser = UserUser > ( ) : Promise < T > {
141- const res = await withAuthRefreshRetry ( ( ) => userGetMe ( defaultRequestOptions ) ) ;
46+ const res = await userGetMe ( defaultRequestOptions ) ;
14247 assertOk ( res . status , "Failed to get current user" , res . data ) ;
14348 return res . data as T ;
14449 } ,
14550
14651 async updateOnboarding ( data : UserOnboardingRequest ) : Promise < void > {
147- const res = await withAuthRefreshRetry ( ( ) => userUpdateUsername ( data , defaultRequestOptions ) ) ;
52+ const res = await userUpdateUsername ( data , defaultRequestOptions ) ;
14853 assertOk ( res . status , "Failed to update onboarding" , res . data ) ;
14954 }
15055} ;
0 commit comments