@@ -17,14 +17,38 @@ export type ApiServiceHook = {
1717 onResponse ?: ( res : Response , req : RequestInit & { url : string } ) => void | Promise < void > ;
1818} ;
1919
20+ export interface ApiServiceConfigWithRefresh extends ApiServiceConfig {
21+ onRefreshToken ?: ( ) => Promise < string | null > ;
22+ }
23+
2024export class ApiService implements ApiService {
21- private config : ApiServiceConfig ;
25+ private config : ApiServiceConfigWithRefresh ;
2226 private hooks : ApiServiceHook [ ] = [ ] ;
2327
24- constructor ( config : ApiServiceConfig ) {
28+ constructor ( config : ApiServiceConfigWithRefresh ) {
2529 this . config = config ;
2630 }
2731
32+ /**
33+ * Internal helper to handle 401, refresh token, and retry once
34+ */
35+ private async handle401AndRetry (
36+ req : RequestInit & { url : string } ,
37+ doRequest : ( ) => Promise < Response > ,
38+ updateHeaders : ( token : string ) => void
39+ ) : Promise < Response > {
40+ let res = await doRequest ( ) ;
41+ if ( res . status === 401 && this . config . onRefreshToken ) {
42+ const newToken = await this . config . onRefreshToken ( ) ;
43+ if ( newToken ) {
44+ this . config . apiKey = newToken ;
45+ updateHeaders ( newToken ) ;
46+ res = await doRequest ( ) ;
47+ }
48+ }
49+ return res ;
50+ }
51+
2852 /**
2953 * Register a request/response hook
3054 */
@@ -45,8 +69,12 @@ export class ApiService implements ApiService {
4569 headers : this . config . apiKey ? { 'Authorization' : `Bearer ${ this . config . apiKey } ` } : undefined ,
4670 } ;
4771 for ( const hook of this . hooks ) if ( hook . onRequest ) await hook . onRequest ( req ) ;
72+ const updateHeaders = ( token : string ) => {
73+ req . headers = { ...( req . headers || { } ) , Authorization : `Bearer ${ token } ` } ;
74+ } ;
4875 try {
49- const res = await fetch ( req . url , req ) ;
76+ const doRequest = ( ) => fetch ( req . url , req ) ;
77+ const res = await this . handle401AndRetry ( req , doRequest , updateHeaders ) ;
5078 for ( const hook of this . hooks ) if ( hook . onResponse ) await hook . onResponse ( res , req ) ;
5179 const data = await res . json ( ) . catch ( ( ) => undefined ) ;
5280 if ( ! res . ok ) {
@@ -80,7 +108,11 @@ export class ApiService implements ApiService {
80108 body,
81109 } ;
82110 for ( const hook of this . hooks ) if ( hook . onRequest ) await hook . onRequest ( req ) ;
83- const res = await fetch ( req . url , req ) ;
111+ const updateHeaders = ( token : string ) => {
112+ req . headers = { ...( req . headers || { } ) , Authorization : `Bearer ${ token } ` } ;
113+ } ;
114+ const doRequest = ( ) => fetch ( req . url , req ) ;
115+ const res = await this . handle401AndRetry ( req , doRequest , updateHeaders ) ;
84116 for ( const hook of this . hooks ) if ( hook . onResponse ) await hook . onResponse ( res , req ) ;
85117 const respData = await res . json ( ) . catch ( ( ) => undefined ) ;
86118 if ( ! res . ok ) {
@@ -100,16 +132,16 @@ export class ApiService implements ApiService {
100132 headers : this . config . apiKey ? { 'Authorization' : `Bearer ${ this . config . apiKey } ` } : undefined ,
101133 } ;
102134 for ( const hook of this . hooks ) if ( hook . onRequest ) await hook . onRequest ( req ) ;
103- try {
104- const res = await fetch ( req . url , req ) ;
105- for ( const hook of this . hooks ) if ( hook . onResponse ) await hook . onResponse ( res , req ) ;
106- const data = await res . json ( ) . catch ( ( ) => undefined ) ;
107- if ( ! res . ok ) {
108- return { isSucceed : false , data, errors : [ res . statusText ] , status : res . status } ;
109- }
110- return { isSucceed : true , data, status : res . status } ;
111- } catch ( err ) {
112- return { isSucceed : false , errors : [ ( err as Error ) . message ] } ;
135+ const updateHeaders = ( token : string ) => {
136+ req . headers = { ...( req . headers || { } ) , Authorization : `Bearer ${ token } ` } ;
137+ } ;
138+ const doRequest = ( ) => fetch ( req . url , req ) ;
139+ const res = await this . handle401AndRetry ( req , doRequest , updateHeaders ) ;
140+ for ( const hook of this . hooks ) if ( hook . onResponse ) await hook . onResponse ( res , req ) ;
141+ const data = await res . json ( ) . catch ( ( ) => undefined ) ;
142+ if ( ! res . ok ) {
143+ return { isSucceed : false , data, errors : [ res . statusText ] , status : res . status } ;
113144 }
145+ return { isSucceed : true , data, status : res . status } ;
114146 }
115147}
0 commit comments