diff --git a/lib/application.js b/lib/application.js
index 0ae50ce..4046a92 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -51,8 +51,10 @@ Application._createApplication = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(APPLICATION_PATH), {application: item});
- request.buffer().then(res =>{
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(APPLICATION_PATH), {application: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -64,8 +66,6 @@ Application._createApplication = function(client, item, callback){
else{
client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/client.js b/lib/client.js
index a4dbca9..aaada7c 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -41,10 +41,13 @@ class Client {
// Start background token refresh if using OAuth and auto-refresh is enabled
if (this.autoRefreshToken && this.clientId && this.clientSecret && !this.tempAccessToken) {
- this.tokenReadyPromise = new Promise((resolve) => {
+ this.tokenReadyPromise = new Promise((resolve, reject) => {
this._tokenReadyResolve = resolve;
+ this._tokenReadyReject = reject;
});
- this._scheduleTokenRefresh();
+ // avoid an unhandled rejection if the fetch fails before any request awaits it
+ this.tokenReadyPromise.catch(() => {});
+ process.nextTick(() => this._refreshToken());
}
}
@@ -199,52 +202,77 @@ class Client {
return this.prepareRequest(superagent.del(this.prepareUrl(path)));
}
- makeRequest (method) {
- var callback = arguments[arguments.length - 1];
- var args = Array.prototype.slice.call(arguments, 1, arguments.length - 1);
- var self = this;
-
- const executeRequest = (isRetry = false) => {
- var request = self["create" + method[0].toUpperCase() + method.substr(1).toLowerCase() + "Request"].apply(self, args);
- request.buffer().then(res => {
- self.checkResponse(res, callback);
- }).catch(err => {
- // Check for 401 error and no retry yet
- if (!isRetry && err.status === 401 && self.autoRefreshToken && self.clientId && self.clientSecret) {
- console.log("Got 401, refreshing token and retrying...");
- self.tempAccessToken = null;
- self.tempAccessTokenExpiration = null;
-
- self.tokenReadyPromise = new Promise((resolve, reject) => {
- self._tokenReadyResolve = resolve;
- self._tokenReadyReject = reject;
- });
- self._refreshToken();
-
- self.tokenReadyPromise.then(() => {
- executeRequest(true);
- }).catch((refreshErr) => {
- console.error("Token refresh failed during retry:", refreshErr);
- return callback(err); // Return original 401
- });
- } else {
- return callback(err);
- }
- });
+ buildAuthenticatedRequest (buildRequest, callback) {
+ const build = (tokenErr) => {
+ let request;
+ try {
+ request = buildRequest();
+ } catch (err) {
+ return callback(tokenErr || err);
+ }
+ callback(null, request);
};
-
+
if (this.tokenReadyPromise) {
- this.tokenReadyPromise.then(() => {
- executeRequest();
- }).catch((err) => {
+ this.tokenReadyPromise.then(() => build(), (err) => {
console.error("Token fetch failed:", err);
- executeRequest();
+ build(err);
});
} else {
- executeRequest();
+ build();
}
}
+ sendRequest (buildRequest, callback) {
+ const execute = (isRetry) => {
+ this.buildAuthenticatedRequest(buildRequest, (err, request) => {
+ if (err) {
+ return callback(err);
+ }
+ request.buffer().then((res) => callback(null, res), (err) => {
+ if (!isRetry && this._canRefreshOn401(err)) {
+ console.log("Got 401, refreshing token and retrying...");
+ return this._forceTokenRefresh().then(() => execute(true), (refreshErr) => {
+ console.error("Token refresh failed during retry:", refreshErr);
+ callback(err);
+ });
+ }
+ callback(err);
+ });
+ });
+ };
+ execute(false);
+ }
+
+ _canRefreshOn401 (err) {
+ return err.status === 401 && this.autoRefreshToken && this.clientId && this.clientSecret;
+ }
+
+ _forceTokenRefresh () {
+ this.tempAccessToken = null;
+ this.tempAccessTokenExpiration = null;
+ const tokenReadyPromise = new Promise((resolve, reject) => {
+ this._tokenReadyResolve = resolve;
+ this._tokenReadyReject = reject;
+ });
+ this.tokenReadyPromise = tokenReadyPromise;
+ this._refreshToken();
+ return tokenReadyPromise;
+ }
+
+ makeRequest (method) {
+ const callback = arguments[arguments.length - 1];
+ const args = Array.prototype.slice.call(arguments, 1, arguments.length - 1);
+ const createRequest = this["create" + method[0].toUpperCase() + method.substr(1).toLowerCase() + "Request"];
+
+ this.sendRequest(() => createRequest.apply(this, args), (err, res) => {
+ if (err) {
+ return callback(err);
+ }
+ this.checkResponse(res, callback);
+ });
+ }
+
checkResponse (res, callback) {
let defaultHandler = function(parsedData){
if (!res.ok) {
diff --git a/lib/csrOrder.js b/lib/csrOrder.js
index 852afe4..e9c67ba 100644
--- a/lib/csrOrder.js
+++ b/lib/csrOrder.js
@@ -68,8 +68,10 @@ CsrOrder.prototype.getNotes = function(callback){
CsrOrder.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(CSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res =>{
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(CSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
diff --git a/lib/disconnect.js b/lib/disconnect.js
index cc4a69c..a8a3cfb 100644
--- a/lib/disconnect.js
+++ b/lib/disconnect.js
@@ -68,8 +68,10 @@ Disconnect.prototype.getNotes = function(callback){
Disconnect.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(DISCONNECT_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res => {
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(DISCONNECT_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -86,8 +88,6 @@ Disconnect.prototype.addNote = function(note, callback){
else{
self.client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/dlda.js b/lib/dlda.js
index c05488f..8db46f1 100644
--- a/lib/dlda.js
+++ b/lib/dlda.js
@@ -11,8 +11,10 @@ Dlda.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(DLDA_PATH), {dldaOrder: item});
- request.buffer().then(res => {
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(DLDA_PATH), {dldaOrder: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err,id){
if(err){
diff --git a/lib/importTnOrder.js b/lib/importTnOrder.js
index 2def779..975b87b 100644
--- a/lib/importTnOrder.js
+++ b/lib/importTnOrder.js
@@ -79,19 +79,26 @@ function sendFile(request, file, mediaType, callback){
else if(Buffer.isBuffer(file)){
stream = streamifier.createReadStream(file);
}
- else if(typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){
+ else if(file && typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){
stream = file;
}
- if(stream){
- request.on("response", callback);
- request.on("error", function(err, result){
- return err;
- });
- stream.pipe(request);
-
- return;
+ if(!stream){
+ return callback(new Error("Invalid data to send"));
}
- throw new Error("Invalid data to send");
+ var done = false;
+ // a 4xx/5xx emits "response" then "error"; only report the first
+ var finish = function(err, res){
+ if(!done){
+ done = true;
+ callback(err, res);
+ }
+ };
+ request.on("response", function(res){
+ finish(null, res);
+ });
+ request.on("error", finish);
+ stream.on("error", finish);
+ stream.pipe(request);
}
ImportTnOrder.prototype.createFile = function(file, mediaType, callback){
@@ -100,18 +107,25 @@ ImportTnOrder.prototype.createFile = function(file, mediaType, callback){
mediaType = null;
}
var client = this.client;
- var request = client.createPostRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH));
- sendFile(request, file, mediaType, function(res){
- client.checkResponse(res, function(err, result){
+ client.buildAuthenticatedRequest(() => client.createPostRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH)), function(err, request){
+ if(err){
+ return callback(err);
+ }
+ sendFile(request, file, mediaType, function(err, res){
if(err){
return callback(err);
}
- if (typeof result !== 'undefined') {
- return callback(null, result.filename);
- }
- else {
- return callback(null, "");
- }
+ client.checkResponse(res, function(err, result){
+ if(err){
+ return callback(err);
+ }
+ if (typeof result !== 'undefined') {
+ return callback(null, result.filename);
+ }
+ else {
+ return callback(null, "");
+ }
+ });
});
});
};
@@ -121,10 +135,17 @@ ImportTnOrder.prototype.updateFile = function(fileName, file, mediaType, callbac
callback = mediaType;
mediaType = null;
}
- var request = this.client.createPutRequest(this.client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
var client = this.client;
- sendFile(request, file, mediaType, function(res){
- client.checkResponse(res, callback);
+ client.buildAuthenticatedRequest(() => client.createPutRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)), function(err, request){
+ if(err){
+ return callback(err);
+ }
+ sendFile(request, file, mediaType, function(err, res){
+ if(err){
+ return callback(err);
+ }
+ client.checkResponse(res, callback);
+ });
});
};
@@ -150,8 +171,25 @@ ImportTnOrder.prototype.getFiles = function(metadata, callback){
});
};
-ImportTnOrder.prototype.getFile = function(fileName, destination){
- var request = this.client.createGetRequest(this.client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
+ImportTnOrder.prototype.getFile = function(fileName, destination, callback){
+ if(typeof destination === "function"){
+ callback = destination;
+ destination = null;
+ }
+ var client = this.client;
+ var buildRequest = () => client.createGetRequest(client.concatAccountPath(IMPORT_TN_ORDER_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
+ if(!callback){
+ return pipeToDestination(buildRequest(), destination);
+ }
+ client.buildAuthenticatedRequest(buildRequest, function(err, request){
+ if(err){
+ return callback(err);
+ }
+ callback(null, pipeToDestination(request, destination));
+ });
+}
+
+function pipeToDestination(request, destination){
if(destination){
var stream = null;
if(typeof destination === "string"){
diff --git a/lib/importToAccount.js b/lib/importToAccount.js
index fc54f5c..92588bf 100644
--- a/lib/importToAccount.js
+++ b/lib/importToAccount.js
@@ -13,8 +13,10 @@ ImportToAccount.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(IMPORT_TO_ACCOUNT_PATH), {importToAccountNumberOrder: item});
- request.buffer().then(res =>{
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(IMPORT_TO_ACCOUNT_PATH), {importToAccountNumberOrder: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok){
callback(null, res.body)
}else {
diff --git a/lib/lidbs.js b/lib/lidbs.js
index 5cdf028..df991fa 100644
--- a/lib/lidbs.js
+++ b/lib/lidbs.js
@@ -11,8 +11,10 @@ Lidbs.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(LIDBS_ORDER_PATH), {lidbOrder: item});
- request.buffer().then(res =>{
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(LIDBS_ORDER_PATH), {lidbOrder: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err,id){
if(err){
diff --git a/lib/lsrOrder.js b/lib/lsrOrder.js
index 9b1e27d..4220597 100644
--- a/lib/lsrOrder.js
+++ b/lib/lsrOrder.js
@@ -41,8 +41,10 @@ LsrOrder.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(LSR_ORDER_PATH), {lsrOrder: item});
- request.buffer().then(res => {
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(LSR_ORDER_PATH), {lsrOrder: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err,id){
if(err){
@@ -83,8 +85,10 @@ LsrOrder.prototype.getNotes = function(callback){
LsrOrder.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(LSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res =>{
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(LSR_ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
diff --git a/lib/order.js b/lib/order.js
index 8cf5ec2..cb0779b 100644
--- a/lib/order.js
+++ b/lib/order.js
@@ -117,8 +117,10 @@ Order.prototype.getNotes = function(callback){
Order.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(ORDER_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res => {
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(ORDER_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -135,8 +137,6 @@ Order.prototype.addNote = function(note, callback){
else{
self.client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/portIn.js b/lib/portIn.js
index 0a02477..ce0a434 100644
--- a/lib/portIn.js
+++ b/lib/portIn.js
@@ -130,8 +130,10 @@ PortIn.prototype.getNotes = function(callback){
PortIn.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res => {
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -148,8 +150,6 @@ PortIn.prototype.addNote = function(note, callback){
else{
self.client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
@@ -172,19 +172,26 @@ function sendFile(request, file, mediaType, callback){
else if(Buffer.isBuffer(file)){
stream = streamifier.createReadStream(file);
}
- else if(typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){
+ else if(file && typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function"){
stream = file;
}
- if(stream){
- request.on("response", callback);
- request.on("error", function(err, result){
- return err;
- });
- stream.pipe(request);
-
- return;
+ if(!stream){
+ return callback(new Error("Invalid data to send"));
}
- throw new Error("Invalid data to send");
+ var done = false;
+ // a 4xx/5xx emits "response" then "error"; only report the first
+ var finish = function(err, res){
+ if(!done){
+ done = true;
+ callback(err, res);
+ }
+ };
+ request.on("response", function(res){
+ finish(null, res);
+ });
+ request.on("error", finish);
+ stream.on("error", finish);
+ stream.pipe(request);
}
PortIn.prototype.createFile = function(file, mediaType, callback){
@@ -193,18 +200,25 @@ PortIn.prototype.createFile = function(file, mediaType, callback){
mediaType = null;
}
var client = this.client;
- var request = client.createPostRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH));
- sendFile(request, file, mediaType, function(res){
- client.checkResponse(res, function(err, result){
+ client.buildAuthenticatedRequest(() => client.createPostRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH)), function(err, request){
+ if(err){
+ return callback(err);
+ }
+ sendFile(request, file, mediaType, function(err, res){
if(err){
return callback(err);
}
- if (typeof result !== 'undefined') {
- return callback(null, result.filename);
- }
- else {
- return callback(null, "");
- }
+ client.checkResponse(res, function(err, result){
+ if(err){
+ return callback(err);
+ }
+ if (typeof result !== 'undefined') {
+ return callback(null, result.filename);
+ }
+ else {
+ return callback(null, "");
+ }
+ });
});
});
};
@@ -214,10 +228,17 @@ PortIn.prototype.updateFile = function(fileName, file, mediaType, callback){
callback = mediaType;
mediaType = null;
}
- var request = this.client.createPutRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
var client = this.client;
- sendFile(request, file, mediaType, function(res){
- client.checkResponse(res, callback);
+ client.buildAuthenticatedRequest(() => client.createPutRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName)), function(err, request){
+ if(err){
+ return callback(err);
+ }
+ sendFile(request, file, mediaType, function(err, res){
+ if(err){
+ return callback(err);
+ }
+ client.checkResponse(res, callback);
+ });
});
};
@@ -243,8 +264,25 @@ PortIn.prototype.getFiles = function(metadata, callback){
});
};
-PortIn.prototype.getFile = function(fileName, destination){
- var request = this.client.createGetRequest(this.client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
+PortIn.prototype.getFile = function(fileName, destination, callback){
+ if(typeof destination === "function"){
+ callback = destination;
+ destination = null;
+ }
+ var client = this.client;
+ var buildRequest = () => client.createGetRequest(client.concatAccountPath(PORT_IN_PATH + "/" + this.id + "/" + LOAS_PATH + "/" + fileName));
+ if(!callback){
+ return pipeToDestination(buildRequest(), destination);
+ }
+ client.buildAuthenticatedRequest(buildRequest, function(err, request){
+ if(err){
+ return callback(err);
+ }
+ callback(null, pipeToDestination(request, destination));
+ });
+}
+
+function pipeToDestination(request, destination){
if(destination){
var stream = null;
if(typeof destination === "string"){
diff --git a/lib/portOut.js b/lib/portOut.js
index cc86318..91b63b3 100644
--- a/lib/portOut.js
+++ b/lib/portOut.js
@@ -49,8 +49,10 @@ PortOut.prototype.getNotes = function(callback){
PortOut.prototype.addNote = function(note, callback){
var self = this;
- var request = this.client.createPostRequest(this.client.concatAccountPath(PORT_OUT_PATH + "/" + this.id + "/notes"), {note: note});
- request.buffer().then(res =>{
+ this.client.sendRequest(() => this.client.createPostRequest(this.client.concatAccountPath(PORT_OUT_PATH + "/" + this.id + "/notes"), {note: note}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -67,8 +69,6 @@ PortOut.prototype.addNote = function(note, callback){
else{
self.client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/site.js b/lib/site.js
index 7aeb26d..c2d3055 100644
--- a/lib/site.js
+++ b/lib/site.js
@@ -52,8 +52,10 @@ Site.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(SITE_PATH), {site: item});
- request.buffer().then(res =>{
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(SITE_PATH), {site: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -65,8 +67,6 @@ Site.create = function(client, item, callback){
else{
client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
@@ -123,8 +123,10 @@ Site.prototype.getSipPeer = function(id, callback){
Site.prototype.createSipPeer = function(item, callback){
var client = this.client;
var self = this;
- var request = client.createPostRequest( this.client.concatAccountPath(SITE_PATH) + "/" + this.id + "/" + SIP_PEER_PATH, {sipPeer: item});
- request.buffer().then(res =>{
+ client.sendRequest(() => client.createPostRequest(this.client.concatAccountPath(SITE_PATH) + "/" + this.id + "/" + SIP_PEER_PATH, {sipPeer: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -136,8 +138,6 @@ Site.prototype.createSipPeer = function(item, callback){
else{
client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/subscription.js b/lib/subscription.js
index c808e30..5f93763 100644
--- a/lib/subscription.js
+++ b/lib/subscription.js
@@ -57,8 +57,10 @@ Subscription.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(SUBSCRIPTION_PATH), {subscription: item});
- request.buffer().then(res => {
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(SUBSCRIPTION_PATH), {subscription: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -70,8 +72,6 @@ Subscription.create = function(client, item, callback){
else{
client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/lib/tnOption.js b/lib/tnOption.js
index cc50165..085a76f 100644
--- a/lib/tnOption.js
+++ b/lib/tnOption.js
@@ -61,7 +61,6 @@ TnOption.create = function(client, order, callback){
group.telephoneNumbers = {telephoneNumber: group.telephoneNumbers}
});
- var request = client.createPostRequest(client.concatAccountPath(TN_OPTION_PATH), {tnOptionOrder: order});
client.makeRequest("post", client.concatAccountPath(TN_OPTION_PATH), {tnOptionOrder: order}, function(err, item){
if(err){
return callback(err);
diff --git a/lib/tnReservation.js b/lib/tnReservation.js
index 107f2d2..627eb08 100644
--- a/lib/tnReservation.js
+++ b/lib/tnReservation.js
@@ -29,8 +29,10 @@ TnReservation.create = function(client, item, callback){
item = client;
client = new Client();
}
- var request = client.createPostRequest(client.concatAccountPath(TN_RESERVATION_PATH), {reservation: item});
- request.buffer().then(res => {
+ client.sendRequest(() => client.createPostRequest(client.concatAccountPath(TN_RESERVATION_PATH), {reservation: item}), function(err, res){
+ if(err){
+ return callback(err);
+ }
if(res.ok && res.headers.location){
Client.getIdFromLocationHeader(res.headers.location, function(err, id){
if(err){
@@ -42,8 +44,6 @@ TnReservation.create = function(client, item, callback){
else{
client.checkResponse(res, callback);
}
- }).catch(err => {
- return callback(err);
});
};
diff --git a/test/client.js b/test/client.js
index b3f4aeb..5b224cf 100644
--- a/test/client.js
+++ b/test/client.js
@@ -418,6 +418,81 @@ describe("client tests", function(){
});
})
});
+ describe("OAuth token handling", function(){
+ var client;
+ afterEach(function(){
+ client.destroy();
+ nock.cleanAll();
+ });
+ it("should wait for the token before making a request", function(done){
+ helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600});
+ helper.nock().matchHeader("authorization", "Bearer fake_token").get("/test")
+ .reply(200, "test", {"Content-Type": "application/xml"});
+ client = helper.createOAuthClient();
+ client.makeRequest("get", "/test", function(err, r){
+ if(err){
+ return done(err);
+ }
+ r.test.should.equal("test");
+ done();
+ });
+ });
+ it("should fail with the token error if token fetch fails and no other auth is available", function(done){
+ helper.nockToken().reply(401);
+ client = helper.createOAuthClient();
+ client.makeRequest("get", "/test", function(err){
+ if(!err){
+ return done(new Error("An error is expected"));
+ }
+ err.status.should.equal(401);
+ done();
+ });
+ });
+ it("should fall back to basic auth if token fetch fails", function(done){
+ helper.nockToken().reply(401);
+ helper.nock().matchHeader("authorization", /^Basic /).get("/test").reply(200);
+ client = new Client("FakeAccountId", "FakeUserName", "FakePassword", "FakeClientId", "FakeClientSecret");
+ client.makeRequest("get", "/test", done);
+ });
+ it("should refresh the token and retry once on 401", function(done){
+ helper.nockToken().reply(200, {access_token: "expired_token", expires_in: 3600});
+ helper.nockToken().reply(200, {access_token: "fresh_token", expires_in: 3600});
+ helper.nock().matchHeader("authorization", "Bearer expired_token").get("/test").reply(401);
+ helper.nock().matchHeader("authorization", "Bearer fresh_token").get("/test")
+ .reply(200, "test", {"Content-Type": "application/xml"});
+ client = helper.createOAuthClient();
+ client.makeRequest("get", "/test", function(err, r){
+ if(err){
+ return done(err);
+ }
+ r.test.should.equal("test");
+ done();
+ });
+ });
+ it("should not retry more than once on 401", function(done){
+ helper.nockToken().times(2).reply(200, {access_token: "fake_token", expires_in: 3600});
+ helper.nock().get("/test").times(2).reply(401);
+ client = helper.createOAuthClient();
+ client.makeRequest("get", "/test", function(err){
+ if(!err){
+ return done(new Error("An error is expected"));
+ }
+ err.status.should.equal(401);
+ done();
+ });
+ });
+ });
+ describe("#buildAuthenticatedRequest", function(){
+ it("should pass auth errors to the callback instead of throwing", function(done){
+ var client = new Client("FakeAccountId", null, null, null, null);
+ client.buildAuthenticatedRequest(function(){
+ return client.createGetRequest("/test");
+ }, function(err){
+ err.message.should.equal("No authentication method available");
+ done();
+ });
+ });
+ });
describe("#concatAccountPath", function(){
it("should return formatted url", function(){
var client = new Client("accountId");
diff --git a/test/dlda.js b/test/dlda.js
index bbb337e..fe61f03 100644
--- a/test/dlda.js
+++ b/test/dlda.js
@@ -53,6 +53,15 @@ describe("Dlda", function(){
done();
});
});
+ it("should fail on error status code", function(done){
+ helper.nock().post("/accounts/FakeAccountId/dldas").reply(400);
+ Dlda.create(helper.createClient(), {}, function(err){
+ if(err){
+ return done();
+ }
+ done(new Error("An error is expected"));
+ });
+ });
});
describe("#getHistory", function(){
it("should return history", function(done){
diff --git a/test/helper.js b/test/helper.js
index ee1b6f3..7e1f00b 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -12,6 +12,14 @@ module.exports = {
return new Client("FakeAccountId", "FakeUserName", "FakePassword");
},
+ createOAuthClient: function(){
+ return new Client("FakeAccountId", null, null, "FakeClientId", "FakeClientSecret");
+ },
+
+ nockToken: function(){
+ return nock("https://api.bandwidth.com").post("/api/v1/oauth2/token", "grant_type=client_credentials");
+ },
+
buildXml: function(obj){
return this.createClient().buildXml(obj);
},
diff --git a/test/portIn.js b/test/portIn.js
index 449a42a..a158e95 100644
--- a/test/portIn.js
+++ b/test/portIn.js
@@ -388,6 +388,42 @@ describe("PortIn", function(){
});
});
});
+ it("should download file to destination file via callback with OAuth client credentials only", function(done){
+ nock.cleanAll();
+ helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600});
+ helper.nock().matchHeader("authorization", "Bearer fake_token").get("/accounts/FakeAccountId/portins/1/loas/file.txt").reply(200, "12345", {"Content-Type": "text/plain"});
+ var order = new PortIn();
+ order.id = 1;
+ order.client = helper.createOAuthClient();
+ order.getFile("file.txt", tmpFile, function(err, stream){
+ if(err){
+ return done(err);
+ }
+ stream.on("finish", function(){
+ order.client.destroy();
+ fs.readFile(tmpFile, "utf8", function(err, text){
+ if(err){
+ done(err);
+ }
+ text.should.equal("12345");
+ done();
+ });
+ });
+ });
+ });
+ it("should fail via callback if no auth method available", function(done){
+ fs.writeFileSync(tmpFile, "");
+ var order = new PortIn();
+ order.id = 1;
+ order.client = new lib.Client("FakeAccountId", null, null, null, null);
+ order.getFile("file.txt", function(err){
+ if(err){
+ err.message.should.equal("No authentication method available");
+ return done();
+ }
+ done(new Error("An error was expected"));
+ });
+ });
});
describe("#createFile", function(){
var order, tmpFile = path.join(os.tmpdir(), "dest.txt");
@@ -450,6 +486,40 @@ describe("PortIn", function(){
done();
});
});
+ it("should upload file to the server with OAuth client credentials only", function(done){
+ nock.cleanAll();
+ helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600});
+ helper.nock().matchHeader("authorization", "Bearer fake_token").post("/accounts/FakeAccountId/portins/1/loas", "12345").reply(200, helper.xml.fileCreated, {"Content-Type": "application/xml"});
+ order.client = helper.createOAuthClient();
+ order.createFile(new Buffer.from("12345", "utf8"), "text/plain", function(err, fileName){
+ order.client.destroy();
+ if(err){
+ return done(err);
+ }
+ fileName.should.equal("test.txt");
+ done();
+ });
+ });
+ it("should fail on invalid data", function(done){
+ order.createFile(12345, "text/plain", function(err){
+ if(err){
+ err.message.should.equal("Invalid data to send");
+ return done();
+ }
+ done(new Error("An error was expected"));
+ });
+ });
+ it("should fail on network error", function(done){
+ nock.cleanAll();
+ helper.nock().post("/accounts/FakeAccountId/portins/1/loas").replyWithError({code: "ECONNRESET", message: "socket hang up"});
+ order.createFile(new Buffer.from("12345", "utf8"), "text/plain", function(err){
+ if(err){
+ err.code.should.equal("ECONNRESET");
+ return done();
+ }
+ done(new Error("An error was expected"));
+ });
+ });
});
describe("#updateFile", function(){
var order, tmpFile = path.join(os.tmpdir(), "dest.txt");
diff --git a/test/tnOption.js b/test/tnOption.js
index 89ded82..6b5d62f 100644
--- a/test/tnOption.js
+++ b/test/tnOption.js
@@ -221,5 +221,28 @@ describe("TnOption", function(){
done(new Error("An error is expected"));
});
});
+
+ it("should create with OAuth client credentials only", function(done){
+ const userInput = {
+ customerOrderId: 'myOrderId',
+ tnOptionGroups: [
+ {
+ sms: 'on',
+ telephoneNumbers: ['2018551020']
+ }
+ ]
+ }
+ const client = helper.createOAuthClient();
+ helper.nockToken().reply(200, {access_token: "fake_token", expires_in: 3600});
+ helper.nock().matchHeader("authorization", "Bearer fake_token").post("/accounts/FakeAccountId/tnoptions").reply(201, helper.xml.tnOptionOrderResponse, {"Location": "/accounts/FakeAccountId/tnoptions/1"});
+ TnOption.create(client, userInput, function(err, item){
+ client.destroy();
+ if(err){
+ return done(err);
+ }
+ item.createdByUser.should.equal('user');
+ done();
+ });
+ });
});
});