Skip to content

Commit a57ac4a

Browse files
committed
use background token task since aysnc breaks everything
1 parent b1ad3c6 commit a57ac4a

File tree

1 file changed

+146
-31
lines changed

1 file changed

+146
-31
lines changed

lib/client.js

Lines changed: 146 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ const {
77
findDescendants,
88
prepareToBuildXml,
99
transformParsedObject,
10-
customTags,
11-
setAuth
10+
customTags
1211
} = require("./clientUtils");
1312

1413
class 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

Comments
 (0)