trello-node-api
Version:
Trello Node API wrapper
191 lines (163 loc) • 5.66 kB
JavaScript
;
var Buffer = require('safe-buffer').Buffer;
var qs = require('qs');
var crypto = require('crypto');
var hasOwn = {}.hasOwnProperty;
var isPlainObject = require('lodash.isplainobject');
var OPTIONS_KEYS = ['api_key'];
var utils = module.exports = {
isAuthKey: function (key) {
return typeof key == 'string' && /^(?:[a-z]{2}_)?[A-z0-9]{32}$/.test(key);
},
isOptionsHash: function (o) {
return isPlainObject(o) && OPTIONS_KEYS.some(function (key) {
return hasOwn.call(o, key);
});
},
/**
* Stringifies an Object, accommodating nested objects
* (forming the conventional key 'parent[child]=value')
*/
stringifyRequestData: function (data) {
return qs.stringify(data, {arrayFormat: 'brackets'});
},
/**
* Outputs a new function with interpolated object property values.
* Use like so:
* var fn = makeURLInterpolator('some/url/{param1}/{param2}');
* fn({ param1: 123, param2: 456 }); // => 'some/url/123/456'
*/
makeURLInterpolator: (function () {
var rc = {
'\n': '\\n', '\"': '\\\"',
'\u2028': '\\u2028', '\u2029': '\\u2029'
};
return function makeURLInterpolator(str) {
var cleanString = str.replace(/["\n\r\u2028\u2029]/g, function ($0) {
return rc[$0];
});
return function (outputs) {
return cleanString.replace(/\{([\s\S]+?)\}/g, function ($0, $1) {
return encodeURIComponent(outputs[$1] || '');
});
};
};
}()),
/**
* Return the data argument from a list of arguments
*/
getDataFromArgs: function (args) {
if (args.length < 1 || !isPlainObject(args[0])) {
return {};
}
if (!utils.isOptionsHash(args[0])) {
return args.shift();
}
var argKeys = Object.keys(args[0]);
var optionKeysInArgs = argKeys.filter(function (key) {
return OPTIONS_KEYS.indexOf(key) > -1;
});
// In some cases options may be the provided as the first argument.
// Here we're detecting a case where there are two distinct arguments
// (the first being args and the second options) and with known
// option keys in the first so that we can warn the user about it.
if (optionKeysInArgs.length > 0 && optionKeysInArgs.length !== argKeys.length) {
console.warn(
'Trello: Options found in arguments (' + optionKeysInArgs.join(', ') + '). Did you mean to pass an options ' +
'object?'
);
}
return {};
},
/**
* Return the options hash from a list of arguments
*/
getOptionsFromArgs: function (args) {
var opts = {
auth: null,
headers: {}
};
if (args.length > 0) {
var arg = args[args.length - 1];
if (utils.isAuthKey(arg)) {
opts.auth = args.pop();
} else if (utils.isOptionsHash(arg)) {
var params = args.pop();
if (params.api_key) {
opts.auth = params.api_key;
}
if (params.trello_version) {
opts.headers['Trello-Version'] = params.trello_version;
}
}
}
return opts;
},
/**
* Provide simple "Class" extension mechanism
*/
protoExtend: function (sub) {
var Super = this;
var Constructor = hasOwn.call(sub, 'constructor') ? sub.constructor : function () {
Super.apply(this, arguments);
};
// This initialization logic is somewhat sensitive to be compatible with
// divergent JS implementations like the one found in Qt. See here for more
// context:
//
Object.assign(Constructor, Super);
Constructor.prototype = Object.create(Super.prototype);
Object.assign(Constructor.prototype, sub);
return Constructor;
},
/**
* Convert an array into an object with integer string attributes
*/
arrayToObject: function (arr) {
if (Array.isArray(arr)) {
var obj = {};
arr.map(function (item, i) {
obj[i.toString()] = item;
});
return obj;
}
return arr;
},
/**
* Secure compare, from https://github.com/freewil/scmp
*/
secureCompare: function (a, b) {
var a = Buffer.from(a);
var b = Buffer.from(b);
// return early here if buffer lengths are not equal since timingSafeEqual
// will throw if buffer lengths are not equal
if (a.length !== b.length) {
return false;
}
// use crypto.timingSafeEqual if available (since Node.js v6.6.0),
// otherwise use our own scmp-internal function.
if (crypto.timingSafeEqual) {
return crypto.timingSafeEqual(a, b);
}
var len = a.length;
var result = 0;
for (var i = 0; i < len; ++i) {
result |= a[i] ^ b[i];
}
return result === 0;
},
/**
* Remove empty values from an object
*/
removeEmpty: function (obj) {
if (typeof obj !== 'object') {
throw new Error('Argument must be an object');
}
Object.keys(obj).forEach(function (key) {
if (obj[key] === null || obj[key] === undefined) {
delete obj[key];
}
});
return obj;
}
};