appnexus-node-api
Version:
Appnexus client for node.js
387 lines (359 loc) • 11.9 kB
JavaScript
'use strict';
var http = require('http'),
debug = require('debug')('appnexus-api'),
util = require('util'),
https = require('https'),
appnexusUtil = require('./appnexus-util'),
events = require('events'),
RateLimiter = require('limiter').RateLimiter,
writeLimiter = new RateLimiter(60, 'second'),
readLimiter = new RateLimiter(100, 'second'),
authLimiter = new RateLimiter(10, 5 * 60 * 1000),
requestRaw = require('request').defaults({
json: false
}),
request = require('request').defaults({
json: true
});
function buildHTTPServer(listener) {
var app = http.createServer(listener),
addr = app.address(),
port, protocol;
if (!addr) {
app.listen(0);
}
port = app.address().port;
protocol = app instanceof https.Server ? 'https' : 'http';
return protocol + '://127.0.0.1:' + port;
}
function endpoint(resource) {
return ('function' === typeof resource) ? buildHTTPServer(resource) : resource;
}
function Appnexus(options) {
options = options || {};
if (!options.endpoint) {
throw new Error('"endpoint" can not be empty or null');
}
options.endpoint = endpoint(options.endpoint);
if (!options.username) {
throw new Error('"user" can not be empty or null');
}
if (!options.password) {
throw new Error('"password" can not be empty or null');
}
events.EventEmitter.call(this);
this.endpoint = endpoint(options.endpoint);
this.username = options.username;
this.password = options.password;
}
util.inherits(Appnexus, events.EventEmitter);
function sanitizeQueryOptions(options) {
options.json = options.json || true;
options.qs = options.qs || {};
for(var op in options){
if(op != 'json' && ~['boolean', 'string', 'number'].indexOf(typeof options[op])){
options.qs[op] = options[op];
}
}
}
function eventForChange(type, method) {
var methodToChangeMap = {
'POST': 'Created',
'PUT': 'Updated',
'DELETE': 'Deleted'
}, typeOfChange = methodToChangeMap[method];
if (typeOfChange) {
return type + typeOfChange;
}
return false;
}
Appnexus.prototype.query = function (type, options, callback) {
var client = this;
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
sanitizeQueryOptions(options);
options.headers = {
'Authorization': client.token
};
options.url = options.url || client.endpoint + '/' + type;
request(options, function (err, response, body) {
if (debug.enabled) {
if (this.body) {
var buffer = new Buffer(this.body);
var objectSent = JSON.stringify(JSON.parse(buffer.toString()), null, '\t');
debug("request %s %s sent: \n %s", this.method, this.href, objectSent);
}
} else {
debug("request %s %s", this.method, this.href);
}
var ret;
if (err) {
return callback(err);
}
if (appnexusUtil.responseContainsError(response, body)) {
return appnexusUtil.handleErrorResponse(body, client, callback);
}
ret = body;
ret = body.response && body.response.id || ret;
ret = body.response && body.response[type] || ret;
callback(null, ret);
if (eventForChange(type, options.method)) {
client.emit(eventForChange(type, options.method), ret);
}
});
};
Appnexus.prototype.queryRaw = function (type, options, callback) {
var client = this;
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
sanitizeQueryOptions(options);
options.headers = {
'Authorization': client.token
};
options.url = options.url || client.endpoint + '/' + type;
requestRaw(options, function (err, response, body) {
if (debug.enabled) {
if (this.body) {
var buffer = new Buffer(this.body);
var objectSent = JSON.stringify(JSON.parse(buffer.toString()), null, '\t');
debug("request %s %s sent: \n %s", this.method, this.href, objectSent);
}
} else {
debug("request %s %s", this.method, this.href);
}
var ret;
if (err) {
return callback(err);
}
ret = body;
callback(null, ret);
});
};
Appnexus.prototype.queryStream = function (type, options, callback) {
var client = this;
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
sanitizeQueryOptions(options);
options.headers = {
'Authorization': client.token,
'accept-encoding': 'gzip,deflate'
};
options.url = options.url || client.endpoint + '/' + type;
callback(null, requestRaw(options));
};
/**
* HTTP VERBS
*/
function id2options(id, options) {
console.log(id);
console.log(options);
if (!id) {
return options;
}
options = options || {};
if (~['string', 'number'].indexOf(typeof id)) {
options.id = id;
return options;
}
return id;
}
function string2options(name, str, options) {
if (!str) {
return options;
}
options = options || {};
if (typeof str == 'string') {
options[name] = str;
return options;
}
return id;
}
Appnexus.prototype.get = function (type, id, options, callback) {
var self = this;
if (id instanceof Object && typeof options === 'function') { //no id. caller passed ("type", options, callback)
callback = options;
options = id;
id = null;
}
else if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", id, callback)
callback = options;
options = null;
}
readLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(type, id2options(id, options), callback);
});
};
Appnexus.prototype.search = function (type, term, options, callback) {
var self = this;
if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", "term", callback)
callback = options;
options = null;
}
readLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(type, string2options("search", term, options), callback);
});
};
Appnexus.prototype.create = function (type, data, options, callback) {
var json = {}, self = this;
if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", data, callback)
callback = options;
options = null;
}
json[type] = data;
options = options || {};
options.json = json;
options.method = 'POST';
writeLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(type, options, callback);
});
};
Appnexus.prototype.destroy = function (type, id, options, callback) {
if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", id, callback)
callback = options;
options = null;
}
options = id2options(id, options);
options.json = true;
options.method = 'DELETE';
var self = this;
writeLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(type, options, callback);
});
};
Appnexus.prototype.update = function (type, id, data, options, callback) {
var json = {}, options, self = this;
if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", id, data, callback)
callback = options;
options = null;
}
json[type] = data;
options = id2options(id, options);
options.json = json;
options.method = 'PUT';
// id can be a string or number, but some operations can require more
// than one identifier, like modifying an existing advertiser segment
// which requires both *code* and *member_id*
writeLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(type, options, callback);
});
};
Appnexus.prototype.authenticate = function (callback) {
var url = this.endpoint + '/auth',
payload = {
auth: {
username: this.username,
password: this.password
}
}, that = this;
authLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
request.post({
url: url,
json: payload
}, function (err, response, body) {
var token;
if (err) {
return callback(err);
}
if (appnexusUtil.responseContainsError(response, body)) {
return appnexusUtil.handleErrorResponse(body, that, callback);
}
token = body.response.token;
if (callback) {
callback(null, token);
}
that.emit('authenticationSucceed', token);
});
});
};
Appnexus.prototype.getRaw = function (type, id, options, callback) {
var self = this;
if (typeof id === 'object' && typeof options === 'function') { //no id. caller passed ("type", options, callback)
callback = options;
options = id;
id = null;
}
else if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", id, callback)
callback = options;
options = null;
}
readLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.queryRaw(type, id2options(id,options), callback);
});
};
Appnexus.prototype.getStream = function (type, id, options, callback) {
var self = this;
if (typeof id === 'object' && typeof options === 'function') { //no id. caller passed ("type", options, callback)
callback = options;
options = id;
id = null;
}
else if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", id, callback)
callback = options;
options = null;
}
readLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.queryStream(type, id2options(id,options), callback);
});
};
Appnexus.prototype.custom = function (method, endpoint, data, options, callback) {
var self = this;
if (typeof callback === 'undefined' && typeof options === 'function') { //no options. caller passed ("type", data, callback)
callback = options;
options = null;
}
options = options || {};
options.json = data;
options.method = method;
writeLimiter.removeTokens(1, function (err, remainingRequests) {
if (err) {
this.emit('error', err);
return;
}
self.query(endpoint, options, callback);
});
};
function create(options) {
return new Appnexus(options);
}
exports.create = create;