77 findDescendants,
88 prepareToBuildXml,
99 transformParsedObject,
10- customTags,
11- setAuth
10+ customTags
1211} = require ( "./clientUtils" ) ;
1312
1413class Client {
@@ -19,7 +18,7 @@ class Client {
1918 clientId = Client . globalOptions . clientId ,
2019 clientSecret = Client . globalOptions . clientSecret ,
2120 options = { }
22- ) {
21+ ) {
2322 if ( ! options . apiEndPoint ) {
2423 options . apiEndPoint = Client . globalOptions . apiEndPoint ;
2524 }
@@ -31,11 +30,104 @@ class Client {
3130 this . apiEndPoint = options . apiEndPoint ;
3231 this . tempAccessToken = options . tempAccessToken || null ;
3332 this . tempAccessTokenExpiration = options . tempAccessTokenExpiration || null ;
33+ this . tokenRefreshTimer = null ;
34+ this . tokenReadyPromise = null ;
35+ this . autoRefreshToken = options . autoRefreshToken !== false ;
3436 this . xml2jsParserOptions = {
3537 explicitArray : false ,
3638 tagNameProcessors : [ xml2js . processors . firstCharLowerCase , customTags ] ,
3739 async : true
3840 } ;
41+
42+ // Start background token refresh if using OAuth and auto-refresh is enabled
43+ if ( this . autoRefreshToken && this . clientId && this . clientSecret && ! this . tempAccessToken ) {
44+ this . tokenReadyPromise = new Promise ( ( resolve ) => {
45+ this . _tokenReadyResolve = resolve ;
46+ } ) ;
47+ this . _scheduleTokenRefresh ( ) ;
48+ }
49+ }
50+
51+ _scheduleTokenRefresh ( ) {
52+ if ( this . tokenRefreshTimer ) {
53+ clearTimeout ( this . tokenRefreshTimer ) ;
54+ this . tokenRefreshTimer = null ;
55+ }
56+
57+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
58+ let refreshIn ;
59+
60+ if ( this . tempAccessToken && this . tempAccessTokenExpiration ) {
61+ const expiresIn = this . tempAccessTokenExpiration - now ;
62+ refreshIn = Math . max ( ( expiresIn - 300 ) * 1000 , 0 ) ; // 5min before
63+ } else {
64+ refreshIn = 0 ;
65+ }
66+
67+ this . tokenRefreshTimer = setTimeout ( ( ) => {
68+ this . _refreshToken ( ) ;
69+ } , refreshIn ) ;
70+
71+ // unref() so timer doesn't keep process alive
72+ if ( this . tokenRefreshTimer . unref ) {
73+ this . tokenRefreshTimer . unref ( ) ;
74+ }
75+ }
76+
77+ _refreshToken ( ) {
78+ if ( ! this . clientId || ! this . clientSecret ) {
79+ if ( this . _tokenReadyReject ) {
80+ this . _tokenReadyReject ( new Error ( "No OAuth credentials configured" ) ) ;
81+ this . _tokenReadyReject = null ;
82+ this . _tokenReadyResolve = null ;
83+ }
84+ return ;
85+ }
86+ const now = Math . floor ( Date . now ( ) / 1000 ) ;
87+
88+ superagent
89+ . post ( 'https://api.bandwidth.com/api/v1/oauth2/token' )
90+ . type ( 'form' )
91+ . auth ( this . clientId , this . clientSecret )
92+ . send ( { grant_type : 'client_credentials' } )
93+ . then ( ( tokenResponse ) => {
94+ this . tempAccessToken = tokenResponse . body . access_token ;
95+ this . tempAccessTokenExpiration = now + tokenResponse . body . expires_in ;
96+
97+ if ( this . _tokenReadyResolve ) {
98+ this . _tokenReadyResolve ( ) ;
99+ this . _tokenReadyResolve = null ;
100+ this . _tokenReadyReject = null ;
101+ this . tokenReadyPromise = null ;
102+ }
103+
104+ this . _scheduleTokenRefresh ( ) ;
105+ } )
106+ . catch ( ( err ) => {
107+ console . error ( "Token refresh failed:" , err . message ) ;
108+
109+ if ( this . _tokenReadyReject ) {
110+ this . _tokenReadyReject ( err ) ;
111+ this . _tokenReadyReject = null ;
112+ this . _tokenReadyResolve = null ;
113+ this . tokenReadyPromise = null ;
114+ }
115+
116+ this . tokenRefreshTimer = setTimeout ( ( ) => {
117+ this . _refreshToken ( ) ;
118+ } , 30000 ) ;
119+
120+ if ( this . tokenRefreshTimer . unref ) {
121+ this . tokenRefreshTimer . unref ( ) ;
122+ }
123+ } ) ;
124+ }
125+
126+ destroy ( ) {
127+ if ( this . tokenRefreshTimer ) {
128+ clearTimeout ( this . tokenRefreshTimer ) ;
129+ this . tokenRefreshTimer = null ;
130+ }
39131 }
40132
41133 static getIdFromHeader ( header , callback ) {
@@ -56,20 +148,9 @@ class Client {
56148 callback ( null , id ) ;
57149 }
58150
59- async prepareRequest ( req ) {
60- const now = Math . floor ( Date . now ( ) / 1000 ) ;
61- if ( this . tempAccessToken && ( ! this . tempAccessTokenExpiration || this . tempAccessTokenExpiration > now + 60 ) ) {
151+ prepareRequest ( req ) {
152+ if ( this . tempAccessToken ) {
62153 req . auth ( this . tempAccessToken , { type : "bearer" } ) ;
63- } else if ( this . clientId && this . clientSecret ) {
64- const tokenResponse = await superagent
65- . post ( 'https://api.bandwidth.com/api/v1/oauth2/token' )
66- . type ( 'form' )
67- . auth ( this . clientId , this . clientSecret )
68- . send ( { grant_type : 'client_credentials' } ) ;
69-
70- req . auth ( tokenResponse . body . access_token , { type : "bearer" } ) ;
71- this . tempAccessToken = tokenResponse . body . access_token ;
72- this . tempAccessTokenExpiration = now + tokenResponse . body . expires_in ;
73154 } else if ( this . userName && this . password ) {
74155 req . auth ( this . userName , this . password ) ;
75156 } else {
@@ -86,19 +167,19 @@ class Client {
86167 return this . apiEndPoint + fixPath ( path ) ;
87168 }
88169
89- async createGetRequest ( path , query , id ) {
170+ createGetRequest ( path , query , id ) {
90171 if ( id ) {
91172 path = `${ path } /${ id } ` ;
92173 }
93- let request = await this . prepareRequest ( superagent . get ( this . prepareUrl ( path ) ) ) ;
174+ let request = this . prepareRequest ( superagent . get ( this . prepareUrl ( path ) ) ) ;
94175 if ( query ) {
95176 return request . query ( query ) ;
96177 }
97178 return request ;
98179 }
99180
100- async _createPostOrPutRequest ( method , path , data ) {
101- const request = await this . prepareRequest ( superagent [ method ] ( this . prepareUrl ( path ) ) ) ;
181+ _createPostOrPutRequest ( method , path , data ) {
182+ const request = this . prepareRequest ( superagent [ method ] ( this . prepareUrl ( path ) ) ) ;
102183 if ( data ) {
103184 const requestBody = this . buildXml ( data ) ;
104185 return request . send ( requestBody ) . type ( "application/xml" ) ;
@@ -114,19 +195,53 @@ class Client {
114195 return this . _createPostOrPutRequest ( "put" , path , data ) ;
115196 }
116197
117- async createDeleteRequest ( path ) {
118- return await this . prepareRequest ( superagent . del ( this . prepareUrl ( path ) ) ) ;
198+ createDeleteRequest ( path ) {
199+ return this . prepareRequest ( superagent . del ( this . prepareUrl ( path ) ) ) ;
119200 }
120201
121- async makeRequest ( method ) {
202+ makeRequest ( method ) {
122203 var callback = arguments [ arguments . length - 1 ] ;
123204 var args = Array . prototype . slice . call ( arguments , 1 , arguments . length - 1 ) ;
124- try {
125- var request = await this [ "create" + method [ 0 ] . toUpperCase ( ) + method . substr ( 1 ) . toLowerCase ( ) + "Request" ] . apply ( this , args ) ;
126- const res = await request ;
127- this . checkResponse ( res , callback ) ;
128- } catch ( err ) {
129- return callback ( err ) ;
205+ var self = this ;
206+
207+ const executeRequest = ( isRetry = false ) => {
208+ var request = self [ "create" + method [ 0 ] . toUpperCase ( ) + method . substr ( 1 ) . toLowerCase ( ) + "Request" ] . apply ( self , args ) ;
209+ request . buffer ( ) . then ( res => {
210+ self . checkResponse ( res , callback ) ;
211+ } ) . catch ( err => {
212+ // Check for 401 error and no retry yet
213+ if ( ! isRetry && err . status === 401 && self . autoRefreshToken && self . clientId && self . clientSecret ) {
214+ console . log ( "Got 401, refreshing token and retrying..." ) ;
215+ self . tempAccessToken = null ;
216+ self . tempAccessTokenExpiration = null ;
217+
218+ self . tokenReadyPromise = new Promise ( ( resolve , reject ) => {
219+ self . _tokenReadyResolve = resolve ;
220+ self . _tokenReadyReject = reject ;
221+ } ) ;
222+ self . _refreshToken ( ) ;
223+
224+ self . tokenReadyPromise . then ( ( ) => {
225+ executeRequest ( true ) ;
226+ } ) . catch ( ( refreshErr ) => {
227+ console . error ( "Token refresh failed during retry:" , refreshErr ) ;
228+ return callback ( err ) ; // Return original 401
229+ } ) ;
230+ } else {
231+ return callback ( err ) ;
232+ }
233+ } ) ;
234+ } ;
235+
236+ if ( this . tokenReadyPromise ) {
237+ this . tokenReadyPromise . then ( ( ) => {
238+ executeRequest ( ) ;
239+ } ) . catch ( ( err ) => {
240+ console . error ( "Token fetch failed:" , err ) ;
241+ executeRequest ( ) ;
242+ } ) ;
243+ } else {
244+ executeRequest ( ) ;
130245 }
131246 }
132247
@@ -142,8 +257,8 @@ class Client {
142257 return callback ( null ) ;
143258 }
144259
145- else if ( typeof res . body !== "undefined" && res . body . length > 0 ) {
146- this . parseXml ( res . body , function ( err , r ) {
260+ else if ( typeof res . text !== "undefined" && res . text . length > 0 ) {
261+ this . parseXml ( res . text , function ( err , r ) {
147262 if ( err ) {
148263 return callback ( err ) ;
149264 }
0 commit comments