From 6d27679d99bd5ba9e1df6b5f88be3d131ae3749c Mon Sep 17 00:00:00 2001 From: Jublo Date: Sat, 18 Apr 2015 12:22:40 +0200 Subject: [PATCH 01/48] [DOC] Explain errors during onclick handlers --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 775a8d0..200f490 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,20 @@ cb.__call( ); ``` +:warning: Codebird server calls do not always go through when +being processed in a hyperlink onclick handler. Be sure to cancel +the default procedure before calling Codebird, like this (jQuery): + +```javascript +$(function() { + + $('#auth').click(function(e) { + e.preventDefault(); + + var cb = new Codebird; +// ... +``` + Now you need to add a PIN box to your website. After the user enters the PIN, complete the authentication: From 8ee76fb61a5831632c48227cfc32f611e84d42c0 Mon Sep 17 00:00:00 2001 From: Hamza EZZI Date: Sun, 6 Sep 2015 19:00:45 +0200 Subject: [PATCH 02/48] Function to be called upon a error response Add a callback when an error response detected like timeout or no internet connection --- codebird.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codebird.js b/codebird.js index baeaddd..d234c9e 100644 --- a/codebird.js +++ b/codebird.js @@ -676,6 +676,12 @@ var Codebird = function () { callback(reply); } }; + // function called when an error occurs, including a timeout + xml.onerror = function(e) { + callback(null,e); + }; + xml.timeout = 30000; // in milliseconds + xml.send(post_fields); }; @@ -1422,6 +1428,12 @@ var Codebird = function () { callback(reply, rate); } }; + // function called when an error occurs, including a timeout + xml.onerror = function(e) { + callback(null,null,e); + }; + xml.timeout = 30000; // in milliseconds + xml.send(httpmethod === "GET" ? null : post_fields); return true; }; From fef2fd4645a6a8cae5719c0f462406b43f16bf72 Mon Sep 17 00:00:00 2001 From: Hamza EZZI Date: Thu, 10 Sep 2015 10:31:21 +0200 Subject: [PATCH 03/48] how to use error callback how to catch error response or timeout exceeded --- README.md | 74 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 200f490..31f1449 100644 --- a/README.md +++ b/README.md @@ -73,18 +73,23 @@ Or you authenticate, like this: cb.__call( "oauth_requestToken", {oauth_callback: "oob"}, - function (reply) { - // stores it - cb.setToken(reply.oauth_token, reply.oauth_token_secret); - - // gets the authorize screen URL - cb.__call( - "oauth_authorize", - {}, - function (auth_url) { - window.codebird_auth = window.open(auth_url); - } - ); + function (reply,rate,err) { + if (err) { + console.log("error response or timeout exceeded" + err.error) + } + if(reply){ + // stores it + cb.setToken(reply.oauth_token, reply.oauth_token_secret); + + // gets the authorize screen URL + cb.__call( + "oauth_authorize", + {}, + function (auth_url) { + window.codebird_auth = window.open(auth_url); + } + ); + } } ); ``` @@ -110,9 +115,14 @@ After the user enters the PIN, complete the authentication: cb.__call( "oauth_accessToken", {oauth_verifier: document.getElementById("PINFIELD").value}, - function (reply) { - // store the authenticated token, which may be different from the request token (!) - cb.setToken(reply.oauth_token, reply.oauth_token_secret); + function (reply,rate,err) { + if (err) { + console.log("error response or timeout exceeded" + err.error) + } + if(reply){ + // store the authenticated token, which may be different from the request token (!) + cb.setToken(reply.oauth_token, reply.oauth_token_secret); + } // if you need to persist the login after page reload, // consider storing the token in a cookie or HTML5 local storage @@ -132,8 +142,14 @@ To obtain an app-only bearer token, call the appropriate API: cb.__call( "oauth2_token", {}, - function (reply) { - var bearer_token = reply.access_token; + function (reply,err) { + var bearer_token; + if (err) { + console.log("error response or timeout exceeded" + err.error) + } + if(reply){ + bearer_token = reply.access_token; + } } ); ``` @@ -186,8 +202,13 @@ if (typeof parameters.oauth_verifier !== "undefined") { { oauth_verifier: parameters.oauth_verifier }, - function (reply) { - cb.setToken(reply.oauth_token, reply.oauth_token_secret); + function (reply,rate,err) { + if (err) { + console.log("error response or timeout exceeded" + err.error) + } + if(reply){ + cb.setToken(reply.oauth_token, reply.oauth_token_secret); + } // if you need to persist the login after page reload, // consider storing the token in a cookie or HTML5 local storage @@ -212,8 +233,9 @@ cb.setToken("YOURTOKEN", "YOURTOKENSECRET"); // see above cb.__call( "statuses_homeTimeline", {}, - function (reply) { + function (reply,rate,err) { console.log(reply); + console.log(err); } ); ``` @@ -224,7 +246,7 @@ Tweeting is as easy as this: cb.__call( "statuses_update", {"status": "Whohoo, I just tweeted!"}, - function (reply) { + function (reply,rate,err) { // ... } ); @@ -238,7 +260,7 @@ var params = "status=" + encodeURIComponent("Fish & chips"); cb.__call( "statuses_update", params, - function (reply) { + function (reply,rate,err) { // ... } ); @@ -254,7 +276,7 @@ var params = { cb.__call( "statuses_update", params, - function (reply) { + function (reply,rate,err) { // ... } ); @@ -267,7 +289,7 @@ var params = { cb.__call( "users_show", params, - function (reply) { + function (reply,rate,err) { // ... } ); @@ -298,7 +320,7 @@ var params = { cb.__call( "media_upload", params, - function (reply) { + function (reply,rate,err) { // you get a media id back: console.log(reply.media_id_string); @@ -317,7 +339,7 @@ cb.__call( "media_ids": "12345678901234567890,9876543210987654321" "status": "Whohoo, I just tweeted two images!" }, - function (reply) { + function (reply,rate,err) { // ... } ); From 13567db6ce7bbea095d17973f2638112940593bc Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 19:08:46 +0200 Subject: [PATCH 04/48] Apply code styleguide --- README.md | 36 ++++++++++++++++++------------------ codebird.js | 12 ++++++------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 31f1449..68aa75d 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,12 @@ cb.__call( {oauth_callback: "oob"}, function (reply,rate,err) { if (err) { - console.log("error response or timeout exceeded" + err.error) + console.log("error response or timeout exceeded" + err.error); } - if(reply){ + if (reply) { // stores it cb.setToken(reply.oauth_token, reply.oauth_token_secret); - + // gets the authorize screen URL cb.__call( "oauth_authorize", @@ -117,9 +117,9 @@ cb.__call( {oauth_verifier: document.getElementById("PINFIELD").value}, function (reply,rate,err) { if (err) { - console.log("error response or timeout exceeded" + err.error) + console.log("error response or timeout exceeded" + err.error); } - if(reply){ + if (reply) { // store the authenticated token, which may be different from the request token (!) cb.setToken(reply.oauth_token, reply.oauth_token_secret); } @@ -142,12 +142,12 @@ To obtain an app-only bearer token, call the appropriate API: cb.__call( "oauth2_token", {}, - function (reply,err) { + function (reply, err) { var bearer_token; if (err) { - console.log("error response or timeout exceeded" + err.error) + console.log("error response or timeout exceeded" + err.error); } - if(reply){ + if (reply) { bearer_token = reply.access_token; } } @@ -202,11 +202,11 @@ if (typeof parameters.oauth_verifier !== "undefined") { { oauth_verifier: parameters.oauth_verifier }, - function (reply,rate,err) { + function (reply, rate, err) { if (err) { - console.log("error response or timeout exceeded" + err.error) + console.log("error response or timeout exceeded" + err.error); } - if(reply){ + if (reply) { cb.setToken(reply.oauth_token, reply.oauth_token_secret); } @@ -233,7 +233,7 @@ cb.setToken("YOURTOKEN", "YOURTOKENSECRET"); // see above cb.__call( "statuses_homeTimeline", {}, - function (reply,rate,err) { + function (reply, rate, err) { console.log(reply); console.log(err); } @@ -246,7 +246,7 @@ Tweeting is as easy as this: cb.__call( "statuses_update", {"status": "Whohoo, I just tweeted!"}, - function (reply,rate,err) { + function (reply, rate, err) { // ... } ); @@ -260,7 +260,7 @@ var params = "status=" + encodeURIComponent("Fish & chips"); cb.__call( "statuses_update", params, - function (reply,rate,err) { + function (reply, rate, err) { // ... } ); @@ -276,7 +276,7 @@ var params = { cb.__call( "statuses_update", params, - function (reply,rate,err) { + function (reply, rate, err) { // ... } ); @@ -289,7 +289,7 @@ var params = { cb.__call( "users_show", params, - function (reply,rate,err) { + function (reply, rate, err) { // ... } ); @@ -320,7 +320,7 @@ var params = { cb.__call( "media_upload", params, - function (reply,rate,err) { + function (reply, rate, err) { // you get a media id back: console.log(reply.media_id_string); @@ -339,7 +339,7 @@ cb.__call( "media_ids": "12345678901234567890,9876543210987654321" "status": "Whohoo, I just tweeted two images!" }, - function (reply,rate,err) { + function (reply, rate, err) { // ... } ); diff --git a/codebird.js b/codebird.js index d234c9e..70e61db 100644 --- a/codebird.js +++ b/codebird.js @@ -678,10 +678,10 @@ var Codebird = function () { }; // function called when an error occurs, including a timeout xml.onerror = function(e) { - callback(null,e); + callback(null, e); }; - xml.timeout = 30000; // in milliseconds - + xml.timeout = 30000; // in milliseconds + xml.send(post_fields); }; @@ -1430,10 +1430,10 @@ var Codebird = function () { }; // function called when an error occurs, including a timeout xml.onerror = function(e) { - callback(null,null,e); + callback(null, null, e); }; - xml.timeout = 30000; // in milliseconds - + xml.timeout = 30000; // in milliseconds + xml.send(httpmethod === "GET" ? null : post_fields); return true; }; From 0a267d1e89c74278e4f256bda729f6800dcdcbf1 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 7 Oct 2015 19:22:17 +0200 Subject: [PATCH 05/48] Add CHANGELOG for #106 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 51eb546..1d10db4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ codebird-js - changelog ======================= +3.0.0 (not yet released) ++ #106 Add error callback + 2.6.0 (2015-04-08) + Allow to get the supported API methods as array - #79 Use POST for users/lookup and statuses/lookup, params may get too long for GET From b256f08105359f11bfb576f77b0d29d6d3baf6b0 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 10:12:04 +0100 Subject: [PATCH 06/48] Add logout method Fix #96 --- CHANGELOG | 1 + README.md | 9 +++++++++ codebird.js | 13 +++++++++++++ 3 files changed, 23 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1d10db4..c628172 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ codebird-js - changelog 3.0.0 (not yet released) + #106 Add error callback ++ #96 Add logout method 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/README.md b/README.md index 68aa75d..2f86c80 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,15 @@ cb.__call( ); ``` +### Logging out + +In case you want to log out the current user (to log in a different user without +creating a new Codebird object), just call the `logout()` method. + +```javascript +cb.logout(); +``` + ### Application-only auth Some API methods also support authenticating on a per-application level. diff --git a/codebird.js b/codebird.js index 70e61db..c022112 100644 --- a/codebird.js +++ b/codebird.js @@ -183,6 +183,18 @@ var Codebird = function () { _oauth_token_secret = secret; }; + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ + var logout = function () { + _oauth_token = + _oauth_token_secret = null; + + return true; + }; + /** * Enables or disables CORS proxy * @@ -1483,6 +1495,7 @@ var Codebird = function () { setConsumerKey: setConsumerKey, getVersion: getVersion, setToken: setToken, + logout: logout, setBearerToken: setBearerToken, setUseProxy: setUseProxy, setProxy: setProxy, From 6cd724de0bbfd7c3799eb4a96e2cfb3dd08feb75 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 11:51:12 +0100 Subject: [PATCH 07/48] Set version to 3.0.0-dev --- codebird.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codebird.js b/codebird.js index c022112..171754b 100644 --- a/codebird.js +++ b/codebird.js @@ -2,7 +2,7 @@ * A Twitter library in JavaScript * * @package codebird - * @version 2.6.0 + * @version 3.0.0-dev * @author Jublo Solutions * @copyright 2010-2015 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 @@ -135,7 +135,7 @@ var Codebird = function () { /** * The current Codebird version */ - var _version = "2.6.0"; + var _version = "3.0.0-dev"; /** * Sets the OAuth consumer key and secret (App key) From 2a389e957bc157b2ae8c704e3ac6a0b0f0b3eda5 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 11:51:33 +0100 Subject: [PATCH 08/48] Support promises --- codebird.js | 158 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 140 insertions(+), 18 deletions(-) diff --git a/codebird.js b/codebird.js index 171754b..c320bb6 100644 --- a/codebird.js +++ b/codebird.js @@ -474,6 +474,54 @@ var Codebird = function () { return httpmethods; }; + /** + * Promise helpers + */ + + /** + * Get a deferred object + */ + var _getDfd = function () { + if (typeof jQuery !== "undefined" && jQuery.Deferred) { + return jQuery.Deferred(); + } + if (typeof Q !== "undefined" && Q.defer) { + return Q.defer(); + } + if (typeof RSVP !== "undefined" && RSVP.defer) { + return RSVP.defer(); + } + if (typeof when !== "undefined" && when.defer) { + return when.defer(); + } + if (typeof require !== "undefined") { + var promise_class; + if (promise_class = require("jquery")) { + return promise_class.Deferred(); + } + if (promise_class = require("q")) { + return promise_class.defer(); + } + if (promise_class = require("rsvp")) { + return promise_class.defer(); + } + if (promise_class = require("when")) { + return promise_class.defer(); + } + } + return false; + }; + + /** + * Get a promise from the dfd object + */ + var _getPromise = function (dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + }; + /** * Main API handler working on any requests you issue * @@ -482,7 +530,7 @@ var Codebird = function () { * @param function callback The callback to call with the reply * @param bool app_only_auth Whether to use app-only auth * - * @return mixed The API reply encoded in the set return_format + * @return object Promise */ var __call = function (fn, params, callback, app_only_auth) { @@ -585,9 +633,10 @@ var Codebird = function () { /** * Gets the OAuth authenticate URL for the current request token * - * @return string The OAuth authenticate URL + * @return object Promise */ var oauth_authenticate = function (params, callback) { + var dfd = _getDfd(); if (typeof params.force_login === "undefined") { params.force_login = null; } @@ -595,7 +644,13 @@ var Codebird = function () { params.screen_name = null; } if (_oauth_token === null) { - console.warn("To get the authenticate URL, the OAuth token must be set."); + var error = "To get the authenticate URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; } var url = _endpoint_oauth + "oauth/authenticate?oauth_token=" + _url(_oauth_token); if (params.force_login === true) { @@ -604,7 +659,14 @@ var Codebird = function () { url += "&screen_name=" + params.screen_name; } } - callback(url); + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve(url); + return _getPromise(dfd); + } + // no promises return true; }; @@ -614,6 +676,7 @@ var Codebird = function () { * @return string The OAuth authorize URL */ var oauth_authorize = function (params, callback) { + var dfd = _getDfd(); if (typeof params.force_login === "undefined") { params.force_login = null; } @@ -621,7 +684,13 @@ var Codebird = function () { params.screen_name = null; } if (_oauth_token === null) { - console.warn("To get the authorize URL, the OAuth token must be set."); + var error = "To get the authorize URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; } var url = _endpoint_oauth + "oauth/authorize?oauth_token=" + _url(_oauth_token); if (params.force_login === true) { @@ -630,22 +699,37 @@ var Codebird = function () { url += "&screen_name=" + params.screen_name; } } - callback(url); + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve(url); + return _getPromise(dfd); + } + // no promises return true; }; /** * Gets the OAuth bearer token * - * @return string The OAuth bearer token + * @return object Promise */ var oauth2_token = function (callback) { + var dfd = _getDfd(); + if (_oauth_consumer_key === null) { - console.warn("To obtain a bearer token, the consumer key must be set."); + var error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; } - if (typeof callback === "undefined") { + if (!dfd && typeof callback === "undefined") { callback = function () {}; } @@ -685,17 +769,29 @@ var Codebird = function () { if (httpstatus === 200) { setBearerToken(reply.access_token); } - callback(reply); + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve(reply); + } } }; // function called when an error occurs, including a timeout - xml.onerror = function(e) { - callback(null, e); + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, e); + } + if (dfd) { + dfd.reject(e); + } }; xml.timeout = 30000; // in milliseconds xml.send(post_fields); - + if (dfd) { + return _getPromise(dfd); + } }; /** @@ -1287,6 +1383,8 @@ var Codebird = function () { */ var _callApi = function (httpmethod, method, params, multipart, app_only_auth, internal, callback) { + var dfd = _getDfd(); + if (typeof params === "undefined") { params = {}; } @@ -1401,14 +1499,25 @@ var Codebird = function () { if (app_only_auth) { if (_oauth_consumer_key === null && _oauth_bearer_token === null - ) { - console.warn("To make an app-only auth API request, consumer key or bearer token must be set."); + ) { + var error = "To make an app-only auth API request, consumer key or bearer token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } } // automatically fetch bearer token, if necessary if (_oauth_bearer_token === null) { - return oauth2_token(function () { + if (dfd) { + return oauth2_token().then(function (token_reply) { + return _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); + }); + } + oauth2_token(function () { _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); }); + return; } authorization = "Bearer " + _oauth_bearer_token; } @@ -1437,16 +1546,29 @@ var Codebird = function () { reset: xml.getResponseHeader("x-rate-limit-reset") }; } - callback(reply, rate); + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve(reply, rate); + } } }; // function called when an error occurs, including a timeout xml.onerror = function(e) { - callback(null, null, e); + if (typeof callback === "function") { + callback(null, null, e); + } + if (dfd) { + dfd.reject(e); + } }; xml.timeout = 30000; // in milliseconds xml.send(httpmethod === "GET" ? null : post_fields); + if (dfd) { + return _getPromise(dfd); + } return true; }; From 2c75b3ad935087334a0cf468df3651df678231dc Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 11:51:43 +0100 Subject: [PATCH 09/48] Add README for promises --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f86c80..44794b8 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,6 @@ For sending an API request with app-only auth, see the ‘Usage examples’ sect ### Authenticating using a callback URL, without PIN - 1. Before sending your user off to Twitter, you have to store the request token and its secret, for example in a cookie. 2. In the callback URL, extract those values and assign them to the Codebird object. 3. Extract the ```oauth_verifier``` field from the request URI. @@ -658,3 +657,54 @@ cb.__call( } ); ``` + +### …use promises instead of callback functions? + +Have you ever heard of the [Pyramid of Doom](http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/)? +It’s when code progresses more to the right because of excessive nesting +than it progresses from top to bottom. + +Because of the asynchronous requests, Codebird will use callbacks that you provide. +They are called when the result from the Twitter API has arrived. +However, to streamline code, there is a sleeker concept for this: Promises. + +There are several popular libraries that support promises. +Codebird will auto-detect and use any of the following: + +- jQuery Deferred +- Q +- RSVP +- when + +Here’s a usage sample for promises: + +```javascript +cb.__call( + "statuses_update", + {"status": "Whohoo, I just tweeted!"} +).then(function (reply, rate, err) { + // ... +}); +``` + +Since the app-only flag is the fourth parameter for `__call`, +you’ll have to provide a callback stub nonetheless even with promises: + +```javascript +cb.__call( + "search_tweets", + {"q": "#PHP7"}, + null, // no callback needed, we have the promise + true // app-only auth + +).then(function (reply, rate, err) { + // ... +}); +``` + +**Tips:** + +- If you provide **both** (callback and promise.then), + Codebird will first call the callback, then resolve the promise. + +- If the request fails due to any errors, Codebird will reject the promise. From a6166c329e890c356b0624dea0319b363d3e6054 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 11:51:49 +0100 Subject: [PATCH 10/48] Add CHANGELOG for promises --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index c628172..a4f74ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ codebird-js - changelog 3.0.0 (not yet released) + #106 Add error callback + #96 Add logout method ++ #98 Support promises as alternative to callbacks 2.6.0 (2015-04-08) + Allow to get the supported API methods as array From cffd25010dcfb47122149d21537d5041688c5185 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 12:01:15 +0100 Subject: [PATCH 11/48] Drop support for undocumented API methods --- CHANGELOG | 1 + README.md | 26 -------------------- codebird.js | 69 ++++++++--------------------------------------------- 3 files changed, 11 insertions(+), 85 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a4f74ad..c84c349 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ codebird-js - changelog + #106 Add error callback + #96 Add logout method + #98 Support promises as alternative to callbacks +- Drop support for undocumented API methods 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/README.md b/README.md index 44794b8..869db0e 100644 --- a/README.md +++ b/README.md @@ -632,32 +632,6 @@ When this error occurs, advise the user to [generate a temporary password](https://twitter.com/settings/applications) on twitter.com and use that to complete signing in to the application. -### …access and use undocumented Twitter API methods? - -Besides the well-documented official methods, the Twitter API also contains -undocumented additional methods. They are used by official Twitter clients, -such as Twitter for iPhone and Twitter for Mac. - -Access to these methods is restricted: Only white-listed applications -(consumer keys) may access undocumented methods. Codebird supports accessing -internal methods, but that will only work if you provide a white-listed API key. -By reason, the API keys and secrets for official Twitter clients are not -provided within this package, since they should have been kept a secret. - -If you provide Codebird with the Twitter for iPhone consumer key and secret, -the following example will get the latest events that happened with you: - -```javascript -cb.__call( - "activity_aboutMe", - {}, - function (reply) { - console.log(reply); - // ... - } -); -``` - ### …use promises instead of callback functions? Have you ever heard of the [Pyramid of Doom](http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/)? diff --git a/codebird.js b/codebird.js index c320bb6..9dce924 100644 --- a/codebird.js +++ b/codebird.js @@ -373,17 +373,21 @@ var Codebird = function () { "saved_searches/list", "saved_searches/show/:id", "search/tweets", + "site", + "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", + "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", + "user", "users/contributees", "users/contributors", "users/profile_banner", @@ -391,28 +395,7 @@ var Codebird = function () { "users/show", "users/suggestions", "users/suggestions/:slug", - "users/suggestions/:slug/members", - - // Internal - "users/recommendations", - "account/push_destinations/device", - "activity/about_me", - "activity/by_friends", - "statuses/media_timeline", - "timeline/home", - "help/experiments", - "search/typeahead", - "search/universal", - "discover/universal", - "conversation/show", - "statuses/:id/activity/summary", - "account/login_verification_enrollment", - "account/login_verification_request", - "prompts/suggest", - - "beta/timelines/custom/list", - "beta/timelines/timeline", - "beta/timelines/custom/show" + "users/suggestions/:slug/members" ], POST: [ "account/remove_profile_banner", @@ -451,24 +434,13 @@ var Codebird = function () { "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", + "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload "users/lookup", - "users/report_spam", - - // Internal - "direct_messages/read", - "account/login_verification_enrollment__post", - "push_destinations/enable_login_verification", - "account/login_verification_request__post", - - "beta/timelines/custom/create", - "beta/timelines/custom/update", - "beta/timelines/custom/destroy", - "beta/timelines/custom/add", - "beta/timelines/custom/remove" + "users/report_spam" ] }; return httpmethods; @@ -617,7 +589,6 @@ var Codebird = function () { var httpmethod = _detectMethod(method_template, apiparams); var multipart = _detectMultipart(method_template); - var internal = _detectInternal(method_template); return _callApi( httpmethod, @@ -625,7 +596,6 @@ var Codebird = function () { apiparams, multipart, app_only_auth, - internal, callback ); }; @@ -1257,20 +1227,6 @@ var Codebird = function () { return multipart_request; }; - /** - * Detects if API call is internal - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in internal API - */ - var _detectInternal = function (method) { - var internals = [ - "users/recommendations" - ]; - return internals.join(" ").indexOf(method) > -1; - }; - /** * Detects if API call should use media endpoint * @@ -1376,13 +1332,12 @@ var Codebird = function () { * @param array optional params The parameters to send along * @param bool optional multipart Whether to use multipart/form-data * @param bool optional app_only_auth Whether to use app-only bearer authentication - * @param bool optional internal Whether to use internal call * @param function callback The function to call with the API call result * * @return mixed The API reply, encoded in the set return_format */ - var _callApi = function (httpmethod, method, params, multipart, app_only_auth, internal, callback) { + var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { var dfd = _getDfd(); if (typeof params === "undefined") { @@ -1397,10 +1352,6 @@ var Codebird = function () { if (typeof callback !== "function") { callback = function () {}; } - if (internal) { - params.adc = "phone"; - params.application_id = 333903271; - } var url = _getEndpoint(method); var authorization = null; @@ -1511,11 +1462,11 @@ var Codebird = function () { if (_oauth_bearer_token === null) { if (dfd) { return oauth2_token().then(function (token_reply) { - return _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); + return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); } oauth2_token(function () { - _callApi(httpmethod, method, params, multipart, app_only_auth, false, callback); + _callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); return; } From 7614e266a34799d22cd5526787506ede6bf5cbf1 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 17:18:48 +0100 Subject: [PATCH 12/48] Fix rate + err promise resolver parameters Now using an object. See #98. --- README.md | 22 ++++-- codebird.js | 224 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 168 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 869db0e..be96c9c 100644 --- a/README.md +++ b/README.md @@ -656,9 +656,14 @@ Here’s a usage sample for promises: cb.__call( "statuses_update", {"status": "Whohoo, I just tweeted!"} -).then(function (reply, rate, err) { - // ... -}); +).then(function (data) { + var reply = data.reply, + rate = data.rate; + // ... + }, + function (err) { + // ... + }); ``` Since the app-only flag is the fourth parameter for `__call`, @@ -671,9 +676,14 @@ cb.__call( null, // no callback needed, we have the promise true // app-only auth -).then(function (reply, rate, err) { - // ... -}); +).then(function (data) { + var reply = data.reply, + rate = data.rate; + // ... + }, + function (err) { + // ... + }); ``` **Tips:** diff --git a/codebird.js b/codebird.js index 9dce924..18f742c 100644 --- a/codebird.js +++ b/codebird.js @@ -529,11 +529,49 @@ var Codebird = function () { case "oauth2_token": return this[fn](callback); } + + // parse parameters + var apiparams = _parseApiParams(params); + + // stringify null and boolean parameters + apiparams = _stringifyNullBoolParams(apiparams); + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) if (fn === "oauth_requestToken") { setToken(null, null); } - // parse parameters + + // map function name to API method + var data = _mapFnToApiMethod(fn, apiparams), + method = data[0], + method_template = data[1]; + + var httpmethod = _detectMethod(method_template, apiparams); + var multipart = _detectMultipart(method_template); + + return _callApi( + httpmethod, + method, + apiparams, + multipart, + app_only_auth, + callback + ); + }; + + + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + var _parseApiParams = function (params) { var apiparams = {}; if (typeof params === "object") { apiparams = params; @@ -541,26 +579,47 @@ var Codebird = function () { _parse_str(params, apiparams); //TODO } - // map function name to API method + return apiparams; + }; + + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + var _stringifyNullBoolParams = function (apiparams) { + var value; + for (var key in apiparams) { + value = apiparams[key]; + if (value === null) { + apiparams[key] = 'null'; + } else if (value === true || value === false) { + apiparams[key] = value ? 'true' : 'false'; + } + } + + return apiparams; + }; + + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + var _mapFnToApiMethod = function (fn, apiparams) { var method = ""; var param, i, j; // replace _ by / - var path = fn.split("_"); - for (i = 0; i < path.length; i++) { - if (i > 0) { - method += "/"; - } - method += path[i]; - } + method = _mapFnInsertSlashes(fn); // undo replacement for URL parameters - var url_parameters_with_underscore = ["screen_name", "place_id"]; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - var replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); - } + method = _mapFnRestoreParamUnderscores(method); // replace AA by URL parameters var method_template = method; @@ -587,25 +646,53 @@ var Codebird = function () { method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); } - var httpmethod = _detectMethod(method_template, apiparams); - var multipart = _detectMultipart(method_template); + return [method, method_template]; + }; - return _callApi( - httpmethod, - method, - apiparams, - multipart, - app_only_auth, - callback - ); + + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + var _mapFnInsertSlashes = function (fn) { + var path = fn.split('_'), + method = path.join('/'); + + return method; + }; + + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + var _mapFnRestoreParamUnderscores = function (method) { + var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } + + return method; }; + + /** + * Uncommon API methods + */ + /** * Gets the OAuth authenticate URL for the current request token * * @return object Promise */ - var oauth_authenticate = function (params, callback) { + var oauth_authenticate = function (params, callback, type) { var dfd = _getDfd(); if (typeof params.force_login === "undefined") { params.force_login = null; @@ -613,8 +700,13 @@ var Codebird = function () { if (typeof params.screen_name === "undefined") { params.screen_name = null; } + if (typeof type === "undefined" + || ["authenticate", "authorize"].indexOf(type) === -1 + ) { + type = "authenticate"; + } if (_oauth_token === null) { - var error = "To get the authenticate URL, the OAuth token must be set."; + var error = "To get the " + type + " URL, the OAuth token must be set."; console.warn(error); if (dfd) { dfd.reject({ error: error }); @@ -622,7 +714,7 @@ var Codebird = function () { } return false; } - var url = _endpoint_oauth + "oauth/authenticate?oauth_token=" + _url(_oauth_token); + var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); if (params.force_login === true) { url += "&force_login=1"; if (params.screen_name !== null) { @@ -633,7 +725,7 @@ var Codebird = function () { callback(url); } if (dfd) { - dfd.resolve(url); + dfd.resolve({ reply: url }); return _getPromise(dfd); } // no promises @@ -646,38 +738,7 @@ var Codebird = function () { * @return string The OAuth authorize URL */ var oauth_authorize = function (params, callback) { - var dfd = _getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (_oauth_token === null) { - var error = "To get the authorize URL, the OAuth token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - return false; - } - var url = _endpoint_oauth + "oauth/authorize?oauth_token=" + _url(_oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; - } - } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve(url); - return _getPromise(dfd); - } - // no promises - return true; + return oauth_authenticate(params, callback, 'authorize'); }; /** @@ -743,7 +804,7 @@ var Codebird = function () { callback(reply); } if (dfd) { - dfd.resolve(reply); + dfd.resolve({ reply: reply }); } } }; @@ -1052,6 +1113,31 @@ var Codebird = function () { return clone; }; + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + var _getSignature = function (httpmethod, method, keys, base_params) { + // convert params to string + var base_string = "", key, value; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += key + "=" + _url(value) + "&"; + } + base_string = base_string.substring(0, base_string.length - 1); + return _sha1( + httpmethod + "&" + + _url(method) + "&" + + _url(base_string) + ); + }; + /** * Generates an OAuth signature * @@ -1094,19 +1180,13 @@ var Codebird = function () { sign_base_params[key] = value; } var keys = _ksort(sign_base_params); - var sign_base_string = ""; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = sign_base_params[key]; - sign_base_string += key + "=" + _url(value) + "&"; - } - sign_base_string = sign_base_string.substring(0, sign_base_string.length - 1); - var signature = _sha1(httpmethod + "&" + _url(method) + "&" + _url(sign_base_string)); + + var signature = _getSignature(httpmethod, method, keys, sign_base_params); params = append_to_get ? sign_base_params : oauth_params; params.oauth_signature = signature; keys = _ksort(params); - var authorization = ""; + var authorization = "", i; if (append_to_get) { for(i = 0; i < keys.length; i++) { key = keys[i]; @@ -1501,7 +1581,7 @@ var Codebird = function () { callback(reply, rate); } if (dfd) { - dfd.resolve(reply, rate); + dfd.resolve({ reply: reply, rate: rate }); } } }; From 64a26a0979edf2a7170d24efd95d4ff903d4de69 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 17:38:45 +0100 Subject: [PATCH 13/48] Fix jshint errors --- codebird.js | 50 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/codebird.js b/codebird.js index 18f742c..f18da23 100644 --- a/codebird.js +++ b/codebird.js @@ -454,30 +454,31 @@ var Codebird = function () { * Get a deferred object */ var _getDfd = function () { - if (typeof jQuery !== "undefined" && jQuery.Deferred) { - return jQuery.Deferred(); + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); } - if (typeof Q !== "undefined" && Q.defer) { - return Q.defer(); + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); } - if (typeof RSVP !== "undefined" && RSVP.defer) { - return RSVP.defer(); + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); } - if (typeof when !== "undefined" && when.defer) { - return when.defer(); + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); } if (typeof require !== "undefined") { - var promise_class; - if (promise_class = require("jquery")) { + var promise_class = require("jquery"); + if (promise_class) { return promise_class.Deferred(); } - if (promise_class = require("q")) { - return promise_class.defer(); + promise_class = require("q"); + if (!promise_class) { + promise_class = require("rsvp"); } - if (promise_class = require("rsvp")) { - return promise_class.defer(); + if (!promise_class) { + promise_class = require("when"); } - if (promise_class = require("when")) { + if (promise_class) { return promise_class.defer(); } } @@ -594,9 +595,9 @@ var Codebird = function () { for (var key in apiparams) { value = apiparams[key]; if (value === null) { - apiparams[key] = 'null'; + apiparams[key] = "null"; } else if (value === true || value === false) { - apiparams[key] = value ? 'true' : 'false'; + apiparams[key] = value ? "true" : "false"; } } @@ -658,8 +659,8 @@ var Codebird = function () { * @return string API method to call */ var _mapFnInsertSlashes = function (fn) { - var path = fn.split('_'), - method = path.join('/'); + var path = fn.split("_"), + method = path.join("/"); return method; }; @@ -738,7 +739,7 @@ var Codebird = function () { * @return string The OAuth authorize URL */ var oauth_authorize = function (params, callback) { - return oauth_authenticate(params, callback, 'authorize'); + return oauth_authenticate(params, callback, "authorize"); }; /** @@ -1386,15 +1387,16 @@ var Codebird = function () { // now, consider RequireJS and/or Node.js objects } else if (typeof require === "function" && require - ) { + ) { + var XMLHttpRequest; // look for xmlhttprequest module try { - var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; xml = new XMLHttpRequest(); } catch (e1) { // or maybe the user is using xhr2 try { - var XMLHttpRequest = require("xhr2"); + XMLHttpRequest = require("xhr2"); xml = new XMLHttpRequest(); } catch (e2) { console.error("xhr2 object not defined, cancelling."); @@ -1541,7 +1543,7 @@ var Codebird = function () { // automatically fetch bearer token, if necessary if (_oauth_bearer_token === null) { if (dfd) { - return oauth2_token().then(function (token_reply) { + return oauth2_token().then(function () { return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); } From 1fa2d0b8c5c747755ce3cdf4f3ef69b235201757 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 17:46:08 +0100 Subject: [PATCH 14/48] Fix jshint errors --- codebird.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/codebird.js b/codebird.js index f18da23..1e3a273 100644 --- a/codebird.js +++ b/codebird.js @@ -859,7 +859,7 @@ var Codebird = function () { * * @return string The hash */ - var _sha1 = function () { + var _sha1 = (function () { function n(e, b) { e[b >> 5] |= 128 << 24 - b % 32; e[(b + 64 >> 9 << 4) + 15] = b; @@ -917,26 +917,26 @@ var Codebird = function () { if (c.length > 16) { c = n(c, b.length * g); } - b = new Array(16); + var bb = new Array(16); for (var a = new Array(16), d = 0; d < 16; d++) { a[d] = c[d] ^ 909522486; - b[d] = c[d] ^ 1549556828; + bb[d] = c[d] ^ 1549556828; } c = n(a.concat(q(e)), 512 + e.length * g); - b = n(b.concat(c), 672); - c = ""; - for (a = 0; a < 4 * b.length; a += 3) { - for (d = (b[a >> 2] >> 8 * (3 - a % 4) & 255) << 16 | (b[a + 1 >> 2] >> - 8 * (3 - (a + 1) % 4) & 255) << 8 | b[a + 2 >> 2] >> 8 * (3 - - (a + 2) % 4) & 255, e = 0; 4 > e; e++) { - c = 8 * a + 6 * e > 32 * b.length ? c + "=" : c + + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> + 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - + (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" .charAt(d >> 6 * (3 - e) & 63); } } - return c; + return b; }; - }(); + })(); /* * Gets the base64 representation for the given data @@ -973,9 +973,9 @@ var Codebird = function () { b &= 63; c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); } while (g < a.length); - c = c.join(""); + i = c.join(""); a = a.length % 3; - return (a ? c.slice(0, a - 3) : c) + "===".slice(a || 3); + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); }; /* From 8a0cd3f8a8c511f775895f091679dceebe60cd4f Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 17:52:44 +0100 Subject: [PATCH 15/48] Add security check hasOwnProperty --- CHANGELOG | 1 + codebird.js | 2999 ++++++++++++++++++++++++++------------------------- 2 files changed, 1509 insertions(+), 1491 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c84c349..a278559 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ codebird-js - changelog + #96 Add logout method + #98 Support promises as alternative to callbacks - Drop support for undocumented API methods ++ Add security check hasOwnProperty 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.js b/codebird.js index 1e3a273..769f287 100644 --- a/codebird.js +++ b/codebird.js @@ -27,1667 +27,1684 @@ define, require */ (function (undefined) { -"use strict"; - -/** - * Array.indexOf polyfill - */ -if (! Array.prototype.indexOf) { - Array.prototype.indexOf = function (obj, start) { - for (var i = (start || 0); i < this.length; i++) { - if (this[i] === obj) { - return i; - } - } - return -1; - }; -} - -/** - * A Twitter library in JavaScript - * - * @package codebird - * @subpackage codebird-js - */ -/* jshint -W098 */ -var Codebird = function () { -/* jshint +W098 */ - - /** - * The OAuth consumer key of your registered app - */ - var _oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - var _oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - var _oauth_bearer_token = null; - - /** - * The API endpoint base to use - */ - var _endpoint_base = "https://api.twitter.com/"; - - /** - * The media API endpoint base to use - */ - var _endpoint_base_media = "https://upload.twitter.com/"; - - /** - * The API endpoint to use - */ - var _endpoint = _endpoint_base + "1.1/"; - - /** - * The media API endpoint to use - */ - var _endpoint_media = _endpoint_base_media + "1.1/"; + "use strict"; /** - * The API endpoint base to use + * Array.indexOf polyfill */ - var _endpoint_oauth = _endpoint_base; - - /** - * API proxy endpoint - */ - var _endpoint_proxy = "https://api.jublo.net/codebird/"; - - /** - * The API endpoint to use for old requests - */ - var _endpoint_old = _endpoint_base + "1/"; - - /** - * Use JSONP for GET requests in IE7-9 - */ - var _use_jsonp = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - && (navigator.userAgent.indexOf("Trident/4") > -1 - || navigator.userAgent.indexOf("Trident/5") > -1 - || navigator.userAgent.indexOf("MSIE 7.0") > -1 - ) - ); - - /** - * Whether to access the API via a proxy that is allowed by CORS - * Assume that CORS is only necessary in browsers - */ - var _use_proxy = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - ); - - /** - * The Request or access token. Used to sign requests - */ - var _oauth_token = null; + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (obj, start) { + for (var i = (start || 0); i < this.length; i++) { + if (this[i] === obj) { + return i; + } + } + return -1; + }; + } /** - * The corresponding request or access token secret + * A Twitter library in JavaScript + * + * @package codebird + * @subpackage codebird-js */ - var _oauth_token_secret = null; + /* jshint -W098 */ + var Codebird = function () { + /* jshint +W098 */ + + /** + * The OAuth consumer key of your registered app + */ + var _oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + var _oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + var _oauth_bearer_token = null; + + /** + * The API endpoint base to use + */ + var _endpoint_base = "https://api.twitter.com/"; + + /** + * The media API endpoint base to use + */ + var _endpoint_base_media = "https://upload.twitter.com/"; + + /** + * The API endpoint to use + */ + var _endpoint = _endpoint_base + "1.1/"; + + /** + * The media API endpoint to use + */ + var _endpoint_media = _endpoint_base_media + "1.1/"; + + /** + * The API endpoint base to use + */ + var _endpoint_oauth = _endpoint_base; + + /** + * API proxy endpoint + */ + var _endpoint_proxy = "https://api.jublo.net/codebird/"; + + /** + * The API endpoint to use for old requests + */ + var _endpoint_old = _endpoint_base + "1/"; + + /** + * Use JSONP for GET requests in IE7-9 + */ + var _use_jsonp = (typeof navigator !== "undefined" + && typeof navigator.userAgent !== "undefined" + && (navigator.userAgent.indexOf("Trident/4") > -1 + || navigator.userAgent.indexOf("Trident/5") > -1 + || navigator.userAgent.indexOf("MSIE 7.0") > -1 + ) + ); - /** - * The current Codebird version - */ - var _version = "3.0.0-dev"; + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + var _use_proxy = (typeof navigator !== "undefined" + && typeof navigator.userAgent !== "undefined" + ); - /** - * Sets the OAuth consumer key and secret (App key) - * - * @param string key OAuth consumer key - * @param string secret OAuth consumer secret - * - * @return void - */ - var setConsumerKey = function (key, secret) { - _oauth_consumer_key = key; - _oauth_consumer_secret = secret; - }; + /** + * The Request or access token. Used to sign requests + */ + var _oauth_token = null; + + /** + * The corresponding request or access token secret + */ + var _oauth_token_secret = null; + + /** + * The current Codebird version + */ + var _version = "3.0.0-dev"; + + /** + * Sets the OAuth consumer key and secret (App key) + * + * @param string key OAuth consumer key + * @param string secret OAuth consumer secret + * + * @return void + */ + var setConsumerKey = function (key, secret) { + _oauth_consumer_key = key; + _oauth_consumer_secret = secret; + }; - /** - * Sets the OAuth2 app-only auth bearer token - * - * @param string token OAuth2 bearer token - * - * @return void - */ - var setBearerToken = function (token) { - _oauth_bearer_token = token; - }; + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string token OAuth2 bearer token + * + * @return void + */ + var setBearerToken = function (token) { + _oauth_bearer_token = token; + }; - /** - * Gets the current Codebird version - * - * @return string The version number - */ - var getVersion = function () { - return _version; - }; + /** + * Gets the current Codebird version + * + * @return string The version number + */ + var getVersion = function () { + return _version; + }; - /** - * Sets the OAuth request or access token and secret (User key) - * - * @param string token OAuth request or access token - * @param string secret OAuth request or access token secret - * - * @return void - */ - var setToken = function (token, secret) { - _oauth_token = token; - _oauth_token_secret = secret; - }; + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string token OAuth request or access token + * @param string secret OAuth request or access token secret + * + * @return void + */ + var setToken = function (token, secret) { + _oauth_token = token; + _oauth_token_secret = secret; + }; - /** - * Forgets the OAuth request or access token and secret (User key) - * - * @return bool - */ - var logout = function () { - _oauth_token = - _oauth_token_secret = null; + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ + var logout = function () { + _oauth_token = + _oauth_token_secret = null; - return true; - }; + return true; + }; - /** - * Enables or disables CORS proxy - * - * @param bool use_proxy Whether to use CORS proxy or not - * - * @return void - */ - var setUseProxy = function (use_proxy) { - _use_proxy = !! use_proxy; - }; + /** + * Enables or disables CORS proxy + * + * @param bool use_proxy Whether to use CORS proxy or not + * + * @return void + */ + var setUseProxy = function (use_proxy) { + _use_proxy = !!use_proxy; + }; - /** - * Sets custom CORS proxy server - * - * @param string proxy Address of proxy server to use - * - * @return void - */ - var setProxy = function (proxy) { - // add trailing slash if missing - if (! proxy.match(/\/$/)) { - proxy += "/"; - } - _endpoint_proxy = proxy; - }; + /** + * Sets custom CORS proxy server + * + * @param string proxy Address of proxy server to use + * + * @return void + */ + var setProxy = function (proxy) { + // add trailing slash if missing + if (!proxy.match(/\/$/)) { + proxy += "/"; + } + _endpoint_proxy = proxy; + }; - /** - * Parse URL-style parameters into object - * - * version: 1109.2015 - * discuss at: http://phpjs.org/functions/parse_str - * + original by: Cagri Ekin - * + improved by: Michael White (http://getsprink.com) - * + tweaked by: Jack - * + bugfixed by: Onno Marsman - * + reimplemented by: stag019 - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: stag019 - * - depends on: urldecode - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * % note 1: When no argument is specified, will put variables in global scope. - * - * @param string str String to parse - * @param array array to load data into - * - * @return object - */ - var _parse_str = function (str, array) { - var glue1 = "=", - glue2 = "&", - array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), - i, j, chr, tmp, key, value, bracket, keys, evalStr, - fixStr = function (str) { - return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); - }; - if (! array) { - array = this.window; - } + /** + * Parse URL-style parameters into object + * + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object + */ + var _parse_str = function (str, array) { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, j, chr, tmp, key, value, bracket, keys, evalStr, + fixStr = function (str) { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + }; + if (!array) { + array = this.window; + } - for (i = 0; i < array2.length; i++) { - tmp = array2[i].split(glue1); - if (tmp.length < 2) { - tmp = [tmp, ""]; - } - key = fixStr(tmp[0]); - value = fixStr(tmp[1]); - while (key.charAt(0) === " ") { - key = key.substr(1); - } - if (key.indexOf("\0") !== -1) { - key = key.substr(0, key.indexOf("\0")); - } - if (key && key.charAt(0) !== "[") { - keys = []; - bracket = 0; - for (j = 0; j < key.length; j++) { - if (key.charAt(j) === "[" && !bracket) { - bracket = j + 1; - } else if (key.charAt(j) === "]") { - if (bracket) { - if (!keys.length) { - keys.push(key.substr(0, bracket - 1)); - } - keys.push(key.substr(bracket, j - bracket)); - bracket = 0; - if (key.charAt(j + 1) !== "[") { - break; - } - } - } + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; + } + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); } - if (!keys.length) { - keys = [key]; + if (key.indexOf("\0") !== -1) { + key = key.substr(0, key.indexOf("\0")); } - for (j = 0; j < keys[0].length; j++) { - chr = keys[0].charAt(j); - if (chr === " " || chr === "." || chr === "[") { - keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); + } + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; + } + } + } } - if (chr === "[") { - break; + if (!keys.length) { + keys = [key]; } - } - /* jshint -W061 */ - evalStr = "array"; - for (j = 0; j < keys.length; j++) { - key = keys[j]; - if ((key !== "" && key !== " ") || j === 0) { - key = "'" + key + "'"; - } else { - key = eval(evalStr + ".push([]);") - 1; + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } } - evalStr += "[" + key + "]"; - if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { - eval(evalStr + " = [];"); + /* jshint -W061 */ + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if ((key !== "" && key !== " ") || j === 0) { + key = "'" + key + "'"; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += "[" + key + "]"; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); + } } + evalStr += " = '" + value + "';\n"; + eval(evalStr); + /* jshint +W061 */ } - evalStr += " = '" + value + "';\n"; - eval(evalStr); - /* jshint +W061 */ } - } - }; + }; - /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! - * - * @return array $apimethods - */ - var getApiMethods = function () { - var httpmethods = { - GET: [ - "account/settings", - "account/verify_credentials", - "application/rate_limit_status", - "blocks/ids", - "blocks/list", - "direct_messages", - "direct_messages/sent", - "direct_messages/show", - "favorites/list", - "followers/ids", - "followers/list", - "friends/ids", - "friends/list", - "friendships/incoming", - "friendships/lookup", - "friendships/lookup", - "friendships/no_retweets/ids", - "friendships/outgoing", - "friendships/show", - "geo/id/:place_id", - "geo/reverse_geocode", - "geo/search", - "geo/similar_places", - "help/configuration", - "help/languages", - "help/privacy", - "help/tos", - "lists/list", - "lists/members", - "lists/members/show", - "lists/memberships", - "lists/ownerships", - "lists/show", - "lists/statuses", - "lists/subscribers", - "lists/subscribers/show", - "lists/subscriptions", - "mutes/users/ids", - "mutes/users/list", - "oauth/authenticate", - "oauth/authorize", - "saved_searches/list", - "saved_searches/show/:id", - "search/tweets", - "site", - "statuses/firehose", - "statuses/home_timeline", - "statuses/mentions_timeline", - "statuses/oembed", - "statuses/retweeters/ids", - "statuses/retweets/:id", - "statuses/retweets_of_me", - "statuses/sample", - "statuses/show/:id", - "statuses/user_timeline", - "trends/available", - "trends/closest", - "trends/place", - "user", - "users/contributees", - "users/contributors", - "users/profile_banner", - "users/search", - "users/show", - "users/suggestions", - "users/suggestions/:slug", - "users/suggestions/:slug/members" - ], - POST: [ - "account/remove_profile_banner", - "account/settings__post", - "account/update_delivery_device", - "account/update_profile", - "account/update_profile_background_image", - "account/update_profile_banner", - "account/update_profile_colors", - "account/update_profile_image", - "blocks/create", - "blocks/destroy", - "direct_messages/destroy", - "direct_messages/new", - "favorites/create", - "favorites/destroy", - "friendships/create", - "friendships/destroy", - "friendships/update", - "lists/create", - "lists/destroy", - "lists/members/create", - "lists/members/create_all", - "lists/members/destroy", - "lists/members/destroy_all", - "lists/subscribers/create", - "lists/subscribers/destroy", - "lists/update", - "media/upload", - "mutes/users/create", - "mutes/users/destroy", - "oauth/access_token", - "oauth/request_token", - "oauth2/invalidate_token", - "oauth2/token", - "saved_searches/create", - "saved_searches/destroy/:id", - "statuses/destroy/:id", - "statuses/filter", - "statuses/lookup", - "statuses/retweet/:id", - "statuses/update", - "statuses/update_with_media", // deprecated, use media/upload - "users/lookup", - "users/report_spam" - ] + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! + * + * @return array $apimethods + */ + var getApiMethods = function () { + var httpmethods = { + GET: [ + "account/settings", + "account/verify_credentials", + "application/rate_limit_status", + "blocks/ids", + "blocks/list", + "direct_messages", + "direct_messages/sent", + "direct_messages/show", + "favorites/list", + "followers/ids", + "followers/list", + "friends/ids", + "friends/list", + "friendships/incoming", + "friendships/lookup", + "friendships/lookup", + "friendships/no_retweets/ids", + "friendships/outgoing", + "friendships/show", + "geo/id/:place_id", + "geo/reverse_geocode", + "geo/search", + "geo/similar_places", + "help/configuration", + "help/languages", + "help/privacy", + "help/tos", + "lists/list", + "lists/members", + "lists/members/show", + "lists/memberships", + "lists/ownerships", + "lists/show", + "lists/statuses", + "lists/subscribers", + "lists/subscribers/show", + "lists/subscriptions", + "mutes/users/ids", + "mutes/users/list", + "oauth/authenticate", + "oauth/authorize", + "saved_searches/list", + "saved_searches/show/:id", + "search/tweets", + "site", + "statuses/firehose", + "statuses/home_timeline", + "statuses/mentions_timeline", + "statuses/oembed", + "statuses/retweeters/ids", + "statuses/retweets/:id", + "statuses/retweets_of_me", + "statuses/sample", + "statuses/show/:id", + "statuses/user_timeline", + "trends/available", + "trends/closest", + "trends/place", + "user", + "users/contributees", + "users/contributors", + "users/profile_banner", + "users/search", + "users/show", + "users/suggestions", + "users/suggestions/:slug", + "users/suggestions/:slug/members" + ], + POST: [ + "account/remove_profile_banner", + "account/settings__post", + "account/update_delivery_device", + "account/update_profile", + "account/update_profile_background_image", + "account/update_profile_banner", + "account/update_profile_colors", + "account/update_profile_image", + "blocks/create", + "blocks/destroy", + "direct_messages/destroy", + "direct_messages/new", + "favorites/create", + "favorites/destroy", + "friendships/create", + "friendships/destroy", + "friendships/update", + "lists/create", + "lists/destroy", + "lists/members/create", + "lists/members/create_all", + "lists/members/destroy", + "lists/members/destroy_all", + "lists/subscribers/create", + "lists/subscribers/destroy", + "lists/update", + "media/upload", + "mutes/users/create", + "mutes/users/destroy", + "oauth/access_token", + "oauth/request_token", + "oauth2/invalidate_token", + "oauth2/token", + "saved_searches/create", + "saved_searches/destroy/:id", + "statuses/destroy/:id", + "statuses/filter", + "statuses/lookup", + "statuses/retweet/:id", + "statuses/update", + "statuses/update_with_media", // deprecated, use media/upload + "users/lookup", + "users/report_spam" + ] + }; + return httpmethods; }; - return httpmethods; - }; - /** - * Promise helpers - */ + /** + * Promise helpers + */ - /** - * Get a deferred object - */ - var _getDfd = function () { - if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { - return window.jQuery.Deferred(); - } - if (typeof window.Q !== "undefined" && window.Q.defer) { - return window.Q.defer(); - } - if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { - return window.RSVP.defer(); - } - if (typeof window.when !== "undefined" && window.when.defer) { - return window.when.defer(); - } - if (typeof require !== "undefined") { - var promise_class = require("jquery"); - if (promise_class) { - return promise_class.Deferred(); + /** + * Get a deferred object + */ + var _getDfd = function () { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); } - promise_class = require("q"); - if (!promise_class) { - promise_class = require("rsvp"); + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); } - if (!promise_class) { - promise_class = require("when"); + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); } - if (promise_class) { - return promise_class.defer(); + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); } - } - return false; - }; - - /** - * Get a promise from the dfd object - */ - var _getPromise = function (dfd) { - if (typeof dfd.promise === "function") { - return dfd.promise(); - } - return dfd.promise; // object - }; - - /** - * Main API handler working on any requests you issue - * - * @param string fn The member function you called - * @param array params The parameters you sent along - * @param function callback The callback to call with the reply - * @param bool app_only_auth Whether to use app-only auth - * - * @return object Promise - */ + if (typeof require !== "undefined") { + var promise_class = require("jquery"); + if (promise_class) { + return promise_class.Deferred(); + } + promise_class = require("q"); + if (!promise_class) { + promise_class = require("rsvp"); + } + if (!promise_class) { + promise_class = require("when"); + } + if (promise_class) { + return promise_class.defer(); + } + } + return false; + }; - var __call = function (fn, params, callback, app_only_auth) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function" && typeof params === "function") { - callback = params; - params = {}; - if (typeof callback === "boolean") { - app_only_auth = callback; - } - } else if (typeof callback === "undefined") { - callback = function () {}; - } - switch (fn) { - case "oauth_authenticate": - case "oauth_authorize": - return this[fn](params, callback); + /** + * Get a promise from the dfd object + */ + var _getPromise = function (dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + }; - case "oauth2_token": - return this[fn](callback); - } + /** + * Main API handler working on any requests you issue + * + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth + * + * @return object Promise + */ + + var __call = function (fn, params, callback, app_only_auth) { + if (typeof params === "undefined") { + params = {}; + } + if (typeof app_only_auth === "undefined") { + app_only_auth = false; + } + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = function () { }; + } + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return this[fn](params, callback); - // parse parameters - var apiparams = _parseApiParams(params); + case "oauth2_token": + return this[fn](callback); + } - // stringify null and boolean parameters - apiparams = _stringifyNullBoolParams(apiparams); + // parse parameters + var apiparams = _parseApiParams(params); - // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) - if (fn === "oauth_requestToken") { - setToken(null, null); - } + // stringify null and boolean parameters + apiparams = _stringifyNullBoolParams(apiparams); - // map function name to API method - var data = _mapFnToApiMethod(fn, apiparams), - method = data[0], - method_template = data[1]; - - var httpmethod = _detectMethod(method_template, apiparams); - var multipart = _detectMultipart(method_template); - - return _callApi( - httpmethod, - method, - apiparams, - multipart, - app_only_auth, - callback - ); - }; + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + setToken(null, null); + } + // map function name to API method + var data = _mapFnToApiMethod(fn, apiparams), + method = data[0], + method_template = data[1]; + + var httpmethod = _detectMethod(method_template, apiparams); + var multipart = _detectMultipart(method_template); + + return _callApi( + httpmethod, + method, + apiparams, + multipart, + app_only_auth, + callback + ); + }; - /** - * __call() helpers - */ - /** - * Parse given params, detect query-style params - * - * @param array|string params Parameters to parse - * - * @return array apiparams - */ - var _parseApiParams = function (params) { - var apiparams = {}; - if (typeof params === "object") { - apiparams = params; - } else { - _parse_str(params, apiparams); //TODO - } + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + var _parseApiParams = function (params) { + var apiparams = {}; + if (typeof params === "object") { + apiparams = params; + } else { + _parse_str(params, apiparams); //TODO + } - return apiparams; - }; + return apiparams; + }; - /** - * Replace null and boolean parameters with their string representations - * - * @param array apiparams Parameter array to replace in - * - * @return array apiparams - */ - var _stringifyNullBoolParams = function (apiparams) { - var value; - for (var key in apiparams) { - value = apiparams[key]; - if (value === null) { - apiparams[key] = "null"; - } else if (value === true || value === false) { - apiparams[key] = value ? "true" : "false"; + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + var _stringifyNullBoolParams = function (apiparams) { + var value; + for (var key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { + continue; + } + value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; + } } - } - return apiparams; - }; + return apiparams; + }; - /** - * Maps called PHP magic method name to Twitter API method - * - * @param string $fn Function called - * @param array $apiparams byref API parameters - * - * @return string[] (string method, string method_template) - */ - var _mapFnToApiMethod = function (fn, apiparams) { - var method = ""; - var param, i, j; - - // replace _ by / - method = _mapFnInsertSlashes(fn); - - // undo replacement for URL parameters - method = _mapFnRestoreParamUnderscores(method); - - // replace AA by URL parameters - var method_template = method; - var match = method.match(/[A-Z_]{2,}/); - if (match) { - for (i = 0; i < match.length; i++) { - param = match[i]; - var param_l = param.toLowerCase(); - method_template = method_template.split(param).join(":" + param_l); - if (typeof apiparams[param_l] === "undefined") { - for (j = 0; j < 26; j++) { - method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + var _mapFnToApiMethod = function (fn, apiparams) { + var method = ""; + var param, i, j; + + // replace _ by / + method = _mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = _mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + var method_template = method; + var match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + var param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); } - console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; } - method = method.split(param).join(apiparams[param_l]); - delete apiparams[param_l]; } - } - // replace A-Z by _a-z - for (i = 0; i < 26; i++) { - method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - } + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + } - return [method, method_template]; - }; + return [method, method_template]; + }; - /** - * API method mapping: Replaces _ with / character - * - * @param string fn Function called - * - * @return string API method to call - */ - var _mapFnInsertSlashes = function (fn) { - var path = fn.split("_"), - method = path.join("/"); + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + var _mapFnInsertSlashes = function (fn) { + var path = fn.split("_"), + method = path.join("/"); - return method; - }; + return method; + }; - /** - * API method mapping: Restore _ character in named parameters - * - * @param string method API method to call - * - * @return string API method with restored underscores - */ - var _mapFnRestoreParamUnderscores = function (method) { - var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); - } + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + var _mapFnRestoreParamUnderscores = function (method) { + var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } - return method; - }; + return method; + }; - /** - * Uncommon API methods - */ + /** + * Uncommon API methods + */ - /** - * Gets the OAuth authenticate URL for the current request token - * - * @return object Promise - */ - var oauth_authenticate = function (params, callback, type) { - var dfd = _getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (typeof type === "undefined" - || ["authenticate", "authorize"].indexOf(type) === -1 - ) { - type = "authenticate"; - } - if (_oauth_token === null) { - var error = "To get the " + type + " URL, the OAuth token must be set."; - console.warn(error); + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + var oauth_authenticate = function (params, callback, type) { + var dfd = _getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (typeof type === "undefined" + || ["authenticate", "authorize"].indexOf(type) === -1 + ) { + type = "authenticate"; + } + if (_oauth_token === null) { + var error = "To get the " + type + " URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; + } + var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); + if (params.force_login === true) { + url += "&force_login=1"; + if (params.screen_name !== null) { + url += "&screen_name=" + params.screen_name; + } + } + if (typeof callback === "function") { + callback(url); + } if (dfd) { - dfd.reject({ error: error }); + dfd.resolve({ reply: url }); return _getPromise(dfd); } - return false; - } - var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; - } - } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve({ reply: url }); - return _getPromise(dfd); - } - // no promises - return true; - }; + // no promises + return true; + }; - /** - * Gets the OAuth authorize URL for the current request token - * - * @return string The OAuth authorize URL - */ - var oauth_authorize = function (params, callback) { - return oauth_authenticate(params, callback, "authorize"); - }; + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ + var oauth_authorize = function (params, callback) { + return oauth_authenticate(params, callback, "authorize"); + }; - /** - * Gets the OAuth bearer token - * - * @return object Promise - */ + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ - var oauth2_token = function (callback) { - var dfd = _getDfd(); + var oauth2_token = function (callback) { + var dfd = _getDfd(); - if (_oauth_consumer_key === null) { - var error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); + if (_oauth_consumer_key === null) { + var error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; } - return false; - } - if (!dfd && typeof callback === "undefined") { - callback = function () {}; - } + if (!dfd && typeof callback === "undefined") { + callback = function () { }; + } - var post_fields = "grant_type=client_credentials"; - var url = _endpoint_oauth + "oauth2/token"; + var post_fields = "grant_type=client_credentials"; + var url = _endpoint_oauth + "oauth2/token"; - if (_use_proxy) { - url = url.replace( - _endpoint_base, - _endpoint_proxy - ); - } + if (_use_proxy) { + url = url.replace( + _endpoint_base, + _endpoint_proxy + ); + } - var xml = _getXmlRequestObject(); - if (xml === null) { - return; - } - xml.open("POST", url, true); - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xml.setRequestHeader( - (_use_proxy ? "X-" : "") + "Authorization", - "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) - ); - - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = _parseApiReply(response); - reply.httpstatus = httpstatus; - if (httpstatus === 200) { - setBearerToken(reply.access_token); + var xml = _getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader( + (_use_proxy ? "X-" : "") + "Authorization", + "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) + ); + + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + var response = ""; + try { + response = xml.responseText; + } catch (e) { } + var reply = _parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({ reply: reply }); + } } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { if (typeof callback === "function") { - callback(reply); + callback(null, e); } if (dfd) { - dfd.resolve({ reply: reply }); + dfd.reject(e); } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, e); - } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(post_fields); if (dfd) { - dfd.reject(e); + return _getPromise(dfd); } }; - xml.timeout = 30000; // in milliseconds - xml.send(post_fields); - if (dfd) { - return _getPromise(dfd); - } - }; - - /** - * Signing helpers - */ - - /** - * URL-encodes the given data - * - * @param mixed data - * - * @return mixed The encoded data - */ - var _url = function (data) { - if ((/boolean|number|string/).test(typeof data)) { - return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); - } else { - return ""; - } - }; + /** + * Signing helpers + */ + + /** + * URL-encodes the given data + * + * @param mixed data + * + * @return mixed The encoded data + */ + var _url = function (data) { + if ((/boolean|number|string/).test(typeof data)) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; + } + }; - /** - * Gets the base64-encoded SHA1 hash for the given data - * - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - * - * @param string data The data to calculate the hash from - * - * @return string The hash - */ - var _sha1 = (function () { - function n(e, b) { - e[b >> 5] |= 128 << 24 - b % 32; - e[(b + 64 >> 9 << 4) + 15] = b; - for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * + * @param string data The data to calculate the hash from + * + * @return string The hash + */ + var _sha1 = (function () { + function n(e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { - for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m; + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + var m; + + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } - if (f < 16) { - m = e[p + f]; - } else { - m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; - m = m << 1 | m >>> 31; + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ + h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( + l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : + 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; } - - c[f] = m; - m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ - h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( - l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : - 60 > f ? -1894007588 : -899497514)); - g = k; - k = h; - h = d << 30 | d >>> 2; - d = a; - a = m; + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); } - a = l(a, o); - d = l(d, q); - h = l(h, r); - k = l(k, s); - g = l(g, t); + return [a, d, h, k, g]; } - return [a, d, h, k, g]; - } - function l(e, b) { - var c = (e & 65535) + (b & 65535); - return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; - } + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; + } - function q(e) { - for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { - b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; } - return b; - } - var g = 8; - return function (e) { - var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? - _oauth_token_secret : ""); - if (_oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); - } - var c = q(b); - if (c.length > 16) { - c = n(c, b.length * g); - } - var bb = new Array(16); - for (var a = new Array(16), d = 0; d < 16; d++) { - a[d] = c[d] ^ 909522486; - bb[d] = c[d] ^ 1549556828; - } - c = n(a.concat(q(e)), 512 + e.length * g); - bb = n(bb.concat(c), 672); - b = ""; - for (g = 0; g < 4 * bb.length; g += 3) { - for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> - 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - .charAt(d >> 6 * (3 - e) & 63); + var g = 8; + return function (e) { + var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? + _oauth_token_secret : ""); + if (_oauth_consumer_secret === null) { + console.warn("To generate a hash, the consumer secret must be set."); + } + var c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); + } + var bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; } + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> + 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - + (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .charAt(d >> 6 * (3 - e) & 63); + } + } + return b; + }; + })(); + + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation + */ + var _base64_encode = function (a) { + var d, e, f, b, g = 0, + h = 0, + i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + c = []; + if (!a) { + return a; } - return b; + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); }; - })(); - - /* - * Gets the base64 representation for the given data - * - * http://phpjs.org - * + original by: Tyler Akins (http://rumkin.com) - * + improved by: Bayron Guevara - * + improved by: Thunder.m - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfixed by: Pellentesque Malesuada - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Rafał Kukawski (http://kukawski.pl) - * - * @param string data The data to calculate the base64 representation from - * - * @return string The base64 representation - */ - var _base64_encode = function (a) { - var d, e, f, b, g = 0, - h = 0, - i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - c = []; - if (!a) { - return a; - } - do { - d = a.charCodeAt(g++); - e = a.charCodeAt(g++); - f = a.charCodeAt(g++); - b = d << 16 | e << 8 | f; - d = b >> 18 & 63; - e = b >> 12 & 63; - f = b >> 6 & 63; - b &= 63; - c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); - } while (g < a.length); - i = c.join(""); - a = a.length % 3; - return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); - }; - /* - * Builds a HTTP query string from the given data - * - * http://phpjs.org - * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Legaev Andrey - * + improved by: Michael White (http://getsprink.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Brett Zamir (http://brett-zamir.me) - * + revised by: stag019 - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) - * - * @param string data The data to concatenate - * - * @return string The HTTP query - */ - var _http_build_query = function (e, f, b) { - function g(c, a, d) { - var b, e = []; - if (a === true) { - a = "1"; - } else if (a === false) { - a = "0"; - } - if (null !== a) { - if (typeof a === "object") { - for (b in a) { - if (a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); + /* + * Builds a HTTP query string from the given data + * + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + * + * @param string data The data to concatenate + * + * @return string The HTTP query + */ + var _http_build_query = function (e, f, b) { + function g(c, a, d) { + var b, e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if (typeof a === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g(c + "[" + b + "]", a[b], d)); + } } + return e.join(d); + } + if (typeof a !== "function") { + return _url(c) + "=" + _url(a); } - return e.join(d); + console.warn("There was an error processing for http_build_query()."); + } else { + return ""; + } + } + var d, c, h = []; + if (!b) { + b = "&"; + } + for (c in e) { + if (!e.hasOwnProperty(c)) { + continue; } - if (typeof a !== "function") { - return _url(c) + "=" + _url(a); + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g(c, d, b); + if (d !== "") { + h.push(d); } - console.warn("There was an error processing for http_build_query()."); - } else { - return ""; } - } - var d, c, h = []; - if (! b) { - b = "&"; - } - for (c in e) { - d = e[c]; - if (f && ! isNaN(c)) { - c = String(f) + c; + return h.join(b); + }; + + /** + * Generates a (hopefully) unique random string + * + * @param int optional length The length of the string to generate + * + * @return string The random string + */ + var _nonce = function (length) { + if (typeof length === "undefined") { + length = 8; } - d = g(c, d, b); - if (d !== "") { - h.push(d); + if (length < 1) { + console.warn("Invalid nonce length."); } - } - return h.join(b); - }; + var nonce = ""; + for (var i = 0; i < length; i++) { + var character = Math.floor(Math.random() * 61); + nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); + } + return nonce; + }; - /** - * Generates a (hopefully) unique random string - * - * @param int optional length The length of the string to generate - * - * @return string The random string - */ - var _nonce = function (length) { - if (typeof length === "undefined") { - length = 8; - } - if (length < 1) { - console.warn("Invalid nonce length."); - } - var nonce = ""; - for (var i = 0; i < length; i++) { - var character = Math.floor(Math.random() * 61); - nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); - } - return nonce; - }; + /** + * Sort array elements by key + * + * @param array input_arr The array to sort + * + * @return array The sorted keys + */ + var _ksort = function (input_arr) { + var keys = [], sorter, k; + + sorter = function (a, b) { + var a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; - /** - * Sort array elements by key - * - * @param array input_arr The array to sort - * - * @return array The sorted keys - */ - var _ksort = function (input_arr) { - var keys = [], sorter, k; - - sorter = function (a, b) { - var a_float = parseFloat(a), - b_float = parseFloat(b), - a_numeric = a_float + "" === a, - b_numeric = b_float + "" === b; - if (a_numeric && b_numeric) { - return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; - } else if (a_numeric && !b_numeric) { - return 1; - } else if (!a_numeric && b_numeric) { - return -1; - } - return a > b ? 1 : a < b ? -1 : 0; + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } + } + keys.sort(sorter); + return keys; }; - // Make a list of key names - for (k in input_arr) { - if (input_arr.hasOwnProperty(k)) { - keys.push(k); + /** + * Clone objects + * + * @param object obj The object to clone + * + * @return object clone The cloned object + */ + var _clone = function (obj) { + var clone = {}; + for (var i in obj) { + if (typeof (obj[i]) === "object") { + clone[i] = _clone(obj[i]); + } else { + clone[i] = obj[i]; + } } - } - keys.sort(sorter); - return keys; - }; + return clone; + }; - /** - * Clone objects - * - * @param object obj The object to clone - * - * @return object clone The cloned object - */ - var _clone = function (obj) { - var clone = {}; - for (var i in obj) { - if (typeof(obj[i]) === "object") { - clone[i] = _clone(obj[i]); - } else { - clone[i] = obj[i]; + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + var _getSignature = function (httpmethod, method, keys, base_params) { + // convert params to string + var base_string = "", key, value; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += key + "=" + _url(value) + "&"; } - } - return clone; - }; - - /** - * Signature helper - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array base_params The signature base parameters - * - * @return string signature - */ - var _getSignature = function (httpmethod, method, keys, base_params) { - // convert params to string - var base_string = "", key, value; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = base_params[key]; - base_string += key + "=" + _url(value) + "&"; - } - base_string = base_string.substring(0, base_string.length - 1); - return _sha1( - httpmethod + "&" + - _url(method) + "&" + - _url(base_string) - ); - }; - - /** - * Generates an OAuth signature - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array optional params The API call parameters, associative - * @param bool optional append_to_get Whether to append the OAuth params to GET - * - * @return string Authorization HTTP header - */ - var _sign = function (httpmethod, method, params, append_to_get) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof append_to_get === "undefined") { - append_to_get = false; - } - if (_oauth_consumer_key === null) { - console.warn("To generate a signature, the consumer key must be set."); - } - var sign_params = { - consumer_key: _oauth_consumer_key, - version: "1.0", - timestamp: Math.round(new Date().getTime() / 1000), - nonce: _nonce(), - signature_method: "HMAC-SHA1" + base_string = base_string.substring(0, base_string.length - 1); + return _sha1( + httpmethod + "&" + + _url(method) + "&" + + _url(base_string) + ); }; - var sign_base_params = {}; - var value; - for (var key in sign_params) { - value = sign_params[key]; - sign_base_params["oauth_" + key] = _url(value); - } - if (_oauth_token !== null) { - sign_base_params.oauth_token = _url(_oauth_token); - } - var oauth_params = _clone(sign_base_params); - for (key in params) { - value = params[key]; - sign_base_params[key] = value; - } - var keys = _ksort(sign_base_params); - - var signature = _getSignature(httpmethod, method, keys, sign_base_params); - params = append_to_get ? sign_base_params : oauth_params; - params.oauth_signature = signature; - keys = _ksort(params); - var authorization = "", i; - if (append_to_get) { - for(i = 0; i < keys.length; i++) { + /** + * Generates an OAuth signature + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array optional params The API call parameters, associative + * @param bool optional append_to_get Whether to append the OAuth params to GET + * + * @return string Authorization HTTP header + */ + var _sign = function (httpmethod, method, params, append_to_get) { + if (typeof params === "undefined") { + params = {}; + } + if (typeof append_to_get === "undefined") { + append_to_get = false; + } + if (_oauth_consumer_key === null) { + console.warn("To generate a signature, the consumer key must be set."); + } + var sign_params = { + consumer_key: _oauth_consumer_key, + version: "1.0", + timestamp: Math.round(new Date().getTime() / 1000), + nonce: _nonce(), + signature_method: "HMAC-SHA1" + }; + var sign_base_params = {}; + var value; + for (var key in sign_params) { + if (!sign_params.hasOwnProperty(key)) { + continue; + } + value = sign_params[key]; + sign_base_params["oauth_" + key] = _url(value); + } + if (_oauth_token !== null) { + sign_base_params.oauth_token = _url(_oauth_token); + } + var oauth_params = _clone(sign_base_params); + for (key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + value = params[key]; + sign_base_params[key] = value; + } + var keys = _ksort(sign_base_params); + + var signature = _getSignature(httpmethod, method, keys, sign_base_params); + + params = append_to_get ? sign_base_params : oauth_params; + params.oauth_signature = signature; + keys = _ksort(params); + var authorization = "", i; + if (append_to_get) { + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = params[key]; + authorization += key + "=" + _url(value) + "&"; + } + return authorization.substring(0, authorization.length - 1); + } + authorization = "OAuth "; + for (i = 0; i < keys.length; i++) { key = keys[i]; value = params[key]; - authorization += key + "=" + _url(value) + "&"; + authorization += key + "=\"" + _url(value) + "\", "; } - return authorization.substring(0, authorization.length - 1); - } - authorization = "OAuth "; - for (i = 0; i < keys.length; i++) { - key = keys[i]; - value = params[key]; - authorization += key + "=\"" + _url(value) + "\", "; - } - return authorization.substring(0, authorization.length - 2); - }; + return authorization.substring(0, authorization.length - 2); + }; - /** - * Detects HTTP method to use for API call - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return string The HTTP method that should be used - */ - var _detectMethod = function (method, params) { - // multi-HTTP method endpoints - switch (method) { - case "account/settings": - case "account/login_verification_enrollment": - case "account/login_verification_request": - method = params.length ? method + "__post" : method; - break; - } + /** + * Detects HTTP method to use for API call + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return string The HTTP method that should be used + */ + var _detectMethod = function (method, params) { + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = params.length ? method + "__post" : method; + break; + } - var apimethods = getApiMethods(); - for (var httpmethod in apimethods) { - if (apimethods[httpmethod].indexOf(method) > -1) { - return httpmethod; + var apimethods = getApiMethods(); + for (var httpmethod in apimethods) { + if (apimethods.hasOwnProperty(httpmethod) + && apimethods[httpmethod].indexOf(method) > -1 + ) { + return httpmethod; + } } - } - console.warn("Can't find HTTP method to use for \"" + method + "\"."); - }; + console.warn("Can't find HTTP method to use for \"" + method + "\"."); + }; - /** - * Detects if API call should use multipart/form-data - * - * @param string method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - var _detectMultipart = function (method) { - var multiparts = [ + /** + * Detects if API call should use multipart/form-data + * + * @param string method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + var _detectMultipart = function (method) { + var multiparts = [ // Tweets - "statuses/update_with_media", + "statuses/update_with_media", // Users - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; - return multiparts.indexOf(method) > -1; - }; + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + return multiparts.indexOf(method) > -1; + }; - /** - * Build multipart request from upload params - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return null|string The built multipart request body - */ - var _buildMultipart = function (method, params) { - // well, files will only work in multipart methods - if (! _detectMultipart(method)) { - return; - } + /** + * Build multipart request from upload params + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return null|string The built multipart request body + */ + var _buildMultipart = function (method, params) { + // well, files will only work in multipart methods + if (!_detectMultipart(method)) { + return; + } - // only check specific parameters - var possible_methods = [ - // Tweets - "statuses/update_with_media", - // Accounts - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; - var possible_files = { + // only check specific parameters + var possible_methods = [ // Tweets - "statuses/update_with_media": "media[]", + "statuses/update_with_media", // Accounts - "account/update_profile_background_image": "image", - "account/update_profile_image": "image", - "account/update_profile_banner": "banner" - }; - // method might have files? - if (possible_methods.indexOf(method) === -1) { - return; - } + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + var possible_files = { + // Tweets + "statuses/update_with_media": "media[]", + // Accounts + "account/update_profile_background_image": "image", + "account/update_profile_image": "image", + "account/update_profile_banner": "banner" + }; + // method might have files? + if (possible_methods.indexOf(method) === -1) { + return; + } - // check for filenames - possible_files = possible_files[method].split(" "); + // check for filenames + possible_files = possible_files[method].split(" "); - var multipart_border = "--------------------" + _nonce(); - var multipart_request = ""; - for (var key in params) { - multipart_request += + var multipart_border = "--------------------" + _nonce(); + var multipart_request = ""; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + multipart_request += "--" + multipart_border + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\""; - if (possible_files.indexOf(key) > -1) { - multipart_request += + if (possible_files.indexOf(key) > -1) { + multipart_request += "\r\nContent-Transfer-Encoding: base64"; - } - multipart_request += + } + multipart_request += "\r\n\r\n" + params[key] + "\r\n"; - } - multipart_request += "--" + multipart_border + "--"; - return multipart_request; - }; + } + multipart_request += "--" + multipart_border + "--"; + return multipart_request; + }; - /** - * Detects if API call should use media endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in media API - */ - var _detectMedia = function (method) { - var medias = [ - "media/upload" - ]; - return medias.join(" ").indexOf(method) > -1; - }; + /** + * Detects if API call should use media endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in media API + */ + var _detectMedia = function (method) { + var medias = [ + "media/upload" + ]; + return medias.join(" ").indexOf(method) > -1; + }; - /** - * Detects if API call should use old endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in old API - */ - var _detectOld = function (method) { - var olds = [ - "account/push_destinations/device" - ]; - return olds.join(" ").indexOf(method) > -1; - }; + /** + * Detects if API call should use old endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in old API + */ + var _detectOld = function (method) { + var olds = [ + "account/push_destinations/device" + ]; + return olds.join(" ").indexOf(method) > -1; + }; - /** - * Builds the complete API endpoint url - * - * @param string method The API method to call - * - * @return string The URL to send the request to - */ - var _getEndpoint = function (method) { - var url; - if (method.substring(0, 5) === "oauth") { - url = _endpoint_oauth + method; - } else if (_detectMedia(method)) { - url = _endpoint_media + method + ".json"; - } else if (_detectOld(method)) { - url = _endpoint_old + method + ".json"; - } else { - url = _endpoint + method + ".json"; - } - return url; - }; + /** + * Builds the complete API endpoint url + * + * @param string method The API method to call + * + * @return string The URL to send the request to + */ + var _getEndpoint = function (method) { + var url; + if (method.substring(0, 5) === "oauth") { + url = _endpoint_oauth + method; + } else if (_detectMedia(method)) { + url = _endpoint_media + method + ".json"; + } else if (_detectOld(method)) { + url = _endpoint_old + method + ".json"; + } else { + url = _endpoint + method + ".json"; + } + return url; + }; - /** - * Gets the XML HTTP Request object, trying to load it in various ways - * - * @return object The XMLHttpRequest object instance - */ - var _getXmlRequestObject = function () { - var xml = null; - // first, try the W3-standard object - if (typeof window === "object" - && window - && typeof window.XMLHttpRequest !== "undefined" - ) { - xml = new window.XMLHttpRequest(); - // then, try Titanium framework object - } else if (typeof Ti === "object" - && Ti - && typeof Ti.Network.createHTTPClient !== "undefined" - ) { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined" - ) { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - console.error("ActiveXObject object not defined."); - } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" - && require - ) { - var XMLHttpRequest; - // look for xmlhttprequest module - try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 + /** + * Gets the XML HTTP Request object, trying to load it in various ways + * + * @return object The XMLHttpRequest object instance + */ + var _getXmlRequestObject = function () { + var xml = null; + // first, try the W3-standard object + if (typeof window === "object" + && window + && typeof window.XMLHttpRequest !== "undefined" + ) { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if (typeof Ti === "object" + && Ti + && typeof Ti.Network.createHTTPClient !== "undefined" + ) { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined" + ) { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + console.error("ActiveXObject object not defined."); + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function" + && require + ) { + var XMLHttpRequest; + // look for xmlhttprequest module try { - XMLHttpRequest = require("xhr2"); + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; xml = new XMLHttpRequest(); - } catch (e2) { - console.error("xhr2 object not defined, cancelling."); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + console.error("xhr2 object not defined, cancelling."); + } } } - } - return xml; - }; - - /** - * Calls the API using cURL - * - * @param string httpmethod The HTTP method to use for making the request - * @param string method The API method to call - * @param array optional params The parameters to send along - * @param bool optional multipart Whether to use multipart/form-data - * @param bool optional app_only_auth Whether to use app-only bearer authentication - * @param function callback The function to call with the API call result - * - * @return mixed The API reply, encoded in the set return_format - */ - - var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { - var dfd = _getDfd(); - - if (typeof params === "undefined") { - params = {}; - } - if (typeof multipart === "undefined") { - multipart = false; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function") { - callback = function () {}; - } + return xml; + }; - var url = _getEndpoint(method); - var authorization = null; + /** + * Calls the API using cURL + * + * @param string httpmethod The HTTP method to use for making the request + * @param string method The API method to call + * @param array optional params The parameters to send along + * @param bool optional multipart Whether to use multipart/form-data + * @param bool optional app_only_auth Whether to use app-only bearer authentication + * @param function callback The function to call with the API call result + * + * @return mixed The API reply, encoded in the set return_format + */ + + var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { + var dfd = _getDfd(); + + if (typeof params === "undefined") { + params = {}; + } + if (typeof multipart === "undefined") { + multipart = false; + } + if (typeof app_only_auth === "undefined") { + app_only_auth = false; + } + if (typeof callback !== "function") { + callback = function () { }; + } - var xml = _getXmlRequestObject(); - if (xml === null) { - return; - } - var post_fields; + var url = _getEndpoint(method); + var authorization = null; - if (httpmethod === "GET") { - var url_with_params = url; - if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + _http_build_query(params); - } - if (! app_only_auth) { - authorization = _sign(httpmethod, url, params); + var xml = _getXmlRequestObject(); + if (xml === null) { + return; } + var post_fields; - // append auth params to GET url for IE7-9, to send via JSONP - if (_use_jsonp) { + if (httpmethod === "GET") { + var url_with_params = url; if (JSON.stringify(params) !== "{}") { - url_with_params += "&"; - } else { - url_with_params += "?"; + url_with_params += "?" + _http_build_query(params); + } + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); } - var callback_name = _nonce(); - window[callback_name] = function (reply) { - reply.httpstatus = 200; - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; + // append auth params to GET url for IE7-9, to send via JSONP + if (_use_jsonp) { + if (JSON.stringify(params) !== "{}") { + url_with_params += "&"; + } else { + url_with_params += "?"; } - callback(reply, rate); - }; - params.callback = callback_name; - url_with_params = url + "?" + _sign(httpmethod, url, params, true); - var tag = document.createElement("script"); - tag.type = "text/javascript"; - tag.src = url_with_params; - var body = document.getElementsByTagName("body")[0]; - body.appendChild(tag); - return; - - } else if (_use_proxy) { - url_with_params = url_with_params.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); - } - xml.open(httpmethod, url_with_params, true); - } else { - if (_use_jsonp) { - console.warn("Sending POST requests is not supported for IE7-9."); - return; - } - if (multipart) { - if (! app_only_auth) { - authorization = _sign(httpmethod, url, {}); + var callback_name = _nonce(); + window[callback_name] = function (reply) { + reply.httpstatus = 200; + + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" + && xml.getResponseHeader("x-rate-limit-limit") !== "" + ) { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + callback(reply, rate); + }; + params.callback = callback_name; + url_with_params = url + "?" + _sign(httpmethod, url, params, true); + var tag = document.createElement("script"); + tag.type = "text/javascript"; + tag.src = url_with_params; + var body = document.getElementsByTagName("body")[0]; + body.appendChild(tag); + return; + + } else if (_use_proxy) { + url_with_params = url_with_params.replace( + _endpoint_base, + _endpoint_proxy + ).replace( + _endpoint_base_media, + _endpoint_proxy + ); } - params = _buildMultipart(method, params); + xml.open(httpmethod, url_with_params, true); } else { - if (! app_only_auth) { - authorization = _sign(httpmethod, url, params); + if (_use_jsonp) { + console.warn("Sending POST requests is not supported for IE7-9."); + return; } - params = _http_build_query(params); - } - post_fields = params; - if (_use_proxy || multipart) { // force proxy for multipart base64 - url = url.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); - } - xml.open(httpmethod, url, true); - if (multipart) { - xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" - + post_fields.split("\r\n")[0].substring(2)); - } else { - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - } - } - if (app_only_auth) { - if (_oauth_consumer_key === null - && _oauth_bearer_token === null - ) { - var error = "To make an app-only auth API request, consumer key or bearer token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); + if (multipart) { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, {}); + } + params = _buildMultipart(method, params); + } else { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } + params = _http_build_query(params); + } + post_fields = params; + if (_use_proxy || multipart) { // force proxy for multipart base64 + url = url.replace( + _endpoint_base, + _endpoint_proxy + ).replace( + _endpoint_base_media, + _endpoint_proxy + ); + } + xml.open(httpmethod, url, true); + if (multipart) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + + post_fields.split("\r\n")[0].substring(2)); + } else { + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } } - // automatically fetch bearer token, if necessary - if (_oauth_bearer_token === null) { - if (dfd) { - return oauth2_token().then(function () { - return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + if (app_only_auth) { + if (_oauth_consumer_key === null + && _oauth_bearer_token === null + ) { + var error = "To make an app-only auth API request, consumer key or bearer token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + } + // automatically fetch bearer token, if necessary + if (_oauth_bearer_token === null) { + if (dfd) { + return oauth2_token().then(function () { + return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + } + oauth2_token(function () { + _callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); + return; } - oauth2_token(function () { - _callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); - return; + authorization = "Bearer " + _oauth_bearer_token; } - authorization = "Bearer " + _oauth_bearer_token; - } - if (authorization !== null) { - xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); - } - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = _parseApiReply(response); - reply.httpstatus = httpstatus; - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; + if (authorization !== null) { + xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); + } + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + var response = ""; + try { + response = xml.responseText; + } catch (e) { } + var reply = _parseApiReply(response); + reply.httpstatus = httpstatus; + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" + && xml.getResponseHeader("x-rate-limit-limit") !== "" + ) { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve({ reply: reply, rate: rate }); + } } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { if (typeof callback === "function") { - callback(reply, rate); + callback(null, null, e); } if (dfd) { - dfd.resolve({ reply: reply, rate: rate }); + dfd.reject(e); } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function(e) { - if (typeof callback === "function") { - callback(null, null, e); - } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(httpmethod === "GET" ? null : post_fields); if (dfd) { - dfd.reject(e); + return _getPromise(dfd); } + return true; }; - xml.timeout = 30000; // in milliseconds - xml.send(httpmethod === "GET" ? null : post_fields); - if (dfd) { - return _getPromise(dfd); - } - return true; - }; - - /** - * Parses the API reply to encode it in the set return_format - * - * @param string reply The actual reply, JSON-encoded or URL-encoded - * - * @return array|object The parsed reply - */ - var _parseApiReply = function (reply) { - if (typeof reply !== "string" || reply === "") { - return {}; - } - if (reply === "[]") { - return []; - } - var parsed; - try { - parsed = JSON.parse(reply); - } catch (e) { - parsed = {}; - if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { - // we received XML... - // since this only happens for errors, - // don't perform a full decoding - parsed.request = reply.match(/(.*)<\/request>/)[1]; - parsed.error = reply.match(/(.*)<\/error>/)[1]; - } else { - // assume query format - var elements = reply.split("&"); - for (var i = 0; i < elements.length; i++) { - var element = elements[i].split("=", 2); - if (element.length > 1) { - parsed[element[0]] = decodeURIComponent(element[1]); - } else { - parsed[element[0]] = null; + /** + * Parses the API reply to encode it in the set return_format + * + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply + */ + var _parseApiReply = function (reply) { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + var parsed; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { + // we received XML... + // since this only happens for errors, + // don't perform a full decoding + parsed.request = reply.match(/(.*)<\/request>/)[1]; + parsed.error = reply.match(/(.*)<\/error>/)[1]; + } else { + // assume query format + var elements = reply.split("&"); + for (var i = 0; i < elements.length; i++) { + var element = elements[i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } } } } - } - return parsed; - }; + return parsed; + }; - return { - setConsumerKey: setConsumerKey, - getVersion: getVersion, - setToken: setToken, - logout: logout, - setBearerToken: setBearerToken, - setUseProxy: setUseProxy, - setProxy: setProxy, - getApiMethods: getApiMethods, - __call: __call, - oauth_authenticate: oauth_authenticate, - oauth_authorize: oauth_authorize, - oauth2_token: oauth2_token + return { + setConsumerKey: setConsumerKey, + getVersion: getVersion, + setToken: setToken, + logout: logout, + setBearerToken: setBearerToken, + setUseProxy: setUseProxy, + setProxy: setProxy, + getApiMethods: getApiMethods, + __call: __call, + oauth_authenticate: oauth_authenticate, + oauth_authorize: oauth_authorize, + oauth2_token: oauth2_token + }; }; -}; - -if (typeof module === "object" - && module - && typeof module.exports === "object" -) { - // Expose codebird as module.exports in loaders that implement the Node - // module pattern (including browserify). Do not create the global, since - // the user will be storing it themselves locally, and globals are frowned - // upon in the Node module world. - module.exports = Codebird; -} else { - // Otherwise expose codebird to the global object as usual - if (typeof window === "object" - && window) { - window.Codebird = Codebird; - } - // Register as a named AMD module, since codebird can be concatenated with other - // files that may use define, but not via a proper concatenation script that - // understands anonymous AMD modules. A named AMD is safest and most robust - // way to register. Lowercase codebird is used because AMD module names are - // derived from file names, and codebird is normally delivered in a lowercase - // file name. Do this after creating the global so that if an AMD module wants - // to call noConflict to hide this version of codebird, it will work. - if (typeof define === "function" && define.amd) { - define("codebird", [], function () { return Codebird; }); + if (typeof module === "object" + && module + && typeof module.exports === "object" + ) { + // Expose codebird as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = Codebird; + } else { + // Otherwise expose codebird to the global object as usual + if (typeof window === "object" + && window) { + window.Codebird = Codebird; + } + + // Register as a named AMD module, since codebird can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase codebird is used because AMD module names are + // derived from file names, and codebird is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of codebird, it will work. + if (typeof define === "function" && define.amd) { + define("codebird", [], function () { return Codebird; }); + } } -} })(); From 79c388feb4ff76ae9f78c69e30f170ab2ef377bb Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 18:11:33 +0100 Subject: [PATCH 16/48] Drop support for old API endpoint --- codebird.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/codebird.js b/codebird.js index 769f287..9ccb9b3 100644 --- a/codebird.js +++ b/codebird.js @@ -98,11 +98,6 @@ */ var _endpoint_proxy = "https://api.jublo.net/codebird/"; - /** - * The API endpoint to use for old requests - */ - var _endpoint_old = _endpoint_base + "1/"; - /** * Use JSONP for GET requests in IE7-9 */ @@ -1339,20 +1334,6 @@ return medias.join(" ").indexOf(method) > -1; }; - /** - * Detects if API call should use old endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in old API - */ - var _detectOld = function (method) { - var olds = [ - "account/push_destinations/device" - ]; - return olds.join(" ").indexOf(method) > -1; - }; - /** * Builds the complete API endpoint url * @@ -1366,8 +1347,6 @@ url = _endpoint_oauth + method; } else if (_detectMedia(method)) { url = _endpoint_media + method + ".json"; - } else if (_detectOld(method)) { - url = _endpoint_old + method + ".json"; } else { url = _endpoint + method + ".json"; } From 709e4fd9fb860b435da1e0eeee4c0600266ddc79 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 4 Dec 2015 20:58:26 +0100 Subject: [PATCH 17/48] Add support for Collections API Fix #110 --- CHANGELOG | 1 + README.md | 32 ++++++++++++++++++++++++++++++++ codebird.js | 31 ++++++++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a278559..b36e111 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ codebird-js - changelog + #98 Support promises as alternative to callbacks - Drop support for undocumented API methods + Add security check hasOwnProperty ++ #110 Add support for Collections API 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/README.md b/README.md index be96c9c..4f8805d 100644 --- a/README.md +++ b/README.md @@ -632,6 +632,38 @@ When this error occurs, advise the user to [generate a temporary password](https://twitter.com/settings/applications) on twitter.com and use that to complete signing in to the application. +### …access the Collections API? + +Collections are a type of timeline that you control and can be hand curated +and/or programmed using an API. + +Pay close attention to the differences in how collections are presented — +often they will be decomposed, efficient objects with information about users, +Tweets, and timelines grouped, simplified, and stripped of unnecessary repetition. + +Never care about the OAuth signing specialities and the JSON POST body +for POST collections/entries/curate.json. Codebird takes off the work for you +and will always send the correct Content-Type automatically. + +Find out more about the [Collections API](https://dev.twitter.com/rest/collections/about) in the Twitter API docs. + +Here’s a sample for adding a tweet using that API method: + +```javascript +cb.__call( + "collections_entries_curate", + { + "id": "custom-672852634622144512", + "changes": [ + {"op": "add", "tweet_id": "672727928262828032"} + ] + }, + function (reply, rate) { + document.body.innerText = JSON.stringify(reply); + } +); +``` + ### …use promises instead of callback functions? Have you ever heard of the [Pyramid of Doom](http://calculist.org/blog/2011/12/14/why-coroutines-wont-work-on-the-web/)? diff --git a/codebird.js b/codebird.js index 9ccb9b3..49f84cf 100644 --- a/codebird.js +++ b/codebird.js @@ -329,6 +329,9 @@ "application/rate_limit_status", "blocks/ids", "blocks/list", + "collections/entries", + "collections/list", + "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", @@ -403,6 +406,13 @@ "account/update_profile_image", "blocks/create", "blocks/destroy", + "collections/create", + "collections/destroy", + "collections/entries/add", + "collections/entries/curate", + "collections/entries/move", + "collections/entries/remove", + "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", @@ -1331,7 +1341,21 @@ var medias = [ "media/upload" ]; - return medias.join(" ").indexOf(method) > -1; + return medias.indexOf(method) > -1; + }; + + /** + * Detects if API call should use JSON body + * + * @param string method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + var _detectJsonBody = function (method) { + var json_bodies = [ + "collections/entries/curate" + ]; + return json_bodies.indexOf(method) > -1; }; /** @@ -1501,6 +1525,9 @@ authorization = _sign(httpmethod, url, {}); } params = _buildMultipart(method, params); + } else if (_detectJsonBody(method)) { + authorization = _sign(httpmethod, url, {}); + params = JSON.stringify(params); } else { if (!app_only_auth) { authorization = _sign(httpmethod, url, params); @@ -1521,6 +1548,8 @@ if (multipart) { xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); + } else if (_detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "application/json"); } else { xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } From 9c8de9465af096e27549a0df9f602fd95afd7f93 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 1 Jan 2016 22:54:06 +0100 Subject: [PATCH 18/48] Ignore node_modules in Git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2bc2c81..09c504b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +node_modules RELEASE_MESSAGE* test* *.jpg From 7dc9b08b4ac6fde3532a9c111ddd362a9909e16a Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 1 Jan 2016 22:54:25 +0100 Subject: [PATCH 19/48] Set year to 2016 --- README.md | 2 +- codebird.js | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4f8805d..729b802 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ codebird-js =========== *A Twitter library in JavaScript.* -Copyright (C) 2010-2015 Jublo Solutions +Copyright (C) 2010-2016 Jublo Solutions This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/codebird.js b/codebird.js index 49f84cf..381453d 100644 --- a/codebird.js +++ b/codebird.js @@ -4,7 +4,7 @@ * @package codebird * @version 3.0.0-dev * @author Jublo Solutions - * @copyright 2010-2015 Jublo Solutions + * @copyright 2010-2016 Jublo Solutions * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ diff --git a/package.json b/package.json index 9a98048..c098766 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codebird", - "version": "2.6.0", + "version": "3.0.0-dev", "description": "A Twitter library in JavaScript.", "keywords": [ "Twitter", From c0bfc36c651bcc4a7ee65849853acca19a2900c6 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Fri, 1 Jan 2016 22:54:39 +0100 Subject: [PATCH 20/48] Add unit testing suite stub --- .travis.yml | 26 ++++++++++++++++++++++++++ README.md | 2 ++ package.json | 7 ++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6e6b67c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +# The Travis setup: +# - run lint for every JS version +# - run testsuite for every JS version + +language: node_js + +node_js: + - "0.10" + - "0.12" + - iojs + - "4" + - "5" + +sudo: false + +script: + - ./node_modules/eslint/bin/eslint.js codebird.js + - npm test + +matrix: + fast_finish: true + +# trigger Buildtime Trend Service to parse Travis CI log +notifications: + webhooks: + - https://buildtimetrend.herokuapp.com/travis diff --git a/README.md b/README.md index 729b802..28ce72e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . +[![Travis Status](https://img.shields.io/travis/jublonet/codebird-js/develop.svg)](https://travis-ci.org/jublonet/codebird-js/branches) + Including Codebird ------------------ diff --git a/package.json b/package.json index c098766..0a2fa64 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,11 @@ "url": "https://github.com/jublonet/codebird-js.git" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "blue-tape -r ./codebird test/*.js | faucet" + }, + "devDependencies": { + "blue-tape": "^0.1.11", + "eslint": "^1.10.3", + "faucet": "0.0.1" } } From 8ca2bd8941390ced7e71d19e4eaf98b99593379b Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 2 Jan 2016 11:23:50 +0100 Subject: [PATCH 21/48] Convert to 2 spaces indentation --- CONTRIBUTING.md | 2 +- bower.json | 5 +- codebird.js | 3234 +++++++++++++++++++++++------------------------ 3 files changed, 1620 insertions(+), 1621 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f1a2b6b..70e6d51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ - New features are added to the develop branch, too. ### Code style -- Please use 4 soft spaces per indent level. +- Please use 2 soft spaces per indent level. - Take a look at the coding style used in codebird.js and apply the same convention to your contributed code. ### License diff --git a/bower.json b/bower.json index c19e1aa..9a440af 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "codebird-js", - "version": "2.6.0", + "version": "3.0.0-dev", "homepage": "http://www.jublo.net/projects/codebird/js", "authors": [ "Joshua Atkins ", @@ -25,7 +25,6 @@ "node_modules", "bower_components", "RELEASE_MESSAGE*", - "test", - "tests" + "test" ] } diff --git a/codebird.js b/codebird.js index 381453d..f959c26 100644 --- a/codebird.js +++ b/codebird.js @@ -10,1709 +10,1709 @@ */ /* jshint curly: true, - eqeqeq: true, - latedef: true, - quotmark: double, - undef: true, - unused: true, - trailing: true, - laxbreak: true */ + eqeqeq: true, + latedef: true, + quotmark: double, + undef: true, + unused: true, + trailing: true, + laxbreak: true */ /* global window, - document, - navigator, - console, - Ti, - ActiveXObject, - module, - define, - require */ + document, + navigator, + console, + Ti, + ActiveXObject, + module, + define, + require */ (function (undefined) { - "use strict"; + "use strict"; + + /** + * Array.indexOf polyfill + */ + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function (obj, start) { + for (var i = (start || 0); i < this.length; i++) { + if (this[i] === obj) { + return i; + } + } + return -1; + }; + } + + /** + * A Twitter library in JavaScript + * + * @package codebird + * @subpackage codebird-js + */ + /* jshint -W098 */ + var Codebird = function () { + /* jshint +W098 */ /** - * Array.indexOf polyfill + * The OAuth consumer key of your registered app */ - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (obj, start) { - for (var i = (start || 0); i < this.length; i++) { - if (this[i] === obj) { - return i; - } - } - return -1; - }; - } + var _oauth_consumer_key = null; /** - * A Twitter library in JavaScript - * - * @package codebird - * @subpackage codebird-js + * The corresponding consumer secret */ - /* jshint -W098 */ - var Codebird = function () { - /* jshint +W098 */ - - /** - * The OAuth consumer key of your registered app - */ - var _oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - var _oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - var _oauth_bearer_token = null; - - /** - * The API endpoint base to use - */ - var _endpoint_base = "https://api.twitter.com/"; - - /** - * The media API endpoint base to use - */ - var _endpoint_base_media = "https://upload.twitter.com/"; - - /** - * The API endpoint to use - */ - var _endpoint = _endpoint_base + "1.1/"; - - /** - * The media API endpoint to use - */ - var _endpoint_media = _endpoint_base_media + "1.1/"; - - /** - * The API endpoint base to use - */ - var _endpoint_oauth = _endpoint_base; - - /** - * API proxy endpoint - */ - var _endpoint_proxy = "https://api.jublo.net/codebird/"; - - /** - * Use JSONP for GET requests in IE7-9 - */ - var _use_jsonp = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - && (navigator.userAgent.indexOf("Trident/4") > -1 - || navigator.userAgent.indexOf("Trident/5") > -1 - || navigator.userAgent.indexOf("MSIE 7.0") > -1 - ) - ); - - /** - * Whether to access the API via a proxy that is allowed by CORS - * Assume that CORS is only necessary in browsers - */ - var _use_proxy = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - ); - - /** - * The Request or access token. Used to sign requests - */ - var _oauth_token = null; - - /** - * The corresponding request or access token secret - */ - var _oauth_token_secret = null; - - /** - * The current Codebird version - */ - var _version = "3.0.0-dev"; - - /** - * Sets the OAuth consumer key and secret (App key) - * - * @param string key OAuth consumer key - * @param string secret OAuth consumer secret - * - * @return void - */ - var setConsumerKey = function (key, secret) { - _oauth_consumer_key = key; - _oauth_consumer_secret = secret; - }; + var _oauth_consumer_secret = null; - /** - * Sets the OAuth2 app-only auth bearer token - * - * @param string token OAuth2 bearer token - * - * @return void - */ - var setBearerToken = function (token) { - _oauth_bearer_token = token; - }; - - /** - * Gets the current Codebird version - * - * @return string The version number - */ - var getVersion = function () { - return _version; - }; + /** + * The app-only bearer token. Used to authorize app-only requests + */ + var _oauth_bearer_token = null; - /** - * Sets the OAuth request or access token and secret (User key) - * - * @param string token OAuth request or access token - * @param string secret OAuth request or access token secret - * - * @return void - */ - var setToken = function (token, secret) { - _oauth_token = token; - _oauth_token_secret = secret; - }; + /** + * The API endpoint base to use + */ + var _endpoint_base = "https://api.twitter.com/"; - /** - * Forgets the OAuth request or access token and secret (User key) - * - * @return bool - */ - var logout = function () { - _oauth_token = - _oauth_token_secret = null; + /** + * The media API endpoint base to use + */ + var _endpoint_base_media = "https://upload.twitter.com/"; - return true; - }; + /** + * The API endpoint to use + */ + var _endpoint = _endpoint_base + "1.1/"; - /** - * Enables or disables CORS proxy - * - * @param bool use_proxy Whether to use CORS proxy or not - * - * @return void - */ - var setUseProxy = function (use_proxy) { - _use_proxy = !!use_proxy; - }; + /** + * The media API endpoint to use + */ + var _endpoint_media = _endpoint_base_media + "1.1/"; - /** - * Sets custom CORS proxy server - * - * @param string proxy Address of proxy server to use - * - * @return void - */ - var setProxy = function (proxy) { - // add trailing slash if missing - if (!proxy.match(/\/$/)) { - proxy += "/"; - } - _endpoint_proxy = proxy; - }; + /** + * The API endpoint base to use + */ + var _endpoint_oauth = _endpoint_base; - /** - * Parse URL-style parameters into object - * - * version: 1109.2015 - * discuss at: http://phpjs.org/functions/parse_str - * + original by: Cagri Ekin - * + improved by: Michael White (http://getsprink.com) - * + tweaked by: Jack - * + bugfixed by: Onno Marsman - * + reimplemented by: stag019 - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: stag019 - * - depends on: urldecode - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * % note 1: When no argument is specified, will put variables in global scope. - * - * @param string str String to parse - * @param array array to load data into - * - * @return object - */ - var _parse_str = function (str, array) { - var glue1 = "=", - glue2 = "&", - array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), - i, j, chr, tmp, key, value, bracket, keys, evalStr, - fixStr = function (str) { - return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); - }; - if (!array) { - array = this.window; - } + /** + * API proxy endpoint + */ + var _endpoint_proxy = "https://api.jublo.net/codebird/"; - for (i = 0; i < array2.length; i++) { - tmp = array2[i].split(glue1); - if (tmp.length < 2) { - tmp = [tmp, ""]; - } - key = fixStr(tmp[0]); - value = fixStr(tmp[1]); - while (key.charAt(0) === " ") { - key = key.substr(1); - } - if (key.indexOf("\0") !== -1) { - key = key.substr(0, key.indexOf("\0")); - } - if (key && key.charAt(0) !== "[") { - keys = []; - bracket = 0; - for (j = 0; j < key.length; j++) { - if (key.charAt(j) === "[" && !bracket) { - bracket = j + 1; - } else if (key.charAt(j) === "]") { - if (bracket) { - if (!keys.length) { - keys.push(key.substr(0, bracket - 1)); - } - keys.push(key.substr(bracket, j - bracket)); - bracket = 0; - if (key.charAt(j + 1) !== "[") { - break; - } - } - } - } - if (!keys.length) { - keys = [key]; - } - for (j = 0; j < keys[0].length; j++) { - chr = keys[0].charAt(j); - if (chr === " " || chr === "." || chr === "[") { - keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); - } - if (chr === "[") { - break; - } - } - /* jshint -W061 */ - evalStr = "array"; - for (j = 0; j < keys.length; j++) { - key = keys[j]; - if ((key !== "" && key !== " ") || j === 0) { - key = "'" + key + "'"; - } else { - key = eval(evalStr + ".push([]);") - 1; - } - evalStr += "[" + key + "]"; - if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { - eval(evalStr + " = [];"); - } - } - evalStr += " = '" + value + "';\n"; - eval(evalStr); - /* jshint +W061 */ - } - } - }; + /** + * Use JSONP for GET requests in IE7-9 + */ + var _use_jsonp = (typeof navigator !== "undefined" + && typeof navigator.userAgent !== "undefined" + && (navigator.userAgent.indexOf("Trident/4") > -1 + || navigator.userAgent.indexOf("Trident/5") > -1 + || navigator.userAgent.indexOf("MSIE 7.0") > -1 + ) + ); - /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! - * - * @return array $apimethods - */ - var getApiMethods = function () { - var httpmethods = { - GET: [ - "account/settings", - "account/verify_credentials", - "application/rate_limit_status", - "blocks/ids", - "blocks/list", - "collections/entries", - "collections/list", - "collections/show", - "direct_messages", - "direct_messages/sent", - "direct_messages/show", - "favorites/list", - "followers/ids", - "followers/list", - "friends/ids", - "friends/list", - "friendships/incoming", - "friendships/lookup", - "friendships/lookup", - "friendships/no_retweets/ids", - "friendships/outgoing", - "friendships/show", - "geo/id/:place_id", - "geo/reverse_geocode", - "geo/search", - "geo/similar_places", - "help/configuration", - "help/languages", - "help/privacy", - "help/tos", - "lists/list", - "lists/members", - "lists/members/show", - "lists/memberships", - "lists/ownerships", - "lists/show", - "lists/statuses", - "lists/subscribers", - "lists/subscribers/show", - "lists/subscriptions", - "mutes/users/ids", - "mutes/users/list", - "oauth/authenticate", - "oauth/authorize", - "saved_searches/list", - "saved_searches/show/:id", - "search/tweets", - "site", - "statuses/firehose", - "statuses/home_timeline", - "statuses/mentions_timeline", - "statuses/oembed", - "statuses/retweeters/ids", - "statuses/retweets/:id", - "statuses/retweets_of_me", - "statuses/sample", - "statuses/show/:id", - "statuses/user_timeline", - "trends/available", - "trends/closest", - "trends/place", - "user", - "users/contributees", - "users/contributors", - "users/profile_banner", - "users/search", - "users/show", - "users/suggestions", - "users/suggestions/:slug", - "users/suggestions/:slug/members" - ], - POST: [ - "account/remove_profile_banner", - "account/settings__post", - "account/update_delivery_device", - "account/update_profile", - "account/update_profile_background_image", - "account/update_profile_banner", - "account/update_profile_colors", - "account/update_profile_image", - "blocks/create", - "blocks/destroy", - "collections/create", - "collections/destroy", - "collections/entries/add", - "collections/entries/curate", - "collections/entries/move", - "collections/entries/remove", - "collections/update", - "direct_messages/destroy", - "direct_messages/new", - "favorites/create", - "favorites/destroy", - "friendships/create", - "friendships/destroy", - "friendships/update", - "lists/create", - "lists/destroy", - "lists/members/create", - "lists/members/create_all", - "lists/members/destroy", - "lists/members/destroy_all", - "lists/subscribers/create", - "lists/subscribers/destroy", - "lists/update", - "media/upload", - "mutes/users/create", - "mutes/users/destroy", - "oauth/access_token", - "oauth/request_token", - "oauth2/invalidate_token", - "oauth2/token", - "saved_searches/create", - "saved_searches/destroy/:id", - "statuses/destroy/:id", - "statuses/filter", - "statuses/lookup", - "statuses/retweet/:id", - "statuses/update", - "statuses/update_with_media", // deprecated, use media/upload - "users/lookup", - "users/report_spam" - ] - }; - return httpmethods; - }; + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + var _use_proxy = (typeof navigator !== "undefined" + && typeof navigator.userAgent !== "undefined" + ); - /** - * Promise helpers - */ + /** + * The Request or access token. Used to sign requests + */ + var _oauth_token = null; - /** - * Get a deferred object - */ - var _getDfd = function () { - if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { - return window.jQuery.Deferred(); - } - if (typeof window.Q !== "undefined" && window.Q.defer) { - return window.Q.defer(); - } - if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { - return window.RSVP.defer(); - } - if (typeof window.when !== "undefined" && window.when.defer) { - return window.when.defer(); - } - if (typeof require !== "undefined") { - var promise_class = require("jquery"); - if (promise_class) { - return promise_class.Deferred(); - } - promise_class = require("q"); - if (!promise_class) { - promise_class = require("rsvp"); - } - if (!promise_class) { - promise_class = require("when"); - } - if (promise_class) { - return promise_class.defer(); - } - } - return false; - }; + /** + * The corresponding request or access token secret + */ + var _oauth_token_secret = null; - /** - * Get a promise from the dfd object - */ - var _getPromise = function (dfd) { - if (typeof dfd.promise === "function") { - return dfd.promise(); - } - return dfd.promise; // object - }; + /** + * The current Codebird version + */ + var _version = "3.0.0-dev"; - /** - * Main API handler working on any requests you issue - * - * @param string fn The member function you called - * @param array params The parameters you sent along - * @param function callback The callback to call with the reply - * @param bool app_only_auth Whether to use app-only auth - * - * @return object Promise - */ - - var __call = function (fn, params, callback, app_only_auth) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function" && typeof params === "function") { - callback = params; - params = {}; - if (typeof callback === "boolean") { - app_only_auth = callback; - } - } else if (typeof callback === "undefined") { - callback = function () { }; - } - switch (fn) { - case "oauth_authenticate": - case "oauth_authorize": - return this[fn](params, callback); + /** + * Sets the OAuth consumer key and secret (App key) + * + * @param string key OAuth consumer key + * @param string secret OAuth consumer secret + * + * @return void + */ + var setConsumerKey = function (key, secret) { + _oauth_consumer_key = key; + _oauth_consumer_secret = secret; + }; - case "oauth2_token": - return this[fn](callback); - } + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string token OAuth2 bearer token + * + * @return void + */ + var setBearerToken = function (token) { + _oauth_bearer_token = token; + }; - // parse parameters - var apiparams = _parseApiParams(params); + /** + * Gets the current Codebird version + * + * @return string The version number + */ + var getVersion = function () { + return _version; + }; - // stringify null and boolean parameters - apiparams = _stringifyNullBoolParams(apiparams); + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string token OAuth request or access token + * @param string secret OAuth request or access token secret + * + * @return void + */ + var setToken = function (token, secret) { + _oauth_token = token; + _oauth_token_secret = secret; + }; - // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) - if (fn === "oauth_requestToken") { - setToken(null, null); - } + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ + var logout = function () { + _oauth_token = + _oauth_token_secret = null; - // map function name to API method - var data = _mapFnToApiMethod(fn, apiparams), - method = data[0], - method_template = data[1]; - - var httpmethod = _detectMethod(method_template, apiparams); - var multipart = _detectMultipart(method_template); - - return _callApi( - httpmethod, - method, - apiparams, - multipart, - app_only_auth, - callback - ); - }; + return true; + }; + /** + * Enables or disables CORS proxy + * + * @param bool use_proxy Whether to use CORS proxy or not + * + * @return void + */ + var setUseProxy = function (use_proxy) { + _use_proxy = !!use_proxy; + }; - /** - * __call() helpers - */ - - /** - * Parse given params, detect query-style params - * - * @param array|string params Parameters to parse - * - * @return array apiparams - */ - var _parseApiParams = function (params) { - var apiparams = {}; - if (typeof params === "object") { - apiparams = params; - } else { - _parse_str(params, apiparams); //TODO - } + /** + * Sets custom CORS proxy server + * + * @param string proxy Address of proxy server to use + * + * @return void + */ + var setProxy = function (proxy) { + // add trailing slash if missing + if (!proxy.match(/\/$/)) { + proxy += "/"; + } + _endpoint_proxy = proxy; + }; - return apiparams; + /** + * Parse URL-style parameters into object + * + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object + */ + var _parse_str = function (str, array) { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, j, chr, tmp, key, value, bracket, keys, evalStr, + fixStr = function (str) { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); }; - - /** - * Replace null and boolean parameters with their string representations - * - * @param array apiparams Parameter array to replace in - * - * @return array apiparams - */ - var _stringifyNullBoolParams = function (apiparams) { - var value; - for (var key in apiparams) { - if (!apiparams.hasOwnProperty(key)) { - continue; + if (!array) { + array = this.window; + } + + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; + } + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); + } + if (key.indexOf("\0") !== -1) { + key = key.substr(0, key.indexOf("\0")); + } + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); } - value = apiparams[key]; - if (value === null) { - apiparams[key] = "null"; - } else if (value === true || value === false) { - apiparams[key] = value ? "true" : "false"; + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; } + } + } + } + if (!keys.length) { + keys = [key]; + } + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } + } + /* jshint -W061 */ + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if ((key !== "" && key !== " ") || j === 0) { + key = "'" + key + "'"; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += "[" + key + "]"; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); } + } + evalStr += " = '" + value + "';\n"; + eval(evalStr); + /* jshint +W061 */ + } + } + }; - return apiparams; - }; + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! + * + * @return array $apimethods + */ + var getApiMethods = function () { + var httpmethods = { + GET: [ + "account/settings", + "account/verify_credentials", + "application/rate_limit_status", + "blocks/ids", + "blocks/list", + "collections/entries", + "collections/list", + "collections/show", + "direct_messages", + "direct_messages/sent", + "direct_messages/show", + "favorites/list", + "followers/ids", + "followers/list", + "friends/ids", + "friends/list", + "friendships/incoming", + "friendships/lookup", + "friendships/lookup", + "friendships/no_retweets/ids", + "friendships/outgoing", + "friendships/show", + "geo/id/:place_id", + "geo/reverse_geocode", + "geo/search", + "geo/similar_places", + "help/configuration", + "help/languages", + "help/privacy", + "help/tos", + "lists/list", + "lists/members", + "lists/members/show", + "lists/memberships", + "lists/ownerships", + "lists/show", + "lists/statuses", + "lists/subscribers", + "lists/subscribers/show", + "lists/subscriptions", + "mutes/users/ids", + "mutes/users/list", + "oauth/authenticate", + "oauth/authorize", + "saved_searches/list", + "saved_searches/show/:id", + "search/tweets", + "site", + "statuses/firehose", + "statuses/home_timeline", + "statuses/mentions_timeline", + "statuses/oembed", + "statuses/retweeters/ids", + "statuses/retweets/:id", + "statuses/retweets_of_me", + "statuses/sample", + "statuses/show/:id", + "statuses/user_timeline", + "trends/available", + "trends/closest", + "trends/place", + "user", + "users/contributees", + "users/contributors", + "users/profile_banner", + "users/search", + "users/show", + "users/suggestions", + "users/suggestions/:slug", + "users/suggestions/:slug/members" + ], + POST: [ + "account/remove_profile_banner", + "account/settings__post", + "account/update_delivery_device", + "account/update_profile", + "account/update_profile_background_image", + "account/update_profile_banner", + "account/update_profile_colors", + "account/update_profile_image", + "blocks/create", + "blocks/destroy", + "collections/create", + "collections/destroy", + "collections/entries/add", + "collections/entries/curate", + "collections/entries/move", + "collections/entries/remove", + "collections/update", + "direct_messages/destroy", + "direct_messages/new", + "favorites/create", + "favorites/destroy", + "friendships/create", + "friendships/destroy", + "friendships/update", + "lists/create", + "lists/destroy", + "lists/members/create", + "lists/members/create_all", + "lists/members/destroy", + "lists/members/destroy_all", + "lists/subscribers/create", + "lists/subscribers/destroy", + "lists/update", + "media/upload", + "mutes/users/create", + "mutes/users/destroy", + "oauth/access_token", + "oauth/request_token", + "oauth2/invalidate_token", + "oauth2/token", + "saved_searches/create", + "saved_searches/destroy/:id", + "statuses/destroy/:id", + "statuses/filter", + "statuses/lookup", + "statuses/retweet/:id", + "statuses/update", + "statuses/update_with_media", // deprecated, use media/upload + "users/lookup", + "users/report_spam" + ] + }; + return httpmethods; + }; - /** - * Maps called PHP magic method name to Twitter API method - * - * @param string $fn Function called - * @param array $apiparams byref API parameters - * - * @return string[] (string method, string method_template) - */ - var _mapFnToApiMethod = function (fn, apiparams) { - var method = ""; - var param, i, j; - - // replace _ by / - method = _mapFnInsertSlashes(fn); - - // undo replacement for URL parameters - method = _mapFnRestoreParamUnderscores(method); - - // replace AA by URL parameters - var method_template = method; - var match = method.match(/[A-Z_]{2,}/); - if (match) { - for (i = 0; i < match.length; i++) { - param = match[i]; - var param_l = param.toLowerCase(); - method_template = method_template.split(param).join(":" + param_l); - if (typeof apiparams[param_l] === "undefined") { - for (j = 0; j < 26; j++) { - method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); - } - console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); - } - method = method.split(param).join(apiparams[param_l]); - delete apiparams[param_l]; - } - } + /** + * Promise helpers + */ - // replace A-Z by _a-z - for (i = 0; i < 26; i++) { - method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - } + /** + * Get a deferred object + */ + var _getDfd = function () { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); + } + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); + } + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); + } + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); + } + if (typeof require !== "undefined") { + var promise_class = require("jquery"); + if (promise_class) { + return promise_class.Deferred(); + } + promise_class = require("q"); + if (!promise_class) { + promise_class = require("rsvp"); + } + if (!promise_class) { + promise_class = require("when"); + } + if (promise_class) { + return promise_class.defer(); + } + } + return false; + }; - return [method, method_template]; - }; + /** + * Get a promise from the dfd object + */ + var _getPromise = function (dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + }; + /** + * Main API handler working on any requests you issue + * + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth + * + * @return object Promise + */ - /** - * API method mapping: Replaces _ with / character - * - * @param string fn Function called - * - * @return string API method to call - */ - var _mapFnInsertSlashes = function (fn) { - var path = fn.split("_"), - method = path.join("/"); + var __call = function (fn, params, callback, app_only_auth) { + if (typeof params === "undefined") { + params = {}; + } + if (typeof app_only_auth === "undefined") { + app_only_auth = false; + } + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = function () { }; + } + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return this[fn](params, callback); + + case "oauth2_token": + return this[fn](callback); + } + + // parse parameters + var apiparams = _parseApiParams(params); + + // stringify null and boolean parameters + apiparams = _stringifyNullBoolParams(apiparams); + + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + setToken(null, null); + } + + // map function name to API method + var data = _mapFnToApiMethod(fn, apiparams), + method = data[0], + method_template = data[1]; + + var httpmethod = _detectMethod(method_template, apiparams); + var multipart = _detectMultipart(method_template); + + return _callApi( + httpmethod, + method, + apiparams, + multipart, + app_only_auth, + callback + ); + }; - return method; - }; - /** - * API method mapping: Restore _ character in named parameters - * - * @param string method API method to call - * - * @return string API method with restored underscores - */ - var _mapFnRestoreParamUnderscores = function (method) { - var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); - } + /** + * __call() helpers + */ - return method; - }; + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + var _parseApiParams = function (params) { + var apiparams = {}; + if (typeof params === "object") { + apiparams = params; + } else { + _parse_str(params, apiparams); //TODO + } + + return apiparams; + }; + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + var _stringifyNullBoolParams = function (apiparams) { + var value; + for (var key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { + continue; + } + value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; + } + } - /** - * Uncommon API methods - */ + return apiparams; + }; - /** - * Gets the OAuth authenticate URL for the current request token - * - * @return object Promise - */ - var oauth_authenticate = function (params, callback, type) { - var dfd = _getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (typeof type === "undefined" - || ["authenticate", "authorize"].indexOf(type) === -1 - ) { - type = "authenticate"; - } - if (_oauth_token === null) { - var error = "To get the " + type + " URL, the OAuth token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - return false; - } - var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; - } - } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve({ reply: url }); - return _getPromise(dfd); - } - // no promises - return true; - }; + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + var _mapFnToApiMethod = function (fn, apiparams) { + var method = ""; + var param, i, j; + + // replace _ by / + method = _mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = _mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + var method_template = method; + var match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + var param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); + } + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; + } + } - /** - * Gets the OAuth authorize URL for the current request token - * - * @return string The OAuth authorize URL - */ - var oauth_authorize = function (params, callback) { - return oauth_authenticate(params, callback, "authorize"); - }; + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + } - /** - * Gets the OAuth bearer token - * - * @return object Promise - */ - - var oauth2_token = function (callback) { - var dfd = _getDfd(); - - if (_oauth_consumer_key === null) { - var error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - return false; - } + return [method, method_template]; + }; - if (!dfd && typeof callback === "undefined") { - callback = function () { }; - } - var post_fields = "grant_type=client_credentials"; - var url = _endpoint_oauth + "oauth2/token"; + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + var _mapFnInsertSlashes = function (fn) { + var path = fn.split("_"), + method = path.join("/"); - if (_use_proxy) { - url = url.replace( - _endpoint_base, - _endpoint_proxy - ); - } + return method; + }; - var xml = _getXmlRequestObject(); - if (xml === null) { - return; - } - xml.open("POST", url, true); - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xml.setRequestHeader( - (_use_proxy ? "X-" : "") + "Authorization", - "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) - ); - - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) { } - var response = ""; - try { - response = xml.responseText; - } catch (e) { } - var reply = _parseApiReply(response); - reply.httpstatus = httpstatus; - if (httpstatus === 200) { - setBearerToken(reply.access_token); - } - if (typeof callback === "function") { - callback(reply); - } - if (dfd) { - dfd.resolve({ reply: reply }); - } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, e); - } - if (dfd) { - dfd.reject(e); - } - }; - xml.timeout = 30000; // in milliseconds + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + var _mapFnRestoreParamUnderscores = function (method) { + var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } + + return method; + }; - xml.send(post_fields); - if (dfd) { - return _getPromise(dfd); - } - }; - /** - * Signing helpers - */ - - /** - * URL-encodes the given data - * - * @param mixed data - * - * @return mixed The encoded data - */ - var _url = function (data) { - if ((/boolean|number|string/).test(typeof data)) { - return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); - } else { - return ""; - } - }; + /** + * Uncommon API methods + */ - /** - * Gets the base64-encoded SHA1 hash for the given data - * - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - * - * @param string data The data to calculate the hash from - * - * @return string The hash - */ - var _sha1 = (function () { - function n(e, b) { - e[b >> 5] |= 128 << 24 - b % 32; - e[(b + 64 >> 9 << 4) + 15] = b; - for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, - k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { - for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m; - - if (f < 16) { - m = e[p + f]; - } else { - m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; - m = m << 1 | m >>> 31; - } - - c[f] = m; - m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ - h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( - l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : - 60 > f ? -1894007588 : -899497514)); - g = k; - k = h; - h = d << 30 | d >>> 2; - d = a; - a = m; - } - a = l(a, o); - d = l(d, q); - h = l(h, r); - k = l(k, s); - g = l(g, t); - } - return [a, d, h, k, g]; - } + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + var oauth_authenticate = function (params, callback, type) { + var dfd = _getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (typeof type === "undefined" + || ["authenticate", "authorize"].indexOf(type) === -1 + ) { + type = "authenticate"; + } + if (_oauth_token === null) { + var error = "To get the " + type + " URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; + } + var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); + if (params.force_login === true) { + url += "&force_login=1"; + if (params.screen_name !== null) { + url += "&screen_name=" + params.screen_name; + } + } + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve({ reply: url }); + return _getPromise(dfd); + } + // no promises + return true; + }; - function l(e, b) { - var c = (e & 65535) + (b & 65535); - return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; - } + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ + var oauth_authorize = function (params, callback) { + return oauth_authenticate(params, callback, "authorize"); + }; - function q(e) { - for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { - b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; - } - return b; - } - var g = 8; - return function (e) { - var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? - _oauth_token_secret : ""); - if (_oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); - } - var c = q(b); - if (c.length > 16) { - c = n(c, b.length * g); - } - var bb = new Array(16); - for (var a = new Array(16), d = 0; d < 16; d++) { - a[d] = c[d] ^ 909522486; - bb[d] = c[d] ^ 1549556828; - } - c = n(a.concat(q(e)), 512 + e.length * g); - bb = n(bb.concat(c), 672); - b = ""; - for (g = 0; g < 4 * bb.length; g += 3) { - for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> - 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - .charAt(d >> 6 * (3 - e) & 63); - } - } - return b; - }; - })(); - - /* - * Gets the base64 representation for the given data - * - * http://phpjs.org - * + original by: Tyler Akins (http://rumkin.com) - * + improved by: Bayron Guevara - * + improved by: Thunder.m - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfixed by: Pellentesque Malesuada - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Rafał Kukawski (http://kukawski.pl) - * - * @param string data The data to calculate the base64 representation from - * - * @return string The base64 representation - */ - var _base64_encode = function (a) { - var d, e, f, b, g = 0, - h = 0, - i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - c = []; - if (!a) { - return a; - } - do { - d = a.charCodeAt(g++); - e = a.charCodeAt(g++); - f = a.charCodeAt(g++); - b = d << 16 | e << 8 | f; - d = b >> 18 & 63; - e = b >> 12 & 63; - f = b >> 6 & 63; - b &= 63; - c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); - } while (g < a.length); - i = c.join(""); - a = a.length % 3; - return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); - }; + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ - /* - * Builds a HTTP query string from the given data - * - * http://phpjs.org - * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Legaev Andrey - * + improved by: Michael White (http://getsprink.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Brett Zamir (http://brett-zamir.me) - * + revised by: stag019 - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) - * - * @param string data The data to concatenate - * - * @return string The HTTP query - */ - var _http_build_query = function (e, f, b) { - function g(c, a, d) { - var b, e = []; - if (a === true) { - a = "1"; - } else if (a === false) { - a = "0"; - } - if (null !== a) { - if (typeof a === "object") { - for (b in a) { - if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); - } - } - return e.join(d); - } - if (typeof a !== "function") { - return _url(c) + "=" + _url(a); - } - console.warn("There was an error processing for http_build_query()."); - } else { - return ""; - } - } - var d, c, h = []; - if (!b) { - b = "&"; - } - for (c in e) { - if (!e.hasOwnProperty(c)) { - continue; - } - d = e[c]; - if (f && !isNaN(c)) { - c = String(f) + c; - } - d = g(c, d, b); - if (d !== "") { - h.push(d); - } - } - return h.join(b); - }; + var oauth2_token = function (callback) { + var dfd = _getDfd(); - /** - * Generates a (hopefully) unique random string - * - * @param int optional length The length of the string to generate - * - * @return string The random string - */ - var _nonce = function (length) { - if (typeof length === "undefined") { - length = 8; - } - if (length < 1) { - console.warn("Invalid nonce length."); - } - var nonce = ""; - for (var i = 0; i < length; i++) { - var character = Math.floor(Math.random() * 61); - nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); - } - return nonce; - }; + if (_oauth_consumer_key === null) { + var error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + return false; + } + + if (!dfd && typeof callback === "undefined") { + callback = function () { }; + } + + var post_fields = "grant_type=client_credentials"; + var url = _endpoint_oauth + "oauth2/token"; + + if (_use_proxy) { + url = url.replace( + _endpoint_base, + _endpoint_proxy + ); + } + + var xml = _getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader( + (_use_proxy ? "X-" : "") + "Authorization", + "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) + ); + + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + var response = ""; + try { + response = xml.responseText; + } catch (e) { } + var reply = _parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({ reply: reply }); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds - /** - * Sort array elements by key - * - * @param array input_arr The array to sort - * - * @return array The sorted keys - */ - var _ksort = function (input_arr) { - var keys = [], sorter, k; - - sorter = function (a, b) { - var a_float = parseFloat(a), - b_float = parseFloat(b), - a_numeric = a_float + "" === a, - b_numeric = b_float + "" === b; - if (a_numeric && b_numeric) { - return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; - } else if (a_numeric && !b_numeric) { - return 1; - } else if (!a_numeric && b_numeric) { - return -1; - } - return a > b ? 1 : a < b ? -1 : 0; - }; + xml.send(post_fields); + if (dfd) { + return _getPromise(dfd); + } + }; - // Make a list of key names - for (k in input_arr) { - if (input_arr.hasOwnProperty(k)) { - keys.push(k); - } - } - keys.sort(sorter); - return keys; - }; + /** + * Signing helpers + */ - /** - * Clone objects - * - * @param object obj The object to clone - * - * @return object clone The cloned object - */ - var _clone = function (obj) { - var clone = {}; - for (var i in obj) { - if (typeof (obj[i]) === "object") { - clone[i] = _clone(obj[i]); - } else { - clone[i] = obj[i]; - } - } - return clone; - }; + /** + * URL-encodes the given data + * + * @param mixed data + * + * @return mixed The encoded data + */ + var _url = function (data) { + if ((/boolean|number|string/).test(typeof data)) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; + } + }; - /** - * Signature helper - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array base_params The signature base parameters - * - * @return string signature - */ - var _getSignature = function (httpmethod, method, keys, base_params) { - // convert params to string - var base_string = "", key, value; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = base_params[key]; - base_string += key + "=" + _url(value) + "&"; - } - base_string = base_string.substring(0, base_string.length - 1); - return _sha1( - httpmethod + "&" + - _url(method) + "&" + - _url(base_string) - ); - }; + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * + * @param string data The data to calculate the hash from + * + * @return string The hash + */ + var _sha1 = (function () { + function n(e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, + k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + var m; + + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } + + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ + h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( + l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : + 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; + } + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); + } + return [a, d, h, k, g]; + } - /** - * Generates an OAuth signature - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array optional params The API call parameters, associative - * @param bool optional append_to_get Whether to append the OAuth params to GET - * - * @return string Authorization HTTP header - */ - var _sign = function (httpmethod, method, params, append_to_get) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof append_to_get === "undefined") { - append_to_get = false; - } - if (_oauth_consumer_key === null) { - console.warn("To generate a signature, the consumer key must be set."); - } - var sign_params = { - consumer_key: _oauth_consumer_key, - version: "1.0", - timestamp: Math.round(new Date().getTime() / 1000), - nonce: _nonce(), - signature_method: "HMAC-SHA1" - }; - var sign_base_params = {}; - var value; - for (var key in sign_params) { - if (!sign_params.hasOwnProperty(key)) { - continue; - } - value = sign_params[key]; - sign_base_params["oauth_" + key] = _url(value); - } - if (_oauth_token !== null) { - sign_base_params.oauth_token = _url(_oauth_token); - } - var oauth_params = _clone(sign_base_params); - for (key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - value = params[key]; - sign_base_params[key] = value; - } - var keys = _ksort(sign_base_params); - - var signature = _getSignature(httpmethod, method, keys, sign_base_params); - - params = append_to_get ? sign_base_params : oauth_params; - params.oauth_signature = signature; - keys = _ksort(params); - var authorization = "", i; - if (append_to_get) { - for (i = 0; i < keys.length; i++) { - key = keys[i]; - value = params[key]; - authorization += key + "=" + _url(value) + "&"; - } - return authorization.substring(0, authorization.length - 1); - } - authorization = "OAuth "; - for (i = 0; i < keys.length; i++) { - key = keys[i]; - value = params[key]; - authorization += key + "=\"" + _url(value) + "\", "; - } - return authorization.substring(0, authorization.length - 2); - }; + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; + } - /** - * Detects HTTP method to use for API call - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return string The HTTP method that should be used - */ - var _detectMethod = function (method, params) { - // multi-HTTP method endpoints - switch (method) { - case "account/settings": - case "account/login_verification_enrollment": - case "account/login_verification_request": - method = params.length ? method + "__post" : method; - break; - } + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; + } + var g = 8; + return function (e) { + var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? + _oauth_token_secret : ""); + if (_oauth_consumer_secret === null) { + console.warn("To generate a hash, the consumer secret must be set."); + } + var c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); + } + var bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; + } + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> + 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - + (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + .charAt(d >> 6 * (3 - e) & 63); + } + } + return b; + }; + })(); - var apimethods = getApiMethods(); - for (var httpmethod in apimethods) { - if (apimethods.hasOwnProperty(httpmethod) - && apimethods[httpmethod].indexOf(method) > -1 - ) { - return httpmethod; - } - } - console.warn("Can't find HTTP method to use for \"" + method + "\"."); - }; + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation + */ + var _base64_encode = function (a) { + var d, e, f, b, g = 0, + h = 0, + i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + c = []; + if (!a) { + return a; + } + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); + }; - /** - * Detects if API call should use multipart/form-data - * - * @param string method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - var _detectMultipart = function (method) { - var multiparts = [ - // Tweets - "statuses/update_with_media", - - // Users - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; - return multiparts.indexOf(method) > -1; - }; + /* + * Builds a HTTP query string from the given data + * + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + * + * @param string data The data to concatenate + * + * @return string The HTTP query + */ + var _http_build_query = function (e, f, b) { + function g(c, a, d) { + var b, e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if (typeof a === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g(c + "[" + b + "]", a[b], d)); + } + } + return e.join(d); + } + if (typeof a !== "function") { + return _url(c) + "=" + _url(a); + } + console.warn("There was an error processing for http_build_query()."); + } else { + return ""; + } + } + var d, c, h = []; + if (!b) { + b = "&"; + } + for (c in e) { + if (!e.hasOwnProperty(c)) { + continue; + } + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g(c, d, b); + if (d !== "") { + h.push(d); + } + } + return h.join(b); + }; - /** - * Build multipart request from upload params - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return null|string The built multipart request body - */ - var _buildMultipart = function (method, params) { - // well, files will only work in multipart methods - if (!_detectMultipart(method)) { - return; - } + /** + * Generates a (hopefully) unique random string + * + * @param int optional length The length of the string to generate + * + * @return string The random string + */ + var _nonce = function (length) { + if (typeof length === "undefined") { + length = 8; + } + if (length < 1) { + console.warn("Invalid nonce length."); + } + var nonce = ""; + for (var i = 0; i < length; i++) { + var character = Math.floor(Math.random() * 61); + nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); + } + return nonce; + }; - // only check specific parameters - var possible_methods = [ - // Tweets - "statuses/update_with_media", - // Accounts - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; - var possible_files = { - // Tweets - "statuses/update_with_media": "media[]", - // Accounts - "account/update_profile_background_image": "image", - "account/update_profile_image": "image", - "account/update_profile_banner": "banner" - }; - // method might have files? - if (possible_methods.indexOf(method) === -1) { - return; - } + /** + * Sort array elements by key + * + * @param array input_arr The array to sort + * + * @return array The sorted keys + */ + var _ksort = function (input_arr) { + var keys = [], sorter, k; + + sorter = function (a, b) { + var a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; - // check for filenames - possible_files = possible_files[method].split(" "); + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } + } + keys.sort(sorter); + return keys; + }; - var multipart_border = "--------------------" + _nonce(); - var multipart_request = ""; - for (var key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - multipart_request += - "--" + multipart_border + "\r\n" - + "Content-Disposition: form-data; name=\"" + key + "\""; - if (possible_files.indexOf(key) > -1) { - multipart_request += - "\r\nContent-Transfer-Encoding: base64"; - } - multipart_request += - "\r\n\r\n" + params[key] + "\r\n"; - } - multipart_request += "--" + multipart_border + "--"; - return multipart_request; - }; + /** + * Clone objects + * + * @param object obj The object to clone + * + * @return object clone The cloned object + */ + var _clone = function (obj) { + var clone = {}; + for (var i in obj) { + if (typeof (obj[i]) === "object") { + clone[i] = _clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + return clone; + }; - /** - * Detects if API call should use media endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in media API - */ - var _detectMedia = function (method) { - var medias = [ - "media/upload" - ]; - return medias.indexOf(method) > -1; - }; + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + var _getSignature = function (httpmethod, method, keys, base_params) { + // convert params to string + var base_string = "", key, value; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += key + "=" + _url(value) + "&"; + } + base_string = base_string.substring(0, base_string.length - 1); + return _sha1( + httpmethod + "&" + + _url(method) + "&" + + _url(base_string) + ); + }; - /** - * Detects if API call should use JSON body - * - * @param string method The API method to call - * - * @return bool Whether the method is defined as accepting JSON body - */ - var _detectJsonBody = function (method) { - var json_bodies = [ - "collections/entries/curate" - ]; - return json_bodies.indexOf(method) > -1; - }; + /** + * Generates an OAuth signature + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array optional params The API call parameters, associative + * @param bool optional append_to_get Whether to append the OAuth params to GET + * + * @return string Authorization HTTP header + */ + var _sign = function (httpmethod, method, params, append_to_get) { + if (typeof params === "undefined") { + params = {}; + } + if (typeof append_to_get === "undefined") { + append_to_get = false; + } + if (_oauth_consumer_key === null) { + console.warn("To generate a signature, the consumer key must be set."); + } + var sign_params = { + consumer_key: _oauth_consumer_key, + version: "1.0", + timestamp: Math.round(new Date().getTime() / 1000), + nonce: _nonce(), + signature_method: "HMAC-SHA1" + }; + var sign_base_params = {}; + var value; + for (var key in sign_params) { + if (!sign_params.hasOwnProperty(key)) { + continue; + } + value = sign_params[key]; + sign_base_params["oauth_" + key] = _url(value); + } + if (_oauth_token !== null) { + sign_base_params.oauth_token = _url(_oauth_token); + } + var oauth_params = _clone(sign_base_params); + for (key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + value = params[key]; + sign_base_params[key] = value; + } + var keys = _ksort(sign_base_params); + + var signature = _getSignature(httpmethod, method, keys, sign_base_params); + + params = append_to_get ? sign_base_params : oauth_params; + params.oauth_signature = signature; + keys = _ksort(params); + var authorization = "", i; + if (append_to_get) { + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = params[key]; + authorization += key + "=" + _url(value) + "&"; + } + return authorization.substring(0, authorization.length - 1); + } + authorization = "OAuth "; + for (i = 0; i < keys.length; i++) { + key = keys[i]; + value = params[key]; + authorization += key + "=\"" + _url(value) + "\", "; + } + return authorization.substring(0, authorization.length - 2); + }; - /** - * Builds the complete API endpoint url - * - * @param string method The API method to call - * - * @return string The URL to send the request to - */ - var _getEndpoint = function (method) { - var url; - if (method.substring(0, 5) === "oauth") { - url = _endpoint_oauth + method; - } else if (_detectMedia(method)) { - url = _endpoint_media + method + ".json"; - } else { - url = _endpoint + method + ".json"; - } - return url; - }; + /** + * Detects HTTP method to use for API call + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return string The HTTP method that should be used + */ + var _detectMethod = function (method, params) { + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = params.length ? method + "__post" : method; + break; + } + + var apimethods = getApiMethods(); + for (var httpmethod in apimethods) { + if (apimethods.hasOwnProperty(httpmethod) + && apimethods[httpmethod].indexOf(method) > -1 + ) { + return httpmethod; + } + } + console.warn("Can't find HTTP method to use for \"" + method + "\"."); + }; - /** - * Gets the XML HTTP Request object, trying to load it in various ways - * - * @return object The XMLHttpRequest object instance - */ - var _getXmlRequestObject = function () { - var xml = null; - // first, try the W3-standard object - if (typeof window === "object" - && window - && typeof window.XMLHttpRequest !== "undefined" - ) { - xml = new window.XMLHttpRequest(); - // then, try Titanium framework object - } else if (typeof Ti === "object" - && Ti - && typeof Ti.Network.createHTTPClient !== "undefined" - ) { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined" - ) { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - console.error("ActiveXObject object not defined."); - } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" - && require - ) { - var XMLHttpRequest; - // look for xmlhttprequest module - try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 - try { - XMLHttpRequest = require("xhr2"); - xml = new XMLHttpRequest(); - } catch (e2) { - console.error("xhr2 object not defined, cancelling."); - } - } - } - return xml; - }; + /** + * Detects if API call should use multipart/form-data + * + * @param string method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + var _detectMultipart = function (method) { + var multiparts = [ + // Tweets + "statuses/update_with_media", + + // Users + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + return multiparts.indexOf(method) > -1; + }; - /** - * Calls the API using cURL - * - * @param string httpmethod The HTTP method to use for making the request - * @param string method The API method to call - * @param array optional params The parameters to send along - * @param bool optional multipart Whether to use multipart/form-data - * @param bool optional app_only_auth Whether to use app-only bearer authentication - * @param function callback The function to call with the API call result - * - * @return mixed The API reply, encoded in the set return_format - */ - - var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { - var dfd = _getDfd(); - - if (typeof params === "undefined") { - params = {}; - } - if (typeof multipart === "undefined") { - multipart = false; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function") { - callback = function () { }; - } + /** + * Build multipart request from upload params + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return null|string The built multipart request body + */ + var _buildMultipart = function (method, params) { + // well, files will only work in multipart methods + if (!_detectMultipart(method)) { + return; + } + + // only check specific parameters + var possible_methods = [ + // Tweets + "statuses/update_with_media", + // Accounts + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + var possible_files = { + // Tweets + "statuses/update_with_media": "media[]", + // Accounts + "account/update_profile_background_image": "image", + "account/update_profile_image": "image", + "account/update_profile_banner": "banner" + }; + // method might have files? + if (possible_methods.indexOf(method) === -1) { + return; + } + + // check for filenames + possible_files = possible_files[method].split(" "); + + var multipart_border = "--------------------" + _nonce(); + var multipart_request = ""; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + multipart_request += + "--" + multipart_border + "\r\n" + + "Content-Disposition: form-data; name=\"" + key + "\""; + if (possible_files.indexOf(key) > -1) { + multipart_request += + "\r\nContent-Transfer-Encoding: base64"; + } + multipart_request += + "\r\n\r\n" + params[key] + "\r\n"; + } + multipart_request += "--" + multipart_border + "--"; + return multipart_request; + }; - var url = _getEndpoint(method); - var authorization = null; + /** + * Detects if API call should use media endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in media API + */ + var _detectMedia = function (method) { + var medias = [ + "media/upload" + ]; + return medias.indexOf(method) > -1; + }; - var xml = _getXmlRequestObject(); - if (xml === null) { - return; - } - var post_fields; + /** + * Detects if API call should use JSON body + * + * @param string method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + var _detectJsonBody = function (method) { + var json_bodies = [ + "collections/entries/curate" + ]; + return json_bodies.indexOf(method) > -1; + }; - if (httpmethod === "GET") { - var url_with_params = url; - if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + _http_build_query(params); - } - if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); - } + /** + * Builds the complete API endpoint url + * + * @param string method The API method to call + * + * @return string The URL to send the request to + */ + var _getEndpoint = function (method) { + var url; + if (method.substring(0, 5) === "oauth") { + url = _endpoint_oauth + method; + } else if (_detectMedia(method)) { + url = _endpoint_media + method + ".json"; + } else { + url = _endpoint + method + ".json"; + } + return url; + }; - // append auth params to GET url for IE7-9, to send via JSONP - if (_use_jsonp) { - if (JSON.stringify(params) !== "{}") { - url_with_params += "&"; - } else { - url_with_params += "?"; - } - var callback_name = _nonce(); - window[callback_name] = function (reply) { - reply.httpstatus = 200; - - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; - } - callback(reply, rate); - }; - params.callback = callback_name; - url_with_params = url + "?" + _sign(httpmethod, url, params, true); - var tag = document.createElement("script"); - tag.type = "text/javascript"; - tag.src = url_with_params; - var body = document.getElementsByTagName("body")[0]; - body.appendChild(tag); - return; - - } else if (_use_proxy) { - url_with_params = url_with_params.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); - } - xml.open(httpmethod, url_with_params, true); - } else { - if (_use_jsonp) { - console.warn("Sending POST requests is not supported for IE7-9."); - return; - } - if (multipart) { - if (!app_only_auth) { - authorization = _sign(httpmethod, url, {}); - } - params = _buildMultipart(method, params); - } else if (_detectJsonBody(method)) { - authorization = _sign(httpmethod, url, {}); - params = JSON.stringify(params); - } else { - if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); - } - params = _http_build_query(params); - } - post_fields = params; - if (_use_proxy || multipart) { // force proxy for multipart base64 - url = url.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); - } - xml.open(httpmethod, url, true); - if (multipart) { - xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" - + post_fields.split("\r\n")[0].substring(2)); - } else if (_detectJsonBody(method)) { - xml.setRequestHeader("Content-Type", "application/json"); - } else { - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - } - } - if (app_only_auth) { - if (_oauth_consumer_key === null - && _oauth_bearer_token === null - ) { - var error = "To make an app-only auth API request, consumer key or bearer token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - } - // automatically fetch bearer token, if necessary - if (_oauth_bearer_token === null) { - if (dfd) { - return oauth2_token().then(function () { - return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); - } - oauth2_token(function () { - _callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); - return; - } - authorization = "Bearer " + _oauth_bearer_token; - } - if (authorization !== null) { - xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); - } - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) { } - var response = ""; - try { - response = xml.responseText; - } catch (e) { } - var reply = _parseApiReply(response); - reply.httpstatus = httpstatus; - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; - } - if (typeof callback === "function") { - callback(reply, rate); - } - if (dfd) { - dfd.resolve({ reply: reply, rate: rate }); - } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, null, e); - } - if (dfd) { - dfd.reject(e); - } - }; - xml.timeout = 30000; // in milliseconds + /** + * Gets the XML HTTP Request object, trying to load it in various ways + * + * @return object The XMLHttpRequest object instance + */ + var _getXmlRequestObject = function () { + var xml = null; + // first, try the W3-standard object + if (typeof window === "object" + && window + && typeof window.XMLHttpRequest !== "undefined" + ) { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if (typeof Ti === "object" + && Ti + && typeof Ti.Network.createHTTPClient !== "undefined" + ) { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined" + ) { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + console.error("ActiveXObject object not defined."); + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function" + && require + ) { + var XMLHttpRequest; + // look for xmlhttprequest module + try { + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + xml = new XMLHttpRequest(); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + console.error("xhr2 object not defined, cancelling."); + } + } + } + return xml; + }; - xml.send(httpmethod === "GET" ? null : post_fields); - if (dfd) { - return _getPromise(dfd); - } - return true; - }; + /** + * Calls the API using cURL + * + * @param string httpmethod The HTTP method to use for making the request + * @param string method The API method to call + * @param array optional params The parameters to send along + * @param bool optional multipart Whether to use multipart/form-data + * @param bool optional app_only_auth Whether to use app-only bearer authentication + * @param function callback The function to call with the API call result + * + * @return mixed The API reply, encoded in the set return_format + */ - /** - * Parses the API reply to encode it in the set return_format - * - * @param string reply The actual reply, JSON-encoded or URL-encoded - * - * @return array|object The parsed reply - */ - var _parseApiReply = function (reply) { - if (typeof reply !== "string" || reply === "") { - return {}; - } - if (reply === "[]") { - return []; - } - var parsed; - try { - parsed = JSON.parse(reply); - } catch (e) { - parsed = {}; - if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { - // we received XML... - // since this only happens for errors, - // don't perform a full decoding - parsed.request = reply.match(/(.*)<\/request>/)[1]; - parsed.error = reply.match(/(.*)<\/error>/)[1]; - } else { - // assume query format - var elements = reply.split("&"); - for (var i = 0; i < elements.length; i++) { - var element = elements[i].split("=", 2); - if (element.length > 1) { - parsed[element[0]] = decodeURIComponent(element[1]); - } else { - parsed[element[0]] = null; - } - } - } - } - return parsed; - }; + var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { + var dfd = _getDfd(); + + if (typeof params === "undefined") { + params = {}; + } + if (typeof multipart === "undefined") { + multipart = false; + } + if (typeof app_only_auth === "undefined") { + app_only_auth = false; + } + if (typeof callback !== "function") { + callback = function () { }; + } + + var url = _getEndpoint(method); + var authorization = null; + + var xml = _getXmlRequestObject(); + if (xml === null) { + return; + } + var post_fields; + + if (httpmethod === "GET") { + var url_with_params = url; + if (JSON.stringify(params) !== "{}") { + url_with_params += "?" + _http_build_query(params); + } + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } - return { - setConsumerKey: setConsumerKey, - getVersion: getVersion, - setToken: setToken, - logout: logout, - setBearerToken: setBearerToken, - setUseProxy: setUseProxy, - setProxy: setProxy, - getApiMethods: getApiMethods, - __call: __call, - oauth_authenticate: oauth_authenticate, - oauth_authorize: oauth_authorize, - oauth2_token: oauth2_token - }; + // append auth params to GET url for IE7-9, to send via JSONP + if (_use_jsonp) { + if (JSON.stringify(params) !== "{}") { + url_with_params += "&"; + } else { + url_with_params += "?"; + } + var callback_name = _nonce(); + window[callback_name] = function (reply) { + reply.httpstatus = 200; + + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" + && xml.getResponseHeader("x-rate-limit-limit") !== "" + ) { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + callback(reply, rate); + }; + params.callback = callback_name; + url_with_params = url + "?" + _sign(httpmethod, url, params, true); + var tag = document.createElement("script"); + tag.type = "text/javascript"; + tag.src = url_with_params; + var body = document.getElementsByTagName("body")[0]; + body.appendChild(tag); + return; + + } else if (_use_proxy) { + url_with_params = url_with_params.replace( + _endpoint_base, + _endpoint_proxy + ).replace( + _endpoint_base_media, + _endpoint_proxy + ); + } + xml.open(httpmethod, url_with_params, true); + } else { + if (_use_jsonp) { + console.warn("Sending POST requests is not supported for IE7-9."); + return; + } + if (multipart) { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, {}); + } + params = _buildMultipart(method, params); + } else if (_detectJsonBody(method)) { + authorization = _sign(httpmethod, url, {}); + params = JSON.stringify(params); + } else { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } + params = _http_build_query(params); + } + post_fields = params; + if (_use_proxy || multipart) { // force proxy for multipart base64 + url = url.replace( + _endpoint_base, + _endpoint_proxy + ).replace( + _endpoint_base_media, + _endpoint_proxy + ); + } + xml.open(httpmethod, url, true); + if (multipart) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + + post_fields.split("\r\n")[0].substring(2)); + } else if (_detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "application/json"); + } else { + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } + } + if (app_only_auth) { + if (_oauth_consumer_key === null + && _oauth_bearer_token === null + ) { + var error = "To make an app-only auth API request, consumer key or bearer token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return _getPromise(dfd); + } + } + // automatically fetch bearer token, if necessary + if (_oauth_bearer_token === null) { + if (dfd) { + return oauth2_token().then(function () { + return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + } + oauth2_token(function () { + _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + return; + } + authorization = "Bearer " + _oauth_bearer_token; + } + if (authorization !== null) { + xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); + } + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + var response = ""; + try { + response = xml.responseText; + } catch (e) { } + var reply = _parseApiReply(response); + reply.httpstatus = httpstatus; + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" + && xml.getResponseHeader("x-rate-limit-limit") !== "" + ) { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve({ reply: reply, rate: rate }); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(httpmethod === "GET" ? null : post_fields); + if (dfd) { + return _getPromise(dfd); + } + return true; }; - if (typeof module === "object" - && module - && typeof module.exports === "object" - ) { - // Expose codebird as module.exports in loaders that implement the Node - // module pattern (including browserify). Do not create the global, since - // the user will be storing it themselves locally, and globals are frowned - // upon in the Node module world. - module.exports = Codebird; - } else { - // Otherwise expose codebird to the global object as usual - if (typeof window === "object" - && window) { - window.Codebird = Codebird; + /** + * Parses the API reply to encode it in the set return_format + * + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply + */ + var _parseApiReply = function (reply) { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + var parsed; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { + // we received XML... + // since this only happens for errors, + // don't perform a full decoding + parsed.request = reply.match(/(.*)<\/request>/)[1]; + parsed.error = reply.match(/(.*)<\/error>/)[1]; + } else { + // assume query format + var elements = reply.split("&"); + for (var i = 0; i < elements.length; i++) { + var element = elements[i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } + } } + } + return parsed; + }; - // Register as a named AMD module, since codebird can be concatenated with other - // files that may use define, but not via a proper concatenation script that - // understands anonymous AMD modules. A named AMD is safest and most robust - // way to register. Lowercase codebird is used because AMD module names are - // derived from file names, and codebird is normally delivered in a lowercase - // file name. Do this after creating the global so that if an AMD module wants - // to call noConflict to hide this version of codebird, it will work. - if (typeof define === "function" && define.amd) { - define("codebird", [], function () { return Codebird; }); - } + return { + setConsumerKey: setConsumerKey, + getVersion: getVersion, + setToken: setToken, + logout: logout, + setBearerToken: setBearerToken, + setUseProxy: setUseProxy, + setProxy: setProxy, + getApiMethods: getApiMethods, + __call: __call, + oauth_authenticate: oauth_authenticate, + oauth_authorize: oauth_authorize, + oauth2_token: oauth2_token + }; + }; + + if (typeof module === "object" + && module + && typeof module.exports === "object" + ) { + // Expose codebird as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = Codebird; + } else { + // Otherwise expose codebird to the global object as usual + if (typeof window === "object" + && window) { + window.Codebird = Codebird; + } + + // Register as a named AMD module, since codebird can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase codebird is used because AMD module names are + // derived from file names, and codebird is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of codebird, it will work. + if (typeof define === "function" && define.amd) { + define("codebird", [], function () { return Codebird; }); } + } })(); From 7c6c3aef6dc154c171a6fa5e7486b16f18118935 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 2 Jan 2016 18:52:59 +0100 Subject: [PATCH 22/48] Convert to ES7 codebase, add unit testing suite Testing suite is still in progress. --- .babelrc | 3 + .eslintrc | 63 ++ .gitignore | 2 +- CHANGELOG | 2 + CONTRIBUTING.md | 6 +- bower.json | 2 +- codebird.es7.js | 1631 ++++++++++++++++++++++++++++++ codebird.js | 2078 ++++++++++++++++++--------------------- package.json | 14 +- test/README | 9 + test/detection_tests.js | 87 ++ test/oauth_tests.js | 104 ++ 12 files changed, 2846 insertions(+), 1155 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintrc create mode 100644 codebird.es7.js create mode 100644 test/README create mode 100644 test/detection_tests.js create mode 100644 test/oauth_tests.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..c13c5f6 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["es2015"] +} diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ba76d1b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,63 @@ +{ + // http://eslint.org/docs/rules/ + + "ecmaFeatures": { + "binaryLiterals": false, // enable binary literals + "blockBindings": true, // enable let and const (aka block bindings) + "defaultParams": false, // enable default function parameters + "forOf": true, // enable for-of loops + "generators": false, // enable generators + "objectLiteralComputedProperties": false, // enable computed object literal property names + "objectLiteralDuplicateProperties": false, // enable duplicate object literal properties in strict mode + "objectLiteralShorthandMethods": false, // enable object literal shorthand methods + "objectLiteralShorthandProperties": false, // enable object literal shorthand properties + "octalLiterals": false, // enable octal literals + "regexUFlag": false, // enable the regular expression u flag + "regexYFlag": false, // enable the regular expression y flag + "templateStrings": true , // enable template strings + "unicodeCodePointEscapes": false, // enable code point escapes + "jsx": false, // enable JSX + "modules": true + }, + + "env": { + "browser": true, // browser global variables. + "node": true, // Node.js global variables and Node.js-specific rules. + "amd": true, // defines require() and define() as global variables as per the amd spec. + "es6": true // EcmaScript 6 + }, + + "globals": { + // e.g. "angular": true + }, + + "plugins": [ + // e.g. "react" (must run `npm install eslint-plugin-react` first) + ], + + "rules": { + ////////// Possible Errors ////////// + + "valid-typeof": 2, // Ensure that the results of typeof are compared against a valid string + + + ////////// Best Practices ////////// + + "eqeqeq": 2, // require the use of === and !== + "no-alert": 2, // disallow the use of alert, confirm, and prompt + + ////////// Stylistic Issues ////////// + + "consistent-this": 2, // enforces consistent naming when capturing the current execution context (off by default) + "eol-last": 2, // enforce newline at the end of file, with no multiple empty lines + "no-lonely-if": 2, // disallow if as the only statement in an else block (off by default) + "no-mixed-spaces-and-tabs": 2, // disallow mixed spaces and tabs for indentation + "no-multiple-empty-lines": 2, // disallow multiple empty lines (off by default) + "no-trailing-spaces": 2, // disallow trailing whitespace at the end of lines + + "quotes": [ + 2, + "double" + ] + } +} diff --git a/.gitignore b/.gitignore index 09c504b..1ccb0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ node_modules RELEASE_MESSAGE* -test* +test*.html *.jpg diff --git a/CHANGELOG b/CHANGELOG index b36e111..1c1d7ab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,8 @@ codebird-js - changelog - Drop support for undocumented API methods + Add security check hasOwnProperty + #110 Add support for Collections API ++ Transform codebase to EcmaScript 7 ++ #25 Add unit testing suite 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70e6d51..dd443d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,9 +3,13 @@ - Normal bugs are fixed in the develop branch. - New features are added to the develop branch, too. +### Which file? +- Please apply all edits to the `codebird.es7.js` file. + The codebird.js file is transpiled from it using BabelJS. + ### Code style - Please use 2 soft spaces per indent level. -- Take a look at the coding style used in codebird.js and apply the same convention to your contributed code. +- Take a look at the coding style used in `codebird.es7.js` and apply the same convention to your contributed code. ### License - Code contributed by you will get the same license as Codebird itself, that is, GPU General Public License V3. diff --git a/bower.json b/bower.json index 9a440af..80d45d5 100644 --- a/bower.json +++ b/bower.json @@ -7,7 +7,7 @@ "J.M. " ], "description": "A Twitter library in JavaScript.", - "main": "codebird.js", + "main": "codebird.es7.js", "moduleType": [ "amd", "globals", diff --git a/codebird.es7.js b/codebird.es7.js new file mode 100644 index 0000000..42a305f --- /dev/null +++ b/codebird.es7.js @@ -0,0 +1,1631 @@ +/** + * A Twitter library in JavaScript + * + * @package codebird + * @version 3.0.0-dev + * @author Jublo Solutions + * @copyright 2010-2016 Jublo Solutions + * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 + * @link https://github.com/jublonet/codebird-php + */ + +/* global window, + document, + navigator, + console, + Ti, + ActiveXObject, + module, + define, + require */ + +(() => { + /** + * A Twitter library in JavaScript + * + * @package codebird + * @subpackage codebird-js + */ + const Codebird = () => { + + let props = {}; + /** + * The OAuth consumer key of your registered app + */ + props._oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + props._oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + props._oauth_bearer_token = null; + + /** + * The API endpoint base to use + */ + props._endpoint_base = "https://api.twitter.com/"; + + /** + * The media API endpoint base to use + */ + props._endpoint_base_media = "https://upload.twitter.com/"; + + /** + * The API endpoint to use + */ + props._endpoint = `${props._endpoint_base}1.1/`; + + /** + * The media API endpoint to use + */ + props._endpoint_media = `${props._endpoint_base_media}1.1/`; + + /** + * The API endpoint base to use + */ + props._endpoint_oauth = props._endpoint_base; + + /** + * API proxy endpoint + */ + props._endpoint_proxy = "https://api.jublo.net/codebird/"; + + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + props._use_proxy = (typeof navigator !== "undefined" + && typeof navigator.userAgent !== "undefined" + ); + + /** + * The Request or access token. Used to sign requests + */ + props._oauth_token = null; + + /** + * The corresponding request or access token secret + */ + props._oauth_token_secret = null; + + /** + * The current Codebird version + */ + props._version = "3.0.0-dev"; + + let methods = {}; + + /** + * Sets the OAuth consumer key and secret (App key) + * + * @param string key OAuth consumer key + * @param string secret OAuth consumer secret + * + * @return void + */ + methods.setConsumerKey = (key, secret) => { + props._oauth_consumer_key = key; + props._oauth_consumer_secret = secret; + }; + + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string token OAuth2 bearer token + * + * @return void + */ + methods.setBearerToken = token => { + props._oauth_bearer_token = token; + }; + + /** + * Gets the current Codebird version + * + * @return string The version number + */ + methods.getVersion = () => { + return props._version; + }; + + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string token OAuth request or access token + * @param string secret OAuth request or access token secret + * + * @return void + */ + methods.setToken = (token, secret) => { + props._oauth_token = token; + props._oauth_token_secret = secret; + }; + + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ + methods.logout = () => { + props._oauth_token = + props._oauth_token_secret = null; + + return true; + }; + + /** + * Enables or disables CORS proxy + * + * @param bool use_proxy Whether to use CORS proxy or not + * + * @return void + */ + methods.setUseProxy = use_proxy => { + props._use_proxy = !!use_proxy; + }; + + /** + * Sets custom CORS proxy server + * + * @param string proxy Address of proxy server to use + * + * @return void + */ + methods.setProxy = proxy => { + // add trailing slash if missing + if (!proxy.match(/\/$/)) { + proxy += "/"; + } + props._endpoint_proxy = proxy; + }; + + /** + * Signing helpers + */ + + /** + * URL-encodes the given data + * + * @param mixed data + * + * @return mixed The encoded data + */ + methods._url = data => { + if ((/boolean|number|string/).test(typeof data)) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; + } + }; + + const b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * + * @param string data The data to calculate the hash from + * + * @return string The hash + */ + methods._sha1 = (() => { + function n (e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, + k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + let m; + + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } + + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ + h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( + l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : + 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; + } + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); + } + return [a, d, h, k, g]; + } + + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; + } + + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; + } + var g = 8; + return e => { + let b = `${props._oauth_consumer_secret}&${null !== props._oauth_token_secret ? + props._oauth_token_secret : ""}`; + if (props._oauth_consumer_secret === null) { + console.warn("To generate a hash, the consumer secret must be set."); + } + let c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); + } + let bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; + } + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> + 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - + (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + + b64_alphabet.charAt(d >> 6 * (3 - e) & 63); + } + } + return b; + }; + })(); + + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation + */ + methods._base64_encode = a => { + let d, e, f, b, g = 0, + h = 0, + i = b64_alphabet, + c = []; + if (!a) { + return a; + } + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); + }; + + /* + * Builds a HTTP query string from the given data + * + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + * + * @param string data The data to concatenate + * + * @return string The HTTP query + */ + methods._http_build_query = (e, f, b) => { + function g(c, a, d) { + let b, e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if (typeof a === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g(c + "[" + b + "]", a[b], d)); + } + } + return e.join(d); + } + if (typeof a !== "function") { + return methods._url(c) + "=" + methods._url(a); + } + console.warn("There was an error processing for http_build_query()."); + } else { + return ""; + } + } + var d, c, h = []; + if (!b) { + b = "&"; + } + for (c in e) { + if (!e.hasOwnProperty(c)) { + continue; + } + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g(c, d, b); + if (d !== "") { + h.push(d); + } + } + return h.join(b); + }; + + /** + * Generates a (hopefully) unique random string + * + * @param int optional length The length of the string to generate + * + * @return string The random string + */ + methods._nonce = (length = 8) => { + if (length < 1) { + console.warn("Invalid nonce length."); + } + let nonce = ""; + for (let i = 0; i < length; i++) { + let character = Math.floor(Math.random() * 61); + nonce += b64_alphabet.substring(character, character + 1); + } + return nonce; + }; + + /** + * Sort array elements by key + * + * @param array input_arr The array to sort + * + * @return array The sorted keys + */ + methods._ksort = input_arr => { + let keys = [], sorter, k; + + sorter = (a, b) => { + let a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; + + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } + } + keys.sort(sorter); + return keys; + }; + + /** + * Clone objects + * + * @param object obj The object to clone + * + * @return object clone The cloned object + */ + methods._clone = obj => { + let clone = {}; + for (let i in obj) { + if (typeof (obj[i]) === "object") { + clone[i] = methods._clone(obj[i]); + } else { + clone[i] = obj[i]; + } + } + return clone; + }; + + /** + * Gets the XML HTTP Request object, trying to load it in various ways + * + * @return object The XMLHttpRequest object instance + */ + methods._getXmlRequestObject = () => { + let xml = null; + // first, try the W3-standard object + if (typeof window === "object" + && window + && typeof window.XMLHttpRequest !== "undefined" + ) { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if (typeof Ti === "object" + && Ti + && typeof Ti.Network.createHTTPClient !== "undefined" + ) { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined" + ) { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + console.error("ActiveXObject object not defined."); + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function" + && require + ) { + var XMLHttpRequest; + // look for xmlhttprequest module + try { + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + xml = new XMLHttpRequest(); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + console.error("xhr2 object not defined, cancelling."); + } + } + } + return xml; + }; + + /** + * Parse URL-style parameters into object + * + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object + */ + methods._parse_str = (str, array) => { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, j, chr, tmp, key, value, bracket, keys, evalStr, + fixStr = str => { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + }; + if (!array) { + array = this.window; + } + + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; + } + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); + } + if (key.indexOf("\0") > -1) { + key = key.substr(0, key.indexOf("\0")); + } + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); + } + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; + } + } + } + } + if (!keys.length) { + keys = [key]; + } + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } + } + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if ((key !== "" && key !== " ") || j === 0) { + key = `'${key}'`; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += `[${key}']`; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); + } + } + evalStr += " = '" + value + "';\n"; + eval(evalStr); + } + } + }; + + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! + * + * @return array $apimethods + */ + methods.getApiMethods = () => { + const httpmethods = { + GET: [ + "account/settings", + "account/verify_credentials", + "application/rate_limit_status", + "blocks/ids", + "blocks/list", + "collections/entries", + "collections/list", + "collections/show", + "direct_messages", + "direct_messages/sent", + "direct_messages/show", + "favorites/list", + "followers/ids", + "followers/list", + "friends/ids", + "friends/list", + "friendships/incoming", + "friendships/lookup", + "friendships/lookup", + "friendships/no_retweets/ids", + "friendships/outgoing", + "friendships/show", + "geo/id/:place_id", + "geo/reverse_geocode", + "geo/search", + "geo/similar_places", + "help/configuration", + "help/languages", + "help/privacy", + "help/tos", + "lists/list", + "lists/members", + "lists/members/show", + "lists/memberships", + "lists/ownerships", + "lists/show", + "lists/statuses", + "lists/subscribers", + "lists/subscribers/show", + "lists/subscriptions", + "mutes/users/ids", + "mutes/users/list", + "oauth/authenticate", + "oauth/authorize", + "saved_searches/list", + "saved_searches/show/:id", + "search/tweets", + "site", + "statuses/firehose", + "statuses/home_timeline", + "statuses/mentions_timeline", + "statuses/oembed", + "statuses/retweeters/ids", + "statuses/retweets/:id", + "statuses/retweets_of_me", + "statuses/sample", + "statuses/show/:id", + "statuses/user_timeline", + "trends/available", + "trends/closest", + "trends/place", + "user", + "users/contributees", + "users/contributors", + "users/profile_banner", + "users/search", + "users/show", + "users/suggestions", + "users/suggestions/:slug", + "users/suggestions/:slug/members" + ], + POST: [ + "account/remove_profile_banner", + "account/settings__post", + "account/update_delivery_device", + "account/update_profile", + "account/update_profile_background_image", + "account/update_profile_banner", + "account/update_profile_colors", + "account/update_profile_image", + "blocks/create", + "blocks/destroy", + "collections/create", + "collections/destroy", + "collections/entries/add", + "collections/entries/curate", + "collections/entries/move", + "collections/entries/remove", + "collections/update", + "direct_messages/destroy", + "direct_messages/new", + "favorites/create", + "favorites/destroy", + "friendships/create", + "friendships/destroy", + "friendships/update", + "lists/create", + "lists/destroy", + "lists/members/create", + "lists/members/create_all", + "lists/members/destroy", + "lists/members/destroy_all", + "lists/subscribers/create", + "lists/subscribers/destroy", + "lists/update", + "media/upload", + "mutes/users/create", + "mutes/users/destroy", + "oauth/access_token", + "oauth/request_token", + "oauth2/invalidate_token", + "oauth2/token", + "saved_searches/create", + "saved_searches/destroy/:id", + "statuses/destroy/:id", + "statuses/filter", + "statuses/lookup", + "statuses/retweet/:id", + "statuses/update", + "statuses/update_with_media", // deprecated, use media/upload + "users/lookup", + "users/report_spam" + ] + }; + return httpmethods; + }; + + /** + * Promise helpers + */ + + /** + * Get a deferred object + */ + methods._getDfd = () => { + if (typeof window !== "undefined") { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); + } + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); + } + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); + } + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); + } + } + if (typeof require !== "undefined") { + let promise_class = false; + try { + promise_class = require("jquery"); + } catch (e) { } + if (promise_class) { + return promise_class.Deferred(); + } + try { + promise_class = require("q"); + } catch (e) { } + if (!promise_class) { + try { + promise_class = require("rsvp"); + } catch (e) { } + } + if (!promise_class) { + try { + promise_class = require("when"); + } catch (e) { } + } + if (promise_class) { + try { + return promise_class.defer(); + } catch (e) { } + } + } + return false; + }; + + /** + * Get a promise from the dfd object + */ + methods._getPromise = dfd => { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + }; + + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + methods._parseApiParams = params => { + let apiparams = {}; + if (typeof params === "object") { + apiparams = params; + } else { + methods._parse_str(params, apiparams); //TODO + } + + return apiparams; + }; + + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + methods._stringifyNullBoolParams = apiparams => { + for (let key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { + continue; + } + let value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; + } + } + + return apiparams; + }; + + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + methods._mapFnInsertSlashes = fn => fn.split("_").join("/"); + + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + methods._mapFnRestoreParamUnderscores = method => { + const url_parameters_with_underscore = ["screen_name", "place_id"]; + let i, param, replacement_was; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } + + return method; + }; + + + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + methods._mapFnToApiMethod = (fn, apiparams) => { + let method = "", + param, i, j; + + // replace _ by / + method = methods._mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = methods._mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + let method_template = method; + const match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + let param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + console.warn(`To call the templated method "${method_template}", specify the parameter value for "${param_l}".`); + } + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; + } + } + + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + } + + return [method, method_template]; + }; + + + /** + * Detects HTTP method to use for API call + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return string The HTTP method that should be used + */ + methods._detectMethod = (method, params) => { + if (typeof params.httpmethod !== "undefined") { + let httpmethod = params.httpmethod; + delete params.httpmethod; + return httpmethod; + } + + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = Object.keys(params).length ? `${method}__post` : method; + break; + } + + const apimethods = methods.getApiMethods(); + for (let httpmethod in apimethods) { + if (apimethods.hasOwnProperty(httpmethod) + && apimethods[httpmethod].indexOf(method) > -1 + ) { + return httpmethod; + } + } + throw `Can't find HTTP method to use for "${method}".`; + }; + + /** + * Detects if API call should use multipart/form-data + * + * @param string method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + methods._detectMultipart = method => { + const multiparts = [ + // Tweets + "statuses/update_with_media", + "media/upload", + + // Users + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + return multiparts.indexOf(method) > -1; + }; + + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + methods._getSignature = (httpmethod, method, keys, base_params) => { + const {_url, _sha1} = methods; + // convert params to string + let base_string = "", key, value; + for (let i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += `${key}=${_url(value)}&`; + } + base_string = base_string.substring(0, base_string.length - 1); + return _sha1(`${httpmethod}&${_url(method)}&${_url(base_string)}`); + }; + + /** + * Generates the UNIX timestamp + */ + methods._time = () => Math.round(new Date().getTime() / 1000); + + /** + * Generates an OAuth signature + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array optional params The API call parameters, associative + * + * @return string Authorization HTTP header + */ + methods._sign = (httpmethod, method, params = {}) => { + const {_url, _ksort, _clone, _getSignature} = methods; + if (props._oauth_consumer_key === null) { + console.warn("To generate a signature, the consumer key must be set."); + } + const sign_params = { + consumer_key: props._oauth_consumer_key, + version: "1.0", + timestamp: methods._time(), + nonce: methods._nonce(), + signature_method: "HMAC-SHA1" + }; + let sign_base_params = {}; + for (var key in sign_params) { + if (!sign_params.hasOwnProperty(key)) { + continue; + } + let value = sign_params[key]; + sign_base_params[`oauth_${key}`] = _url(value); + } + if (props._oauth_token !== null) { + sign_base_params.oauth_token = _url(props._oauth_token); + } + const oauth_params = _clone(sign_base_params); + for (key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + sign_base_params[key] = params[key]; + } + let keys = _ksort(sign_base_params); + + const signature = _getSignature(httpmethod, method, keys, sign_base_params); + + params = oauth_params; + params.oauth_signature = signature; + keys = _ksort(params); + let authorization = "OAuth "; + for (let i = 0; i < keys.length; i++) { + key = keys[i]; + authorization += `${key}="${_url(params[key])}", `; + } + return authorization.substring(0, authorization.length - 2); + }; + + /** + * Build multipart request from upload params + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return null|string The built multipart request body + */ + methods._buildMultipart = (method, params) => { + // well, files will only work in multipart methods + if (!methods._detectMultipart(method)) { + return; + } + + // only check specific parameters + const possible_methods = [ + // Tweets + "statuses/update_with_media", + // Accounts + "account/update_profile_background_image", + "account/update_profile_image", + "account/update_profile_banner" + ]; + let possible_files = { + // Tweets + "statuses/update_with_media": "media[]", + // Accounts + "account/update_profile_background_image": "image", + "account/update_profile_image": "image", + "account/update_profile_banner": "banner" + }; + // method might have files? + if (possible_methods.indexOf(method) === -1) { + return; + } + + // check for filenames + possible_files = possible_files[method].split(" "); + + const multipart_border = `--------------------${methods._nonce()}`; + let multipart_request = ""; + for (let key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + multipart_request += + `--${multipart_border}\r\nContent-Disposition: form-data; name="${key}"`; + if (possible_files.indexOf(key) === -1) { + multipart_request += "\r\nContent-Transfer-Encoding: base64"; + } + multipart_request += `\r\n\r\n${params[key]}\r\n`; + } + multipart_request += `--${multipart_border}--`; + return multipart_request; + }; + + /** + * Detects if API call should use media endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in media API + */ + methods._detectMedia = method => { + const medias = [ + "media/upload" + ]; + return medias.indexOf(method) > -1; + }; + + /** + * Detects if API call should use JSON body + * + * @param string method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + methods._detectJsonBody = method => { + const json_bodies = [ + "collections/entries/curate" + ]; + return json_bodies.indexOf(method) > -1; + }; + + /** + * Builds the complete API endpoint url + * + * @param string method The API method to call + * + * @return string The URL to send the request to + */ + methods._getEndpoint = method => { + let url; + if (method.substring(0, 5) === "oauth") { + url = props._endpoint_oauth + method; + } else if (methods._detectMedia(method)) { + url = props._endpoint_media + method + ".json"; + } else { + url = props._endpoint + method + ".json"; + } + return url; + }; + + /** + * Parses the API reply to encode it in the set return_format + * + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply + */ + methods._parseApiReply = reply => { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + let parsed; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + // assume query format + let elements = reply.split("&"); + for (let i = 0; i < elements.length; i++) { + let element = elements[i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } + } + } + return parsed; + }; + + + /** + * Uncommon API methods + */ + + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + methods.oauth_authenticate = (params = {}, callback = undefined, type = "authenticate") => { + const dfd = methods._getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (["authenticate", "authorize"].indexOf(type) === -1) { + type = "authenticate"; + } + if (props._oauth_token === null) { + const error = `To get the ${type} URL, the OAuth token must be set.`; + console.warn(error); + if (dfd) { + dfd.reject({ error }); + return methods._getPromise(dfd); + } + throw error; + } + let url = `${props._endpoint_oauth}oauth/${type}?oauth_token=${methods._url(props._oauth_token)}`; + if (params.force_login === true) { + url += "&force_login=1"; + } + if (params.screen_name !== null) { + url += `&screen_name=${params.screen_name}`; + } + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve({ reply: url }); + return methods._getPromise(dfd); + } + // no promises + return true; + }; + + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ + methods.oauth_authorize = (params, callback) => { + return methods.oauth_authenticate(params, callback, "authorize"); + }; + + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ + + methods.oauth2_token = callback => { + const dfd = methods._getDfd(); + + if (props._oauth_consumer_key === null) { + const error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error }); + return methods._getPromise(dfd); + } + return false; + } + + if (!dfd && typeof callback === "undefined") { + callback = () => {}; + } + + const post_fields = "grant_type=client_credentials"; + let url = props._endpoint_oauth + "oauth2/token"; + + if (props._use_proxy) { + url = url.replace( + props._endpoint_base, + props._endpoint_proxy + ); + } + + const xml = methods._getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader( + `${props._use_proxy ? "X-" : ""}Authorization`, + "Basic " + methods._base64_encode(`${props._oauth_consumer_key}:${props._oauth_consumer_secret}`) + ); + + xml.onreadystatechange = () => { + if (xml.readyState >= 4) { + let httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + let response = ""; + try { + response = xml.responseText; + } catch (e) { } + let reply = methods._parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + methods.setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({ reply }); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = e => { + if (typeof callback === "function") { + callback(null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(post_fields); + if (dfd) { + return methods._getPromise(dfd); + } + }; + + /** + * Calls the API using cURL + * + * @param string httpmethod The HTTP method to use for making the request + * @param string method The API method to call + * @param array optional params The parameters to send along + * @param bool optional multipart Whether to use multipart/form-data + * @param bool optional app_only_auth Whether to use app-only bearer authentication + * @param function callback The function to call with the API call result + * + * @return mixed The API reply, encoded in the set return_format + */ + + methods._callApi = ( + httpmethod, + method, + params = {}, + multipart = false, + app_only_auth = false, + callback = () => {} + ) => { + const dfd = methods._getDfd(); + + let url = methods._getEndpoint(method), + authorization = null; + + const xml = methods._getXmlRequestObject(); + if (xml === null) { + return; + } + let post_fields; + const _sign = methods._sign; + + if (httpmethod === "GET") { + let url_with_params = url; + if (JSON.stringify(params) !== "{}") { + url_with_params += "?" + methods._http_build_query(params); + } + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } + + if (props._use_proxy) { + url_with_params = url_with_params.replace( + props._endpoint_base, + props._endpoint_proxy + ).replace( + props._endpoint_base_media, + props._endpoint_proxy + ); + } + xml.open(httpmethod, url_with_params, true); + } else { + if (multipart) { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, {}); + } + params = methods._buildMultipart(method, params); + } else if (methods._detectJsonBody(method)) { + authorization = _sign(httpmethod, url, {}); + params = JSON.stringify(params); + } else { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } + params = methods._http_build_query(params); + } + post_fields = params; + if (props._use_proxy || multipart) { // force proxy for multipart base64 + url = url.replace( + props._endpoint_base, + props._endpoint_proxy + ).replace( + props._endpoint_base_media, + props._endpoint_proxy + ); + } + xml.open(httpmethod, url, true); + if (multipart) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + + post_fields.split("\r\n")[0].substring(2)); + } else if (methods._detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "application/json"); + } else { + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + } + } + if (app_only_auth) { + if (props._oauth_consumer_key === null + && props._oauth_bearer_token === null + ) { + const error = "To make an app-only auth API request, consumer key or bearer token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error }); + return methods._getPromise(dfd); + } + } + // automatically fetch bearer token, if necessary + if (props._oauth_bearer_token === null) { + if (dfd) { + return methods.oauth2_token().then(() => { + return methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + } + methods.oauth2_token(() => { + methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + return; + } + authorization = "Bearer " + props._oauth_bearer_token; + } + if (authorization !== null) { + xml.setRequestHeader(`${props._use_proxy ? "X-" : ""}Authorization`, authorization); + } + xml.onreadystatechange = () => { + if (xml.readyState >= 4) { + let httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) { } + let response = ""; + try { + response = xml.responseText; + } catch (e) { } + let reply = methods._parseApiReply(response); + reply.httpstatus = httpstatus; + let rate = null; + if (typeof xml.getResponseHeader !== "undefined" + && xml.getResponseHeader("x-rate-limit-limit") !== "" + ) { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve({ reply, rate }); + } + } + }; + // function called when an error occurs, including a timeout + xml.onerror = e => { + if (typeof callback === "function") { + callback(null, null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(httpmethod === "GET" ? null : post_fields); + if (dfd) { + return methods._getPromise(dfd); + } + return true; + }; + + /** + * Main API handler working on any requests you issue + * + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth + * + * @return object Promise + */ + + methods.__call = (fn, params = {}, callback, app_only_auth = false) => { + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = () => {}; + } + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return this[fn](params, callback); + + case "oauth2_token": + return this[fn](callback); + } + + // parse parameters + let apiparams = methods._parseApiParams(params); + + // stringify null and boolean parameters + apiparams = methods._stringifyNullBoolParams(apiparams); + + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + methods.setToken(null, null); + } + + // map function name to API method + const [method, method_template] = methods._mapFnToApiMethod(fn, apiparams), + httpmethod = methods._detectMethod(method_template, apiparams), + multipart = methods._detectMultipart(method_template); + + return methods._callApi( + httpmethod, + method, + apiparams, + multipart, + app_only_auth, + callback + ); + }; + + // Unit testing code + const __test = { + call: (name, params = []) => methods[name].apply(undefined, params), + get: name => props[name], + mock: methods_to_mock => { + for (let name in methods_to_mock) { + if (methods_to_mock.hasOwnProperty(name)) { + methods[name] = methods_to_mock[name]; + } + } + } + }; + + return { + __call: methods.__call, + __test, + getApiMethods: methods.getApiMethods, + getVersion: methods.getVersion, + logout: methods.logout, + oauth2_token: methods.oauth2_token, + oauth_authenticate: methods.oauth_authenticate, + oauth_authorize: methods.oauth_authorize, + setBearerToken: methods.setBearerToken, + setConsumerKey: methods.setConsumerKey, + setProxy: methods.setProxy, + setToken: methods.setToken, + setUseProxy: methods.setUseProxy + }; + }; + + if (typeof module === "object" + && module + && typeof module.exports === "object" + ) { + // Expose codebird as module.exports in loaders that implement the Node + // module pattern (including browserify). Do not create the global, since + // the user will be storing it themselves locally, and globals are frowned + // upon in the Node module world. + module.exports = Codebird; + } else { + // Otherwise expose codebird to the global object as usual + if (typeof window === "object" + && window) { + window.Codebird = Codebird; + } + + // Register as a named AMD module, since codebird can be concatenated with other + // files that may use define, but not via a proper concatenation script that + // understands anonymous AMD modules. A named AMD is safest and most robust + // way to register. Lowercase codebird is used because AMD module names are + // derived from file names, and codebird is normally delivered in a lowercase + // file name. Do this after creating the global so that if an AMD module wants + // to call noConflict to hide this version of codebird, it will work. + if (typeof define === "function" && define.amd) { + define("codebird", [], () => Codebird); + } + } + +})(); diff --git a/codebird.js b/codebird.js index f959c26..100cbb8 100644 --- a/codebird.js +++ b/codebird.js @@ -1,3 +1,9 @@ +"use strict"; + +var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); + +function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } + /** * A Twitter library in JavaScript * @@ -9,14 +15,6 @@ * @link https://github.com/jublonet/codebird-php */ -/* jshint curly: true, - eqeqeq: true, - latedef: true, - quotmark: double, - undef: true, - unused: true, - trailing: true, - laxbreak: true */ /* global window, document, navigator, @@ -26,111 +24,84 @@ module, define, require */ -(function (undefined) { - "use strict"; - - /** - * Array.indexOf polyfill - */ - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function (obj, start) { - for (var i = (start || 0); i < this.length; i++) { - if (this[i] === obj) { - return i; - } - } - return -1; - }; - } +(function () { /** * A Twitter library in JavaScript * * @package codebird * @subpackage codebird-js */ - /* jshint -W098 */ - var Codebird = function () { - /* jshint +W098 */ + var Codebird = function Codebird() { + var props = {}; /** * The OAuth consumer key of your registered app */ - var _oauth_consumer_key = null; + props._oauth_consumer_key = null; /** * The corresponding consumer secret */ - var _oauth_consumer_secret = null; + props._oauth_consumer_secret = null; /** * The app-only bearer token. Used to authorize app-only requests */ - var _oauth_bearer_token = null; + props._oauth_bearer_token = null; /** * The API endpoint base to use */ - var _endpoint_base = "https://api.twitter.com/"; + props._endpoint_base = "https://api.twitter.com/"; /** * The media API endpoint base to use */ - var _endpoint_base_media = "https://upload.twitter.com/"; + props._endpoint_base_media = "https://upload.twitter.com/"; /** * The API endpoint to use */ - var _endpoint = _endpoint_base + "1.1/"; + props._endpoint = props._endpoint_base + "1.1/"; /** * The media API endpoint to use */ - var _endpoint_media = _endpoint_base_media + "1.1/"; + props._endpoint_media = props._endpoint_base_media + "1.1/"; /** * The API endpoint base to use */ - var _endpoint_oauth = _endpoint_base; + props._endpoint_oauth = props._endpoint_base; /** * API proxy endpoint */ - var _endpoint_proxy = "https://api.jublo.net/codebird/"; - - /** - * Use JSONP for GET requests in IE7-9 - */ - var _use_jsonp = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - && (navigator.userAgent.indexOf("Trident/4") > -1 - || navigator.userAgent.indexOf("Trident/5") > -1 - || navigator.userAgent.indexOf("MSIE 7.0") > -1 - ) - ); + props._endpoint_proxy = "https://api.jublo.net/codebird/"; /** * Whether to access the API via a proxy that is allowed by CORS * Assume that CORS is only necessary in browsers */ - var _use_proxy = (typeof navigator !== "undefined" - && typeof navigator.userAgent !== "undefined" - ); + props._use_proxy = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined"; /** * The Request or access token. Used to sign requests */ - var _oauth_token = null; + props._oauth_token = null; /** * The corresponding request or access token secret */ - var _oauth_token_secret = null; + props._oauth_token_secret = null; /** * The current Codebird version */ - var _version = "3.0.0-dev"; + props._version = "3.0.0-dev"; + + var methods = {}; /** * Sets the OAuth consumer key and secret (App key) @@ -140,9 +111,9 @@ * * @return void */ - var setConsumerKey = function (key, secret) { - _oauth_consumer_key = key; - _oauth_consumer_secret = secret; + methods.setConsumerKey = function (key, secret) { + props._oauth_consumer_key = key; + props._oauth_consumer_secret = secret; }; /** @@ -152,8 +123,8 @@ * * @return void */ - var setBearerToken = function (token) { - _oauth_bearer_token = token; + methods.setBearerToken = function (token) { + props._oauth_bearer_token = token; }; /** @@ -161,8 +132,8 @@ * * @return string The version number */ - var getVersion = function () { - return _version; + methods.getVersion = function () { + return props._version; }; /** @@ -173,9 +144,9 @@ * * @return void */ - var setToken = function (token, secret) { - _oauth_token = token; - _oauth_token_secret = secret; + methods.setToken = function (token, secret) { + props._oauth_token = token; + props._oauth_token_secret = secret; }; /** @@ -183,9 +154,8 @@ * * @return bool */ - var logout = function () { - _oauth_token = - _oauth_token_secret = null; + methods.logout = function () { + props._oauth_token = props._oauth_token_secret = null; return true; }; @@ -197,8 +167,8 @@ * * @return void */ - var setUseProxy = function (use_proxy) { - _use_proxy = !!use_proxy; + methods.setUseProxy = function (use_proxy) { + props._use_proxy = !!use_proxy; }; /** @@ -208,921 +178,701 @@ * * @return void */ - var setProxy = function (proxy) { + methods.setProxy = function (proxy) { // add trailing slash if missing if (!proxy.match(/\/$/)) { proxy += "/"; } - _endpoint_proxy = proxy; + props._endpoint_proxy = proxy; }; /** - * Parse URL-style parameters into object - * - * version: 1109.2015 - * discuss at: http://phpjs.org/functions/parse_str - * + original by: Cagri Ekin - * + improved by: Michael White (http://getsprink.com) - * + tweaked by: Jack - * + bugfixed by: Onno Marsman - * + reimplemented by: stag019 - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: stag019 - * - depends on: urldecode - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * % note 1: When no argument is specified, will put variables in global scope. + * Signing helpers + */ + + /** + * URL-encodes the given data * - * @param string str String to parse - * @param array array to load data into + * @param mixed data * - * @return object + * @return mixed The encoded data */ - var _parse_str = function (str, array) { - var glue1 = "=", - glue2 = "&", - array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), - i, j, chr, tmp, key, value, bracket, keys, evalStr, - fixStr = function (str) { - return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); - }; - if (!array) { - array = this.window; - } - - for (i = 0; i < array2.length; i++) { - tmp = array2[i].split(glue1); - if (tmp.length < 2) { - tmp = [tmp, ""]; - } - key = fixStr(tmp[0]); - value = fixStr(tmp[1]); - while (key.charAt(0) === " ") { - key = key.substr(1); - } - if (key.indexOf("\0") !== -1) { - key = key.substr(0, key.indexOf("\0")); - } - if (key && key.charAt(0) !== "[") { - keys = []; - bracket = 0; - for (j = 0; j < key.length; j++) { - if (key.charAt(j) === "[" && !bracket) { - bracket = j + 1; - } else if (key.charAt(j) === "]") { - if (bracket) { - if (!keys.length) { - keys.push(key.substr(0, bracket - 1)); - } - keys.push(key.substr(bracket, j - bracket)); - bracket = 0; - if (key.charAt(j + 1) !== "[") { - break; - } - } - } - } - if (!keys.length) { - keys = [key]; - } - for (j = 0; j < keys[0].length; j++) { - chr = keys[0].charAt(j); - if (chr === " " || chr === "." || chr === "[") { - keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); - } - if (chr === "[") { - break; - } - } - /* jshint -W061 */ - evalStr = "array"; - for (j = 0; j < keys.length; j++) { - key = keys[j]; - if ((key !== "" && key !== " ") || j === 0) { - key = "'" + key + "'"; - } else { - key = eval(evalStr + ".push([]);") - 1; - } - evalStr += "[" + key + "]"; - if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { - eval(evalStr + " = [];"); - } - } - evalStr += " = '" + value + "';\n"; - eval(evalStr); - /* jshint +W061 */ - } + methods._url = function (data) { + if (/boolean|number|string/.test(typeof data === "undefined" ? "undefined" : _typeof(data))) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; } }; + var b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! + * Gets the base64-encoded SHA1 hash for the given data * - * @return array $apimethods + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * + * @param string data The data to calculate the hash from + * + * @return string The hash */ - var getApiMethods = function () { - var httpmethods = { - GET: [ - "account/settings", - "account/verify_credentials", - "application/rate_limit_status", - "blocks/ids", - "blocks/list", - "collections/entries", - "collections/list", - "collections/show", - "direct_messages", - "direct_messages/sent", - "direct_messages/show", - "favorites/list", - "followers/ids", - "followers/list", - "friends/ids", - "friends/list", - "friendships/incoming", - "friendships/lookup", - "friendships/lookup", - "friendships/no_retweets/ids", - "friendships/outgoing", - "friendships/show", - "geo/id/:place_id", - "geo/reverse_geocode", - "geo/search", - "geo/similar_places", - "help/configuration", - "help/languages", - "help/privacy", - "help/tos", - "lists/list", - "lists/members", - "lists/members/show", - "lists/memberships", - "lists/ownerships", - "lists/show", - "lists/statuses", - "lists/subscribers", - "lists/subscribers/show", - "lists/subscriptions", - "mutes/users/ids", - "mutes/users/list", - "oauth/authenticate", - "oauth/authorize", - "saved_searches/list", - "saved_searches/show/:id", - "search/tweets", - "site", - "statuses/firehose", - "statuses/home_timeline", - "statuses/mentions_timeline", - "statuses/oembed", - "statuses/retweeters/ids", - "statuses/retweets/:id", - "statuses/retweets_of_me", - "statuses/sample", - "statuses/show/:id", - "statuses/user_timeline", - "trends/available", - "trends/closest", - "trends/place", - "user", - "users/contributees", - "users/contributors", - "users/profile_banner", - "users/search", - "users/show", - "users/suggestions", - "users/suggestions/:slug", - "users/suggestions/:slug/members" - ], - POST: [ - "account/remove_profile_banner", - "account/settings__post", - "account/update_delivery_device", - "account/update_profile", - "account/update_profile_background_image", - "account/update_profile_banner", - "account/update_profile_colors", - "account/update_profile_image", - "blocks/create", - "blocks/destroy", - "collections/create", - "collections/destroy", - "collections/entries/add", - "collections/entries/curate", - "collections/entries/move", - "collections/entries/remove", - "collections/update", - "direct_messages/destroy", - "direct_messages/new", - "favorites/create", - "favorites/destroy", - "friendships/create", - "friendships/destroy", - "friendships/update", - "lists/create", - "lists/destroy", - "lists/members/create", - "lists/members/create_all", - "lists/members/destroy", - "lists/members/destroy_all", - "lists/subscribers/create", - "lists/subscribers/destroy", - "lists/update", - "media/upload", - "mutes/users/create", - "mutes/users/destroy", - "oauth/access_token", - "oauth/request_token", - "oauth2/invalidate_token", - "oauth2/token", - "saved_searches/create", - "saved_searches/destroy/:id", - "statuses/destroy/:id", - "statuses/filter", - "statuses/lookup", - "statuses/retweet/:id", - "statuses/update", - "statuses/update_with_media", // deprecated, use media/upload - "users/lookup", - "users/report_spam" - ] - }; - return httpmethods; - }; + methods._sha1 = (function () { + function n(e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + var m = undefined; - /** - * Promise helpers - */ + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } - /** - * Get a deferred object - */ - var _getDfd = function () { - if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { - return window.jQuery.Deferred(); - } - if (typeof window.Q !== "undefined" && window.Q.defer) { - return window.Q.defer(); + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l(l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; + } + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); + } + return [a, d, h, k, g]; } - if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { - return window.RSVP.defer(); + + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; } - if (typeof window.when !== "undefined" && window.when.defer) { - return window.when.defer(); + + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; } - if (typeof require !== "undefined") { - var promise_class = require("jquery"); - if (promise_class) { - return promise_class.Deferred(); + var g = 8; + return function (e) { + var b = props._oauth_consumer_secret + "&" + (null !== props._oauth_token_secret ? props._oauth_token_secret : ""); + if (props._oauth_consumer_secret === null) { + console.warn("To generate a hash, the consumer secret must be set."); } - promise_class = require("q"); - if (!promise_class) { - promise_class = require("rsvp"); + var c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); } - if (!promise_class) { - promise_class = require("when"); + var bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; } - if (promise_class) { - return promise_class.defer(); + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + b64_alphabet.charAt(d >> 6 * (3 - e) & 63); + } } - } - return false; - }; + return b; + }; + })(); - /** - * Get a promise from the dfd object + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation */ - var _getPromise = function (dfd) { - if (typeof dfd.promise === "function") { - return dfd.promise(); + methods._base64_encode = function (a) { + var d = undefined, + e = undefined, + f = undefined, + b = undefined, + g = 0, + h = 0, + i = b64_alphabet, + c = []; + if (!a) { + return a; } - return dfd.promise; // object + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); }; - /** - * Main API handler working on any requests you issue + /* + * Builds a HTTP query string from the given data * - * @param string fn The member function you called - * @param array params The parameters you sent along - * @param function callback The callback to call with the reply - * @param bool app_only_auth Whether to use app-only auth + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) * - * @return object Promise + * @param string data The data to concatenate + * + * @return string The HTTP query */ - - var __call = function (fn, params, callback, app_only_auth) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function" && typeof params === "function") { - callback = params; - params = {}; - if (typeof callback === "boolean") { - app_only_auth = callback; + methods._http_build_query = function (e, f, b) { + function g(c, a, d) { + var b = undefined, + e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g(c + "[" + b + "]", a[b], d)); + } + } + return e.join(d); + } + if (typeof a !== "function") { + return methods._url(c) + "=" + methods._url(a); + } + console.warn("There was an error processing for http_build_query()."); + } else { + return ""; } - } else if (typeof callback === "undefined") { - callback = function () { }; - } - switch (fn) { - case "oauth_authenticate": - case "oauth_authorize": - return this[fn](params, callback); - - case "oauth2_token": - return this[fn](callback); - } - - // parse parameters - var apiparams = _parseApiParams(params); - - // stringify null and boolean parameters - apiparams = _stringifyNullBoolParams(apiparams); - - // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) - if (fn === "oauth_requestToken") { - setToken(null, null); } - - // map function name to API method - var data = _mapFnToApiMethod(fn, apiparams), - method = data[0], - method_template = data[1]; - - var httpmethod = _detectMethod(method_template, apiparams); - var multipart = _detectMultipart(method_template); - - return _callApi( - httpmethod, - method, - apiparams, - multipart, - app_only_auth, - callback - ); - }; - - - /** - * __call() helpers - */ - - /** - * Parse given params, detect query-style params - * - * @param array|string params Parameters to parse - * - * @return array apiparams - */ - var _parseApiParams = function (params) { - var apiparams = {}; - if (typeof params === "object") { - apiparams = params; - } else { - _parse_str(params, apiparams); //TODO + var d, + c, + h = []; + if (!b) { + b = "&"; } - - return apiparams; - }; - - /** - * Replace null and boolean parameters with their string representations - * - * @param array apiparams Parameter array to replace in - * - * @return array apiparams - */ - var _stringifyNullBoolParams = function (apiparams) { - var value; - for (var key in apiparams) { - if (!apiparams.hasOwnProperty(key)) { + for (c in e) { + if (!e.hasOwnProperty(c)) { continue; } - value = apiparams[key]; - if (value === null) { - apiparams[key] = "null"; - } else if (value === true || value === false) { - apiparams[key] = value ? "true" : "false"; + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g(c, d, b); + if (d !== "") { + h.push(d); } } - - return apiparams; + return h.join(b); }; /** - * Maps called PHP magic method name to Twitter API method + * Generates a (hopefully) unique random string * - * @param string $fn Function called - * @param array $apiparams byref API parameters + * @param int optional length The length of the string to generate * - * @return string[] (string method, string method_template) + * @return string The random string */ - var _mapFnToApiMethod = function (fn, apiparams) { - var method = ""; - var param, i, j; - - // replace _ by / - method = _mapFnInsertSlashes(fn); + methods._nonce = function () { + var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; - // undo replacement for URL parameters - method = _mapFnRestoreParamUnderscores(method); - - // replace AA by URL parameters - var method_template = method; - var match = method.match(/[A-Z_]{2,}/); - if (match) { - for (i = 0; i < match.length; i++) { - param = match[i]; - var param_l = param.toLowerCase(); - method_template = method_template.split(param).join(":" + param_l); - if (typeof apiparams[param_l] === "undefined") { - for (j = 0; j < 26; j++) { - method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); - } - console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); - } - method = method.split(param).join(apiparams[param_l]); - delete apiparams[param_l]; - } + if (length < 1) { + console.warn("Invalid nonce length."); } - - // replace A-Z by _a-z - for (i = 0; i < 26; i++) { - method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + var nonce = ""; + for (var i = 0; i < length; i++) { + var character = Math.floor(Math.random() * 61); + nonce += b64_alphabet.substring(character, character + 1); } - - return [method, method_template]; + return nonce; }; - /** - * API method mapping: Replaces _ with / character + * Sort array elements by key * - * @param string fn Function called + * @param array input_arr The array to sort * - * @return string API method to call + * @return array The sorted keys */ - var _mapFnInsertSlashes = function (fn) { - var path = fn.split("_"), - method = path.join("/"); + methods._ksort = function (input_arr) { + var keys = [], + sorter = undefined, + k = undefined; - return method; - }; + sorter = function (a, b) { + var a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; - /** - * API method mapping: Restore _ character in named parameters - * - * @param string method API method to call - * - * @return string API method with restored underscores - */ - var _mapFnRestoreParamUnderscores = function (method) { - var url_parameters_with_underscore = ["screen_name", "place_id"], i, param, replacement_was; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } } - - return method; + keys.sort(sorter); + return keys; }; - - /** - * Uncommon API methods - */ - /** - * Gets the OAuth authenticate URL for the current request token + * Clone objects * - * @return object Promise + * @param object obj The object to clone + * + * @return object clone The cloned object */ - var oauth_authenticate = function (params, callback, type) { - var dfd = _getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (typeof type === "undefined" - || ["authenticate", "authorize"].indexOf(type) === -1 - ) { - type = "authenticate"; - } - if (_oauth_token === null) { - var error = "To get the " + type + " URL, the OAuth token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - return false; - } - var url = _endpoint_oauth + "oauth/" + type + "?oauth_token=" + _url(_oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; + methods._clone = function (obj) { + var clone = {}; + for (var i in obj) { + if (_typeof(obj[i]) === "object") { + clone[i] = methods._clone(obj[i]); + } else { + clone[i] = obj[i]; } } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve({ reply: url }); - return _getPromise(dfd); - } - // no promises - return true; + return clone; }; /** - * Gets the OAuth authorize URL for the current request token + * Gets the XML HTTP Request object, trying to load it in various ways * - * @return string The OAuth authorize URL + * @return object The XMLHttpRequest object instance */ - var oauth_authorize = function (params, callback) { - return oauth_authenticate(params, callback, "authorize"); + methods._getXmlRequestObject = function () { + var xml = null; + // first, try the W3-standard object + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window && typeof window.XMLHttpRequest !== "undefined") { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined") { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + console.error("ActiveXObject object not defined."); + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function" && require) { + var XMLHttpRequest; + // look for xmlhttprequest module + try { + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + xml = new XMLHttpRequest(); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + console.error("xhr2 object not defined, cancelling."); + } + } + } + return xml; }; /** - * Gets the OAuth bearer token + * Parse URL-style parameters into object * - * @return object Promise + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object */ - - var oauth2_token = function (callback) { - var dfd = _getDfd(); - - if (_oauth_consumer_key === null) { - var error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return _getPromise(dfd); - } - return false; - } - - if (!dfd && typeof callback === "undefined") { - callback = function () { }; - } - - var post_fields = "grant_type=client_credentials"; - var url = _endpoint_oauth + "oauth2/token"; - - if (_use_proxy) { - url = url.replace( - _endpoint_base, - _endpoint_proxy - ); - } - - var xml = _getXmlRequestObject(); - if (xml === null) { - return; + methods._parse_str = function (str, array) { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, + j, + chr, + tmp, + key, + value, + bracket, + keys, + evalStr, + fixStr = function fixStr(str) { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + }; + if (!array) { + array = undefined.window; } - xml.open("POST", url, true); - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xml.setRequestHeader( - (_use_proxy ? "X-" : "") + "Authorization", - "Basic " + _base64_encode(_oauth_consumer_key + ":" + _oauth_consumer_secret) - ); - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) { } - var response = ""; - try { - response = xml.responseText; - } catch (e) { } - var reply = _parseApiReply(response); - reply.httpstatus = httpstatus; - if (httpstatus === 200) { - setBearerToken(reply.access_token); + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; + } + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); + } + if (key.indexOf("\0") > -1) { + key = key.substr(0, key.indexOf("\0")); + } + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); + } + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; + } + } + } } - if (typeof callback === "function") { - callback(reply); + if (!keys.length) { + keys = [key]; } - if (dfd) { - dfd.resolve({ reply: reply }); + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } } + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if (key !== "" && key !== " " || j === 0) { + key = "'" + key + "'"; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += "[" + key + "']"; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); + } + } + evalStr += " = '" + value + "';\n"; + eval(evalStr); } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, e); - } - if (dfd) { - dfd.reject(e); - } - }; - xml.timeout = 30000; // in milliseconds - - xml.send(post_fields); - if (dfd) { - return _getPromise(dfd); } }; /** - * Signing helpers - */ - - /** - * URL-encodes the given data - * - * @param mixed data + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! * - * @return mixed The encoded data + * @return array $apimethods */ - var _url = function (data) { - if ((/boolean|number|string/).test(typeof data)) { - return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); - } else { - return ""; - } + methods.getApiMethods = function () { + var httpmethods = { + GET: ["account/settings", "account/verify_credentials", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", "favorites/list", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "site", "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "user", "users/contributees", "users/contributors", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], + POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload + "users/lookup", "users/report_spam"] + }; + return httpmethods; }; /** - * Gets the base64-encoded SHA1 hash for the given data - * - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - * - * @param string data The data to calculate the hash from - * - * @return string The hash + * Promise helpers */ - var _sha1 = (function () { - function n(e, b) { - e[b >> 5] |= 128 << 24 - b % 32; - e[(b + 64 >> 9 << 4) + 15] = b; - for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, - k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { - for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m; - if (f < 16) { - m = e[p + f]; - } else { - m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; - m = m << 1 | m >>> 31; - } - - c[f] = m; - m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ - h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l( - l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : - 60 > f ? -1894007588 : -899497514)); - g = k; - k = h; - h = d << 30 | d >>> 2; - d = a; - a = m; - } - a = l(a, o); - d = l(d, q); - h = l(h, r); - k = l(k, s); - g = l(g, t); - } - return [a, d, h, k, g]; - } - - function l(e, b) { - var c = (e & 65535) + (b & 65535); - return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; - } - - function q(e) { - for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { - b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + /** + * Get a deferred object + */ + methods._getDfd = function () { + if (typeof window !== "undefined") { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); + } + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); + } + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); + } + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); } - return b; } - var g = 8; - return function (e) { - var b = _oauth_consumer_secret + "&" + (null !== _oauth_token_secret ? - _oauth_token_secret : ""); - if (_oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); + if (typeof require !== "undefined") { + var promise_class = false; + try { + promise_class = require("jquery"); + } catch (e) {} + if (promise_class) { + return promise_class.Deferred(); } - var c = q(b); - if (c.length > 16) { - c = n(c, b.length * g); + try { + promise_class = require("q"); + } catch (e) {} + if (!promise_class) { + try { + promise_class = require("rsvp"); + } catch (e) {} } - var bb = new Array(16); - for (var a = new Array(16), d = 0; d < 16; d++) { - a[d] = c[d] ^ 909522486; - bb[d] = c[d] ^ 1549556828; + if (!promise_class) { + try { + promise_class = require("when"); + } catch (e) {} } - c = n(a.concat(q(e)), 512 + e.length * g); - bb = n(bb.concat(c), 672); - b = ""; - for (g = 0; g < 4 * bb.length; g += 3) { - for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> - 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - .charAt(d >> 6 * (3 - e) & 63); - } + if (promise_class) { + try { + return promise_class.defer(); + } catch (e) {} } - return b; - }; - })(); + } + return false; + }; - /* - * Gets the base64 representation for the given data - * - * http://phpjs.org - * + original by: Tyler Akins (http://rumkin.com) - * + improved by: Bayron Guevara - * + improved by: Thunder.m - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfixed by: Pellentesque Malesuada - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Rafał Kukawski (http://kukawski.pl) + /** + * Get a promise from the dfd object + */ + methods._getPromise = function (dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object + }; + + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params * - * @param string data The data to calculate the base64 representation from + * @param array|string params Parameters to parse * - * @return string The base64 representation + * @return array apiparams */ - var _base64_encode = function (a) { - var d, e, f, b, g = 0, - h = 0, - i = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", - c = []; - if (!a) { - return a; + methods._parseApiParams = function (params) { + var apiparams = {}; + if ((typeof params === "undefined" ? "undefined" : _typeof(params)) === "object") { + apiparams = params; + } else { + methods._parse_str(params, apiparams); //TODO } - do { - d = a.charCodeAt(g++); - e = a.charCodeAt(g++); - f = a.charCodeAt(g++); - b = d << 16 | e << 8 | f; - d = b >> 18 & 63; - e = b >> 12 & 63; - f = b >> 6 & 63; - b &= 63; - c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); - } while (g < a.length); - i = c.join(""); - a = a.length % 3; - return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); + + return apiparams; }; - /* - * Builds a HTTP query string from the given data - * - * http://phpjs.org - * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Legaev Andrey - * + improved by: Michael White (http://getsprink.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Brett Zamir (http://brett-zamir.me) - * + revised by: stag019 - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + /** + * Replace null and boolean parameters with their string representations * - * @param string data The data to concatenate + * @param array apiparams Parameter array to replace in * - * @return string The HTTP query + * @return array apiparams */ - var _http_build_query = function (e, f, b) { - function g(c, a, d) { - var b, e = []; - if (a === true) { - a = "1"; - } else if (a === false) { - a = "0"; - } - if (null !== a) { - if (typeof a === "object") { - for (b in a) { - if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); - } - } - return e.join(d); - } - if (typeof a !== "function") { - return _url(c) + "=" + _url(a); - } - console.warn("There was an error processing for http_build_query()."); - } else { - return ""; - } - } - var d, c, h = []; - if (!b) { - b = "&"; - } - for (c in e) { - if (!e.hasOwnProperty(c)) { + methods._stringifyNullBoolParams = function (apiparams) { + for (var key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { continue; } - d = e[c]; - if (f && !isNaN(c)) { - c = String(f) + c; - } - d = g(c, d, b); - if (d !== "") { - h.push(d); + var value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; } } - return h.join(b); + + return apiparams; }; /** - * Generates a (hopefully) unique random string + * API method mapping: Replaces _ with / character * - * @param int optional length The length of the string to generate + * @param string fn Function called * - * @return string The random string + * @return string API method to call + */ + methods._mapFnInsertSlashes = function (fn) { + return fn.split("_").join("/"); + }; + + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores */ - var _nonce = function (length) { - if (typeof length === "undefined") { - length = 8; + methods._mapFnRestoreParamUnderscores = function (method) { + var url_parameters_with_underscore = ["screen_name", "place_id"]; + var i = undefined, + param = undefined, + replacement_was = undefined; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); } - if (length < 1) { - console.warn("Invalid nonce length."); + + return method; + }; + + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + methods._mapFnToApiMethod = function (fn, apiparams) { + var method = "", + param = undefined, + i = undefined, + j = undefined; + + // replace _ by / + method = methods._mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = methods._mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + var method_template = method; + var match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + var param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); + } + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; + } } - var nonce = ""; - for (var i = 0; i < length; i++) { - var character = Math.floor(Math.random() * 61); - nonce += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz".substring(character, character + 1); + + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); } - return nonce; + + return [method, method_template]; }; /** - * Sort array elements by key + * Detects HTTP method to use for API call * - * @param array input_arr The array to sort + * @param string method The API method to call + * @param array params The parameters to send along * - * @return array The sorted keys + * @return string The HTTP method that should be used */ - var _ksort = function (input_arr) { - var keys = [], sorter, k; + methods._detectMethod = function (method, params) { + if (typeof params.httpmethod !== "undefined") { + var httpmethod = params.httpmethod; + delete params.httpmethod; + return httpmethod; + } - sorter = function (a, b) { - var a_float = parseFloat(a), - b_float = parseFloat(b), - a_numeric = a_float + "" === a, - b_numeric = b_float + "" === b; - if (a_numeric && b_numeric) { - return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; - } else if (a_numeric && !b_numeric) { - return 1; - } else if (!a_numeric && b_numeric) { - return -1; - } - return a > b ? 1 : a < b ? -1 : 0; - }; + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = Object.keys(params).length ? method + "__post" : method; + break; + } - // Make a list of key names - for (k in input_arr) { - if (input_arr.hasOwnProperty(k)) { - keys.push(k); + var apimethods = methods.getApiMethods(); + for (var httpmethod in apimethods) { + if (apimethods.hasOwnProperty(httpmethod) && apimethods[httpmethod].indexOf(method) > -1) { + return httpmethod; } } - keys.sort(sorter); - return keys; + throw "Can't find HTTP method to use for \"" + method + "\"."; }; /** - * Clone objects + * Detects if API call should use multipart/form-data * - * @param object obj The object to clone + * @param string method The API method to call * - * @return object clone The cloned object + * @return bool Whether the method should be sent as multipart */ - var _clone = function (obj) { - var clone = {}; - for (var i in obj) { - if (typeof (obj[i]) === "object") { - clone[i] = _clone(obj[i]); - } else { - clone[i] = obj[i]; - } - } - return clone; + methods._detectMultipart = function (method) { + var multiparts = [ + // Tweets + "statuses/update_with_media", "media/upload", + + // Users + "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; + return multiparts.indexOf(method) > -1; }; /** @@ -1134,20 +884,28 @@ * * @return string signature */ - var _getSignature = function (httpmethod, method, keys, base_params) { + methods._getSignature = function (httpmethod, method, keys, base_params) { + var _url = methods._url; + var _sha1 = methods._sha1; // convert params to string - var base_string = "", key, value; + + var base_string = "", + key = undefined, + value = undefined; for (var i = 0; i < keys.length; i++) { key = keys[i]; value = base_params[key]; base_string += key + "=" + _url(value) + "&"; } base_string = base_string.substring(0, base_string.length - 1); - return _sha1( - httpmethod + "&" + - _url(method) + "&" + - _url(base_string) - ); + return _sha1(httpmethod + "&" + _url(method) + "&" + _url(base_string)); + }; + + /** + * Generates the UNIX timestamp + */ + methods._time = function () { + return Math.round(new Date().getTime() / 1000); }; /** @@ -1156,121 +914,59 @@ * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' * @param string method The API method to call * @param array optional params The API call parameters, associative - * @param bool optional append_to_get Whether to append the OAuth params to GET * * @return string Authorization HTTP header */ - var _sign = function (httpmethod, method, params, append_to_get) { - if (typeof params === "undefined") { - params = {}; - } - if (typeof append_to_get === "undefined") { - append_to_get = false; - } - if (_oauth_consumer_key === null) { + methods._sign = function (httpmethod, method) { + var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var _url = methods._url; + var _ksort = methods._ksort; + var _clone = methods._clone; + var _getSignature = methods._getSignature; + + if (props._oauth_consumer_key === null) { console.warn("To generate a signature, the consumer key must be set."); } var sign_params = { - consumer_key: _oauth_consumer_key, + consumer_key: props._oauth_consumer_key, version: "1.0", - timestamp: Math.round(new Date().getTime() / 1000), - nonce: _nonce(), + timestamp: methods._time(), + nonce: methods._nonce(), signature_method: "HMAC-SHA1" }; var sign_base_params = {}; - var value; for (var key in sign_params) { if (!sign_params.hasOwnProperty(key)) { continue; } - value = sign_params[key]; + var value = sign_params[key]; sign_base_params["oauth_" + key] = _url(value); } - if (_oauth_token !== null) { - sign_base_params.oauth_token = _url(_oauth_token); + if (props._oauth_token !== null) { + sign_base_params.oauth_token = _url(props._oauth_token); } var oauth_params = _clone(sign_base_params); for (key in params) { if (!params.hasOwnProperty(key)) { continue; } - value = params[key]; - sign_base_params[key] = value; + sign_base_params[key] = params[key]; } var keys = _ksort(sign_base_params); var signature = _getSignature(httpmethod, method, keys, sign_base_params); - params = append_to_get ? sign_base_params : oauth_params; + params = oauth_params; params.oauth_signature = signature; keys = _ksort(params); - var authorization = "", i; - if (append_to_get) { - for (i = 0; i < keys.length; i++) { - key = keys[i]; - value = params[key]; - authorization += key + "=" + _url(value) + "&"; - } - return authorization.substring(0, authorization.length - 1); - } - authorization = "OAuth "; - for (i = 0; i < keys.length; i++) { + var authorization = "OAuth "; + for (var i = 0; i < keys.length; i++) { key = keys[i]; - value = params[key]; - authorization += key + "=\"" + _url(value) + "\", "; + authorization += key + "=\"" + _url(params[key]) + "\", "; } return authorization.substring(0, authorization.length - 2); }; - /** - * Detects HTTP method to use for API call - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return string The HTTP method that should be used - */ - var _detectMethod = function (method, params) { - // multi-HTTP method endpoints - switch (method) { - case "account/settings": - case "account/login_verification_enrollment": - case "account/login_verification_request": - method = params.length ? method + "__post" : method; - break; - } - - var apimethods = getApiMethods(); - for (var httpmethod in apimethods) { - if (apimethods.hasOwnProperty(httpmethod) - && apimethods[httpmethod].indexOf(method) > -1 - ) { - return httpmethod; - } - } - console.warn("Can't find HTTP method to use for \"" + method + "\"."); - }; - - /** - * Detects if API call should use multipart/form-data - * - * @param string method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - var _detectMultipart = function (method) { - var multiparts = [ - // Tweets - "statuses/update_with_media", - - // Users - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; - return multiparts.indexOf(method) > -1; - }; - /** * Build multipart request from upload params * @@ -1279,21 +975,18 @@ * * @return null|string The built multipart request body */ - var _buildMultipart = function (method, params) { + methods._buildMultipart = function (method, params) { // well, files will only work in multipart methods - if (!_detectMultipart(method)) { + if (!methods._detectMultipart(method)) { return; } // only check specific parameters var possible_methods = [ // Tweets - "statuses/update_with_media", + "statuses/update_with_media", // Accounts - "account/update_profile_background_image", - "account/update_profile_image", - "account/update_profile_banner" - ]; + "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; var possible_files = { // Tweets "statuses/update_with_media": "media[]", @@ -1310,21 +1003,17 @@ // check for filenames possible_files = possible_files[method].split(" "); - var multipart_border = "--------------------" + _nonce(); + var multipart_border = "--------------------" + methods._nonce(); var multipart_request = ""; for (var key in params) { if (!params.hasOwnProperty(key)) { continue; } - multipart_request += - "--" + multipart_border + "\r\n" - + "Content-Disposition: form-data; name=\"" + key + "\""; - if (possible_files.indexOf(key) > -1) { - multipart_request += - "\r\nContent-Transfer-Encoding: base64"; + multipart_request += "--" + multipart_border + "\r\nContent-Disposition: form-data; name=\"" + key + "\""; + if (possible_files.indexOf(key) === -1) { + multipart_request += "\r\nContent-Transfer-Encoding: base64"; } - multipart_request += - "\r\n\r\n" + params[key] + "\r\n"; + multipart_request += "\r\n\r\n" + params[key] + "\r\n"; } multipart_request += "--" + multipart_border + "--"; return multipart_request; @@ -1337,10 +1026,8 @@ * * @return bool Whether the method is defined in media API */ - var _detectMedia = function (method) { - var medias = [ - "media/upload" - ]; + methods._detectMedia = function (method) { + var medias = ["media/upload"]; return medias.indexOf(method) > -1; }; @@ -1351,10 +1038,8 @@ * * @return bool Whether the method is defined as accepting JSON body */ - var _detectJsonBody = function (method) { - var json_bodies = [ - "collections/entries/curate" - ]; + methods._detectJsonBody = function (method) { + var json_bodies = ["collections/entries/curate"]; return json_bodies.indexOf(method) > -1; }; @@ -1365,65 +1050,187 @@ * * @return string The URL to send the request to */ - var _getEndpoint = function (method) { - var url; + methods._getEndpoint = function (method) { + var url = undefined; if (method.substring(0, 5) === "oauth") { - url = _endpoint_oauth + method; - } else if (_detectMedia(method)) { - url = _endpoint_media + method + ".json"; + url = props._endpoint_oauth + method; + } else if (methods._detectMedia(method)) { + url = props._endpoint_media + method + ".json"; } else { - url = _endpoint + method + ".json"; + url = props._endpoint + method + ".json"; } return url; }; /** - * Gets the XML HTTP Request object, trying to load it in various ways + * Parses the API reply to encode it in the set return_format * - * @return object The XMLHttpRequest object instance + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply */ - var _getXmlRequestObject = function () { - var xml = null; - // first, try the W3-standard object - if (typeof window === "object" - && window - && typeof window.XMLHttpRequest !== "undefined" - ) { - xml = new window.XMLHttpRequest(); - // then, try Titanium framework object - } else if (typeof Ti === "object" - && Ti - && typeof Ti.Network.createHTTPClient !== "undefined" - ) { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined" - ) { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - console.error("ActiveXObject object not defined."); + methods._parseApiReply = function (reply) { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + var parsed = undefined; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + // assume query format + var elements = reply.split("&"); + for (var i = 0; i < elements.length; i++) { + var element = elements[i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" - && require - ) { - var XMLHttpRequest; - // look for xmlhttprequest module - try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 + } + return parsed; + }; + + /** + * Uncommon API methods + */ + + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + methods.oauth_authenticate = function () { + var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var callback = arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1]; + var type = arguments.length <= 2 || arguments[2] === undefined ? "authenticate" : arguments[2]; + + var dfd = methods._getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (["authenticate", "authorize"].indexOf(type) === -1) { + type = "authenticate"; + } + if (props._oauth_token === null) { + var error = "To get the " + type + " URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return methods._getPromise(dfd); + } + throw error; + } + var url = props._endpoint_oauth + "oauth/" + type + "?oauth_token=" + methods._url(props._oauth_token); + if (params.force_login === true) { + url += "&force_login=1"; + } + if (params.screen_name !== null) { + url += "&screen_name=" + params.screen_name; + } + if (typeof callback === "function") { + callback(url); + } + if (dfd) { + dfd.resolve({ reply: url }); + return methods._getPromise(dfd); + } + // no promises + return true; + }; + + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ + methods.oauth_authorize = function (params, callback) { + return methods.oauth_authenticate(params, callback, "authorize"); + }; + + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ + + methods.oauth2_token = function (callback) { + var dfd = methods._getDfd(); + + if (props._oauth_consumer_key === null) { + var error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return methods._getPromise(dfd); + } + return false; + } + + if (!dfd && typeof callback === "undefined") { + callback = function () {}; + } + + var post_fields = "grant_type=client_credentials"; + var url = props._endpoint_oauth + "oauth2/token"; + + if (props._use_proxy) { + url = url.replace(props._endpoint_base, props._endpoint_proxy); + } + + var xml = methods._getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader((props._use_proxy ? "X-" : "") + "Authorization", "Basic " + methods._base64_encode(props._oauth_consumer_key + ":" + props._oauth_consumer_secret)); + + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; try { - XMLHttpRequest = require("xhr2"); - xml = new XMLHttpRequest(); - } catch (e2) { - console.error("xhr2 object not defined, cancelling."); + httpstatus = xml.status; + } catch (e) {} + var response = ""; + try { + response = xml.responseText; + } catch (e) {} + var reply = methods._parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + methods.setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({ reply: reply }); } } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { + if (typeof callback === "function") { + callback(null, e); + } + if (dfd) { + dfd.reject(e); + } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(post_fields); + if (dfd) { + return methods._getPromise(dfd); } - return xml; }; /** @@ -1439,165 +1246,106 @@ * @return mixed The API reply, encoded in the set return_format */ - var _callApi = function (httpmethod, method, params, multipart, app_only_auth, callback) { - var dfd = _getDfd(); + methods._callApi = function (httpmethod, method) { + var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var multipart = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + var app_only_auth = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; + var callback = arguments.length <= 5 || arguments[5] === undefined ? function () {} : arguments[5]; - if (typeof params === "undefined") { - params = {}; - } - if (typeof multipart === "undefined") { - multipart = false; - } - if (typeof app_only_auth === "undefined") { - app_only_auth = false; - } - if (typeof callback !== "function") { - callback = function () { }; - } + var dfd = methods._getDfd(); - var url = _getEndpoint(method); - var authorization = null; + var url = methods._getEndpoint(method), + authorization = null; - var xml = _getXmlRequestObject(); + var xml = methods._getXmlRequestObject(); if (xml === null) { return; } - var post_fields; + var post_fields = undefined; + var _sign = methods._sign; if (httpmethod === "GET") { var url_with_params = url; if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + _http_build_query(params); + url_with_params += "?" + methods._http_build_query(params); } if (!app_only_auth) { authorization = _sign(httpmethod, url, params); } - // append auth params to GET url for IE7-9, to send via JSONP - if (_use_jsonp) { - if (JSON.stringify(params) !== "{}") { - url_with_params += "&"; - } else { - url_with_params += "?"; - } - var callback_name = _nonce(); - window[callback_name] = function (reply) { - reply.httpstatus = 200; - - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; - } - callback(reply, rate); - }; - params.callback = callback_name; - url_with_params = url + "?" + _sign(httpmethod, url, params, true); - var tag = document.createElement("script"); - tag.type = "text/javascript"; - tag.src = url_with_params; - var body = document.getElementsByTagName("body")[0]; - body.appendChild(tag); - return; - - } else if (_use_proxy) { - url_with_params = url_with_params.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); + if (props._use_proxy) { + url_with_params = url_with_params.replace(props._endpoint_base, props._endpoint_proxy).replace(props._endpoint_base_media, props._endpoint_proxy); } xml.open(httpmethod, url_with_params, true); } else { - if (_use_jsonp) { - console.warn("Sending POST requests is not supported for IE7-9."); - return; - } if (multipart) { if (!app_only_auth) { authorization = _sign(httpmethod, url, {}); } - params = _buildMultipart(method, params); - } else if (_detectJsonBody(method)) { + params = methods._buildMultipart(method, params); + } else if (methods._detectJsonBody(method)) { authorization = _sign(httpmethod, url, {}); params = JSON.stringify(params); } else { if (!app_only_auth) { authorization = _sign(httpmethod, url, params); } - params = _http_build_query(params); + params = methods._http_build_query(params); } post_fields = params; - if (_use_proxy || multipart) { // force proxy for multipart base64 - url = url.replace( - _endpoint_base, - _endpoint_proxy - ).replace( - _endpoint_base_media, - _endpoint_proxy - ); + if (props._use_proxy || multipart) { + // force proxy for multipart base64 + url = url.replace(props._endpoint_base, props._endpoint_proxy).replace(props._endpoint_base_media, props._endpoint_proxy); } xml.open(httpmethod, url, true); if (multipart) { - xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" - + post_fields.split("\r\n")[0].substring(2)); - } else if (_detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); + } else if (methods._detectJsonBody(method)) { xml.setRequestHeader("Content-Type", "application/json"); } else { xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } } if (app_only_auth) { - if (_oauth_consumer_key === null - && _oauth_bearer_token === null - ) { + if (props._oauth_consumer_key === null && props._oauth_bearer_token === null) { var error = "To make an app-only auth API request, consumer key or bearer token must be set."; console.warn(error); if (dfd) { dfd.reject({ error: error }); - return _getPromise(dfd); + return methods._getPromise(dfd); } } // automatically fetch bearer token, if necessary - if (_oauth_bearer_token === null) { + if (props._oauth_bearer_token === null) { if (dfd) { - return oauth2_token().then(function () { - return _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + return methods.oauth2_token().then(function () { + return methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); } - oauth2_token(function () { - _callApi(httpmethod, method, params, multipart, app_only_auth, callback); + methods.oauth2_token(function () { + methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); return; } - authorization = "Bearer " + _oauth_bearer_token; + authorization = "Bearer " + props._oauth_bearer_token; } if (authorization !== null) { - xml.setRequestHeader((_use_proxy ? "X-" : "") + "Authorization", authorization); + xml.setRequestHeader((props._use_proxy ? "X-" : "") + "Authorization", authorization); } xml.onreadystatechange = function () { if (xml.readyState >= 4) { var httpstatus = 12027; try { httpstatus = xml.status; - } catch (e) { } + } catch (e) {} var response = ""; try { response = xml.responseText; - } catch (e) { } - var reply = _parseApiReply(response); + } catch (e) {} + var reply = methods._parseApiReply(response); reply.httpstatus = httpstatus; var rate = null; - if (typeof xml.getResponseHeader !== "undefined" - && xml.getResponseHeader("x-rate-limit-limit") !== "" - ) { + if (typeof xml.getResponseHeader !== "undefined" && xml.getResponseHeader("x-rate-limit-limit") !== "") { rate = { limit: xml.getResponseHeader("x-rate-limit-limit"), remaining: xml.getResponseHeader("x-rate-limit-remaining"), @@ -1625,72 +1373,106 @@ xml.send(httpmethod === "GET" ? null : post_fields); if (dfd) { - return _getPromise(dfd); + return methods._getPromise(dfd); } return true; }; /** - * Parses the API reply to encode it in the set return_format + * Main API handler working on any requests you issue * - * @param string reply The actual reply, JSON-encoded or URL-encoded + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth * - * @return array|object The parsed reply + * @return object Promise */ - var _parseApiReply = function (reply) { - if (typeof reply !== "string" || reply === "") { - return {}; + + methods.__call = function (fn) { + var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var callback = arguments[2]; + var app_only_auth = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = function () {}; } - if (reply === "[]") { - return []; + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return undefined[fn](params, callback); + + case "oauth2_token": + return undefined[fn](callback); } - var parsed; - try { - parsed = JSON.parse(reply); - } catch (e) { - parsed = {}; - if (reply.indexOf("<" + "?xml version=\"1.0\" encoding=\"UTF-8\"?" + ">") === 0) { - // we received XML... - // since this only happens for errors, - // don't perform a full decoding - parsed.request = reply.match(/(.*)<\/request>/)[1]; - parsed.error = reply.match(/(.*)<\/error>/)[1]; - } else { - // assume query format - var elements = reply.split("&"); - for (var i = 0; i < elements.length; i++) { - var element = elements[i].split("=", 2); - if (element.length > 1) { - parsed[element[0]] = decodeURIComponent(element[1]); - } else { - parsed[element[0]] = null; - } + + // parse parameters + var apiparams = methods._parseApiParams(params); + + // stringify null and boolean parameters + apiparams = methods._stringifyNullBoolParams(apiparams); + + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + methods.setToken(null, null); + } + + // map function name to API method + + var _methods$_mapFnToApiM = methods._mapFnToApiMethod(fn, apiparams); + + var _methods$_mapFnToApiM2 = _slicedToArray(_methods$_mapFnToApiM, 2); + + var method = _methods$_mapFnToApiM2[0]; + var method_template = _methods$_mapFnToApiM2[1]; + var httpmethod = methods._detectMethod(method_template, apiparams); + var multipart = methods._detectMultipart(method_template); + + return methods._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); + }; + + // Unit testing code + var __test = { + call: function call(name) { + var params = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; + return methods[name].apply(undefined, params); + }, + get: function get(name) { + return props[name]; + }, + mock: function mock(methods_to_mock) { + for (var name in methods_to_mock) { + if (methods_to_mock.hasOwnProperty(name)) { + methods[name] = methods_to_mock[name]; } } } - return parsed; }; return { - setConsumerKey: setConsumerKey, - getVersion: getVersion, - setToken: setToken, - logout: logout, - setBearerToken: setBearerToken, - setUseProxy: setUseProxy, - setProxy: setProxy, - getApiMethods: getApiMethods, - __call: __call, - oauth_authenticate: oauth_authenticate, - oauth_authorize: oauth_authorize, - oauth2_token: oauth2_token + __call: methods.__call, + __test: __test, + getApiMethods: methods.getApiMethods, + getVersion: methods.getVersion, + logout: methods.logout, + oauth2_token: methods.oauth2_token, + oauth_authenticate: methods.oauth_authenticate, + oauth_authorize: methods.oauth_authorize, + setBearerToken: methods.setBearerToken, + setConsumerKey: methods.setConsumerKey, + setProxy: methods.setProxy, + setToken: methods.setToken, + setUseProxy: methods.setUseProxy }; }; - if (typeof module === "object" - && module - && typeof module.exports === "object" - ) { + if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === "object" && module && _typeof(module.exports) === "object") { // Expose codebird as module.exports in loaders that implement the Node // module pattern (including browserify). Do not create the global, since // the user will be storing it themselves locally, and globals are frowned @@ -1698,8 +1480,7 @@ module.exports = Codebird; } else { // Otherwise expose codebird to the global object as usual - if (typeof window === "object" - && window) { + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window) { window.Codebird = Codebird; } @@ -1711,8 +1492,9 @@ // file name. Do this after creating the global so that if an AMD module wants // to call noConflict to hide this version of codebird, it will work. if (typeof define === "function" && define.amd) { - define("codebird", [], function () { return Codebird; }); + define("codebird", [], function () { + return Codebird; + }); } } - })(); diff --git a/package.json b/package.json index 0a2fa64..f722919 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "Joshua Atkins (http://atkins.im/)", "J.M. (http://mynetx.net/)" ], - "main": "codebird.js", + "main": "codebird.es7.js", "directories": { "example": "examples" }, @@ -25,11 +25,17 @@ "url": "https://github.com/jublonet/codebird-js.git" }, "scripts": { - "test": "blue-tape -r ./codebird test/*.js | faucet" + "build": "babel codebird.es7.js -o codebird.js", + "test": "babel codebird.es7.js -o codebird.js && tape test/*.js | faucet" }, "devDependencies": { - "blue-tape": "^0.1.11", + "babel-cli": "^6.3.17", + "babel-preset-es2015": "^6.3.13", "eslint": "^1.10.3", - "faucet": "0.0.1" + "faucet": "0.0.1", + "q": "^1.4.1", + "tape": "^4.4.0", + "tape-promise": "^1.0.1", + "xmlhttprequest": "^1.8.0" } } diff --git a/test/README b/test/README new file mode 100644 index 0000000..c2249fc --- /dev/null +++ b/test/README @@ -0,0 +1,9 @@ +The following methods are covered by unit tests: + +_detectMethod +_detectMultipart +_detectMedia +_getEndpoint +oauth_authenticate +oauth_authorize +oauth2_token diff --git a/test/detection_tests.js b/test/detection_tests.js new file mode 100644 index 0000000..e3c1d0e --- /dev/null +++ b/test/detection_tests.js @@ -0,0 +1,87 @@ +const tape = require("tape"), + _test = require("tape-promise"), + test = _test(tape), // decorate tape + Codebird = require("../codebird"); + +test("Tests _detectMethod", (t) => { + const cb = new Codebird; + + t.throws( + () => cb.__test.call("_detectMethod", ["non-existent", {}]), + /^Can't find HTTP method to use for "non-existent".$/ + ); + + // forced httpmethod + t.equal( + cb.__test.call("_detectMethod", ["doesnt-matter", {httpmethod: "DELETE"}]), + "DELETE" + ); + + // normal detection + t.equal( + cb.__test.call("_detectMethod", ["search/tweets", {}]), + "GET" + ); + t.equal( + cb.__test.call("_detectMethod", ["statuses/update", {}]), + "POST" + ); + t.equal( + cb.__test.call("_detectMethod", ["statuses/destroy/:id", {}]), + "POST" + ); + + // parameter-based detection + t.equal( + cb.__test.call("_detectMethod", ["account/settings", {}]), + "GET" + ); + t.equal( + cb.__test.call("_detectMethod", ["account/settings", {test: 12}]), + "POST" + ); + + t.end(); +}); + +test("Tests _detectMultipart", (t) => { + const cb = new Codebird; + + t.false(cb.__test.call("_detectMultipart", ["statuses/update"])); + t.true(cb.__test.call("_detectMultipart", ["statuses/update_with_media"])); + t.true(cb.__test.call("_detectMultipart", ["media/upload"])); + + t.end(); +}); + +test("Tests _detectMedia", (t) => { + const cb = new Codebird; + + t.false(cb.__test.call("_detectMedia", ["statuses/update"])); + t.true(cb.__test.call("_detectMedia", ["media/upload"])); + + t.end(); +}); + +test("Tests _getEndpoint", (t) => { + const cb = new Codebird; + + t.equal( + cb.__test.call("_getEndpoint", ["statuses/update", "statuses/update"]), + "https://api.twitter.com/1.1/statuses/update.json" + ); + t.equal( + cb.__test.call("_getEndpoint", ["oauth/authenticate", "oauth/authenticate"]), + "https://api.twitter.com/oauth/authenticate" + ); + t.equal( + cb.__test.call("_getEndpoint", ["oauth2/token", "oauth2/token"]), + "https://api.twitter.com/oauth2/token" + ); + t.equal( + cb.__test.call("_getEndpoint", ["media/upload", "media/upload"]), + "https://upload.twitter.com/1.1/media/upload.json" + ); + + t.end(); +}); diff --git a/test/oauth_tests.js b/test/oauth_tests.js new file mode 100644 index 0000000..e5c8cb9 --- /dev/null +++ b/test/oauth_tests.js @@ -0,0 +1,104 @@ +const tape = require("tape"), + _test = require("tape-promise"), + test = _test(tape), // decorate tape + Codebird = require("../codebird"); + +function mock() { + var cb = new Codebird(); + cb.setConsumerKey("123", "456"); + + var xml = { + readyState: 4, + status: 200, + open: url => { + this.url = url + }, + setRequestHeader: () => true, + responseText: "{\"token_type\":\"bearer\",\"access_token\":\"VqiO0n2HrKE\"}" + }; + xml.send = () => { + setTimeout(xml.onreadystatechange, 200); + }; + + cb.__test.mock({ + _getXmlRequestObject: () => { + return xml + } + }); + + return cb; +} + +test("Tests oauth_authenticate Promise", t => { + const cb = mock(); + t.plan(1); + + cb.setToken("123", "456"); + return cb.oauth_authenticate().then(a => + t.deepEqual( + a, + {reply: "https://api.twitter.com/oauth/authenticate?oauth_token=123"} + ) + ); +}); + +test("Tests oauth_authenticate callback", t => { + const cb = mock(); + t.plan(4); + + cb.setToken("123", "456"); + cb.oauth_authenticate({}, a => t.equal( + a, "https://api.twitter.com/oauth/authenticate?oauth_token=123" + ) + ); + cb.oauth_authenticate({force_login: true}, a => t.equal( + a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&force_login=1" + ) + ); + cb.oauth_authenticate({force_login: true, screen_name: "TwitterAPI"}, a => t.equal( + a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&force_login=1&screen_name=TwitterAPI" + ) + ); + cb.oauth_authenticate({screen_name: "TwitterAPI"}, a => t.equal( + a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&screen_name=TwitterAPI" + ) + ); +}); + +test("Tests oauth_authorize callback", t => { + const cb = mock(); + t.plan(4); + + cb.setToken("123", "456"); + cb.oauth_authorize({}, a => t.equal( + a, "https://api.twitter.com/oauth/authorize?oauth_token=123" + ) + ); + cb.oauth_authorize({force_login: true}, a => t.equal( + a, "https://api.twitter.com/oauth/authorize?oauth_token=123&force_login=1" + ) + ); + cb.oauth_authorize({force_login: true, screen_name: "TwitterAPI"}, a => t.equal( + a, "https://api.twitter.com/oauth/authorize?oauth_token=123&force_login=1&screen_name=TwitterAPI" + ) + ); + cb.oauth_authorize({screen_name: "TwitterAPI"}, a => t.equal( + a, "https://api.twitter.com/oauth/authorize?oauth_token=123&screen_name=TwitterAPI" + ) + ); +}); + +test("Tests oauth2_token", t => { + const cb = mock(); + t.plan(1); + + cb.oauth2_token( + a => t.deepEqual( + a, { + token_type: "bearer", + access_token: "VqiO0n2HrKE", + httpstatus: 200 + } + ) + ); +}); From 92c18de4723d1e07b3ffe1c76c5397789d8820ab Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 2 Jan 2016 19:01:24 +0100 Subject: [PATCH 23/48] =?UTF-8?q?ES5=20for=20tests,=20old=20Node=20doesn?= =?UTF-8?q?=E2=80=99t=20like=20modern=20stuff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/detection_tests.js | 6 ++-- test/oauth_tests.js | 75 +++++++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/test/detection_tests.js b/test/detection_tests.js index e3c1d0e..73a189a 100644 --- a/test/detection_tests.js +++ b/test/detection_tests.js @@ -44,7 +44,7 @@ test("Tests _detectMethod", (t) => { t.end(); }); -test("Tests _detectMultipart", (t) => { +test("Tests _detectMultipart", function (t) { const cb = new Codebird; t.false(cb.__test.call("_detectMultipart", ["statuses/update"])); @@ -54,7 +54,7 @@ test("Tests _detectMultipart", (t) => { t.end(); }); -test("Tests _detectMedia", (t) => { +test("Tests _detectMedia", function (t) { const cb = new Codebird; t.false(cb.__test.call("_detectMedia", ["statuses/update"])); @@ -63,7 +63,7 @@ test("Tests _detectMedia", (t) => { t.end(); }); -test("Tests _getEndpoint", (t) => { +test("Tests _getEndpoint", function (t) { const cb = new Codebird; t.equal( diff --git a/test/oauth_tests.js b/test/oauth_tests.js index e5c8cb9..bd928ca 100644 --- a/test/oauth_tests.js +++ b/test/oauth_tests.js @@ -13,15 +13,15 @@ function mock() { open: url => { this.url = url }, - setRequestHeader: () => true, + setRequestHeader: function () { return true; }, responseText: "{\"token_type\":\"bearer\",\"access_token\":\"VqiO0n2HrKE\"}" }; - xml.send = () => { + xml.send = function () { setTimeout(xml.onreadystatechange, 200); }; cb.__test.mock({ - _getXmlRequestObject: () => { + _getXmlRequestObject: function () { return xml } }); @@ -29,76 +29,85 @@ function mock() { return cb; } -test("Tests oauth_authenticate Promise", t => { +test("Tests oauth_authenticate Promise", function (t) { const cb = mock(); t.plan(1); cb.setToken("123", "456"); - return cb.oauth_authenticate().then(a => + return cb.oauth_authenticate().then(function (a) { t.deepEqual( a, {reply: "https://api.twitter.com/oauth/authenticate?oauth_token=123"} ) - ); + }); }); -test("Tests oauth_authenticate callback", t => { +test("Tests oauth_authenticate callback", function (t) { const cb = mock(); t.plan(4); cb.setToken("123", "456"); - cb.oauth_authenticate({}, a => t.equal( + cb.oauth_authenticate({}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authenticate?oauth_token=123" ) - ); - cb.oauth_authenticate({force_login: true}, a => t.equal( + }); + cb.oauth_authenticate({force_login: true}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&force_login=1" ) - ); - cb.oauth_authenticate({force_login: true, screen_name: "TwitterAPI"}, a => t.equal( + }); + cb.oauth_authenticate({force_login: true, screen_name: "TwitterAPI"}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&force_login=1&screen_name=TwitterAPI" ) - ); - cb.oauth_authenticate({screen_name: "TwitterAPI"}, a => t.equal( + }); + cb.oauth_authenticate({screen_name: "TwitterAPI"}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authenticate?oauth_token=123&screen_name=TwitterAPI" ) - ); + }); }); -test("Tests oauth_authorize callback", t => { +test("Tests oauth_authorize callback", function (t) { const cb = mock(); t.plan(4); cb.setToken("123", "456"); - cb.oauth_authorize({}, a => t.equal( + cb.oauth_authorize({}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authorize?oauth_token=123" ) - ); - cb.oauth_authorize({force_login: true}, a => t.equal( + }); + cb.oauth_authorize({force_login: true}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authorize?oauth_token=123&force_login=1" ) - ); - cb.oauth_authorize({force_login: true, screen_name: "TwitterAPI"}, a => t.equal( + }); + cb.oauth_authorize({force_login: true, screen_name: "TwitterAPI"}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authorize?oauth_token=123&force_login=1&screen_name=TwitterAPI" ) - ); - cb.oauth_authorize({screen_name: "TwitterAPI"}, a => t.equal( + }); + cb.oauth_authorize({screen_name: "TwitterAPI"}, function (a) { + t.equal( a, "https://api.twitter.com/oauth/authorize?oauth_token=123&screen_name=TwitterAPI" ) - ); + }); }); -test("Tests oauth2_token", t => { +test("Tests oauth2_token", function (t) { const cb = mock(); t.plan(1); cb.oauth2_token( - a => t.deepEqual( - a, { - token_type: "bearer", - access_token: "VqiO0n2HrKE", - httpstatus: 200 - } - ) - ); + function (a) { + t.deepEqual( + a, { + token_type: "bearer", + access_token: "VqiO0n2HrKE", + httpstatus: 200 + } + ); + }); }); From 60d460d077aa437cf2311cf7aeb544040adac65b Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 2 Jan 2016 19:29:55 +0100 Subject: [PATCH 24/48] More fixes for ES5 tests --- test/detection_tests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/detection_tests.js b/test/detection_tests.js index 73a189a..fdaa95d 100644 --- a/test/detection_tests.js +++ b/test/detection_tests.js @@ -3,11 +3,13 @@ const tape = require("tape"), test = _test(tape), // decorate tape Codebird = require("../codebird"); -test("Tests _detectMethod", (t) => { +test("Tests _detectMethod", function (t) { const cb = new Codebird; t.throws( - () => cb.__test.call("_detectMethod", ["non-existent", {}]), + function () { + cb.__test.call("_detectMethod", ["non-existent", {}]); + }, /^Can't find HTTP method to use for "non-existent".$/ ); From 471019f59566a49960915c66e8531d2934d685db Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 2 Jan 2016 19:34:14 +0100 Subject: [PATCH 25/48] Still more test fixes --- test/oauth_tests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/oauth_tests.js b/test/oauth_tests.js index bd928ca..6db433a 100644 --- a/test/oauth_tests.js +++ b/test/oauth_tests.js @@ -10,10 +10,12 @@ function mock() { var xml = { readyState: 4, status: 200, - open: url => { + open: function (url) { this.url = url }, - setRequestHeader: function () { return true; }, + setRequestHeader: function () { + return true; + }, responseText: "{\"token_type\":\"bearer\",\"access_token\":\"VqiO0n2HrKE\"}" }; xml.send = function () { From 290d28828a03ad53a4db1202a644f3c3c0b8cdfe Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 3 Jan 2016 15:34:48 +0100 Subject: [PATCH 26/48] Use proper ES6 classing, fix tests --- .gitignore | 2 + codebird.es7.js | 556 ++++----- codebird.js | 2578 ++++++++++++++++++++------------------- package.json | 8 +- test/codebirdm.es7.js | 66 + test/codebirdt.es7.js | 93 ++ test/detection_tests.js | 46 +- test/oauth_tests.js | 35 +- 8 files changed, 1798 insertions(+), 1586 deletions(-) create mode 100644 test/codebirdm.es7.js create mode 100644 test/codebirdt.es7.js diff --git a/.gitignore b/.gitignore index 1ccb0a9..6f58266 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules RELEASE_MESSAGE* +test/codebirdm.js +test/codebirdt.js test*.html *.jpg diff --git a/codebird.es7.js b/codebird.es7.js index 42a305f..bb387a6 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -26,78 +26,79 @@ * @package codebird * @subpackage codebird-js */ - const Codebird = () => { - - let props = {}; - /** - * The OAuth consumer key of your registered app - */ - props._oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - props._oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - props._oauth_bearer_token = null; - - /** - * The API endpoint base to use - */ - props._endpoint_base = "https://api.twitter.com/"; - - /** - * The media API endpoint base to use - */ - props._endpoint_base_media = "https://upload.twitter.com/"; - - /** - * The API endpoint to use - */ - props._endpoint = `${props._endpoint_base}1.1/`; - - /** - * The media API endpoint to use - */ - props._endpoint_media = `${props._endpoint_base_media}1.1/`; - - /** - * The API endpoint base to use - */ - props._endpoint_oauth = props._endpoint_base; - - /** - * API proxy endpoint - */ - props._endpoint_proxy = "https://api.jublo.net/codebird/"; - - /** - * Whether to access the API via a proxy that is allowed by CORS - * Assume that CORS is only necessary in browsers - */ - props._use_proxy = (typeof navigator !== "undefined" + class Codebird { + + constructor() { + /** + * The OAuth consumer key of your registered app + */ + this._oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + this._oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + this._oauth_bearer_token = null; + + /** + * The API endpoint base to use + */ + this._endpoint_base = "https://api.twitter.com/"; + + /** + * The media API endpoint base to use + */ + this._endpoint_base_media = "https://upload.twitter.com/"; + + /** + * The API endpoint to use + */ + this._endpoint = `${this._endpoint_base}1.1/`; + + /** + * The media API endpoint to use + */ + this._endpoint_media = `${this._endpoint_base_media}1.1/`; + + /** + * The API endpoint base to use + */ + this._endpoint_oauth = this._endpoint_base; + + /** + * API proxy endpoint + */ + this._endpoint_proxy = "https://api.jublo.net/codebird/"; + + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + this._use_proxy = (typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined" ); - /** - * The Request or access token. Used to sign requests - */ - props._oauth_token = null; + /** + * The Request or access token. Used to sign requests + */ + this._oauth_token = null; - /** - * The corresponding request or access token secret - */ - props._oauth_token_secret = null; + /** + * The corresponding request or access token secret + */ + this._oauth_token_secret = null; - /** - * The current Codebird version - */ - props._version = "3.0.0-dev"; + /** + * The current Codebird version + */ + this._version = "3.0.0-dev"; - let methods = {}; + this.b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + } /** * Sets the OAuth consumer key and secret (App key) @@ -107,10 +108,10 @@ * * @return void */ - methods.setConsumerKey = (key, secret) => { - props._oauth_consumer_key = key; - props._oauth_consumer_secret = secret; - }; + setConsumerKey(key, secret) { + this._oauth_consumer_key = key; + this._oauth_consumer_secret = secret; + } /** * Sets the OAuth2 app-only auth bearer token @@ -119,18 +120,18 @@ * * @return void */ - methods.setBearerToken = token => { - props._oauth_bearer_token = token; - }; + setBearerToken(token) { + this._oauth_bearer_token = token; + } /** * Gets the current Codebird version * * @return string The version number */ - methods.getVersion = () => { - return props._version; - }; + getVersion() { + return this._version; + } /** * Sets the OAuth request or access token and secret (User key) @@ -140,22 +141,22 @@ * * @return void */ - methods.setToken = (token, secret) => { - props._oauth_token = token; - props._oauth_token_secret = secret; - }; + setToken(token, secret) { + this._oauth_token = token; + this._oauth_token_secret = secret; + } /** * Forgets the OAuth request or access token and secret (User key) * * @return bool */ - methods.logout = () => { - props._oauth_token = - props._oauth_token_secret = null; + logout() { + this._oauth_token = + this._oauth_token_secret = null; return true; - }; + } /** * Enables or disables CORS proxy @@ -164,9 +165,9 @@ * * @return void */ - methods.setUseProxy = use_proxy => { - props._use_proxy = !!use_proxy; - }; + setUseProxy(use_proxy) { + this._use_proxy = !!use_proxy; + } /** * Sets custom CORS proxy server @@ -175,13 +176,13 @@ * * @return void */ - methods.setProxy = proxy => { + setProxy(proxy) { // add trailing slash if missing if (!proxy.match(/\/$/)) { proxy += "/"; } - props._endpoint_proxy = proxy; - }; + this._endpoint_proxy = proxy; + } /** * Signing helpers @@ -194,15 +195,13 @@ * * @return mixed The encoded data */ - methods._url = data => { + _url(data) { if ((/boolean|number|string/).test(typeof data)) { return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); } else { return ""; } - }; - - const b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + } /** * Gets the base64-encoded SHA1 hash for the given data @@ -218,7 +217,7 @@ * * @return string The hash */ - methods._sha1 = (() => { + _sha1(e) { function n (e, b) { e[b >> 5] |= 128 << 24 - b % 32; e[(b + 64 >> 9 << 4) + 15] = b; @@ -266,35 +265,34 @@ return b; } var g = 8; - return e => { - let b = `${props._oauth_consumer_secret}&${null !== props._oauth_token_secret ? - props._oauth_token_secret : ""}`; - if (props._oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); - } - let c = q(b); - if (c.length > 16) { - c = n(c, b.length * g); - } - let bb = new Array(16); - for (var a = new Array(16), d = 0; d < 16; d++) { - a[d] = c[d] ^ 909522486; - bb[d] = c[d] ^ 1549556828; - } - c = n(a.concat(q(e)), 512 + e.length * g); - bb = n(bb.concat(c), 672); - b = ""; - for (g = 0; g < 4 * bb.length; g += 3) { - for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> - 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + - b64_alphabet.charAt(d >> 6 * (3 - e) & 63); - } + + let b = `${this._oauth_consumer_secret}&${null !== this._oauth_token_secret ? + this._oauth_token_secret : ""}`; + if (this._oauth_consumer_secret === null) { + console.warn("To generate a hash, the consumer secret must be set."); + } + let c = q(b); + if (c.length > 16) { + c = n(c, b.length * g); + } + let bb = new Array(16); + for (var a = new Array(16), d = 0; d < 16; d++) { + a[d] = c[d] ^ 909522486; + bb[d] = c[d] ^ 1549556828; + } + c = n(a.concat(q(e)), 512 + e.length * g); + bb = n(bb.concat(c), 672); + b = ""; + for (g = 0; g < 4 * bb.length; g += 3) { + for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> + 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - + (g + 2) % 4) & 255, e = 0; 4 > e; e++) { + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + + this.b64_alphabet.charAt(d >> 6 * (3 - e) & 63); } - return b; - }; - })(); + } + return b; + } /* * Gets the base64 representation for the given data @@ -312,10 +310,10 @@ * * @return string The base64 representation */ - methods._base64_encode = a => { + _base64_encode(a) { let d, e, f, b, g = 0, h = 0, - i = b64_alphabet, + i = this.b64_alphabet, c = []; if (!a) { return a; @@ -334,7 +332,7 @@ i = c.join(""); a = a.length % 3; return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); - }; + } /* * Builds a HTTP query string from the given data @@ -354,7 +352,7 @@ * * @return string The HTTP query */ - methods._http_build_query = (e, f, b) => { + _http_build_query(e, f, b) { function g(c, a, d) { let b, e = []; if (a === true) { @@ -372,7 +370,7 @@ return e.join(d); } if (typeof a !== "function") { - return methods._url(c) + "=" + methods._url(a); + return this._url(c) + "=" + this._url(a); } console.warn("There was an error processing for http_build_query()."); } else { @@ -397,7 +395,7 @@ } } return h.join(b); - }; + } /** * Generates a (hopefully) unique random string @@ -406,17 +404,17 @@ * * @return string The random string */ - methods._nonce = (length = 8) => { + _nonce(length = 8) { if (length < 1) { console.warn("Invalid nonce length."); } let nonce = ""; for (let i = 0; i < length; i++) { let character = Math.floor(Math.random() * 61); - nonce += b64_alphabet.substring(character, character + 1); + nonce += this.b64_alphabet.substring(character, character + 1); } return nonce; - }; + } /** * Sort array elements by key @@ -425,7 +423,7 @@ * * @return array The sorted keys */ - methods._ksort = input_arr => { + _ksort(input_arr) { let keys = [], sorter, k; sorter = (a, b) => { @@ -451,7 +449,7 @@ } keys.sort(sorter); return keys; - }; + } /** * Clone objects @@ -460,24 +458,24 @@ * * @return object clone The cloned object */ - methods._clone = obj => { + _clone(obj) { let clone = {}; for (let i in obj) { if (typeof (obj[i]) === "object") { - clone[i] = methods._clone(obj[i]); + clone[i] = this._clone(obj[i]); } else { clone[i] = obj[i]; } } return clone; - }; + } /** * Gets the XML HTTP Request object, trying to load it in various ways * * @return object The XMLHttpRequest object instance */ - methods._getXmlRequestObject = () => { + _getXmlRequestObject() { let xml = null; // first, try the W3-standard object if (typeof window === "object" @@ -519,7 +517,7 @@ } } return xml; - }; + } /** * Parse URL-style parameters into object @@ -543,7 +541,7 @@ * * @return object */ - methods._parse_str = (str, array) => { + _parse_str(str, array) { var glue1 = "=", glue2 = "&", array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), @@ -616,7 +614,7 @@ eval(evalStr); } } - }; + } /** * Get allowed API methods, sorted by GET or POST @@ -624,7 +622,7 @@ * * @return array $apimethods */ - methods.getApiMethods = () => { + getApiMethods() { const httpmethods = { GET: [ "account/settings", @@ -752,7 +750,7 @@ ] }; return httpmethods; - }; + } /** * Promise helpers @@ -761,7 +759,7 @@ /** * Get a deferred object */ - methods._getDfd = () => { + _getDfd() { if (typeof window !== "undefined") { if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { return window.jQuery.Deferred(); @@ -804,17 +802,17 @@ } } return false; - }; + } /** * Get a promise from the dfd object */ - methods._getPromise = dfd => { + _getPromise(dfd) { if (typeof dfd.promise === "function") { return dfd.promise(); } return dfd.promise; // object - }; + } /** * __call() helpers @@ -827,16 +825,16 @@ * * @return array apiparams */ - methods._parseApiParams = params => { + _parseApiParams(params) { let apiparams = {}; if (typeof params === "object") { apiparams = params; } else { - methods._parse_str(params, apiparams); //TODO + this._parse_str(params, apiparams); //TODO } return apiparams; - }; + } /** * Replace null and boolean parameters with their string representations @@ -845,7 +843,7 @@ * * @return array apiparams */ - methods._stringifyNullBoolParams = apiparams => { + _stringifyNullBoolParams(apiparams) { for (let key in apiparams) { if (!apiparams.hasOwnProperty(key)) { continue; @@ -859,7 +857,7 @@ } return apiparams; - }; + } /** * API method mapping: Replaces _ with / character @@ -868,7 +866,9 @@ * * @return string API method to call */ - methods._mapFnInsertSlashes = fn => fn.split("_").join("/"); + _mapFnInsertSlashes(fn) { + return fn.split("_").join("/"); + } /** * API method mapping: Restore _ character in named parameters @@ -877,7 +877,7 @@ * * @return string API method with restored underscores */ - methods._mapFnRestoreParamUnderscores = method => { + _mapFnRestoreParamUnderscores(method) { const url_parameters_with_underscore = ["screen_name", "place_id"]; let i, param, replacement_was; for (i = 0; i < url_parameters_with_underscore.length; i++) { @@ -887,7 +887,7 @@ } return method; - }; + } /** @@ -898,15 +898,15 @@ * * @return string[] (string method, string method_template) */ - methods._mapFnToApiMethod = (fn, apiparams) => { + _mapFnToApiMethod(fn, apiparams) { let method = "", param, i, j; // replace _ by / - method = methods._mapFnInsertSlashes(fn); + method = this._mapFnInsertSlashes(fn); // undo replacement for URL parameters - method = methods._mapFnRestoreParamUnderscores(method); + method = this._mapFnRestoreParamUnderscores(method); // replace AA by URL parameters let method_template = method; @@ -934,7 +934,7 @@ } return [method, method_template]; - }; + } /** @@ -945,7 +945,7 @@ * * @return string The HTTP method that should be used */ - methods._detectMethod = (method, params) => { + _detectMethod(method, params) { if (typeof params.httpmethod !== "undefined") { let httpmethod = params.httpmethod; delete params.httpmethod; @@ -961,7 +961,7 @@ break; } - const apimethods = methods.getApiMethods(); + const apimethods = this.getApiMethods(); for (let httpmethod in apimethods) { if (apimethods.hasOwnProperty(httpmethod) && apimethods[httpmethod].indexOf(method) > -1 @@ -970,7 +970,7 @@ } } throw `Can't find HTTP method to use for "${method}".`; - }; + } /** * Detects if API call should use multipart/form-data @@ -979,7 +979,7 @@ * * @return bool Whether the method should be sent as multipart */ - methods._detectMultipart = method => { + _detectMultipart(method) { const multiparts = [ // Tweets "statuses/update_with_media", @@ -991,7 +991,7 @@ "account/update_profile_banner" ]; return multiparts.indexOf(method) > -1; - }; + } /** * Signature helper @@ -1002,8 +1002,8 @@ * * @return string signature */ - methods._getSignature = (httpmethod, method, keys, base_params) => { - const {_url, _sha1} = methods; + _getSignature(httpmethod, method, keys, base_params) { + const {_url, _sha1} = this; // convert params to string let base_string = "", key, value; for (let i = 0; i < keys.length; i++) { @@ -1013,12 +1013,14 @@ } base_string = base_string.substring(0, base_string.length - 1); return _sha1(`${httpmethod}&${_url(method)}&${_url(base_string)}`); - }; + } /** * Generates the UNIX timestamp */ - methods._time = () => Math.round(new Date().getTime() / 1000); + _time() { + Math.round(new Date().getTime() / 1000); + } /** * Generates an OAuth signature @@ -1029,16 +1031,16 @@ * * @return string Authorization HTTP header */ - methods._sign = (httpmethod, method, params = {}) => { - const {_url, _ksort, _clone, _getSignature} = methods; - if (props._oauth_consumer_key === null) { + _sign(httpmethod, method, params = {}) { + const {_url, _ksort, _clone, _getSignature} = this; + if (this._oauth_consumer_key === null) { console.warn("To generate a signature, the consumer key must be set."); } const sign_params = { - consumer_key: props._oauth_consumer_key, + consumer_key: this._oauth_consumer_key, version: "1.0", - timestamp: methods._time(), - nonce: methods._nonce(), + timestamp: this._time(), + nonce: this._nonce(), signature_method: "HMAC-SHA1" }; let sign_base_params = {}; @@ -1049,8 +1051,8 @@ let value = sign_params[key]; sign_base_params[`oauth_${key}`] = _url(value); } - if (props._oauth_token !== null) { - sign_base_params.oauth_token = _url(props._oauth_token); + if (this._oauth_token !== null) { + sign_base_params.oauth_token = _url(this._oauth_token); } const oauth_params = _clone(sign_base_params); for (key in params) { @@ -1072,7 +1074,7 @@ authorization += `${key}="${_url(params[key])}", `; } return authorization.substring(0, authorization.length - 2); - }; + } /** * Build multipart request from upload params @@ -1082,9 +1084,9 @@ * * @return null|string The built multipart request body */ - methods._buildMultipart = (method, params) => { + _buildMultipart(method, params) { // well, files will only work in multipart methods - if (!methods._detectMultipart(method)) { + if (!this._detectMultipart(method)) { return; } @@ -1113,7 +1115,7 @@ // check for filenames possible_files = possible_files[method].split(" "); - const multipart_border = `--------------------${methods._nonce()}`; + const multipart_border = `--------------------${this._nonce()}`; let multipart_request = ""; for (let key in params) { if (!params.hasOwnProperty(key)) { @@ -1128,7 +1130,7 @@ } multipart_request += `--${multipart_border}--`; return multipart_request; - }; + } /** * Detects if API call should use media endpoint @@ -1137,12 +1139,12 @@ * * @return bool Whether the method is defined in media API */ - methods._detectMedia = method => { + _detectMedia(method) { const medias = [ "media/upload" ]; return medias.indexOf(method) > -1; - }; + } /** * Detects if API call should use JSON body @@ -1151,12 +1153,12 @@ * * @return bool Whether the method is defined as accepting JSON body */ - methods._detectJsonBody = method => { + _detectJsonBody(method) { const json_bodies = [ "collections/entries/curate" ]; return json_bodies.indexOf(method) > -1; - }; + } /** * Builds the complete API endpoint url @@ -1165,17 +1167,17 @@ * * @return string The URL to send the request to */ - methods._getEndpoint = method => { + _getEndpoint(method) { let url; if (method.substring(0, 5) === "oauth") { - url = props._endpoint_oauth + method; - } else if (methods._detectMedia(method)) { - url = props._endpoint_media + method + ".json"; + url = this._endpoint_oauth + method; + } else if (this._detectMedia(method)) { + url = this._endpoint_media + method + ".json"; } else { - url = props._endpoint + method + ".json"; + url = this._endpoint + method + ".json"; } return url; - }; + } /** * Parses the API reply to encode it in the set return_format @@ -1184,7 +1186,7 @@ * * @return array|object The parsed reply */ - methods._parseApiReply = reply => { + _parseApiReply(reply) { if (typeof reply !== "string" || reply === "") { return {}; } @@ -1208,7 +1210,7 @@ } } return parsed; - }; + } /** @@ -1220,8 +1222,8 @@ * * @return object Promise */ - methods.oauth_authenticate = (params = {}, callback = undefined, type = "authenticate") => { - const dfd = methods._getDfd(); + oauth_authenticate(params = {}, callback = undefined, type = "authenticate") { + const dfd = this._getDfd(); if (typeof params.force_login === "undefined") { params.force_login = null; } @@ -1231,16 +1233,16 @@ if (["authenticate", "authorize"].indexOf(type) === -1) { type = "authenticate"; } - if (props._oauth_token === null) { + if (this._oauth_token === null) { const error = `To get the ${type} URL, the OAuth token must be set.`; console.warn(error); if (dfd) { dfd.reject({ error }); - return methods._getPromise(dfd); + return this._getPromise(dfd); } throw error; } - let url = `${props._endpoint_oauth}oauth/${type}?oauth_token=${methods._url(props._oauth_token)}`; + let url = `${this._endpoint_oauth}oauth/${type}?oauth_token=${this._url(this._oauth_token)}`; if (params.force_login === true) { url += "&force_login=1"; } @@ -1252,36 +1254,35 @@ } if (dfd) { dfd.resolve({ reply: url }); - return methods._getPromise(dfd); + return this._getPromise(dfd); } // no promises return true; - }; + } /** * Gets the OAuth authorize URL for the current request token * * @return string The OAuth authorize URL */ - methods.oauth_authorize = (params, callback) => { - return methods.oauth_authenticate(params, callback, "authorize"); - }; + oauth_authorize(params, callback) { + return this.oauth_authenticate(params, callback, "authorize"); + } /** * Gets the OAuth bearer token * * @return object Promise */ + oauth2_token(callback) { + const dfd = this._getDfd(); - methods.oauth2_token = callback => { - const dfd = methods._getDfd(); - - if (props._oauth_consumer_key === null) { + if (this._oauth_consumer_key === null) { const error = "To obtain a bearer token, the consumer key must be set."; console.warn(error); if (dfd) { dfd.reject({ error }); - return methods._getPromise(dfd); + return this._getPromise(dfd); } return false; } @@ -1291,24 +1292,24 @@ } const post_fields = "grant_type=client_credentials"; - let url = props._endpoint_oauth + "oauth2/token"; + let url = this._endpoint_oauth + "oauth2/token"; - if (props._use_proxy) { + if (this._use_proxy) { url = url.replace( - props._endpoint_base, - props._endpoint_proxy + this._endpoint_base, + this._endpoint_proxy ); } - const xml = methods._getXmlRequestObject(); + const xml = this._getXmlRequestObject(); if (xml === null) { return; } xml.open("POST", url, true); xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xml.setRequestHeader( - `${props._use_proxy ? "X-" : ""}Authorization`, - "Basic " + methods._base64_encode(`${props._oauth_consumer_key}:${props._oauth_consumer_secret}`) + `${this._use_proxy ? "X-" : ""}Authorization`, + "Basic " + this._base64_encode(`${this._oauth_consumer_key}:${this._oauth_consumer_secret}`) ); xml.onreadystatechange = () => { @@ -1321,10 +1322,10 @@ try { response = xml.responseText; } catch (e) { } - let reply = methods._parseApiReply(response); + let reply = this._parseApiReply(response); reply.httpstatus = httpstatus; if (httpstatus === 200) { - methods.setBearerToken(reply.access_token); + this.setBearerToken(reply.access_token); } if (typeof callback === "function") { callback(reply); @@ -1347,9 +1348,9 @@ xml.send(post_fields); if (dfd) { - return methods._getPromise(dfd); + return this._getPromise(dfd); } - }; + } /** * Calls the API using cURL @@ -1363,43 +1364,42 @@ * * @return mixed The API reply, encoded in the set return_format */ - - methods._callApi = ( + _callApi( httpmethod, method, params = {}, multipart = false, app_only_auth = false, callback = () => {} - ) => { - const dfd = methods._getDfd(); + ) { + const dfd = this._getDfd(); - let url = methods._getEndpoint(method), + let url = this._getEndpoint(method), authorization = null; - const xml = methods._getXmlRequestObject(); + const xml = this._getXmlRequestObject(); if (xml === null) { return; } let post_fields; - const _sign = methods._sign; + const _sign = this._sign; if (httpmethod === "GET") { let url_with_params = url; if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + methods._http_build_query(params); + url_with_params += "?" + this._http_build_query(params); } if (!app_only_auth) { authorization = _sign(httpmethod, url, params); } - if (props._use_proxy) { + if (this._use_proxy) { url_with_params = url_with_params.replace( - props._endpoint_base, - props._endpoint_proxy + this._endpoint_base, + this._endpoint_proxy ).replace( - props._endpoint_base_media, - props._endpoint_proxy + this._endpoint_base_media, + this._endpoint_proxy ); } xml.open(httpmethod, url_with_params, true); @@ -1408,63 +1408,63 @@ if (!app_only_auth) { authorization = _sign(httpmethod, url, {}); } - params = methods._buildMultipart(method, params); - } else if (methods._detectJsonBody(method)) { + params = this._buildMultipart(method, params); + } else if (this._detectJsonBody(method)) { authorization = _sign(httpmethod, url, {}); params = JSON.stringify(params); } else { if (!app_only_auth) { authorization = _sign(httpmethod, url, params); } - params = methods._http_build_query(params); + params = this._http_build_query(params); } post_fields = params; - if (props._use_proxy || multipart) { // force proxy for multipart base64 + if (this._use_proxy || multipart) { // force proxy for multipart base64 url = url.replace( - props._endpoint_base, - props._endpoint_proxy + this._endpoint_base, + this._endpoint_proxy ).replace( - props._endpoint_base_media, - props._endpoint_proxy + this._endpoint_base_media, + this._endpoint_proxy ); } xml.open(httpmethod, url, true); if (multipart) { xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); - } else if (methods._detectJsonBody(method)) { + } else if (this._detectJsonBody(method)) { xml.setRequestHeader("Content-Type", "application/json"); } else { xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } } if (app_only_auth) { - if (props._oauth_consumer_key === null - && props._oauth_bearer_token === null + if (this._oauth_consumer_key === null + && this._oauth_bearer_token === null ) { const error = "To make an app-only auth API request, consumer key or bearer token must be set."; console.warn(error); if (dfd) { dfd.reject({ error }); - return methods._getPromise(dfd); + return this._getPromise(dfd); } } // automatically fetch bearer token, if necessary - if (props._oauth_bearer_token === null) { + if (this._oauth_bearer_token === null) { if (dfd) { - return methods.oauth2_token().then(() => { - return methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + return this.oauth2_token().then(() => { + return this._callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); } - methods.oauth2_token(() => { - methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + this.oauth2_token(() => { + this._callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); return; } - authorization = "Bearer " + props._oauth_bearer_token; + authorization = "Bearer " + this._oauth_bearer_token; } if (authorization !== null) { - xml.setRequestHeader(`${props._use_proxy ? "X-" : ""}Authorization`, authorization); + xml.setRequestHeader(`${this._use_proxy ? "X-" : ""}Authorization`, authorization); } xml.onreadystatechange = () => { if (xml.readyState >= 4) { @@ -1476,7 +1476,7 @@ try { response = xml.responseText; } catch (e) { } - let reply = methods._parseApiReply(response); + let reply = this._parseApiReply(response); reply.httpstatus = httpstatus; let rate = null; if (typeof xml.getResponseHeader !== "undefined" @@ -1509,10 +1509,10 @@ xml.send(httpmethod === "GET" ? null : post_fields); if (dfd) { - return methods._getPromise(dfd); + return this._getPromise(dfd); } return true; - }; + } /** * Main API handler working on any requests you issue @@ -1524,8 +1524,7 @@ * * @return object Promise */ - - methods.__call = (fn, params = {}, callback, app_only_auth = false) => { + __call(fn, params = {}, callback, app_only_auth = false) { if (typeof callback !== "function" && typeof params === "function") { callback = params; params = {}; @@ -1545,22 +1544,22 @@ } // parse parameters - let apiparams = methods._parseApiParams(params); + let apiparams = this._parseApiParams(params); // stringify null and boolean parameters - apiparams = methods._stringifyNullBoolParams(apiparams); + apiparams = this._stringifyNullBoolParams(apiparams); // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) if (fn === "oauth_requestToken") { - methods.setToken(null, null); + this.setToken(null, null); } // map function name to API method - const [method, method_template] = methods._mapFnToApiMethod(fn, apiparams), - httpmethod = methods._detectMethod(method_template, apiparams), - multipart = methods._detectMultipart(method_template); + const [method, method_template] = this._mapFnToApiMethod(fn, apiparams), + httpmethod = this._detectMethod(method_template, apiparams), + multipart = this._detectMultipart(method_template); - return methods._callApi( + return this._callApi( httpmethod, method, apiparams, @@ -1568,36 +1567,7 @@ app_only_auth, callback ); - }; - - // Unit testing code - const __test = { - call: (name, params = []) => methods[name].apply(undefined, params), - get: name => props[name], - mock: methods_to_mock => { - for (let name in methods_to_mock) { - if (methods_to_mock.hasOwnProperty(name)) { - methods[name] = methods_to_mock[name]; - } - } - } - }; - - return { - __call: methods.__call, - __test, - getApiMethods: methods.getApiMethods, - getVersion: methods.getVersion, - logout: methods.logout, - oauth2_token: methods.oauth2_token, - oauth_authenticate: methods.oauth_authenticate, - oauth_authorize: methods.oauth_authorize, - setBearerToken: methods.setBearerToken, - setConsumerKey: methods.setConsumerKey, - setProxy: methods.setProxy, - setToken: methods.setToken, - setUseProxy: methods.setUseProxy - }; + } }; if (typeof module === "object" diff --git a/codebird.js b/codebird.js index 100cbb8..666f5f6 100644 --- a/codebird.js +++ b/codebird.js @@ -2,8 +2,12 @@ var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + /** * A Twitter library in JavaScript * @@ -32,76 +36,79 @@ function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.const * @package codebird * @subpackage codebird-js */ - var Codebird = function Codebird() { - - var props = {}; - /** - * The OAuth consumer key of your registered app - */ - props._oauth_consumer_key = null; - - /** - * The corresponding consumer secret - */ - props._oauth_consumer_secret = null; - - /** - * The app-only bearer token. Used to authorize app-only requests - */ - props._oauth_bearer_token = null; - - /** - * The API endpoint base to use - */ - props._endpoint_base = "https://api.twitter.com/"; - - /** - * The media API endpoint base to use - */ - props._endpoint_base_media = "https://upload.twitter.com/"; - - /** - * The API endpoint to use - */ - props._endpoint = props._endpoint_base + "1.1/"; - - /** - * The media API endpoint to use - */ - props._endpoint_media = props._endpoint_base_media + "1.1/"; - - /** - * The API endpoint base to use - */ - props._endpoint_oauth = props._endpoint_base; - - /** - * API proxy endpoint - */ - props._endpoint_proxy = "https://api.jublo.net/codebird/"; - /** - * Whether to access the API via a proxy that is allowed by CORS - * Assume that CORS is only necessary in browsers - */ - props._use_proxy = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined"; - - /** - * The Request or access token. Used to sign requests - */ - props._oauth_token = null; - - /** - * The corresponding request or access token secret - */ - props._oauth_token_secret = null; - - /** - * The current Codebird version - */ - props._version = "3.0.0-dev"; - - var methods = {}; + var Codebird = (function () { + function Codebird() { + _classCallCheck(this, Codebird); + + /** + * The OAuth consumer key of your registered app + */ + this._oauth_consumer_key = null; + + /** + * The corresponding consumer secret + */ + this._oauth_consumer_secret = null; + + /** + * The app-only bearer token. Used to authorize app-only requests + */ + this._oauth_bearer_token = null; + + /** + * The API endpoint base to use + */ + this._endpoint_base = "https://api.twitter.com/"; + + /** + * The media API endpoint base to use + */ + this._endpoint_base_media = "https://upload.twitter.com/"; + + /** + * The API endpoint to use + */ + this._endpoint = this._endpoint_base + "1.1/"; + + /** + * The media API endpoint to use + */ + this._endpoint_media = this._endpoint_base_media + "1.1/"; + + /** + * The API endpoint base to use + */ + this._endpoint_oauth = this._endpoint_base; + + /** + * API proxy endpoint + */ + this._endpoint_proxy = "https://api.jublo.net/codebird/"; + + /** + * Whether to access the API via a proxy that is allowed by CORS + * Assume that CORS is only necessary in browsers + */ + this._use_proxy = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined"; + + /** + * The Request or access token. Used to sign requests + */ + this._oauth_token = null; + + /** + * The corresponding request or access token secret + */ + this._oauth_token_secret = null; + + /** + * The current Codebird version + */ + this._version = "3.0.0-dev"; + + this.b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + } /** * Sets the OAuth consumer key and secret (App key) @@ -111,163 +118,188 @@ function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.const * * @return void */ - methods.setConsumerKey = function (key, secret) { - props._oauth_consumer_key = key; - props._oauth_consumer_secret = secret; - }; - /** - * Sets the OAuth2 app-only auth bearer token - * - * @param string token OAuth2 bearer token - * - * @return void - */ - methods.setBearerToken = function (token) { - props._oauth_bearer_token = token; - }; + _createClass(Codebird, [{ + key: "setConsumerKey", + value: function setConsumerKey(key, secret) { + this._oauth_consumer_key = key; + this._oauth_consumer_secret = secret; + } - /** - * Gets the current Codebird version - * - * @return string The version number - */ - methods.getVersion = function () { - return props._version; - }; + /** + * Sets the OAuth2 app-only auth bearer token + * + * @param string token OAuth2 bearer token + * + * @return void + */ + + }, { + key: "setBearerToken", + value: function setBearerToken(token) { + this._oauth_bearer_token = token; + } - /** - * Sets the OAuth request or access token and secret (User key) - * - * @param string token OAuth request or access token - * @param string secret OAuth request or access token secret - * - * @return void - */ - methods.setToken = function (token, secret) { - props._oauth_token = token; - props._oauth_token_secret = secret; - }; + /** + * Gets the current Codebird version + * + * @return string The version number + */ - /** - * Forgets the OAuth request or access token and secret (User key) - * - * @return bool - */ - methods.logout = function () { - props._oauth_token = props._oauth_token_secret = null; + }, { + key: "getVersion", + value: function getVersion() { + return this._version; + } - return true; - }; + /** + * Sets the OAuth request or access token and secret (User key) + * + * @param string token OAuth request or access token + * @param string secret OAuth request or access token secret + * + * @return void + */ + + }, { + key: "setToken", + value: function setToken(token, secret) { + this._oauth_token = token; + this._oauth_token_secret = secret; + } - /** - * Enables or disables CORS proxy - * - * @param bool use_proxy Whether to use CORS proxy or not - * - * @return void - */ - methods.setUseProxy = function (use_proxy) { - props._use_proxy = !!use_proxy; - }; + /** + * Forgets the OAuth request or access token and secret (User key) + * + * @return bool + */ - /** - * Sets custom CORS proxy server - * - * @param string proxy Address of proxy server to use - * - * @return void - */ - methods.setProxy = function (proxy) { - // add trailing slash if missing - if (!proxy.match(/\/$/)) { - proxy += "/"; + }, { + key: "logout", + value: function logout() { + this._oauth_token = this._oauth_token_secret = null; + + return true; } - props._endpoint_proxy = proxy; - }; - /** - * Signing helpers - */ + /** + * Enables or disables CORS proxy + * + * @param bool use_proxy Whether to use CORS proxy or not + * + * @return void + */ + + }, { + key: "setUseProxy", + value: function setUseProxy(use_proxy) { + this._use_proxy = !!use_proxy; + } - /** - * URL-encodes the given data - * - * @param mixed data - * - * @return mixed The encoded data - */ - methods._url = function (data) { - if (/boolean|number|string/.test(typeof data === "undefined" ? "undefined" : _typeof(data))) { - return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); - } else { - return ""; + /** + * Sets custom CORS proxy server + * + * @param string proxy Address of proxy server to use + * + * @return void + */ + + }, { + key: "setProxy", + value: function setProxy(proxy) { + // add trailing slash if missing + if (!proxy.match(/\/$/)) { + proxy += "/"; + } + this._endpoint_proxy = proxy; } - }; - var b64_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + /** + * Signing helpers + */ + + /** + * URL-encodes the given data + * + * @param mixed data + * + * @return mixed The encoded data + */ + + }, { + key: "_url", + value: function _url(data) { + if (/boolean|number|string/.test(typeof data === "undefined" ? "undefined" : _typeof(data))) { + return encodeURIComponent(data).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A"); + } else { + return ""; + } + } - /** - * Gets the base64-encoded SHA1 hash for the given data - * - * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined - * in FIPS PUB 180-1 - * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. - * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet - * Distributed under the BSD License - * See http://pajhome.org.uk/crypt/md5 for details. - * - * @param string data The data to calculate the hash from - * - * @return string The hash - */ - methods._sha1 = (function () { - function n(e, b) { - e[b >> 5] |= 128 << 24 - b % 32; - e[(b + 64 >> 9 << 4) + 15] = b; - for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { - for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m = undefined; - - if (f < 16) { - m = e[p + f]; - } else { - m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; - m = m << 1 | m >>> 31; - } + /** + * Gets the base64-encoded SHA1 hash for the given data + * + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Based on version 2.1 Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + * + * @param string data The data to calculate the hash from + * + * @return string The hash + */ + + }, { + key: "_sha1", + value: function _sha1(e) { + function n(e, b) { + e[b >> 5] |= 128 << 24 - b % 32; + e[(b + 64 >> 9 << 4) + 15] = b; + for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { + for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { + var m = undefined; + + if (f < 16) { + m = e[p + f]; + } else { + m = c[f - 3] ^ c[f - 8] ^ c[f - 14] ^ c[f - 16]; + m = m << 1 | m >>> 31; + } - c[f] = m; - m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l(l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 60 > f ? -1894007588 : -899497514)); - g = k; - k = h; - h = d << 30 | d >>> 2; - d = a; - a = m; + c[f] = m; + m = l(l(a << 5 | a >>> 27, 20 > f ? d & h | ~d & k : 40 > f ? d ^ h ^ k : 60 > f ? d & h | d & k | h & k : d ^ h ^ k), l(l(g, c[f]), 20 > f ? 1518500249 : 40 > f ? 1859775393 : 60 > f ? -1894007588 : -899497514)); + g = k; + k = h; + h = d << 30 | d >>> 2; + d = a; + a = m; + } + a = l(a, o); + d = l(d, q); + h = l(h, r); + k = l(k, s); + g = l(g, t); } - a = l(a, o); - d = l(d, q); - h = l(h, r); - k = l(k, s); - g = l(g, t); + return [a, d, h, k, g]; } - return [a, d, h, k, g]; - } - function l(e, b) { - var c = (e & 65535) + (b & 65535); - return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; - } + function l(e, b) { + var c = (e & 65535) + (b & 65535); + return (e >> 16) + (b >> 16) + (c >> 16) << 16 | c & 65535; + } - function q(e) { - for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { - b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + function q(e) { + for (var b = [], c = (1 << g) - 1, a = 0; a < e.length * g; a += g) { + b[a >> 5] |= (e.charCodeAt(a / g) & c) << 24 - a % 32; + } + return b; } - return b; - } - var g = 8; - return function (e) { - var b = props._oauth_consumer_secret + "&" + (null !== props._oauth_token_secret ? props._oauth_token_secret : ""); - if (props._oauth_consumer_secret === null) { + var g = 8; + + var b = this._oauth_consumer_secret + "&" + (null !== this._oauth_token_secret ? this._oauth_token_secret : ""); + if (this._oauth_consumer_secret === null) { console.warn("To generate a hash, the consumer secret must be set."); } var c = q(b); @@ -284,1193 +316,1255 @@ function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.const b = ""; for (g = 0; g < 4 * bb.length; g += 3) { for (d = (bb[g >> 2] >> 8 * (3 - g % 4) & 255) << 16 | (bb[g + 1 >> 2] >> 8 * (3 - (g + 1) % 4) & 255) << 8 | bb[g + 2 >> 2] >> 8 * (3 - (g + 2) % 4) & 255, e = 0; 4 > e; e++) { - b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + b64_alphabet.charAt(d >> 6 * (3 - e) & 63); + b = 8 * g + 6 * e > 32 * bb.length ? b + "=" : b + this.b64_alphabet.charAt(d >> 6 * (3 - e) & 63); } } return b; - }; - })(); + } - /* - * Gets the base64 representation for the given data - * - * http://phpjs.org - * + original by: Tyler Akins (http://rumkin.com) - * + improved by: Bayron Guevara - * + improved by: Thunder.m - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + bugfixed by: Pellentesque Malesuada - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Rafał Kukawski (http://kukawski.pl) - * - * @param string data The data to calculate the base64 representation from - * - * @return string The base64 representation - */ - methods._base64_encode = function (a) { - var d = undefined, - e = undefined, - f = undefined, - b = undefined, - g = 0, - h = 0, - i = b64_alphabet, - c = []; - if (!a) { - return a; + /* + * Gets the base64 representation for the given data + * + * http://phpjs.org + * + original by: Tyler Akins (http://rumkin.com) + * + improved by: Bayron Guevara + * + improved by: Thunder.m + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + bugfixed by: Pellentesque Malesuada + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Rafał Kukawski (http://kukawski.pl) + * + * @param string data The data to calculate the base64 representation from + * + * @return string The base64 representation + */ + + }, { + key: "_base64_encode", + value: function _base64_encode(a) { + var d = undefined, + e = undefined, + f = undefined, + b = undefined, + g = 0, + h = 0, + i = this.b64_alphabet, + c = []; + if (!a) { + return a; + } + do { + d = a.charCodeAt(g++); + e = a.charCodeAt(g++); + f = a.charCodeAt(g++); + b = d << 16 | e << 8 | f; + d = b >> 18 & 63; + e = b >> 12 & 63; + f = b >> 6 & 63; + b &= 63; + c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); + } while (g < a.length); + i = c.join(""); + a = a.length % 3; + return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); } - do { - d = a.charCodeAt(g++); - e = a.charCodeAt(g++); - f = a.charCodeAt(g++); - b = d << 16 | e << 8 | f; - d = b >> 18 & 63; - e = b >> 12 & 63; - f = b >> 6 & 63; - b &= 63; - c[h++] = i.charAt(d) + i.charAt(e) + i.charAt(f) + i.charAt(b); - } while (g < a.length); - i = c.join(""); - a = a.length % 3; - return (a ? i.slice(0, a - 3) : i) + "===".slice(a || 3); - }; - - /* - * Builds a HTTP query string from the given data - * - * http://phpjs.org - * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Legaev Andrey - * + improved by: Michael White (http://getsprink.com) - * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - * + improved by: Brett Zamir (http://brett-zamir.me) - * + revised by: stag019 - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) - * - * @param string data The data to concatenate - * - * @return string The HTTP query - */ - methods._http_build_query = function (e, f, b) { - function g(c, a, d) { - var b = undefined, - e = []; - if (a === true) { - a = "1"; - } else if (a === false) { - a = "0"; - } - if (null !== a) { - if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { - for (b in a) { - if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); + + /* + * Builds a HTTP query string from the given data + * + * http://phpjs.org + * + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Legaev Andrey + * + improved by: Michael White (http://getsprink.com) + * + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * + improved by: Brett Zamir (http://brett-zamir.me) + * + revised by: stag019 + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: MIO_KODUKI (http://mio-koduki.blogspot.com/) + * + * @param string data The data to concatenate + * + * @return string The HTTP query + */ + + }, { + key: "_http_build_query", + value: function _http_build_query(e, f, b) { + function g(c, a, d) { + var b = undefined, + e = []; + if (a === true) { + a = "1"; + } else if (a === false) { + a = "0"; + } + if (null !== a) { + if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { + for (b in a) { + if (a.hasOwnProperty(b) && a[b] !== null) { + e.push(g(c + "[" + b + "]", a[b], d)); + } } + return e.join(d); } - return e.join(d); + if (typeof a !== "function") { + return this._url(c) + "=" + this._url(a); + } + console.warn("There was an error processing for http_build_query()."); + } else { + return ""; + } + } + var d, + c, + h = []; + if (!b) { + b = "&"; + } + for (c in e) { + if (!e.hasOwnProperty(c)) { + continue; } - if (typeof a !== "function") { - return methods._url(c) + "=" + methods._url(a); + d = e[c]; + if (f && !isNaN(c)) { + c = String(f) + c; + } + d = g(c, d, b); + if (d !== "") { + h.push(d); } - console.warn("There was an error processing for http_build_query()."); - } else { - return ""; } + return h.join(b); } - var d, - c, - h = []; - if (!b) { - b = "&"; - } - for (c in e) { - if (!e.hasOwnProperty(c)) { - continue; - } - d = e[c]; - if (f && !isNaN(c)) { - c = String(f) + c; + + /** + * Generates a (hopefully) unique random string + * + * @param int optional length The length of the string to generate + * + * @return string The random string + */ + + }, { + key: "_nonce", + value: function _nonce() { + var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; + + if (length < 1) { + console.warn("Invalid nonce length."); } - d = g(c, d, b); - if (d !== "") { - h.push(d); + var nonce = ""; + for (var i = 0; i < length; i++) { + var character = Math.floor(Math.random() * 61); + nonce += this.b64_alphabet.substring(character, character + 1); } + return nonce; } - return h.join(b); - }; - /** - * Generates a (hopefully) unique random string - * - * @param int optional length The length of the string to generate - * - * @return string The random string - */ - methods._nonce = function () { - var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; - - if (length < 1) { - console.warn("Invalid nonce length."); - } - var nonce = ""; - for (var i = 0; i < length; i++) { - var character = Math.floor(Math.random() * 61); - nonce += b64_alphabet.substring(character, character + 1); - } - return nonce; - }; + /** + * Sort array elements by key + * + * @param array input_arr The array to sort + * + * @return array The sorted keys + */ + + }, { + key: "_ksort", + value: function _ksort(input_arr) { + var keys = [], + sorter = undefined, + k = undefined; + + sorter = function (a, b) { + var a_float = parseFloat(a), + b_float = parseFloat(b), + a_numeric = a_float + "" === a, + b_numeric = b_float + "" === b; + if (a_numeric && b_numeric) { + return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; + } else if (a_numeric && !b_numeric) { + return 1; + } else if (!a_numeric && b_numeric) { + return -1; + } + return a > b ? 1 : a < b ? -1 : 0; + }; - /** - * Sort array elements by key - * - * @param array input_arr The array to sort - * - * @return array The sorted keys - */ - methods._ksort = function (input_arr) { - var keys = [], - sorter = undefined, - k = undefined; - - sorter = function (a, b) { - var a_float = parseFloat(a), - b_float = parseFloat(b), - a_numeric = a_float + "" === a, - b_numeric = b_float + "" === b; - if (a_numeric && b_numeric) { - return a_float > b_float ? 1 : a_float < b_float ? -1 : 0; - } else if (a_numeric && !b_numeric) { - return 1; - } else if (!a_numeric && b_numeric) { - return -1; - } - return a > b ? 1 : a < b ? -1 : 0; - }; - - // Make a list of key names - for (k in input_arr) { - if (input_arr.hasOwnProperty(k)) { - keys.push(k); + // Make a list of key names + for (k in input_arr) { + if (input_arr.hasOwnProperty(k)) { + keys.push(k); + } } + keys.sort(sorter); + return keys; } - keys.sort(sorter); - return keys; - }; - /** - * Clone objects - * - * @param object obj The object to clone - * - * @return object clone The cloned object - */ - methods._clone = function (obj) { - var clone = {}; - for (var i in obj) { - if (_typeof(obj[i]) === "object") { - clone[i] = methods._clone(obj[i]); - } else { - clone[i] = obj[i]; + /** + * Clone objects + * + * @param object obj The object to clone + * + * @return object clone The cloned object + */ + + }, { + key: "_clone", + value: function _clone(obj) { + var clone = {}; + for (var i in obj) { + if (_typeof(obj[i]) === "object") { + clone[i] = this._clone(obj[i]); + } else { + clone[i] = obj[i]; + } } + return clone; } - return clone; - }; - /** - * Gets the XML HTTP Request object, trying to load it in various ways - * - * @return object The XMLHttpRequest object instance - */ - methods._getXmlRequestObject = function () { - var xml = null; - // first, try the W3-standard object - if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window && typeof window.XMLHttpRequest !== "undefined") { - xml = new window.XMLHttpRequest(); - // then, try Titanium framework object - } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined") { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - console.error("ActiveXObject object not defined."); - } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" && require) { - var XMLHttpRequest; - // look for xmlhttprequest module + /** + * Gets the XML HTTP Request object, trying to load it in various ways + * + * @return object The XMLHttpRequest object instance + */ + + }, { + key: "_getXmlRequestObject", + value: function _getXmlRequestObject() { + var xml = null; + // first, try the W3-standard object + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === "object" && window && typeof window.XMLHttpRequest !== "undefined") { + xml = new window.XMLHttpRequest(); + // then, try Titanium framework object + } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined") { try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + console.error("ActiveXObject object not defined."); + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function" && require) { + var XMLHttpRequest; + // look for xmlhttprequest module try { - XMLHttpRequest = require("xhr2"); + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; xml = new XMLHttpRequest(); - } catch (e2) { - console.error("xhr2 object not defined, cancelling."); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + console.error("xhr2 object not defined, cancelling."); + } } } - } - return xml; - }; - - /** - * Parse URL-style parameters into object - * - * version: 1109.2015 - * discuss at: http://phpjs.org/functions/parse_str - * + original by: Cagri Ekin - * + improved by: Michael White (http://getsprink.com) - * + tweaked by: Jack - * + bugfixed by: Onno Marsman - * + reimplemented by: stag019 - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * + bugfixed by: stag019 - * - depends on: urldecode - * + input by: Dreamer - * + bugfixed by: Brett Zamir (http://brett-zamir.me) - * % note 1: When no argument is specified, will put variables in global scope. - * - * @param string str String to parse - * @param array array to load data into - * - * @return object - */ - methods._parse_str = function (str, array) { - var glue1 = "=", - glue2 = "&", - array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), - i, - j, - chr, - tmp, - key, - value, - bracket, - keys, - evalStr, - fixStr = function fixStr(str) { - return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); - }; - if (!array) { - array = undefined.window; + return xml; } - for (i = 0; i < array2.length; i++) { - tmp = array2[i].split(glue1); - if (tmp.length < 2) { - tmp = [tmp, ""]; - } - key = fixStr(tmp[0]); - value = fixStr(tmp[1]); - while (key.charAt(0) === " ") { - key = key.substr(1); - } - if (key.indexOf("\0") > -1) { - key = key.substr(0, key.indexOf("\0")); - } - if (key && key.charAt(0) !== "[") { - keys = []; - bracket = 0; - for (j = 0; j < key.length; j++) { - if (key.charAt(j) === "[" && !bracket) { - bracket = j + 1; - } else if (key.charAt(j) === "]") { - if (bracket) { - if (!keys.length) { - keys.push(key.substr(0, bracket - 1)); - } - keys.push(key.substr(bracket, j - bracket)); - bracket = 0; - if (key.charAt(j + 1) !== "[") { - break; - } - } - } + /** + * Parse URL-style parameters into object + * + * version: 1109.2015 + * discuss at: http://phpjs.org/functions/parse_str + * + original by: Cagri Ekin + * + improved by: Michael White (http://getsprink.com) + * + tweaked by: Jack + * + bugfixed by: Onno Marsman + * + reimplemented by: stag019 + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * + bugfixed by: stag019 + * - depends on: urldecode + * + input by: Dreamer + * + bugfixed by: Brett Zamir (http://brett-zamir.me) + * % note 1: When no argument is specified, will put variables in global scope. + * + * @param string str String to parse + * @param array array to load data into + * + * @return object + */ + + }, { + key: "_parse_str", + value: function _parse_str(str, array) { + var glue1 = "=", + glue2 = "&", + array2 = String(str).replace(/^&?([\s\S]*?)&?$/, "$1").split(glue2), + i, + j, + chr, + tmp, + key, + value, + bracket, + keys, + evalStr, + fixStr = function fixStr(str) { + return decodeURIComponent(str).replace(/([\\"'])/g, "\\$1").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); + }; + if (!array) { + array = this.window; + } + + for (i = 0; i < array2.length; i++) { + tmp = array2[i].split(glue1); + if (tmp.length < 2) { + tmp = [tmp, ""]; } - if (!keys.length) { - keys = [key]; + key = fixStr(tmp[0]); + value = fixStr(tmp[1]); + while (key.charAt(0) === " ") { + key = key.substr(1); } - for (j = 0; j < keys[0].length; j++) { - chr = keys[0].charAt(j); - if (chr === " " || chr === "." || chr === "[") { - keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + if (key.indexOf("\0") > -1) { + key = key.substr(0, key.indexOf("\0")); + } + if (key && key.charAt(0) !== "[") { + keys = []; + bracket = 0; + for (j = 0; j < key.length; j++) { + if (key.charAt(j) === "[" && !bracket) { + bracket = j + 1; + } else if (key.charAt(j) === "]") { + if (bracket) { + if (!keys.length) { + keys.push(key.substr(0, bracket - 1)); + } + keys.push(key.substr(bracket, j - bracket)); + bracket = 0; + if (key.charAt(j + 1) !== "[") { + break; + } + } + } } - if (chr === "[") { - break; + if (!keys.length) { + keys = [key]; } - } - evalStr = "array"; - for (j = 0; j < keys.length; j++) { - key = keys[j]; - if (key !== "" && key !== " " || j === 0) { - key = "'" + key + "'"; - } else { - key = eval(evalStr + ".push([]);") - 1; + for (j = 0; j < keys[0].length; j++) { + chr = keys[0].charAt(j); + if (chr === " " || chr === "." || chr === "[") { + keys[0] = keys[0].substr(0, j) + "_" + keys[0].substr(j + 1); + } + if (chr === "[") { + break; + } } - evalStr += "[" + key + "']"; - if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { - eval(evalStr + " = [];"); + evalStr = "array"; + for (j = 0; j < keys.length; j++) { + key = keys[j]; + if (key !== "" && key !== " " || j === 0) { + key = "'" + key + "'"; + } else { + key = eval(evalStr + ".push([]);") - 1; + } + evalStr += "[" + key + "']"; + if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { + eval(evalStr + " = [];"); + } } + evalStr += " = '" + value + "';\n"; + eval(evalStr); } - evalStr += " = '" + value + "';\n"; - eval(evalStr); } } - }; - /** - * Get allowed API methods, sorted by GET or POST - * Watch out for multiple-method "account/settings"! - * - * @return array $apimethods - */ - methods.getApiMethods = function () { - var httpmethods = { - GET: ["account/settings", "account/verify_credentials", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", "favorites/list", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "site", "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "user", "users/contributees", "users/contributors", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], - POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload - "users/lookup", "users/report_spam"] - }; - return httpmethods; - }; + /** + * Get allowed API methods, sorted by GET or POST + * Watch out for multiple-method "account/settings"! + * + * @return array $apimethods + */ + + }, { + key: "getApiMethods", + value: function getApiMethods() { + var httpmethods = { + GET: ["account/settings", "account/verify_credentials", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", "favorites/list", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "site", "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "user", "users/contributees", "users/contributors", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], + POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload + "users/lookup", "users/report_spam"] + }; + return httpmethods; + } - /** - * Promise helpers - */ + /** + * Promise helpers + */ - /** - * Get a deferred object - */ - methods._getDfd = function () { - if (typeof window !== "undefined") { - if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { - return window.jQuery.Deferred(); - } - if (typeof window.Q !== "undefined" && window.Q.defer) { - return window.Q.defer(); - } - if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { - return window.RSVP.defer(); - } - if (typeof window.when !== "undefined" && window.when.defer) { - return window.when.defer(); - } - } - if (typeof require !== "undefined") { - var promise_class = false; - try { - promise_class = require("jquery"); - } catch (e) {} - if (promise_class) { - return promise_class.Deferred(); - } - try { - promise_class = require("q"); - } catch (e) {} - if (!promise_class) { - try { - promise_class = require("rsvp"); - } catch (e) {} + /** + * Get a deferred object + */ + + }, { + key: "_getDfd", + value: function _getDfd() { + if (typeof window !== "undefined") { + if (typeof window.jQuery !== "undefined" && window.jQuery.Deferred) { + return window.jQuery.Deferred(); + } + if (typeof window.Q !== "undefined" && window.Q.defer) { + return window.Q.defer(); + } + if (typeof window.RSVP !== "undefined" && window.RSVP.defer) { + return window.RSVP.defer(); + } + if (typeof window.when !== "undefined" && window.when.defer) { + return window.when.defer(); + } } - if (!promise_class) { + if (typeof require !== "undefined") { + var promise_class = false; try { - promise_class = require("when"); + promise_class = require("jquery"); } catch (e) {} - } - if (promise_class) { + if (promise_class) { + return promise_class.Deferred(); + } try { - return promise_class.defer(); + promise_class = require("q"); } catch (e) {} + if (!promise_class) { + try { + promise_class = require("rsvp"); + } catch (e) {} + } + if (!promise_class) { + try { + promise_class = require("when"); + } catch (e) {} + } + if (promise_class) { + try { + return promise_class.defer(); + } catch (e) {} + } } + return false; } - return false; - }; - /** - * Get a promise from the dfd object - */ - methods._getPromise = function (dfd) { - if (typeof dfd.promise === "function") { - return dfd.promise(); + /** + * Get a promise from the dfd object + */ + + }, { + key: "_getPromise", + value: function _getPromise(dfd) { + if (typeof dfd.promise === "function") { + return dfd.promise(); + } + return dfd.promise; // object } - return dfd.promise; // object - }; - /** - * __call() helpers - */ + /** + * __call() helpers + */ + + /** + * Parse given params, detect query-style params + * + * @param array|string params Parameters to parse + * + * @return array apiparams + */ + + }, { + key: "_parseApiParams", + value: function _parseApiParams(params) { + var apiparams = {}; + if ((typeof params === "undefined" ? "undefined" : _typeof(params)) === "object") { + apiparams = params; + } else { + this._parse_str(params, apiparams); //TODO + } - /** - * Parse given params, detect query-style params - * - * @param array|string params Parameters to parse - * - * @return array apiparams - */ - methods._parseApiParams = function (params) { - var apiparams = {}; - if ((typeof params === "undefined" ? "undefined" : _typeof(params)) === "object") { - apiparams = params; - } else { - methods._parse_str(params, apiparams); //TODO + return apiparams; } - return apiparams; - }; - - /** - * Replace null and boolean parameters with their string representations - * - * @param array apiparams Parameter array to replace in - * - * @return array apiparams - */ - methods._stringifyNullBoolParams = function (apiparams) { - for (var key in apiparams) { - if (!apiparams.hasOwnProperty(key)) { - continue; - } - var value = apiparams[key]; - if (value === null) { - apiparams[key] = "null"; - } else if (value === true || value === false) { - apiparams[key] = value ? "true" : "false"; + /** + * Replace null and boolean parameters with their string representations + * + * @param array apiparams Parameter array to replace in + * + * @return array apiparams + */ + + }, { + key: "_stringifyNullBoolParams", + value: function _stringifyNullBoolParams(apiparams) { + for (var key in apiparams) { + if (!apiparams.hasOwnProperty(key)) { + continue; + } + var value = apiparams[key]; + if (value === null) { + apiparams[key] = "null"; + } else if (value === true || value === false) { + apiparams[key] = value ? "true" : "false"; + } } + + return apiparams; } - return apiparams; - }; + /** + * API method mapping: Replaces _ with / character + * + * @param string fn Function called + * + * @return string API method to call + */ + + }, { + key: "_mapFnInsertSlashes", + value: function _mapFnInsertSlashes(fn) { + return fn.split("_").join("/"); + } - /** - * API method mapping: Replaces _ with / character - * - * @param string fn Function called - * - * @return string API method to call - */ - methods._mapFnInsertSlashes = function (fn) { - return fn.split("_").join("/"); - }; + /** + * API method mapping: Restore _ character in named parameters + * + * @param string method API method to call + * + * @return string API method with restored underscores + */ + + }, { + key: "_mapFnRestoreParamUnderscores", + value: function _mapFnRestoreParamUnderscores(method) { + var url_parameters_with_underscore = ["screen_name", "place_id"]; + var i = undefined, + param = undefined, + replacement_was = undefined; + for (i = 0; i < url_parameters_with_underscore.length; i++) { + param = url_parameters_with_underscore[i].toUpperCase(); + replacement_was = param.split("_").join("/"); + method = method.split(replacement_was).join(param); + } - /** - * API method mapping: Restore _ character in named parameters - * - * @param string method API method to call - * - * @return string API method with restored underscores - */ - methods._mapFnRestoreParamUnderscores = function (method) { - var url_parameters_with_underscore = ["screen_name", "place_id"]; - var i = undefined, - param = undefined, - replacement_was = undefined; - for (i = 0; i < url_parameters_with_underscore.length; i++) { - param = url_parameters_with_underscore[i].toUpperCase(); - replacement_was = param.split("_").join("/"); - method = method.split(replacement_was).join(param); + return method; } - return method; - }; - - /** - * Maps called PHP magic method name to Twitter API method - * - * @param string $fn Function called - * @param array $apiparams byref API parameters - * - * @return string[] (string method, string method_template) - */ - methods._mapFnToApiMethod = function (fn, apiparams) { - var method = "", - param = undefined, - i = undefined, - j = undefined; - - // replace _ by / - method = methods._mapFnInsertSlashes(fn); - - // undo replacement for URL parameters - method = methods._mapFnRestoreParamUnderscores(method); - - // replace AA by URL parameters - var method_template = method; - var match = method.match(/[A-Z_]{2,}/); - if (match) { - for (i = 0; i < match.length; i++) { - param = match[i]; - var param_l = param.toLowerCase(); - method_template = method_template.split(param).join(":" + param_l); - if (typeof apiparams[param_l] === "undefined") { - for (j = 0; j < 26; j++) { - method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + /** + * Maps called PHP magic method name to Twitter API method + * + * @param string $fn Function called + * @param array $apiparams byref API parameters + * + * @return string[] (string method, string method_template) + */ + + }, { + key: "_mapFnToApiMethod", + value: function _mapFnToApiMethod(fn, apiparams) { + var method = "", + param = undefined, + i = undefined, + j = undefined; + + // replace _ by / + method = this._mapFnInsertSlashes(fn); + + // undo replacement for URL parameters + method = this._mapFnRestoreParamUnderscores(method); + + // replace AA by URL parameters + var method_template = method; + var match = method.match(/[A-Z_]{2,}/); + if (match) { + for (i = 0; i < match.length; i++) { + param = match[i]; + var param_l = param.toLowerCase(); + method_template = method_template.split(param).join(":" + param_l); + if (typeof apiparams[param_l] === "undefined") { + for (j = 0; j < 26; j++) { + method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); + } + console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); } - console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); + method = method.split(param).join(apiparams[param_l]); + delete apiparams[param_l]; } - method = method.split(param).join(apiparams[param_l]); - delete apiparams[param_l]; } - } - - // replace A-Z by _a-z - for (i = 0; i < 26; i++) { - method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); - } - - return [method, method_template]; - }; - /** - * Detects HTTP method to use for API call - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return string The HTTP method that should be used - */ - methods._detectMethod = function (method, params) { - if (typeof params.httpmethod !== "undefined") { - var httpmethod = params.httpmethod; - delete params.httpmethod; - return httpmethod; - } + // replace A-Z by _a-z + for (i = 0; i < 26; i++) { + method = method.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + method_template = method_template.split(String.fromCharCode(65 + i)).join("_" + String.fromCharCode(97 + i)); + } - // multi-HTTP method endpoints - switch (method) { - case "account/settings": - case "account/login_verification_enrollment": - case "account/login_verification_request": - method = Object.keys(params).length ? method + "__post" : method; - break; + return [method, method_template]; } - var apimethods = methods.getApiMethods(); - for (var httpmethod in apimethods) { - if (apimethods.hasOwnProperty(httpmethod) && apimethods[httpmethod].indexOf(method) > -1) { + /** + * Detects HTTP method to use for API call + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return string The HTTP method that should be used + */ + + }, { + key: "_detectMethod", + value: function _detectMethod(method, params) { + if (typeof params.httpmethod !== "undefined") { + var httpmethod = params.httpmethod; + delete params.httpmethod; return httpmethod; } - } - throw "Can't find HTTP method to use for \"" + method + "\"."; - }; - /** - * Detects if API call should use multipart/form-data - * - * @param string method The API method to call - * - * @return bool Whether the method should be sent as multipart - */ - methods._detectMultipart = function (method) { - var multiparts = [ - // Tweets - "statuses/update_with_media", "media/upload", - - // Users - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; - return multiparts.indexOf(method) > -1; - }; + // multi-HTTP method endpoints + switch (method) { + case "account/settings": + case "account/login_verification_enrollment": + case "account/login_verification_request": + method = Object.keys(params).length ? method + "__post" : method; + break; + } - /** - * Signature helper - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array base_params The signature base parameters - * - * @return string signature - */ - methods._getSignature = function (httpmethod, method, keys, base_params) { - var _url = methods._url; - var _sha1 = methods._sha1; - // convert params to string - - var base_string = "", - key = undefined, - value = undefined; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - value = base_params[key]; - base_string += key + "=" + _url(value) + "&"; + var apimethods = this.getApiMethods(); + for (var httpmethod in apimethods) { + if (apimethods.hasOwnProperty(httpmethod) && apimethods[httpmethod].indexOf(method) > -1) { + return httpmethod; + } + } + throw "Can't find HTTP method to use for \"" + method + "\"."; } - base_string = base_string.substring(0, base_string.length - 1); - return _sha1(httpmethod + "&" + _url(method) + "&" + _url(base_string)); - }; - /** - * Generates the UNIX timestamp - */ - methods._time = function () { - return Math.round(new Date().getTime() / 1000); - }; + /** + * Detects if API call should use multipart/form-data + * + * @param string method The API method to call + * + * @return bool Whether the method should be sent as multipart + */ + + }, { + key: "_detectMultipart", + value: function _detectMultipart(method) { + var multiparts = [ + // Tweets + "statuses/update_with_media", "media/upload", - /** - * Generates an OAuth signature - * - * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' - * @param string method The API method to call - * @param array optional params The API call parameters, associative - * - * @return string Authorization HTTP header - */ - methods._sign = function (httpmethod, method) { - var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - var _url = methods._url; - var _ksort = methods._ksort; - var _clone = methods._clone; - var _getSignature = methods._getSignature; - - if (props._oauth_consumer_key === null) { - console.warn("To generate a signature, the consumer key must be set."); - } - var sign_params = { - consumer_key: props._oauth_consumer_key, - version: "1.0", - timestamp: methods._time(), - nonce: methods._nonce(), - signature_method: "HMAC-SHA1" - }; - var sign_base_params = {}; - for (var key in sign_params) { - if (!sign_params.hasOwnProperty(key)) { - continue; - } - var value = sign_params[key]; - sign_base_params["oauth_" + key] = _url(value); - } - if (props._oauth_token !== null) { - sign_base_params.oauth_token = _url(props._oauth_token); + // Users + "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; + return multiparts.indexOf(method) > -1; } - var oauth_params = _clone(sign_base_params); - for (key in params) { - if (!params.hasOwnProperty(key)) { - continue; + + /** + * Signature helper + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array base_params The signature base parameters + * + * @return string signature + */ + + }, { + key: "_getSignature", + value: function _getSignature(httpmethod, method, keys, base_params) { + var _url = this._url; + var _sha1 = this._sha1; + // convert params to string + + var base_string = "", + key = undefined, + value = undefined; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + value = base_params[key]; + base_string += key + "=" + _url(value) + "&"; } - sign_base_params[key] = params[key]; + base_string = base_string.substring(0, base_string.length - 1); + return _sha1(httpmethod + "&" + _url(method) + "&" + _url(base_string)); } - var keys = _ksort(sign_base_params); - var signature = _getSignature(httpmethod, method, keys, sign_base_params); + /** + * Generates the UNIX timestamp + */ - params = oauth_params; - params.oauth_signature = signature; - keys = _ksort(params); - var authorization = "OAuth "; - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - authorization += key + "=\"" + _url(params[key]) + "\", "; + }, { + key: "_time", + value: function _time() { + Math.round(new Date().getTime() / 1000); } - return authorization.substring(0, authorization.length - 2); - }; - /** - * Build multipart request from upload params - * - * @param string method The API method to call - * @param array params The parameters to send along - * - * @return null|string The built multipart request body - */ - methods._buildMultipart = function (method, params) { - // well, files will only work in multipart methods - if (!methods._detectMultipart(method)) { - return; + /** + * Generates an OAuth signature + * + * @param string httpmethod Usually either 'GET' or 'POST' or 'DELETE' + * @param string method The API method to call + * @param array optional params The API call parameters, associative + * + * @return string Authorization HTTP header + */ + + }, { + key: "_sign", + value: function _sign(httpmethod, method) { + var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var _url = this._url; + var _ksort = this._ksort; + var _clone = this._clone; + var _getSignature = this._getSignature; + + if (this._oauth_consumer_key === null) { + console.warn("To generate a signature, the consumer key must be set."); + } + var sign_params = { + consumer_key: this._oauth_consumer_key, + version: "1.0", + timestamp: this._time(), + nonce: this._nonce(), + signature_method: "HMAC-SHA1" + }; + var sign_base_params = {}; + for (var key in sign_params) { + if (!sign_params.hasOwnProperty(key)) { + continue; + } + var value = sign_params[key]; + sign_base_params["oauth_" + key] = _url(value); + } + if (this._oauth_token !== null) { + sign_base_params.oauth_token = _url(this._oauth_token); + } + var oauth_params = _clone(sign_base_params); + for (key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + sign_base_params[key] = params[key]; + } + var keys = _ksort(sign_base_params); + + var signature = _getSignature(httpmethod, method, keys, sign_base_params); + + params = oauth_params; + params.oauth_signature = signature; + keys = _ksort(params); + var authorization = "OAuth "; + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + authorization += key + "=\"" + _url(params[key]) + "\", "; + } + return authorization.substring(0, authorization.length - 2); } - // only check specific parameters - var possible_methods = [ - // Tweets - "statuses/update_with_media", - // Accounts - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; - var possible_files = { + /** + * Build multipart request from upload params + * + * @param string method The API method to call + * @param array params The parameters to send along + * + * @return null|string The built multipart request body + */ + + }, { + key: "_buildMultipart", + value: function _buildMultipart(method, params) { + // well, files will only work in multipart methods + if (!this._detectMultipart(method)) { + return; + } + + // only check specific parameters + var possible_methods = [ // Tweets - "statuses/update_with_media": "media[]", + "statuses/update_with_media", // Accounts - "account/update_profile_background_image": "image", - "account/update_profile_image": "image", - "account/update_profile_banner": "banner" - }; - // method might have files? - if (possible_methods.indexOf(method) === -1) { - return; - } + "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; + var possible_files = { + // Tweets + "statuses/update_with_media": "media[]", + // Accounts + "account/update_profile_background_image": "image", + "account/update_profile_image": "image", + "account/update_profile_banner": "banner" + }; + // method might have files? + if (possible_methods.indexOf(method) === -1) { + return; + } - // check for filenames - possible_files = possible_files[method].split(" "); + // check for filenames + possible_files = possible_files[method].split(" "); - var multipart_border = "--------------------" + methods._nonce(); - var multipart_request = ""; - for (var key in params) { - if (!params.hasOwnProperty(key)) { - continue; - } - multipart_request += "--" + multipart_border + "\r\nContent-Disposition: form-data; name=\"" + key + "\""; - if (possible_files.indexOf(key) === -1) { - multipart_request += "\r\nContent-Transfer-Encoding: base64"; + var multipart_border = "--------------------" + this._nonce(); + var multipart_request = ""; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + multipart_request += "--" + multipart_border + "\r\nContent-Disposition: form-data; name=\"" + key + "\""; + if (possible_files.indexOf(key) === -1) { + multipart_request += "\r\nContent-Transfer-Encoding: base64"; + } + multipart_request += "\r\n\r\n" + params[key] + "\r\n"; } - multipart_request += "\r\n\r\n" + params[key] + "\r\n"; + multipart_request += "--" + multipart_border + "--"; + return multipart_request; } - multipart_request += "--" + multipart_border + "--"; - return multipart_request; - }; - - /** - * Detects if API call should use media endpoint - * - * @param string method The API method to call - * - * @return bool Whether the method is defined in media API - */ - methods._detectMedia = function (method) { - var medias = ["media/upload"]; - return medias.indexOf(method) > -1; - }; - - /** - * Detects if API call should use JSON body - * - * @param string method The API method to call - * - * @return bool Whether the method is defined as accepting JSON body - */ - methods._detectJsonBody = function (method) { - var json_bodies = ["collections/entries/curate"]; - return json_bodies.indexOf(method) > -1; - }; - /** - * Builds the complete API endpoint url - * - * @param string method The API method to call - * - * @return string The URL to send the request to - */ - methods._getEndpoint = function (method) { - var url = undefined; - if (method.substring(0, 5) === "oauth") { - url = props._endpoint_oauth + method; - } else if (methods._detectMedia(method)) { - url = props._endpoint_media + method + ".json"; - } else { - url = props._endpoint + method + ".json"; + /** + * Detects if API call should use media endpoint + * + * @param string method The API method to call + * + * @return bool Whether the method is defined in media API + */ + + }, { + key: "_detectMedia", + value: function _detectMedia(method) { + var medias = ["media/upload"]; + return medias.indexOf(method) > -1; } - return url; - }; - /** - * Parses the API reply to encode it in the set return_format - * - * @param string reply The actual reply, JSON-encoded or URL-encoded - * - * @return array|object The parsed reply - */ - methods._parseApiReply = function (reply) { - if (typeof reply !== "string" || reply === "") { - return {}; + /** + * Detects if API call should use JSON body + * + * @param string method The API method to call + * + * @return bool Whether the method is defined as accepting JSON body + */ + + }, { + key: "_detectJsonBody", + value: function _detectJsonBody(method) { + var json_bodies = ["collections/entries/curate"]; + return json_bodies.indexOf(method) > -1; } - if (reply === "[]") { - return []; + + /** + * Builds the complete API endpoint url + * + * @param string method The API method to call + * + * @return string The URL to send the request to + */ + + }, { + key: "_getEndpoint", + value: function _getEndpoint(method) { + var url = undefined; + if (method.substring(0, 5) === "oauth") { + url = this._endpoint_oauth + method; + } else if (this._detectMedia(method)) { + url = this._endpoint_media + method + ".json"; + } else { + url = this._endpoint + method + ".json"; + } + return url; } - var parsed = undefined; - try { - parsed = JSON.parse(reply); - } catch (e) { - parsed = {}; - // assume query format - var elements = reply.split("&"); - for (var i = 0; i < elements.length; i++) { - var element = elements[i].split("=", 2); - if (element.length > 1) { - parsed[element[0]] = decodeURIComponent(element[1]); - } else { - parsed[element[0]] = null; + + /** + * Parses the API reply to encode it in the set return_format + * + * @param string reply The actual reply, JSON-encoded or URL-encoded + * + * @return array|object The parsed reply + */ + + }, { + key: "_parseApiReply", + value: function _parseApiReply(reply) { + if (typeof reply !== "string" || reply === "") { + return {}; + } + if (reply === "[]") { + return []; + } + var parsed = undefined; + try { + parsed = JSON.parse(reply); + } catch (e) { + parsed = {}; + // assume query format + var elements = reply.split("&"); + for (var i = 0; i < elements.length; i++) { + var element = elements[i].split("=", 2); + if (element.length > 1) { + parsed[element[0]] = decodeURIComponent(element[1]); + } else { + parsed[element[0]] = null; + } } } + return parsed; } - return parsed; - }; - - /** - * Uncommon API methods - */ - /** - * Gets the OAuth authenticate URL for the current request token - * - * @return object Promise - */ - methods.oauth_authenticate = function () { - var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var callback = arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1]; - var type = arguments.length <= 2 || arguments[2] === undefined ? "authenticate" : arguments[2]; - - var dfd = methods._getDfd(); - if (typeof params.force_login === "undefined") { - params.force_login = null; - } - if (typeof params.screen_name === "undefined") { - params.screen_name = null; - } - if (["authenticate", "authorize"].indexOf(type) === -1) { - type = "authenticate"; - } - if (props._oauth_token === null) { - var error = "To get the " + type + " URL, the OAuth token must be set."; - console.warn(error); + /** + * Uncommon API methods + */ + + /** + * Gets the OAuth authenticate URL for the current request token + * + * @return object Promise + */ + + }, { + key: "oauth_authenticate", + value: function oauth_authenticate() { + var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + var callback = arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1]; + var type = arguments.length <= 2 || arguments[2] === undefined ? "authenticate" : arguments[2]; + + var dfd = this._getDfd(); + if (typeof params.force_login === "undefined") { + params.force_login = null; + } + if (typeof params.screen_name === "undefined") { + params.screen_name = null; + } + if (["authenticate", "authorize"].indexOf(type) === -1) { + type = "authenticate"; + } + if (this._oauth_token === null) { + var error = "To get the " + type + " URL, the OAuth token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return this._getPromise(dfd); + } + throw error; + } + var url = this._endpoint_oauth + "oauth/" + type + "?oauth_token=" + this._url(this._oauth_token); + if (params.force_login === true) { + url += "&force_login=1"; + } + if (params.screen_name !== null) { + url += "&screen_name=" + params.screen_name; + } + if (typeof callback === "function") { + callback(url); + } if (dfd) { - dfd.reject({ error: error }); - return methods._getPromise(dfd); + dfd.resolve({ reply: url }); + return this._getPromise(dfd); } - throw error; - } - var url = props._endpoint_oauth + "oauth/" + type + "?oauth_token=" + methods._url(props._oauth_token); - if (params.force_login === true) { - url += "&force_login=1"; - } - if (params.screen_name !== null) { - url += "&screen_name=" + params.screen_name; + // no promises + return true; } - if (typeof callback === "function") { - callback(url); - } - if (dfd) { - dfd.resolve({ reply: url }); - return methods._getPromise(dfd); + + /** + * Gets the OAuth authorize URL for the current request token + * + * @return string The OAuth authorize URL + */ + + }, { + key: "oauth_authorize", + value: function oauth_authorize(params, callback) { + return this.oauth_authenticate(params, callback, "authorize"); } - // no promises - return true; - }; - /** - * Gets the OAuth authorize URL for the current request token - * - * @return string The OAuth authorize URL - */ - methods.oauth_authorize = function (params, callback) { - return methods.oauth_authenticate(params, callback, "authorize"); - }; + /** + * Gets the OAuth bearer token + * + * @return object Promise + */ - /** - * Gets the OAuth bearer token - * - * @return object Promise - */ + }, { + key: "oauth2_token", + value: function oauth2_token(callback) { + var _this = this; - methods.oauth2_token = function (callback) { - var dfd = methods._getDfd(); + var dfd = this._getDfd(); - if (props._oauth_consumer_key === null) { - var error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return methods._getPromise(dfd); + if (this._oauth_consumer_key === null) { + var error = "To obtain a bearer token, the consumer key must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return this._getPromise(dfd); + } + return false; } - return false; - } - if (!dfd && typeof callback === "undefined") { - callback = function () {}; - } + if (!dfd && typeof callback === "undefined") { + callback = function () {}; + } - var post_fields = "grant_type=client_credentials"; - var url = props._endpoint_oauth + "oauth2/token"; + var post_fields = "grant_type=client_credentials"; + var url = this._endpoint_oauth + "oauth2/token"; - if (props._use_proxy) { - url = url.replace(props._endpoint_base, props._endpoint_proxy); - } + if (this._use_proxy) { + url = url.replace(this._endpoint_base, this._endpoint_proxy); + } - var xml = methods._getXmlRequestObject(); - if (xml === null) { - return; - } - xml.open("POST", url, true); - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xml.setRequestHeader((props._use_proxy ? "X-" : "") + "Authorization", "Basic " + methods._base64_encode(props._oauth_consumer_key + ":" + props._oauth_consumer_secret)); + var xml = this._getXmlRequestObject(); + if (xml === null) { + return; + } + xml.open("POST", url, true); + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", "Basic " + this._base64_encode(this._oauth_consumer_key + ":" + this._oauth_consumer_secret)); - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = methods._parseApiReply(response); - reply.httpstatus = httpstatus; - if (httpstatus === 200) { - methods.setBearerToken(reply.access_token); + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) {} + var response = ""; + try { + response = xml.responseText; + } catch (e) {} + var reply = _this._parseApiReply(response); + reply.httpstatus = httpstatus; + if (httpstatus === 200) { + _this.setBearerToken(reply.access_token); + } + if (typeof callback === "function") { + callback(reply); + } + if (dfd) { + dfd.resolve({ reply: reply }); + } } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { if (typeof callback === "function") { - callback(reply); + callback(null, e); } if (dfd) { - dfd.resolve({ reply: reply }); + dfd.reject(e); } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, e); - } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(post_fields); if (dfd) { - dfd.reject(e); + return this._getPromise(dfd); } - }; - xml.timeout = 30000; // in milliseconds - - xml.send(post_fields); - if (dfd) { - return methods._getPromise(dfd); - } - }; - - /** - * Calls the API using cURL - * - * @param string httpmethod The HTTP method to use for making the request - * @param string method The API method to call - * @param array optional params The parameters to send along - * @param bool optional multipart Whether to use multipart/form-data - * @param bool optional app_only_auth Whether to use app-only bearer authentication - * @param function callback The function to call with the API call result - * - * @return mixed The API reply, encoded in the set return_format - */ - - methods._callApi = function (httpmethod, method) { - var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - var multipart = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; - var app_only_auth = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; - var callback = arguments.length <= 5 || arguments[5] === undefined ? function () {} : arguments[5]; - - var dfd = methods._getDfd(); - - var url = methods._getEndpoint(method), - authorization = null; - - var xml = methods._getXmlRequestObject(); - if (xml === null) { - return; } - var post_fields = undefined; - var _sign = methods._sign; - if (httpmethod === "GET") { - var url_with_params = url; - if (JSON.stringify(params) !== "{}") { - url_with_params += "?" + methods._http_build_query(params); - } - if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); + /** + * Calls the API using cURL + * + * @param string httpmethod The HTTP method to use for making the request + * @param string method The API method to call + * @param array optional params The parameters to send along + * @param bool optional multipart Whether to use multipart/form-data + * @param bool optional app_only_auth Whether to use app-only bearer authentication + * @param function callback The function to call with the API call result + * + * @return mixed The API reply, encoded in the set return_format + */ + + }, { + key: "_callApi", + value: function _callApi(httpmethod, method) { + var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var multipart = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + + var _this2 = this; + + var app_only_auth = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; + var callback = arguments.length <= 5 || arguments[5] === undefined ? function () {} : arguments[5]; + + var dfd = this._getDfd(); + + var url = this._getEndpoint(method), + authorization = null; + + var xml = this._getXmlRequestObject(); + if (xml === null) { + return; } + var post_fields = undefined; + var _sign = this._sign; - if (props._use_proxy) { - url_with_params = url_with_params.replace(props._endpoint_base, props._endpoint_proxy).replace(props._endpoint_base_media, props._endpoint_proxy); - } - xml.open(httpmethod, url_with_params, true); - } else { - if (multipart) { - if (!app_only_auth) { - authorization = _sign(httpmethod, url, {}); + if (httpmethod === "GET") { + var url_with_params = url; + if (JSON.stringify(params) !== "{}") { + url_with_params += "?" + this._http_build_query(params); } - params = methods._buildMultipart(method, params); - } else if (methods._detectJsonBody(method)) { - authorization = _sign(httpmethod, url, {}); - params = JSON.stringify(params); - } else { if (!app_only_auth) { authorization = _sign(httpmethod, url, params); } - params = methods._http_build_query(params); - } - post_fields = params; - if (props._use_proxy || multipart) { - // force proxy for multipart base64 - url = url.replace(props._endpoint_base, props._endpoint_proxy).replace(props._endpoint_base_media, props._endpoint_proxy); - } - xml.open(httpmethod, url, true); - if (multipart) { - xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); - } else if (methods._detectJsonBody(method)) { - xml.setRequestHeader("Content-Type", "application/json"); + + if (this._use_proxy) { + url_with_params = url_with_params.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); + } + xml.open(httpmethod, url_with_params, true); } else { - xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - } - } - if (app_only_auth) { - if (props._oauth_consumer_key === null && props._oauth_bearer_token === null) { - var error = "To make an app-only auth API request, consumer key or bearer token must be set."; - console.warn(error); - if (dfd) { - dfd.reject({ error: error }); - return methods._getPromise(dfd); + if (multipart) { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, {}); + } + params = this._buildMultipart(method, params); + } else if (this._detectJsonBody(method)) { + authorization = _sign(httpmethod, url, {}); + params = JSON.stringify(params); + } else { + if (!app_only_auth) { + authorization = _sign(httpmethod, url, params); + } + params = this._http_build_query(params); + } + post_fields = params; + if (this._use_proxy || multipart) { + // force proxy for multipart base64 + url = url.replace(this._endpoint_base, this._endpoint_proxy).replace(this._endpoint_base_media, this._endpoint_proxy); + } + xml.open(httpmethod, url, true); + if (multipart) { + xml.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + post_fields.split("\r\n")[0].substring(2)); + } else if (this._detectJsonBody(method)) { + xml.setRequestHeader("Content-Type", "application/json"); + } else { + xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); } } - // automatically fetch bearer token, if necessary - if (props._oauth_bearer_token === null) { - if (dfd) { - return methods.oauth2_token().then(function () { - return methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + if (app_only_auth) { + if (this._oauth_consumer_key === null && this._oauth_bearer_token === null) { + var error = "To make an app-only auth API request, consumer key or bearer token must be set."; + console.warn(error); + if (dfd) { + dfd.reject({ error: error }); + return this._getPromise(dfd); + } + } + // automatically fetch bearer token, if necessary + if (this._oauth_bearer_token === null) { + if (dfd) { + return this.oauth2_token().then(function () { + return _this2._callApi(httpmethod, method, params, multipart, app_only_auth, callback); + }); + } + this.oauth2_token(function () { + _this2._callApi(httpmethod, method, params, multipart, app_only_auth, callback); }); + return; } - methods.oauth2_token(function () { - methods._callApi(httpmethod, method, params, multipart, app_only_auth, callback); - }); - return; + authorization = "Bearer " + this._oauth_bearer_token; } - authorization = "Bearer " + props._oauth_bearer_token; - } - if (authorization !== null) { - xml.setRequestHeader((props._use_proxy ? "X-" : "") + "Authorization", authorization); - } - xml.onreadystatechange = function () { - if (xml.readyState >= 4) { - var httpstatus = 12027; - try { - httpstatus = xml.status; - } catch (e) {} - var response = ""; - try { - response = xml.responseText; - } catch (e) {} - var reply = methods._parseApiReply(response); - reply.httpstatus = httpstatus; - var rate = null; - if (typeof xml.getResponseHeader !== "undefined" && xml.getResponseHeader("x-rate-limit-limit") !== "") { - rate = { - limit: xml.getResponseHeader("x-rate-limit-limit"), - remaining: xml.getResponseHeader("x-rate-limit-remaining"), - reset: xml.getResponseHeader("x-rate-limit-reset") - }; + if (authorization !== null) { + xml.setRequestHeader((this._use_proxy ? "X-" : "") + "Authorization", authorization); + } + xml.onreadystatechange = function () { + if (xml.readyState >= 4) { + var httpstatus = 12027; + try { + httpstatus = xml.status; + } catch (e) {} + var response = ""; + try { + response = xml.responseText; + } catch (e) {} + var reply = _this2._parseApiReply(response); + reply.httpstatus = httpstatus; + var rate = null; + if (typeof xml.getResponseHeader !== "undefined" && xml.getResponseHeader("x-rate-limit-limit") !== "") { + rate = { + limit: xml.getResponseHeader("x-rate-limit-limit"), + remaining: xml.getResponseHeader("x-rate-limit-remaining"), + reset: xml.getResponseHeader("x-rate-limit-reset") + }; + } + if (typeof callback === "function") { + callback(reply, rate); + } + if (dfd) { + dfd.resolve({ reply: reply, rate: rate }); + } } + }; + // function called when an error occurs, including a timeout + xml.onerror = function (e) { if (typeof callback === "function") { - callback(reply, rate); + callback(null, null, e); } if (dfd) { - dfd.resolve({ reply: reply, rate: rate }); + dfd.reject(e); } - } - }; - // function called when an error occurs, including a timeout - xml.onerror = function (e) { - if (typeof callback === "function") { - callback(null, null, e); - } + }; + xml.timeout = 30000; // in milliseconds + + xml.send(httpmethod === "GET" ? null : post_fields); if (dfd) { - dfd.reject(e); + return this._getPromise(dfd); } - }; - xml.timeout = 30000; // in milliseconds - - xml.send(httpmethod === "GET" ? null : post_fields); - if (dfd) { - return methods._getPromise(dfd); + return true; } - return true; - }; - /** - * Main API handler working on any requests you issue - * - * @param string fn The member function you called - * @param array params The parameters you sent along - * @param function callback The callback to call with the reply - * @param bool app_only_auth Whether to use app-only auth - * - * @return object Promise - */ + /** + * Main API handler working on any requests you issue + * + * @param string fn The member function you called + * @param array params The parameters you sent along + * @param function callback The callback to call with the reply + * @param bool app_only_auth Whether to use app-only auth + * + * @return object Promise + */ + + }, { + key: "__call", + value: function __call(fn) { + var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var callback = arguments[2]; + var app_only_auth = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + + if (typeof callback !== "function" && typeof params === "function") { + callback = params; + params = {}; + if (typeof callback === "boolean") { + app_only_auth = callback; + } + } else if (typeof callback === "undefined") { + callback = function () {}; + } + switch (fn) { + case "oauth_authenticate": + case "oauth_authorize": + return this[fn](params, callback); + + case "oauth2_token": + return this[fn](callback); + } + + // parse parameters + var apiparams = this._parseApiParams(params); - methods.__call = function (fn) { - var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; - var callback = arguments[2]; - var app_only_auth = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + // stringify null and boolean parameters + apiparams = this._stringifyNullBoolParams(apiparams); - if (typeof callback !== "function" && typeof params === "function") { - callback = params; - params = {}; - if (typeof callback === "boolean") { - app_only_auth = callback; + // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) + if (fn === "oauth_requestToken") { + this.setToken(null, null); } - } else if (typeof callback === "undefined") { - callback = function () {}; - } - switch (fn) { - case "oauth_authenticate": - case "oauth_authorize": - return undefined[fn](params, callback); - case "oauth2_token": - return undefined[fn](callback); - } + // map function name to API method - // parse parameters - var apiparams = methods._parseApiParams(params); + var _mapFnToApiMethod2 = this._mapFnToApiMethod(fn, apiparams); - // stringify null and boolean parameters - apiparams = methods._stringifyNullBoolParams(apiparams); + var _mapFnToApiMethod3 = _slicedToArray(_mapFnToApiMethod2, 2); - // reset token when requesting a new token (causes 401 for signature error on 2nd+ requests) - if (fn === "oauth_requestToken") { - methods.setToken(null, null); - } + var method = _mapFnToApiMethod3[0]; + var method_template = _mapFnToApiMethod3[1]; + var httpmethod = this._detectMethod(method_template, apiparams); + var multipart = this._detectMultipart(method_template); - // map function name to API method - - var _methods$_mapFnToApiM = methods._mapFnToApiMethod(fn, apiparams); - - var _methods$_mapFnToApiM2 = _slicedToArray(_methods$_mapFnToApiM, 2); - - var method = _methods$_mapFnToApiM2[0]; - var method_template = _methods$_mapFnToApiM2[1]; - var httpmethod = methods._detectMethod(method_template, apiparams); - var multipart = methods._detectMultipart(method_template); - - return methods._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); - }; - - // Unit testing code - var __test = { - call: function call(name) { - var params = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1]; - return methods[name].apply(undefined, params); - }, - get: function get(name) { - return props[name]; - }, - mock: function mock(methods_to_mock) { - for (var name in methods_to_mock) { - if (methods_to_mock.hasOwnProperty(name)) { - methods[name] = methods_to_mock[name]; - } - } + return this._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); } - }; - - return { - __call: methods.__call, - __test: __test, - getApiMethods: methods.getApiMethods, - getVersion: methods.getVersion, - logout: methods.logout, - oauth2_token: methods.oauth2_token, - oauth_authenticate: methods.oauth_authenticate, - oauth_authorize: methods.oauth_authorize, - setBearerToken: methods.setBearerToken, - setConsumerKey: methods.setConsumerKey, - setProxy: methods.setProxy, - setToken: methods.setToken, - setUseProxy: methods.setUseProxy - }; - }; + }]); + + return Codebird; + })(); + + ; if ((typeof module === "undefined" ? "undefined" : _typeof(module)) === "object" && module && _typeof(module.exports) === "object") { // Expose codebird as module.exports in loaders that implement the Node diff --git a/package.json b/package.json index f722919..1d6762a 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,12 @@ "url": "https://github.com/jublonet/codebird-js.git" }, "scripts": { - "build": "babel codebird.es7.js -o codebird.js", - "test": "babel codebird.es7.js -o codebird.js && tape test/*.js | faucet" + "build-cb": "babel codebird.es7.js -o codebird.js", + "build-cbt": "babel test/codebirdt.es7.js -o test/codebirdt.js", + "build-cbm": "babel test/codebirdm.es7.js -o test/codebirdm.js", + "build": "npm run build-cb && npm run build-cbt && npm run build-cbm", + "test-run": "tape test/*_tests.js | faucet", + "test": "npm run build && npm run test-run" }, "devDependencies": { "babel-cli": "^6.3.17", diff --git a/test/codebirdm.es7.js b/test/codebirdm.es7.js new file mode 100644 index 0000000..7d26d92 --- /dev/null +++ b/test/codebirdm.es7.js @@ -0,0 +1,66 @@ +import CodebirdT from "./codebirdt"; + +/** + * A Twitter library in JavaScript + * + * @package codebird-test + * @version 3.0.0-dev + * @author Jublo Solutions + * @copyright 2010-2016 Jublo Solutions + * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 + * @link https://github.com/jublonet/codebird-php + */ + +/** + * A Twitter library in JavaScript + * + * @package codebird-test + */ +export default class CodebirdM extends CodebirdT { + + constructor() { + super(); + /** + * Mock API replies + */ + this._mock_replies = { + default: { + httpstatus: 404, + reply: "{\"errors\":[{\"message\":\"Sorry, that page does not exist\",\"code\":34}]}" + }, + "GET https://api.twitter.com/1.1/users/show.json?screen_name=TwitterAPI": { + httpstatus: 200, + reply: "{\"id\":6253282,\"id_str\":\"6253282\",\"name\":\"Twitter API\",\"screen_name\":\"twitterapi\",\"location\":\"San Francisco, CA\",\"profile_location\":null,\"description\":\"The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.\",\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"entities\":{\"url\":{\"urls\":[{\"url\":\"http:\/\/t.co\/78pYTvWfJd\",\"expanded_url\":\"http:\/\/dev.twitter.com\",\"display_url\":\"dev.twitter.com\",\"indices\":[0,22]}]},\"description\":{\"urls\":[]}},\"protected\":false,\"followers_count\":4993679,\"friends_count\":48,\"listed_count\":13001,\"created_at\":\"Wed May 23 06:01:13 +0000 2007\",\"favourites_count\":27,\"utc_offset\":-28800,\"time_zone\":\"Pacific Time (US & Canada)\",\"geo_enabled\":true,\"verified\":true,\"statuses_count\":3553,\"lang\":\"en\",\"status\":{\"created_at\":\"Tue Nov 24 08:56:07 +0000 2015\",\"id\":669077021138493440,\"id_str\":\"669077021138493440\",\"text\":\"Additional 64-bit entity ID migration coming in Feb 2016 https:\/\/t.co\/eQIGvw1rsJ\",\"source\":\"\u003ca href=\\\"https:\/\/about.twitter.com\/products\/tweetdeck\\\" rel=\\\"nofollow\\\"\u003eTweetDeck\u003c\/a\u003e\",\"truncated\":false,\"in_reply_to_status_id\":null,\"in_reply_to_status_id_str\":null,\"in_reply_to_user_id\":null,\"in_reply_to_user_id_str\":null,\"in_reply_to_screen_name\":null,\"geo\":null,\"coordinates\":null,\"place\":null,\"contributors\":null,\"retweet_count\":67,\"favorite_count\":79,\"entities\":{\"hashtags\":[],\"symbols\":[],\"user_mentions\":[],\"urls\":[{\"url\":\"https:\/\/t.co\/eQIGvw1rsJ\",\"expanded_url\":\"https:\/\/twittercommunity.com\/t\/migration-of-twitter-core-entities-to-64-bit-ids\/56881\",\"display_url\":\"twittercommunity.com\/t\/migration-of\u2026\",\"indices\":[57,80]}]},\"favorited\":false,\"retweeted\":false,\"possibly_sensitive\":false,\"lang\":\"en\"},\"contributors_enabled\":false,\"is_translator\":false,\"is_translation_enabled\":false,\"profile_background_color\":\"C0DEED\",\"profile_background_image_url\":\"http:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_background_images\/656927849\/miyt9dpjz77sc0w3d4vj.png\",\"profile_background_tile\":true,\"profile_image_url\":\"http:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_image_url_https\":\"https:\/\/pbs.twimg.com\/profile_images\/2284174872\/7df3h38zabcvjylnyfe3_normal.png\",\"profile_banner_url\":\"https:\/\/pbs.twimg.com\/profile_banners\/6253282\/1431474710\",\"profile_link_color\":\"0084B4\",\"profile_sidebar_border_color\":\"C0DEED\",\"profile_sidebar_fill_color\":\"DDEEF6\",\"profile_text_color\":\"333333\",\"profile_use_background_image\":true,\"has_extended_profile\":false,\"default_profile\":false,\"default_profile_image\":false,\"following\":true,\"follow_request_sent\":false,\"notifications\":false}" + }, + "POST https://api.twitter.com/oauth2/token": { + httpstatus: 200, + reply: "{\"token_type\":\"bearer\",\"access_token\":\"VqiO0n2HrKE\"}" + } + }; + + this.xml = { + readyState: 4, + open: (httpmethod, url) => { + this.xml.httpmethod = httpmethod; + this.xml.url = url; + const key = `${httpmethod} ${url}`; + if (this._mock_replies.hasOwnProperty(key)) { + this.xml.status = this._mock_replies[key].httpstatus; + this.xml.responseText = this._mock_replies[key].reply; + } else { + this.xml.status = this._mock_replies.default.httpstatus; + this.xml.responseText = this._mock_replies.default.reply; + } + }, + setRequestHeader: () => true, + onreadystatechange: () => false, + send: function () { + setTimeout(this.onreadystatechange, 200); + } + }; + } + + _getXmlRequestObject() { + return this.xml; + }; +} diff --git a/test/codebirdt.es7.js b/test/codebirdt.es7.js new file mode 100644 index 0000000..af81df2 --- /dev/null +++ b/test/codebirdt.es7.js @@ -0,0 +1,93 @@ +import Codebird from "../codebird"; + +/** + * A Twitter library in JavaScript + * + * @package codebird-test + * @version 3.0.0-dev + * @author Jublo Solutions + * @copyright 2010-2016 Jublo Solutions + * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 + * @link https://github.com/jublonet/codebird-php + */ + +/** + * A Twitter library in JavaScript + * + * @package codebird-test + */ +export default class CodebirdT extends Codebird { + + /** + * Returns properties + * + * @param string property The property to get + * + * @return mixed Property + */ + get(property) { + if (typeof this.property !== "undefined") { + return this.property; + } + throw `Property ${property} is not defined.`; + } + + /** + * Returns static properties + * + * @param string property The property to get + * + * @return mixed Property + */ + getStatic(property) { + if (typeof CodebirdT.property !== "undefined") { + return CodebirdT.property; + } + throw `Static property ${property} is not defined.`; + } + + /** + * Calls methods + * + * @param string method The method to call + * @param mixed params The parameters to send along + * + * @return mixed Return value + */ + call(method, params = []) { + if (typeof this[method] === "function") { + return this[method].apply(this, params); + } + throw `Method ${method} is not defined.`; + } + + /** + * Calls static methods + * + * @param string method The method to call + * @param mixed params The parameters to send along + * + * @return mixed Return value + */ + callStatic(method, params = []) { + if (typeof CodebirdT[method] === "function") { + return CodebirdT[method].apply(this, params); + } + throw `Static method ${method} is not defined.`; + } + +/* +// Unit testing code + this.__test = { + call: (name, params = []) => this[name].apply(this, params), + get: name => this[name], + mock: methods_to_mock => { + for (let name in methods_to_mock) { + if (methods_to_mock.hasOwnProperty(name)) { + this[name] = methods_to_mock[name]; + } + } + } + }; +*/ +} diff --git a/test/detection_tests.js b/test/detection_tests.js index fdaa95d..68766b8 100644 --- a/test/detection_tests.js +++ b/test/detection_tests.js @@ -1,45 +1,49 @@ const tape = require("tape"), _test = require("tape-promise"), test = _test(tape), // decorate tape - Codebird = require("../codebird"); + CodebirdT = require("./codebirdt"); + +function getCB() { + return new CodebirdT.default; +} test("Tests _detectMethod", function (t) { - const cb = new Codebird; + const cb = getCB(); t.throws( function () { - cb.__test.call("_detectMethod", ["non-existent", {}]); + cb.call("_detectMethod", ["non-existent", {}]); }, /^Can't find HTTP method to use for "non-existent".$/ ); // forced httpmethod t.equal( - cb.__test.call("_detectMethod", ["doesnt-matter", {httpmethod: "DELETE"}]), + cb.call("_detectMethod", ["doesnt-matter", {httpmethod: "DELETE"}]), "DELETE" ); // normal detection t.equal( - cb.__test.call("_detectMethod", ["search/tweets", {}]), + cb.call("_detectMethod", ["search/tweets", {}]), "GET" ); t.equal( - cb.__test.call("_detectMethod", ["statuses/update", {}]), + cb.call("_detectMethod", ["statuses/update", {}]), "POST" ); t.equal( - cb.__test.call("_detectMethod", ["statuses/destroy/:id", {}]), + cb.call("_detectMethod", ["statuses/destroy/:id", {}]), "POST" ); // parameter-based detection t.equal( - cb.__test.call("_detectMethod", ["account/settings", {}]), + cb.call("_detectMethod", ["account/settings", {}]), "GET" ); t.equal( - cb.__test.call("_detectMethod", ["account/settings", {test: 12}]), + cb.call("_detectMethod", ["account/settings", {test: 12}]), "POST" ); @@ -47,41 +51,41 @@ test("Tests _detectMethod", function (t) { }); test("Tests _detectMultipart", function (t) { - const cb = new Codebird; + const cb = getCB(); - t.false(cb.__test.call("_detectMultipart", ["statuses/update"])); - t.true(cb.__test.call("_detectMultipart", ["statuses/update_with_media"])); - t.true(cb.__test.call("_detectMultipart", ["media/upload"])); + t.false(cb.call("_detectMultipart", ["statuses/update"])); + t.true(cb.call("_detectMultipart", ["statuses/update_with_media"])); + t.true(cb.call("_detectMultipart", ["media/upload"])); t.end(); }); test("Tests _detectMedia", function (t) { - const cb = new Codebird; + const cb = getCB(); - t.false(cb.__test.call("_detectMedia", ["statuses/update"])); - t.true(cb.__test.call("_detectMedia", ["media/upload"])); + t.false(cb.call("_detectMedia", ["statuses/update"])); + t.true(cb.call("_detectMedia", ["media/upload"])); t.end(); }); test("Tests _getEndpoint", function (t) { - const cb = new Codebird; + const cb = getCB(); t.equal( - cb.__test.call("_getEndpoint", ["statuses/update", "statuses/update"]), + cb.call("_getEndpoint", ["statuses/update", "statuses/update"]), "https://api.twitter.com/1.1/statuses/update.json" ); t.equal( - cb.__test.call("_getEndpoint", ["oauth/authenticate", "oauth/authenticate"]), + cb.call("_getEndpoint", ["oauth/authenticate", "oauth/authenticate"]), "https://api.twitter.com/oauth/authenticate" ); t.equal( - cb.__test.call("_getEndpoint", ["oauth2/token", "oauth2/token"]), + cb.call("_getEndpoint", ["oauth2/token", "oauth2/token"]), "https://api.twitter.com/oauth2/token" ); t.equal( - cb.__test.call("_getEndpoint", ["media/upload", "media/upload"]), + cb.call("_getEndpoint", ["media/upload", "media/upload"]), "https://upload.twitter.com/1.1/media/upload.json" ); diff --git a/test/oauth_tests.js b/test/oauth_tests.js index 6db433a..863c25e 100644 --- a/test/oauth_tests.js +++ b/test/oauth_tests.js @@ -1,38 +1,17 @@ const tape = require("tape"), _test = require("tape-promise"), test = _test(tape), // decorate tape - Codebird = require("../codebird"); + CodebirdM = require("./codebirdm"); -function mock() { - var cb = new Codebird(); +function getCB() { + var cb = new CodebirdM.default; cb.setConsumerKey("123", "456"); - var xml = { - readyState: 4, - status: 200, - open: function (url) { - this.url = url - }, - setRequestHeader: function () { - return true; - }, - responseText: "{\"token_type\":\"bearer\",\"access_token\":\"VqiO0n2HrKE\"}" - }; - xml.send = function () { - setTimeout(xml.onreadystatechange, 200); - }; - - cb.__test.mock({ - _getXmlRequestObject: function () { - return xml - } - }); - return cb; } test("Tests oauth_authenticate Promise", function (t) { - const cb = mock(); + const cb = getCB(); t.plan(1); cb.setToken("123", "456"); @@ -45,7 +24,7 @@ test("Tests oauth_authenticate Promise", function (t) { }); test("Tests oauth_authenticate callback", function (t) { - const cb = mock(); + const cb = getCB(); t.plan(4); cb.setToken("123", "456"); @@ -72,7 +51,7 @@ test("Tests oauth_authenticate callback", function (t) { }); test("Tests oauth_authorize callback", function (t) { - const cb = mock(); + const cb = getCB(); t.plan(4); cb.setToken("123", "456"); @@ -99,7 +78,7 @@ test("Tests oauth_authorize callback", function (t) { }); test("Tests oauth2_token", function (t) { - const cb = mock(); + const cb = getCB(); t.plan(1); cb.oauth2_token( From 5fae200bda3b721641274fa957aaa7f13d7a3057 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 3 Jan 2016 15:37:57 +0100 Subject: [PATCH 27/48] Disable consistent-this because of Babel transpiling --- .eslintrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index ba76d1b..e9fa74e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -48,7 +48,6 @@ ////////// Stylistic Issues ////////// - "consistent-this": 2, // enforces consistent naming when capturing the current execution context (off by default) "eol-last": 2, // enforce newline at the end of file, with no multiple empty lines "no-lonely-if": 2, // disallow if as the only statement in an else block (off by default) "no-mixed-spaces-and-tabs": 2, // disallow mixed spaces and tabs for indentation From ce36039d3846da6fd422386e3300998af48e4ef8 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 3 Jan 2016 16:17:12 +0100 Subject: [PATCH 28/48] Migrate from console.warn to throwing Exceptions --- codebird.es7.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/codebird.es7.js b/codebird.es7.js index bb387a6..35389c8 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -269,7 +269,7 @@ let b = `${this._oauth_consumer_secret}&${null !== this._oauth_token_secret ? this._oauth_token_secret : ""}`; if (this._oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); + throw "To generate a hash, the consumer secret must be set."; } let c = q(b); if (c.length > 16) { @@ -372,7 +372,7 @@ if (typeof a !== "function") { return this._url(c) + "=" + this._url(a); } - console.warn("There was an error processing for http_build_query()."); + throw "There was an error processing for http_build_query()."; } else { return ""; } @@ -406,7 +406,7 @@ */ _nonce(length = 8) { if (length < 1) { - console.warn("Invalid nonce length."); + throw "Invalid nonce length."; } let nonce = ""; for (let i = 0; i < length; i++) { @@ -920,7 +920,7 @@ for (j = 0; j < 26; j++) { method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); } - console.warn(`To call the templated method "${method_template}", specify the parameter value for "${param_l}".`); + throw `To call the templated method "${method_template}", specify the parameter value for "${param_l}".`; } method = method.split(param).join(apiparams[param_l]); delete apiparams[param_l]; @@ -1034,7 +1034,7 @@ _sign(httpmethod, method, params = {}) { const {_url, _ksort, _clone, _getSignature} = this; if (this._oauth_consumer_key === null) { - console.warn("To generate a signature, the consumer key must be set."); + throw "To generate a signature, the consumer key must be set."; } const sign_params = { consumer_key: this._oauth_consumer_key, @@ -1235,7 +1235,6 @@ } if (this._oauth_token === null) { const error = `To get the ${type} URL, the OAuth token must be set.`; - console.warn(error); if (dfd) { dfd.reject({ error }); return this._getPromise(dfd); @@ -1279,12 +1278,11 @@ if (this._oauth_consumer_key === null) { const error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); if (dfd) { dfd.reject({ error }); return this._getPromise(dfd); } - return false; + throw error; } if (!dfd && typeof callback === "undefined") { @@ -1443,11 +1441,11 @@ && this._oauth_bearer_token === null ) { const error = "To make an app-only auth API request, consumer key or bearer token must be set."; - console.warn(error); if (dfd) { dfd.reject({ error }); return this._getPromise(dfd); } + throw error; } // automatically fetch bearer token, if necessary if (this._oauth_bearer_token === null) { From d85482adb39e0dbeaed6c4942c32a7089d5aee9c Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 3 Jan 2016 16:18:43 +0100 Subject: [PATCH 29/48] Throw Exceptions if XML object fails --- codebird.es7.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/codebird.es7.js b/codebird.es7.js index 35389c8..ed65c69 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -12,7 +12,6 @@ /* global window, document, navigator, - console, Ti, ActiveXObject, module, @@ -495,7 +494,7 @@ try { xml = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { - console.error("ActiveXObject object not defined."); + throw "ActiveXObject object not defined."; } // now, consider RequireJS and/or Node.js objects } else if (typeof require === "function" @@ -512,7 +511,7 @@ XMLHttpRequest = require("xhr2"); xml = new XMLHttpRequest(); } catch (e2) { - console.error("xhr2 object not defined, cancelling."); + throw "xhr2 object not defined, cancelling."; } } } From 172649f0cfd65cb18d11a6d8c1e5e873a51cd554 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Tue, 12 Jan 2016 17:07:03 +0100 Subject: [PATCH 30/48] Add POST statuses/unretweet/:id --- CHANGELOG | 1 + codebird.es7.js | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1c1d7ab..4f8f0e9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ codebird-js - changelog + #110 Add support for Collections API + Transform codebase to EcmaScript 7 + #25 Add unit testing suite ++ Add POST statuses/unretweet/:id 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index ed65c69..debe122 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -742,6 +742,7 @@ "statuses/filter", "statuses/lookup", "statuses/retweet/:id", + "statuses/unretweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload "users/lookup", From 3bd368a42fc39ba7f873349038bfd053baff79a9 Mon Sep 17 00:00:00 2001 From: Luisa Marieth Morales Date: Fri, 11 Mar 2016 13:14:17 -0500 Subject: [PATCH 31/48] Removed extra ' on line 607 Removes SyntaxError (Unexpected string) caused when calling cb.__call () on an app-only auth request. --- codebird.es7.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebird.es7.js b/codebird.es7.js index debe122..c334c80 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -604,7 +604,7 @@ } else { key = eval(evalStr + ".push([]);") - 1; } - evalStr += `[${key}']`; + evalStr += `[${key}]`; if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { eval(evalStr + " = [];"); } From a65464479943c1e572dca92fc02fb881432d3e45 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 13 Mar 2016 16:34:08 +0100 Subject: [PATCH 32/48] Ignore npm debug log --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6f58266..9a5334e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +npm-debug.log RELEASE_MESSAGE* test/codebirdm.js test/codebirdt.js From 691828a3e6c5a79b703260721a59ee893e94479d Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sun, 13 Mar 2016 17:03:42 +0100 Subject: [PATCH 33/48] Handle context correctly when calling protected methods Fix #117. --- codebird.es7.js | 33 ++++++++++----------- codebird.js | 63 +++++++++++++++++----------------------- test/functional_tests.js | 37 +++++++++++++++++++++++ 3 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 test/functional_tests.js diff --git a/codebird.es7.js b/codebird.es7.js index c334c80..1772b6f 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -363,7 +363,7 @@ if (typeof a === "object") { for (b in a) { if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); + e.push(g.call(this, c + "[" + b + "]", a[b], d)); } } return e.join(d); @@ -388,7 +388,7 @@ if (f && !isNaN(c)) { c = String(f) + c; } - d = g(c, d, b); + d = g.call(this, c, d, b); if (d !== "") { h.push(d); } @@ -1003,16 +1003,15 @@ * @return string signature */ _getSignature(httpmethod, method, keys, base_params) { - const {_url, _sha1} = this; // convert params to string let base_string = "", key, value; for (let i = 0; i < keys.length; i++) { key = keys[i]; value = base_params[key]; - base_string += `${key}=${_url(value)}&`; + base_string += `${key}=${this._url(value)}&`; } base_string = base_string.substring(0, base_string.length - 1); - return _sha1(`${httpmethod}&${_url(method)}&${_url(base_string)}`); + return this._sha1(`${httpmethod}&${this._url(method)}&${this._url(base_string)}`); } /** @@ -1032,7 +1031,6 @@ * @return string Authorization HTTP header */ _sign(httpmethod, method, params = {}) { - const {_url, _ksort, _clone, _getSignature} = this; if (this._oauth_consumer_key === null) { throw "To generate a signature, the consumer key must be set."; } @@ -1049,29 +1047,29 @@ continue; } let value = sign_params[key]; - sign_base_params[`oauth_${key}`] = _url(value); + sign_base_params[`oauth_${key}`] = this._url(value); } if (this._oauth_token !== null) { - sign_base_params.oauth_token = _url(this._oauth_token); + sign_base_params.oauth_token = this._url(this._oauth_token); } - const oauth_params = _clone(sign_base_params); + const oauth_params = this._clone(sign_base_params); for (key in params) { if (!params.hasOwnProperty(key)) { continue; } sign_base_params[key] = params[key]; } - let keys = _ksort(sign_base_params); + let keys = this._ksort(sign_base_params); - const signature = _getSignature(httpmethod, method, keys, sign_base_params); + const signature = this._getSignature(httpmethod, method, keys, sign_base_params); params = oauth_params; params.oauth_signature = signature; - keys = _ksort(params); + keys = this._ksort(params); let authorization = "OAuth "; for (let i = 0; i < keys.length; i++) { key = keys[i]; - authorization += `${key}="${_url(params[key])}", `; + authorization += `${key}="${this._url(params[key])}", `; } return authorization.substring(0, authorization.length - 2); } @@ -1380,7 +1378,6 @@ return; } let post_fields; - const _sign = this._sign; if (httpmethod === "GET") { let url_with_params = url; @@ -1388,7 +1385,7 @@ url_with_params += "?" + this._http_build_query(params); } if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); + authorization = this._sign(httpmethod, url, params); } if (this._use_proxy) { @@ -1404,15 +1401,15 @@ } else { if (multipart) { if (!app_only_auth) { - authorization = _sign(httpmethod, url, {}); + authorization = this._sign(httpmethod, url, {}); } params = this._buildMultipart(method, params); } else if (this._detectJsonBody(method)) { - authorization = _sign(httpmethod, url, {}); + authorization = this._sign(httpmethod, url, {}); params = JSON.stringify(params); } else { if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); + authorization = this._sign(httpmethod, url, params); } params = this._http_build_query(params); } diff --git a/codebird.js b/codebird.js index 666f5f6..5b74559 100644 --- a/codebird.js +++ b/codebird.js @@ -22,7 +22,6 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons /* global window, document, navigator, - console, Ti, ActiveXObject, module, @@ -300,7 +299,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons var b = this._oauth_consumer_secret + "&" + (null !== this._oauth_token_secret ? this._oauth_token_secret : ""); if (this._oauth_consumer_secret === null) { - console.warn("To generate a hash, the consumer secret must be set."); + throw "To generate a hash, the consumer secret must be set."; } var c = q(b); if (c.length > 16) { @@ -403,7 +402,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if ((typeof a === "undefined" ? "undefined" : _typeof(a)) === "object") { for (b in a) { if (a.hasOwnProperty(b) && a[b] !== null) { - e.push(g(c + "[" + b + "]", a[b], d)); + e.push(g.call(this, c + "[" + b + "]", a[b], d)); } } return e.join(d); @@ -411,7 +410,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (typeof a !== "function") { return this._url(c) + "=" + this._url(a); } - console.warn("There was an error processing for http_build_query()."); + throw "There was an error processing for http_build_query()."; } else { return ""; } @@ -430,7 +429,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (f && !isNaN(c)) { c = String(f) + c; } - d = g(c, d, b); + d = g.call(this, c, d, b); if (d !== "") { h.push(d); } @@ -452,7 +451,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; if (length < 1) { - console.warn("Invalid nonce length."); + throw "Invalid nonce length."; } var nonce = ""; for (var i = 0; i < length; i++) { @@ -545,7 +544,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons try { xml = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { - console.error("ActiveXObject object not defined."); + throw "ActiveXObject object not defined."; } // now, consider RequireJS and/or Node.js objects } else if (typeof require === "function" && require) { @@ -560,7 +559,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons XMLHttpRequest = require("xhr2"); xml = new XMLHttpRequest(); } catch (e2) { - console.error("xhr2 object not defined, cancelling."); + throw "xhr2 object not defined, cancelling."; } } } @@ -664,7 +663,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons } else { key = eval(evalStr + ".push([]);") - 1; } - evalStr += "[" + key + "']"; + evalStr += "[" + key + "]"; if (j !== keys.length - 1 && eval("typeof " + evalStr) === "undefined") { eval(evalStr + " = [];"); } @@ -687,7 +686,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons value: function getApiMethods() { var httpmethods = { GET: ["account/settings", "account/verify_credentials", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", "favorites/list", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "site", "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "user", "users/contributees", "users/contributors", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], - POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload + POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload "users/lookup", "users/report_spam"] }; return httpmethods; @@ -885,7 +884,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons for (j = 0; j < 26; j++) { method_template = method_template.split(String.fromCharCode(65 + j)).join("_" + String.fromCharCode(97 + j)); } - console.warn("To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."); + throw "To call the templated method \"" + method_template + "\", specify the parameter value for \"" + param_l + "\"."; } method = method.split(param).join(apiparams[param_l]); delete apiparams[param_l]; @@ -970,20 +969,17 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_getSignature", value: function _getSignature(httpmethod, method, keys, base_params) { - var _url = this._url; - var _sha1 = this._sha1; // convert params to string - var base_string = "", key = undefined, value = undefined; for (var i = 0; i < keys.length; i++) { key = keys[i]; value = base_params[key]; - base_string += key + "=" + _url(value) + "&"; + base_string += key + "=" + this._url(value) + "&"; } base_string = base_string.substring(0, base_string.length - 1); - return _sha1(httpmethod + "&" + _url(method) + "&" + _url(base_string)); + return this._sha1(httpmethod + "&" + this._url(method) + "&" + this._url(base_string)); } /** @@ -1010,13 +1006,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "_sign", value: function _sign(httpmethod, method) { var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - var _url = this._url; - var _ksort = this._ksort; - var _clone = this._clone; - var _getSignature = this._getSignature; if (this._oauth_consumer_key === null) { - console.warn("To generate a signature, the consumer key must be set."); + throw "To generate a signature, the consumer key must be set."; } var sign_params = { consumer_key: this._oauth_consumer_key, @@ -1031,29 +1023,29 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons continue; } var value = sign_params[key]; - sign_base_params["oauth_" + key] = _url(value); + sign_base_params["oauth_" + key] = this._url(value); } if (this._oauth_token !== null) { - sign_base_params.oauth_token = _url(this._oauth_token); + sign_base_params.oauth_token = this._url(this._oauth_token); } - var oauth_params = _clone(sign_base_params); + var oauth_params = this._clone(sign_base_params); for (key in params) { if (!params.hasOwnProperty(key)) { continue; } sign_base_params[key] = params[key]; } - var keys = _ksort(sign_base_params); + var keys = this._ksort(sign_base_params); - var signature = _getSignature(httpmethod, method, keys, sign_base_params); + var signature = this._getSignature(httpmethod, method, keys, sign_base_params); params = oauth_params; params.oauth_signature = signature; - keys = _ksort(params); + keys = this._ksort(params); var authorization = "OAuth "; for (var i = 0; i < keys.length; i++) { key = keys[i]; - authorization += key + "=\"" + _url(params[key]) + "\", "; + authorization += key + "=\"" + this._url(params[key]) + "\", "; } return authorization.substring(0, authorization.length - 2); } @@ -1230,7 +1222,6 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons } if (this._oauth_token === null) { var error = "To get the " + type + " URL, the OAuth token must be set."; - console.warn(error); if (dfd) { dfd.reject({ error: error }); return this._getPromise(dfd); @@ -1282,12 +1273,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (this._oauth_consumer_key === null) { var error = "To obtain a bearer token, the consumer key must be set."; - console.warn(error); if (dfd) { dfd.reject({ error: error }); return this._getPromise(dfd); } - return false; + throw error; } if (!dfd && typeof callback === "undefined") { @@ -1383,7 +1373,6 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons return; } var post_fields = undefined; - var _sign = this._sign; if (httpmethod === "GET") { var url_with_params = url; @@ -1391,7 +1380,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons url_with_params += "?" + this._http_build_query(params); } if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); + authorization = this._sign(httpmethod, url, params); } if (this._use_proxy) { @@ -1401,15 +1390,15 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons } else { if (multipart) { if (!app_only_auth) { - authorization = _sign(httpmethod, url, {}); + authorization = this._sign(httpmethod, url, {}); } params = this._buildMultipart(method, params); } else if (this._detectJsonBody(method)) { - authorization = _sign(httpmethod, url, {}); + authorization = this._sign(httpmethod, url, {}); params = JSON.stringify(params); } else { if (!app_only_auth) { - authorization = _sign(httpmethod, url, params); + authorization = this._sign(httpmethod, url, params); } params = this._http_build_query(params); } @@ -1430,11 +1419,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (app_only_auth) { if (this._oauth_consumer_key === null && this._oauth_bearer_token === null) { var error = "To make an app-only auth API request, consumer key or bearer token must be set."; - console.warn(error); if (dfd) { dfd.reject({ error: error }); return this._getPromise(dfd); } + throw error; } // automatically fetch bearer token, if necessary if (this._oauth_bearer_token === null) { diff --git a/test/functional_tests.js b/test/functional_tests.js new file mode 100644 index 0000000..828d9ba --- /dev/null +++ b/test/functional_tests.js @@ -0,0 +1,37 @@ +const tape = require("tape"), + _test = require("tape-promise"), + test = _test(tape), // decorate tape + CodebirdT = require("./codebirdt"); + +function getCB() { + var cb = new CodebirdT.default; + cb.setConsumerKey("123", "456"); + + return cb; +} + +test("Tests statuses/update", function (t) { + const cb = getCB(); + t.plan(1); + + cb.setToken("123", "456"); + return cb.__call( + "statuses_update", + {"status": "Whohoo, I just tweeted!"} + ).then(function (reply, rate, err) { + t.deepEqual( + reply, + { + rate: { + limit: null, remaining: null, reset: null + }, + reply: { + errors: [ + { code: 215, message: "Bad Authentication data." } + ], + httpstatus: 400 + } + } + ) + }); +}); From 907423e0698944902f3bcb4deafa22c46b7b3b2f Mon Sep 17 00:00:00 2001 From: shabin-slr Date: Thu, 25 Aug 2016 21:24:04 +0530 Subject: [PATCH 34/48] Time stamp issue _time() was not returning the timeStamp as there was no return statement --- codebird.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebird.js b/codebird.js index 5b74559..1f3428b 100644 --- a/codebird.js +++ b/codebird.js @@ -989,7 +989,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_time", value: function _time() { - Math.round(new Date().getTime() / 1000); + return Math.round(new Date().getTime() / 1000); } /** From c4ed527c2bf637ac121b101445eea7912bf9c675 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 27 Aug 2016 19:34:51 +0200 Subject: [PATCH 35/48] Update GET statuses/oembed with new endpoint Fix #116. --- CHANGELOG | 1 + codebird.es7.js | 7 +++++++ codebird.js | 9 ++++++++- test/detection_tests.js | 4 ++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4f8f0e9..c4943b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ codebird-js - changelog + Transform codebase to EcmaScript 7 + #25 Add unit testing suite + Add POST statuses/unretweet/:id ++ #116 Update GET statuses/oembed with new endpoint 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index 1772b6f..8498588 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -63,6 +63,11 @@ */ this._endpoint_media = `${this._endpoint_base_media}1.1/`; + /** + * The publish API endpoint to use + */ + this._endpoint_publish = "https://publish.twitter.com/"; + /** * The API endpoint base to use */ @@ -1171,6 +1176,8 @@ url = this._endpoint_oauth + method; } else if (this._detectMedia(method)) { url = this._endpoint_media + method + ".json"; + } else if (method === "statuses/oembed") { + url = this._endpoint_publish + "oembed"; } else { url = this._endpoint + method + ".json"; } diff --git a/codebird.js b/codebird.js index 1f3428b..90a8a9e 100644 --- a/codebird.js +++ b/codebird.js @@ -75,6 +75,11 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons */ this._endpoint_media = this._endpoint_base_media + "1.1/"; + /** + * The publish API endpoint to use + */ + this._endpoint_publish = "https://publish.twitter.com/"; + /** * The API endpoint base to use */ @@ -989,7 +994,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_time", value: function _time() { - return Math.round(new Date().getTime() / 1000); + Math.round(new Date().getTime() / 1000); } /** @@ -1151,6 +1156,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons url = this._endpoint_oauth + method; } else if (this._detectMedia(method)) { url = this._endpoint_media + method + ".json"; + } else if (method === "statuses/oembed") { + url = this._endpoint_publish + "oembed"; } else { url = this._endpoint + method + ".json"; } diff --git a/test/detection_tests.js b/test/detection_tests.js index 68766b8..bd033c6 100644 --- a/test/detection_tests.js +++ b/test/detection_tests.js @@ -88,6 +88,10 @@ test("Tests _getEndpoint", function (t) { cb.call("_getEndpoint", ["media/upload", "media/upload"]), "https://upload.twitter.com/1.1/media/upload.json" ); + t.equal( + cb.call("_getEndpoint", ["statuses/oembed", "statuses/oembed"]), + "https://publish.twitter.com/oembed" + ); t.end(); }); From b896dc76307037c2902b73066048954f12f5fef9 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Sat, 27 Aug 2016 19:55:17 +0200 Subject: [PATCH 36/48] Add test for signing of boolean parameters See #115. --- codebird.es7.js | 2 +- codebird.js | 2 +- test/functional_tests.js | 4 ++-- test/oauth_tests.js | 11 +++++++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/codebird.es7.js b/codebird.es7.js index 8498588..480a668 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -1023,7 +1023,7 @@ * Generates the UNIX timestamp */ _time() { - Math.round(new Date().getTime() / 1000); + return Math.round(new Date().getTime() / 1000); } /** diff --git a/codebird.js b/codebird.js index 90a8a9e..bc8fd01 100644 --- a/codebird.js +++ b/codebird.js @@ -994,7 +994,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_time", value: function _time() { - Math.round(new Date().getTime() / 1000); + return Math.round(new Date().getTime() / 1000); } /** diff --git a/test/functional_tests.js b/test/functional_tests.js index 828d9ba..aa717aa 100644 --- a/test/functional_tests.js +++ b/test/functional_tests.js @@ -27,9 +27,9 @@ test("Tests statuses/update", function (t) { }, reply: { errors: [ - { code: 215, message: "Bad Authentication data." } + { code: 89, message: "Invalid or expired token." } ], - httpstatus: 400 + httpstatus: 401 } } ) diff --git a/test/oauth_tests.js b/test/oauth_tests.js index 863c25e..7c611b1 100644 --- a/test/oauth_tests.js +++ b/test/oauth_tests.js @@ -92,3 +92,14 @@ test("Tests oauth2_token", function (t) { ); }); }); + +test("Tests signing of boolean parameters", function (t) { + const cb = getCB(); + + t.equal( + cb.call("_getSignature", ["GET", "friends/ids", ["stringify_ids"], [true]]), + "OFNuMTEnE82pfI0cAdJPgtO4xzY=" + ); + + t.end(); +}); From c19ef95d8363afef63a82a000502e8de47762571 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 31 Aug 2016 20:31:44 +0200 Subject: [PATCH 37/48] Support POST media/upload with media_data base64 See #124. --- CHANGELOG | 1 + codebird.es7.js | 2 ++ codebird.js | 3 ++- test/media_tests.js | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 test/media_tests.js diff --git a/CHANGELOG b/CHANGELOG index c4943b9..bb44d83 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ codebird-js - changelog + #25 Add unit testing suite + Add POST statuses/unretweet/:id + #116 Update GET statuses/oembed with new endpoint ++ Support POST media/upload with media_data base64 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index 480a668..59f4a37 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -1096,6 +1096,7 @@ // only check specific parameters const possible_methods = [ // Tweets + "media/upload", "statuses/update_with_media", // Accounts "account/update_profile_background_image", @@ -1104,6 +1105,7 @@ ]; let possible_files = { // Tweets + "media/upload": "media", "statuses/update_with_media": "media[]", // Accounts "account/update_profile_background_image": "image", diff --git a/codebird.js b/codebird.js index bc8fd01..60d745d 100644 --- a/codebird.js +++ b/codebird.js @@ -1075,11 +1075,12 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons // only check specific parameters var possible_methods = [ // Tweets - "statuses/update_with_media", + "media/upload", "statuses/update_with_media", // Accounts "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; var possible_files = { // Tweets + "media/upload": "media", "statuses/update_with_media": "media[]", // Accounts "account/update_profile_background_image": "image", diff --git a/test/media_tests.js b/test/media_tests.js new file mode 100644 index 0000000..97781c0 --- /dev/null +++ b/test/media_tests.js @@ -0,0 +1,37 @@ +const tape = require("tape"), + _test = require("tape-promise"), + test = _test(tape), // decorate tape + CodebirdT = require("./codebirdt"); + +function getCB() { + var cb = new CodebirdT.default; + cb.setConsumerKey("123", "456"); + + return cb; +} + +test("Tests media/upload base64", function (t) { + const cb = getCB(); + t.plan(1); + + cb.setToken("123", "456"); + return cb.__call( + "media_upload", + {"media_data": "R0lGODlhPQBEAPeoAJosM//AwO/AwHVYZ/z595kzAP/s7P+goOXMv8+fhw/v739/f+8PD98fH/8mJl+fn/9ZWb8/PzWlwv///6wWGbImAPgTEMImIN9gUFCEm/gDALULDN8PAD6atYdCTX9gUNKlj8wZAKUsAOzZz+UMAOsJAP/Z2ccMDA8PD/95eX5NWvsJCOVNQPtfX/8zM8+QePLl38MGBr8JCP+zs9myn/8GBqwpAP/GxgwJCPny78lzYLgjAJ8vAP9fX/+MjMUcAN8zM/9wcM8ZGcATEL+QePdZWf/29uc/P9cmJu9MTDImIN+/r7+/vz8/P8VNQGNugV8AAF9fX8swMNgTAFlDOICAgPNSUnNWSMQ5MBAQEJE3QPIGAM9AQMqGcG9vb6MhJsEdGM8vLx8fH98AANIWAMuQeL8fABkTEPPQ0OM5OSYdGFl5jo+Pj/+pqcsTE78wMFNGQLYmID4dGPvd3UBAQJmTkP+8vH9QUK+vr8ZWSHpzcJMmILdwcLOGcHRQUHxwcK9PT9DQ0O/v70w5MLypoG8wKOuwsP/g4P/Q0IcwKEswKMl8aJ9fX2xjdOtGRs/Pz+Dg4GImIP8gIH0sKEAwKKmTiKZ8aB/f39Wsl+LFt8dgUE9PT5x5aHBwcP+AgP+WltdgYMyZfyywz78AAAAAAAD///8AAP9mZv///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAKgALAAAAAA9AEQAAAj/AFEJHEiwoMGDCBMqXMiwocAbBww4nEhxoYkUpzJGrMixogkfGUNqlNixJEIDB0SqHGmyJSojM1bKZOmyop0gM3Oe2liTISKMOoPy7GnwY9CjIYcSRYm0aVKSLmE6nfq05QycVLPuhDrxBlCtYJUqNAq2bNWEBj6ZXRuyxZyDRtqwnXvkhACDV+euTeJm1Ki7A73qNWtFiF+/gA95Gly2CJLDhwEHMOUAAuOpLYDEgBxZ4GRTlC1fDnpkM+fOqD6DDj1aZpITp0dtGCDhr+fVuCu3zlg49ijaokTZTo27uG7Gjn2P+hI8+PDPERoUB318bWbfAJ5sUNFcuGRTYUqV/3ogfXp1rWlMc6awJjiAAd2fm4ogXjz56aypOoIde4OE5u/F9x199dlXnnGiHZWEYbGpsAEA3QXYnHwEFliKAgswgJ8LPeiUXGwedCAKABACCN+EA1pYIIYaFlcDhytd51sGAJbo3onOpajiihlO92KHGaUXGwWjUBChjSPiWJuOO/LYIm4v1tXfE6J4gCSJEZ7YgRYUNrkji9P55sF/ogxw5ZkSqIDaZBV6aSGYq/lGZplndkckZ98xoICbTcIJGQAZcNmdmUc210hs35nCyJ58fgmIKX5RQGOZowxaZwYA+JaoKQwswGijBV4C6SiTUmpphMspJx9unX4KaimjDv9aaXOEBteBqmuuxgEHoLX6Kqx+yXqqBANsgCtit4FWQAEkrNbpq7HSOmtwag5w57GrmlJBASEU18ADjUYb3ADTinIttsgSB1oJFfA63bduimuqKB1keqwUhoCSK374wbujvOSu4QG6UvxBRydcpKsav++Ca6G8A6Pr1x2kVMyHwsVxUALDq/krnrhPSOzXG1lUTIoffqGR7Goi2MAxbv6O2kEG56I7CSlRsEFKFVyovDJoIRTg7sugNRDGqCJzJgcKE0ywc0ELm6KBCCJo8DIPFeCWNGcyqNFE06ToAfV0HBRgxsvLThHn1oddQMrXj5DyAQgjEHSAJMWZwS3HPxT/QMbabI/iBCliMLEJKX2EEkomBAUCxRi42VDADxyTYDVogV+wSChqmKxEKCDAYFDFj4OmwbY7bDGdBhtrnTQYOigeChUmc1K3QTnAUfEgGFgAWt88hKA6aCRIXhxnQ1yg3BCayK44EWdkUQcBByEQChFXfCB776aQsG0BIlQgQgE8qO26X1h8cEUep8ngRBnOy74E9QgRgEAC8SvOfQkh7FDBDmS43PmGoIiKUUEGkMEC/PJHgxw0xH74yx/3XnaYRJgMB8obxQW6kL9QYEJ0FIFgByfIL7/IQAlvQwEpnAC7DtLNJCKUoO/w45c44GwCXiAFB/OXAATQryUxdN4LfFiwgjCNYg+kYMIEFkCKDs6PKAIJouyGWMS1FSKJOMRB/BoIxYJIUXFUxNwoIkEKPAgCBZSQHQ1A2EWDfDEUVLyADj5AChSIQW6gu10bE/JG2VnCZGfo4R4d0sdQoBAHhPjhIB94v/wRoRKQWGRHgrhGSQJxCS+0pCZbEhAAOw=="} + ).then(function (reply, rate, err) { + t.deepEqual( + reply, + { + rate: { + limit: null, remaining: null, reset: null + }, + reply: { + errors: [ + { code: 89, message: "Invalid or expired token." } + ], + httpstatus: 200 + } + } + ) + }); +}); From 2e8df0cd22e689e488289d5e5432d11fcff2d641 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Wed, 31 Aug 2016 20:35:43 +0200 Subject: [PATCH 38/48] Fix README for base64 uploads --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28ce72e..45ff521 100644 --- a/README.md +++ b/README.md @@ -325,7 +325,7 @@ base64-encoded. **First** you send each image to Twitter, like this: ```javascript var params = { - "media": "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB+0lEQVR42mP8//8/Ay0BEwONwagFoxZQDljI0PP8x7/Z93/e+PxXmpMpXp5dh4+ZgYHh0bd/clxYnMuINaMtfvRLgp3RVZwVU+rkuz+eRz+//wXVxcrEkKnEceXTX0dRlhoNTmKDaOvzXwHHv6x9+gtN/M9/hpjTX+GmMzAw/P7HMOnOj+ff//35x/Ds+z9iLfjPwPDt7//QE1/Sz319/RNh3PkPf+58+Yup/t7Xf9p8zFKcTMRa4CLGCrFm1v2fSjs+pJ/7uuvl7w+//yO7HRkUq3GEyrCREMk+kqy2IiyH3/xhYGD48uf/rPs/Z93/yczIwM3CiFU9Hw5xnD4ouvTt4Tf0AP37n+HTb+w+UOBmIs2CICm2R9/+EZlqGRkYzIVYSLMgRIYtUYGdSAsMBFgUuJhIy2iMDAwt2pysjAwLHv78RcgnOcrs5BQVHEyMG579Imi6Nh9zrBxZFgixMW624pXnwldYcTAzLjDhZmUit7AzE2K54c7fp8eF1QhWRobFptwmgiwkF3b//jMwMjJ8+P3/zPs/yx/9Wvr412+MgBJlZ1xsyuOOrbAibMHH3/87b32fce/nR2ypnpuFMVGevU6TQ5SdqKKeEVez5cuf/7te/j727s+9L/++/v3PzcyowM1kIcTiLs7Kz8pIfNnOONouGrVg1AIGAJ6gvN4J6V9GAAAAAElFTkSuQmCC" + "media_data": "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB+0lEQVR42mP8//8/Ay0BEwONwagFoxZQDljI0PP8x7/Z93/e+PxXmpMpXp5dh4+ZgYHh0bd/clxYnMuINaMtfvRLgp3RVZwVU+rkuz+eRz+//wXVxcrEkKnEceXTX0dRlhoNTmKDaOvzXwHHv6x9+gtN/M9/hpjTX+GmMzAw/P7HMOnOj+ff//35x/Ds+z9iLfjPwPDt7//QE1/Sz319/RNh3PkPf+58+Yup/t7Xf9p8zFKcTMRa4CLGCrFm1v2fSjs+pJ/7uuvl7w+//yO7HRkUq3GEyrCREMk+kqy2IiyH3/xhYGD48uf/rPs/Z93/yczIwM3CiFU9Hw5xnD4ouvTt4Tf0AP37n+HTb+w+UOBmIs2CICm2R9/+EZlqGRkYzIVYSLMgRIYtUYGdSAsMBFgUuJhIy2iMDAwt2pysjAwLHv78RcgnOcrs5BQVHEyMG579Imi6Nh9zrBxZFgixMW624pXnwldYcTAzLjDhZmUit7AzE2K54c7fp8eF1QhWRobFptwmgiwkF3b//jMwMjJ8+P3/zPs/yx/9Wvr412+MgBJlZ1xsyuOOrbAibMHH3/87b32fce/nR2ypnpuFMVGevU6TQ5SdqKKeEVez5cuf/7te/j727s+9L/++/v3PzcyowM1kIcTiLs7Kz8pIfNnOONouGrVg1AIGAJ6gvN4J6V9GAAAAAElFTkSuQmCC" ); cb.__call( "media_upload", From 6bf3dcfec6538ea3881c7b7d55bc6eef1e69981b Mon Sep 17 00:00:00 2001 From: "J.M" Date: Mon, 5 Sep 2016 19:06:30 +0200 Subject: [PATCH 39/48] Fix check for require for webpack compatibility Fix #123 --- CHANGELOG | 1 + codebird.es7.js | 4 +--- codebird.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bb44d83..36492bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ codebird-js - changelog + Add POST statuses/unretweet/:id + #116 Update GET statuses/oembed with new endpoint + Support POST media/upload with media_data base64 +- Fix check for require for webpack compatibility 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index 59f4a37..34b83a0 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -502,9 +502,7 @@ throw "ActiveXObject object not defined."; } // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" - && require - ) { + } else if (typeof require === "function") { var XMLHttpRequest; // look for xmlhttprequest module try { diff --git a/codebird.js b/codebird.js index 60d745d..afc0523 100644 --- a/codebird.js +++ b/codebird.js @@ -552,7 +552,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons throw "ActiveXObject object not defined."; } // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function" && require) { + } else if (typeof require === "function") { var XMLHttpRequest; // look for xmlhttprequest module try { From 3f3c389defaae35d1f0e8b1fd7f730776a084454 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:45:21 +0200 Subject: [PATCH 40/48] Update metadata --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1d6762a..b2a6cb0 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "homepage": "http://www.jublo.net/projects/codebird/js", "bugs": "https://github.com/jublonet/codebird-js/issues", "license": "GPL-3.0+", - "author": "Jublo Solutions (http://jublo.net/)", + "author": "Jublo Limited (http://jublo.net/)", "contributors": [ "Joshua Atkins (http://atkins.im/)", "J.M. (http://mynetx.net/)" @@ -33,13 +33,13 @@ "test": "npm run build && npm run test-run" }, "devDependencies": { - "babel-cli": "^6.3.17", - "babel-preset-es2015": "^6.3.13", - "eslint": "^1.10.3", + "babel-cli": "^6.26.0", + "babel-preset-es2015": "^6.24.1", + "eslint": "^5.3.0", "faucet": "0.0.1", - "q": "^1.4.1", - "tape": "^4.4.0", - "tape-promise": "^1.0.1", + "q": "^1.5.1", + "tape": "^4.9.1", + "tape-promise": "^1.1.0", "xmlhttprequest": "^1.8.0" } } From f20afb54524812ee29fae476769ec3edb9546875 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:45:42 +0200 Subject: [PATCH 41/48] Update tests --- test/detection_tests.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/detection_tests.js b/test/detection_tests.js index bd033c6..8c7d7a1 100644 --- a/test/detection_tests.js +++ b/test/detection_tests.js @@ -14,7 +14,7 @@ test("Tests _detectMethod", function (t) { function () { cb.call("_detectMethod", ["non-existent", {}]); }, - /^Can't find HTTP method to use for "non-existent".$/ + "Can't find HTTP method to use for \"non-existent\"." ); // forced httpmethod @@ -24,6 +24,10 @@ test("Tests _detectMethod", function (t) { ); // normal detection + t.equal( + cb.call("_detectMethod", ["account_activity/all/:env_name/subscriptions", {}]), + "GET" + ); t.equal( cb.call("_detectMethod", ["search/tweets", {}]), "GET" @@ -54,7 +58,6 @@ test("Tests _detectMultipart", function (t) { const cb = getCB(); t.false(cb.call("_detectMultipart", ["statuses/update"])); - t.true(cb.call("_detectMultipart", ["statuses/update_with_media"])); t.true(cb.call("_detectMultipart", ["media/upload"])); t.end(); From f3cfab425f6bb0cca5d33ff6473e072d16e1a594 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:46:31 +0200 Subject: [PATCH 42/48] Update metadata --- codebird.es7.js | 4 ++-- test/codebirdm.es7.js | 4 ++-- test/codebirdt.es7.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codebird.es7.js b/codebird.es7.js index 34b83a0..6fdf38a 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -3,8 +3,8 @@ * * @package codebird * @version 3.0.0-dev - * @author Jublo Solutions - * @copyright 2010-2016 Jublo Solutions + * @author Jublo Limited + * @copyright 2010-2018 Jublo Limited * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ diff --git a/test/codebirdm.es7.js b/test/codebirdm.es7.js index 7d26d92..766ad4b 100644 --- a/test/codebirdm.es7.js +++ b/test/codebirdm.es7.js @@ -5,8 +5,8 @@ import CodebirdT from "./codebirdt"; * * @package codebird-test * @version 3.0.0-dev - * @author Jublo Solutions - * @copyright 2010-2016 Jublo Solutions + * @author Jublo Limited + * @copyright 2010-2018 Jublo Limited * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ diff --git a/test/codebirdt.es7.js b/test/codebirdt.es7.js index af81df2..88861d0 100644 --- a/test/codebirdt.es7.js +++ b/test/codebirdt.es7.js @@ -5,8 +5,8 @@ import Codebird from "../codebird"; * * @package codebird-test * @version 3.0.0-dev - * @author Jublo Solutions - * @copyright 2010-2016 Jublo Solutions + * @author Jublo Limited + * @copyright 2010-2018 Jublo Limited * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ From 7646ea9bd4da84d81ed9406ffa57009c404edb08 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:47:35 +0200 Subject: [PATCH 43/48] Add Account Activity API methods --- CHANGELOG | 1 + codebird.es7.js | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 36492bd..f887345 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ codebird-js - changelog + #116 Update GET statuses/oembed with new endpoint + Support POST media/upload with media_data base64 - Fix check for require for webpack compatibility ++ Add Account Activity API methods 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index 6fdf38a..e5e7095 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -629,6 +629,14 @@ GET: [ "account/settings", "account/verify_credentials", + "account_activity/all/:env_name/subscriptions", + "account_activity/all/:env_name/subscriptions/list", + "account_activity/all/:env_name/webhooks", + "account_activity/all/webhooks", + "account_activity/subscriptions/count", + "account_activity/webhooks", + "account_activity/webhooks/:webhook_id/subscriptions/all", + "account_activity/webhooks/:webhook_id/subscriptions/all/list", "application/rate_limit_status", "blocks/ids", "blocks/list", @@ -707,6 +715,10 @@ "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", + "account_activity/all/:env_name/subscriptions", + "account_activity/all/:env_name/webhooks", + "account_activity/webhooks", + "account_activity/webhooks/:webhook_id/subscriptions/all", "blocks/create", "blocks/destroy", "collections/create", From 3d707e9fcc3f8cdeb12bc44574cc44e01ef67f1f Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:49:05 +0200 Subject: [PATCH 44/48] Add new Direct Messages API --- CHANGELOG | 1 + codebird.es7.js | 32 ++++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f887345..e9e0d13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ codebird-js - changelog + Support POST media/upload with media_data base64 - Fix check for require for webpack compatibility + Add Account Activity API methods ++ Add new Direct Messages API 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index e5e7095..d99ad84 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -643,10 +643,17 @@ "collections/entries", "collections/list", "collections/show", - "direct_messages", - "direct_messages/sent", - "direct_messages/show", + "custom_profiles/:id", + "custom_profiles/list", + "direct_messages/events/list", + "direct_messages/events/show", + "direct_messages/welcome_messages/list", + "direct_messages/welcome_messages/rules/list", + "direct_messages/welcome_messages/rules/show", + "direct_messages/welcome_messages/show", "favorites/list", + "feedback/events", + "feedback/show/:id", "followers/ids", "followers/list", "friends/ids", @@ -728,10 +735,15 @@ "collections/entries/move", "collections/entries/remove", "collections/update", - "direct_messages/destroy", - "direct_messages/new", + "custom_profiles/new", + "direct_messages/events/new", + "direct_messages/indicate_typing", + "direct_messages/mark_read", + "direct_messages/welcome_messages/new", + "direct_messages/welcome_messages/rules/new", "favorites/create", "favorites/destroy", + "feedback/create", "friendships/create", "friendships/destroy", "friendships/update", @@ -1170,7 +1182,15 @@ */ _detectJsonBody(method) { const json_bodies = [ - "collections/entries/curate" + "collections/entries/curate", + "custom_profiles/new", + "direct_messages/events/new", + "direct_messages/indicate_typing", + "direct_messages/mark_read", + "direct_messages/welcome_messages/new", + "direct_messages/welcome_messages/rules/new", + "direct_messages/welcome_messages/update", + "media/metadata/create" ]; return json_bodies.indexOf(method) > -1; } From 0e3831e95b4e92127221bb94a07911ce83a49d09 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:49:34 +0200 Subject: [PATCH 45/48] - Remove sunsetting API methods --- CHANGELOG | 4 ++++ codebird.es7.js | 18 ++---------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e9e0d13..b492cbf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,10 @@ codebird-js - changelog - Fix check for require for webpack compatibility + Add Account Activity API methods + Add new Direct Messages API +- Remove sunsetting Direct Messages API +- Remove contributor API methods +- Remove deprecated statuses/update_with_media method +- Remove deprecated statuses/update_profile_background_image method 2.6.0 (2015-04-08) + Allow to get the supported API methods as array diff --git a/codebird.es7.js b/codebird.es7.js index d99ad84..b35bcf6 100644 --- a/codebird.es7.js +++ b/codebird.es7.js @@ -667,7 +667,6 @@ "geo/id/:place_id", "geo/reverse_geocode", "geo/search", - "geo/similar_places", "help/configuration", "help/languages", "help/privacy", @@ -689,8 +688,6 @@ "saved_searches/list", "saved_searches/show/:id", "search/tweets", - "site", - "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", @@ -703,9 +700,6 @@ "trends/available", "trends/closest", "trends/place", - "user", - "users/contributees", - "users/contributors", "users/profile_banner", "users/search", "users/show", @@ -716,11 +710,8 @@ POST: [ "account/remove_profile_banner", "account/settings__post", - "account/update_delivery_device", "account/update_profile", - "account/update_profile_background_image", "account/update_profile_banner", - "account/update_profile_colors", "account/update_profile_image", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/webhooks", @@ -756,6 +747,7 @@ "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", + "media/metadata/create", "media/upload", "mutes/users/create", "mutes/users/destroy", @@ -771,7 +763,6 @@ "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", - "statuses/update_with_media", // deprecated, use media/upload "users/lookup", "users/report_spam" ] @@ -1009,11 +1000,9 @@ _detectMultipart(method) { const multiparts = [ // Tweets - "statuses/update_with_media", "media/upload", // Users - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner" ]; @@ -1119,18 +1108,14 @@ const possible_methods = [ // Tweets "media/upload", - "statuses/update_with_media", // Accounts - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner" ]; let possible_files = { // Tweets "media/upload": "media", - "statuses/update_with_media": "media[]", // Accounts - "account/update_profile_background_image": "image", "account/update_profile_image": "image", "account/update_profile_banner": "banner" }; @@ -1168,6 +1153,7 @@ */ _detectMedia(method) { const medias = [ + "media/metadata/create", "media/upload" ]; return medias.indexOf(method) > -1; From 4300b525bf6e7d297cee680b698b1b98b3328156 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:49:48 +0200 Subject: [PATCH 46/48] Update README --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 45ff521..0f05c2d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ codebird-js =========== *A Twitter library in JavaScript.* -Copyright (C) 2010-2016 Jublo Solutions +Copyright (C) 2010-2018 Jublo Limited This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -355,7 +355,7 @@ cb.__call( ); ``` -More [documentation for tweeting with media](https://dev.twitter.com/rest/public/uploading-media-multiple-photos) is available on the Twitter Developer site. +More [documentation for uploading media](https://developer.twitter.com/en/docs/media/upload-media/overview) is available on the Twitter Developer site. ### Requests with app-only auth @@ -417,7 +417,7 @@ The library returns the response HTTP status code, so you can detect rate limits I suggest you to check if the ```reply.httpstatus``` property is ```400``` and check with the Twitter API to find out if you are currently being rate-limited. -See the [Rate Limiting FAQ](https://dev.twitter.com/rest/public/rate-limiting) +See the [Rate Limiting FAQ](https://developer.twitter.com/en/docs/basics/rate-limiting) for more information. If you allow your callback function to accept a second parameter, @@ -644,12 +644,13 @@ often they will be decomposed, efficient objects with information about users, Tweets, and timelines grouped, simplified, and stripped of unnecessary repetition. Never care about the OAuth signing specialities and the JSON POST body -for POST collections/entries/curate.json. Codebird takes off the work for you +for POST and PUT calls to these special APIs. Codebird takes off the work for you and will always send the correct Content-Type automatically. -Find out more about the [Collections API](https://dev.twitter.com/rest/collections/about) in the Twitter API docs. +Find out more about the [Collections API](https://developer.twitter.com/en/docs/tweets/curate-a-collection/overview/about_collections) in the Twitter API docs. +More information on the [Direct Messages API](https://developer.twitter.com/en/docs/direct-messages/api-features) and the [Account Activity API](https://developer.twitter.com/en/docs/accounts-and-users/subscribe-account-activity/overview) is available there as well. -Here’s a sample for adding a tweet using that API method: +Here’s a sample for adding a Tweet using the Collections API: ```javascript cb.__call( From ff7b3f62b41c6c798dc429214884d38a38034893 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:50:05 +0200 Subject: [PATCH 47/48] Update compiled file & package lockfile --- codebird.js | 171 ++- package-lock.json | 3618 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3701 insertions(+), 88 deletions(-) create mode 100644 package-lock.json diff --git a/codebird.js b/codebird.js index afc0523..ce4b7d9 100644 --- a/codebird.js +++ b/codebird.js @@ -1,10 +1,10 @@ "use strict"; -var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; })(); +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); -var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; -function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -13,8 +13,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons * * @package codebird * @version 3.0.0-dev - * @author Jublo Solutions - * @copyright 2010-2016 Jublo Solutions + * @author Jublo Limited + * @copyright 2010-2018 Jublo Limited * @license http://opensource.org/licenses/GPL-3.0 GNU Public License 3.0 * @link https://github.com/jublonet/codebird-php */ @@ -35,8 +35,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons * @package codebird * @subpackage codebird-js */ - - var Codebird = (function () { + var Codebird = function () { function Codebird() { _classCallCheck(this, Codebird); @@ -123,6 +122,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons * @return void */ + _createClass(Codebird, [{ key: "setConsumerKey", value: function setConsumerKey(key, secret) { @@ -263,7 +263,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons e[(b + 64 >> 9 << 4) + 15] = b; for (var c = new Array(80), a = 1732584193, d = -271733879, h = -1732584194, k = 271733878, g = -1009589776, p = 0; p < e.length; p += 16) { for (var o = a, q = d, r = h, s = k, t = g, f = 0; 80 > f; f++) { - var m = undefined; + var m = void 0; if (f < 16) { m = e[p + f]; @@ -346,10 +346,10 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_base64_encode", value: function _base64_encode(a) { - var d = undefined, - e = undefined, - f = undefined, - b = undefined, + var d = void 0, + e = void 0, + f = void 0, + b = void 0, g = 0, h = 0, i = this.b64_alphabet, @@ -396,7 +396,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "_http_build_query", value: function _http_build_query(e, f, b) { function g(c, a, d) { - var b = undefined, + var b = void 0, e = []; if (a === true) { a = "1"; @@ -453,7 +453,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_nonce", value: function _nonce() { - var length = arguments.length <= 0 || arguments[0] === undefined ? 8 : arguments[0]; + var length = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 8; if (length < 1) { throw "Invalid nonce length."; @@ -478,10 +478,10 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "_ksort", value: function _ksort(input_arr) { var keys = [], - sorter = undefined, - k = undefined; + sorter = void 0, + k = void 0; - sorter = function (a, b) { + sorter = function sorter(a, b) { var a_float = parseFloat(a), b_float = parseFloat(b), a_numeric = a_float + "" === a, @@ -543,31 +543,31 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons xml = new window.XMLHttpRequest(); // then, try Titanium framework object } else if ((typeof Ti === "undefined" ? "undefined" : _typeof(Ti)) === "object" && Ti && typeof Ti.Network.createHTTPClient !== "undefined") { - xml = Ti.Network.createHTTPClient(); - // are we in an old Internet Explorer? - } else if (typeof ActiveXObject !== "undefined") { - try { - xml = new ActiveXObject("Microsoft.XMLHTTP"); - } catch (e) { - throw "ActiveXObject object not defined."; - } - // now, consider RequireJS and/or Node.js objects - } else if (typeof require === "function") { - var XMLHttpRequest; - // look for xmlhttprequest module - try { - XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - xml = new XMLHttpRequest(); - } catch (e1) { - // or maybe the user is using xhr2 - try { - XMLHttpRequest = require("xhr2"); - xml = new XMLHttpRequest(); - } catch (e2) { - throw "xhr2 object not defined, cancelling."; - } - } - } + xml = Ti.Network.createHTTPClient(); + // are we in an old Internet Explorer? + } else if (typeof ActiveXObject !== "undefined") { + try { + xml = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) { + throw "ActiveXObject object not defined."; + } + // now, consider RequireJS and/or Node.js objects + } else if (typeof require === "function") { + var XMLHttpRequest; + // look for xmlhttprequest module + try { + XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + xml = new XMLHttpRequest(); + } catch (e1) { + // or maybe the user is using xhr2 + try { + XMLHttpRequest = require("xhr2"); + xml = new XMLHttpRequest(); + } catch (e2) { + throw "xhr2 object not defined, cancelling."; + } + } + } return xml; } @@ -690,9 +690,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "getApiMethods", value: function getApiMethods() { var httpmethods = { - GET: ["account/settings", "account/verify_credentials", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "direct_messages", "direct_messages/sent", "direct_messages/show", "favorites/list", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "geo/similar_places", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "site", "statuses/firehose", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "user", "users/contributees", "users/contributors", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], - POST: ["account/remove_profile_banner", "account/settings__post", "account/update_delivery_device", "account/update_profile", "account/update_profile_background_image", "account/update_profile_banner", "account/update_profile_colors", "account/update_profile_image", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "direct_messages/destroy", "direct_messages/new", "favorites/create", "favorites/destroy", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", "statuses/update_with_media", // deprecated, use media/upload - "users/lookup", "users/report_spam"] + GET: ["account/settings", "account/verify_credentials", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/subscriptions/list", "account_activity/all/:env_name/webhooks", "account_activity/all/webhooks", "account_activity/subscriptions/count", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "account_activity/webhooks/:webhook_id/subscriptions/all/list", "application/rate_limit_status", "blocks/ids", "blocks/list", "collections/entries", "collections/list", "collections/show", "custom_profiles/:id", "custom_profiles/list", "direct_messages/events/list", "direct_messages/events/show", "direct_messages/welcome_messages/list", "direct_messages/welcome_messages/rules/list", "direct_messages/welcome_messages/rules/show", "direct_messages/welcome_messages/show", "favorites/list", "feedback/events", "feedback/show/:id", "followers/ids", "followers/list", "friends/ids", "friends/list", "friendships/incoming", "friendships/lookup", "friendships/lookup", "friendships/no_retweets/ids", "friendships/outgoing", "friendships/show", "geo/id/:place_id", "geo/reverse_geocode", "geo/search", "help/configuration", "help/languages", "help/privacy", "help/tos", "lists/list", "lists/members", "lists/members/show", "lists/memberships", "lists/ownerships", "lists/show", "lists/statuses", "lists/subscribers", "lists/subscribers/show", "lists/subscriptions", "mutes/users/ids", "mutes/users/list", "oauth/authenticate", "oauth/authorize", "saved_searches/list", "saved_searches/show/:id", "search/tweets", "statuses/home_timeline", "statuses/mentions_timeline", "statuses/oembed", "statuses/retweeters/ids", "statuses/retweets/:id", "statuses/retweets_of_me", "statuses/sample", "statuses/show/:id", "statuses/user_timeline", "trends/available", "trends/closest", "trends/place", "users/profile_banner", "users/search", "users/show", "users/suggestions", "users/suggestions/:slug", "users/suggestions/:slug/members"], + POST: ["account/remove_profile_banner", "account/settings__post", "account/update_profile", "account/update_profile_banner", "account/update_profile_image", "account_activity/all/:env_name/subscriptions", "account_activity/all/:env_name/webhooks", "account_activity/webhooks", "account_activity/webhooks/:webhook_id/subscriptions/all", "blocks/create", "blocks/destroy", "collections/create", "collections/destroy", "collections/entries/add", "collections/entries/curate", "collections/entries/move", "collections/entries/remove", "collections/update", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "favorites/create", "favorites/destroy", "feedback/create", "friendships/create", "friendships/destroy", "friendships/update", "lists/create", "lists/destroy", "lists/members/create", "lists/members/create_all", "lists/members/destroy", "lists/members/destroy_all", "lists/subscribers/create", "lists/subscribers/destroy", "lists/update", "media/metadata/create", "media/upload", "mutes/users/create", "mutes/users/destroy", "oauth/access_token", "oauth/request_token", "oauth2/invalidate_token", "oauth2/token", "saved_searches/create", "saved_searches/destroy/:id", "statuses/destroy/:id", "statuses/filter", "statuses/lookup", "statuses/retweet/:id", "statuses/unretweet/:id", "statuses/update", "users/lookup", "users/report_spam"] }; return httpmethods; } @@ -842,9 +841,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "_mapFnRestoreParamUnderscores", value: function _mapFnRestoreParamUnderscores(method) { var url_parameters_with_underscore = ["screen_name", "place_id"]; - var i = undefined, - param = undefined, - replacement_was = undefined; + var i = void 0, + param = void 0, + replacement_was = void 0; for (i = 0; i < url_parameters_with_underscore.length; i++) { param = url_parameters_with_underscore[i].toUpperCase(); replacement_was = param.split("_").join("/"); @@ -867,9 +866,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons key: "_mapFnToApiMethod", value: function _mapFnToApiMethod(fn, apiparams) { var method = "", - param = undefined, - i = undefined, - j = undefined; + param = void 0, + i = void 0, + j = void 0; // replace _ by / method = this._mapFnInsertSlashes(fn); @@ -933,9 +932,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons } var apimethods = this.getApiMethods(); - for (var httpmethod in apimethods) { - if (apimethods.hasOwnProperty(httpmethod) && apimethods[httpmethod].indexOf(method) > -1) { - return httpmethod; + for (var _httpmethod in apimethods) { + if (apimethods.hasOwnProperty(_httpmethod) && apimethods[_httpmethod].indexOf(method) > -1) { + return _httpmethod; } } throw "Can't find HTTP method to use for \"" + method + "\"."; @@ -954,10 +953,10 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons value: function _detectMultipart(method) { var multiparts = [ // Tweets - "statuses/update_with_media", "media/upload", + "media/upload", // Users - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; + "account/update_profile_image", "account/update_profile_banner"]; return multiparts.indexOf(method) > -1; } @@ -976,8 +975,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons value: function _getSignature(httpmethod, method, keys, base_params) { // convert params to string var base_string = "", - key = undefined, - value = undefined; + key = void 0, + value = void 0; for (var i = 0; i < keys.length; i++) { key = keys[i]; value = base_params[key]; @@ -1010,7 +1009,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_sign", value: function _sign(httpmethod, method) { - var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; + var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (this._oauth_consumer_key === null) { throw "To generate a signature, the consumer key must be set."; @@ -1075,15 +1074,13 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons // only check specific parameters var possible_methods = [ // Tweets - "media/upload", "statuses/update_with_media", + "media/upload", // Accounts - "account/update_profile_background_image", "account/update_profile_image", "account/update_profile_banner"]; + "account/update_profile_image", "account/update_profile_banner"]; var possible_files = { // Tweets "media/upload": "media", - "statuses/update_with_media": "media[]", // Accounts - "account/update_profile_background_image": "image", "account/update_profile_image": "image", "account/update_profile_banner": "banner" }; @@ -1122,7 +1119,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_detectMedia", value: function _detectMedia(method) { - var medias = ["media/upload"]; + var medias = ["media/metadata/create", "media/upload"]; return medias.indexOf(method) > -1; } @@ -1137,7 +1134,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_detectJsonBody", value: function _detectJsonBody(method) { - var json_bodies = ["collections/entries/curate"]; + var json_bodies = ["collections/entries/curate", "custom_profiles/new", "direct_messages/events/new", "direct_messages/indicate_typing", "direct_messages/mark_read", "direct_messages/welcome_messages/new", "direct_messages/welcome_messages/rules/new", "direct_messages/welcome_messages/update", "media/metadata/create"]; return json_bodies.indexOf(method) > -1; } @@ -1152,7 +1149,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_getEndpoint", value: function _getEndpoint(method) { - var url = undefined; + var url = void 0; if (method.substring(0, 5) === "oauth") { url = this._endpoint_oauth + method; } else if (this._detectMedia(method)) { @@ -1182,7 +1179,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (reply === "[]") { return []; } - var parsed = undefined; + var parsed = void 0; try { parsed = JSON.parse(reply); } catch (e) { @@ -1214,9 +1211,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "oauth_authenticate", value: function oauth_authenticate() { - var params = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; - var callback = arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1]; - var type = arguments.length <= 2 || arguments[2] === undefined ? "authenticate" : arguments[2]; + var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var callback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "authenticate"; var dfd = this._getDfd(); if (typeof params.force_login === "undefined") { @@ -1289,7 +1286,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons } if (!dfd && typeof callback === "undefined") { - callback = function () {}; + callback = function callback() {}; } var post_fields = "grant_type=client_credentials"; @@ -1363,13 +1360,13 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "_callApi", value: function _callApi(httpmethod, method) { - var params = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; - var multipart = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var multipart = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var _this2 = this; - var app_only_auth = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4]; - var callback = arguments.length <= 5 || arguments[5] === undefined ? function () {} : arguments[5]; + var app_only_auth = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; + var callback = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : function () {}; var dfd = this._getDfd(); @@ -1380,7 +1377,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons if (xml === null) { return; } - var post_fields = undefined; + var post_fields = void 0; if (httpmethod === "GET") { var url_with_params = url; @@ -1510,9 +1507,9 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons }, { key: "__call", value: function __call(fn) { - var params = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var callback = arguments[2]; - var app_only_auth = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3]; + var app_only_auth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; if (typeof callback !== "function" && typeof params === "function") { callback = params; @@ -1521,7 +1518,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons app_only_auth = callback; } } else if (typeof callback === "undefined") { - callback = function () {}; + callback = function callback() {}; } switch (fn) { case "oauth_authenticate": @@ -1545,21 +1542,19 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons // map function name to API method - var _mapFnToApiMethod2 = this._mapFnToApiMethod(fn, apiparams); - - var _mapFnToApiMethod3 = _slicedToArray(_mapFnToApiMethod2, 2); - - var method = _mapFnToApiMethod3[0]; - var method_template = _mapFnToApiMethod3[1]; - var httpmethod = this._detectMethod(method_template, apiparams); - var multipart = this._detectMultipart(method_template); + var _mapFnToApiMethod2 = this._mapFnToApiMethod(fn, apiparams), + _mapFnToApiMethod3 = _slicedToArray(_mapFnToApiMethod2, 2), + method = _mapFnToApiMethod3[0], + method_template = _mapFnToApiMethod3[1], + httpmethod = this._detectMethod(method_template, apiparams), + multipart = this._detectMultipart(method_template); return this._callApi(httpmethod, method, apiparams, multipart, app_only_auth, callback); } }]); return Codebird; - })(); + }(); ; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f4adf61 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3618 @@ +{ + "name": "codebird", + "version": "3.0.0-dev", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", + "dev": true + }, + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "dev": true, + "requires": { + "acorn": "^5.0.3" + } + }, + "ajv": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", + "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.1" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "dev": true + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", + "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "optional": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "optional": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true, + "optional": true + }, + "babel-cli": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "chokidar": "^1.6.1", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", + "fs-readdir-recursive": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", + "slash": "^1.0.0", + "source-map": "^0.5.6", + "v8flags": "^2.1.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + } + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", + "dev": true + } + } + }, + "babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + } + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + } + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "optional": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", + "dev": true, + "requires": { + "color-name": "1.1.1" + } + }, + "color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", + "dev": true + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "es-abstract": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, + "escape-string-regexp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.4.tgz", + "integrity": "sha1-uF5nm0b3LQP7voo79yWdU1whti8=", + "dev": true + }, + "eslint": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", + "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", + "dev": true, + "requires": { + "ajv": "^6.5.0", + "babel-code-frame": "^6.26.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.2", + "imurmurhash": "^0.1.4", + "inquirer": "^5.2.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.11.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.5.0", + "string.prototype.matchall": "^2.0.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^4.0.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "dev": true, + "requires": { + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "optional": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "optional": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faucet": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/faucet/-/faucet-0.0.1.tgz", + "integrity": "sha1-WX3PHSGJosBiMhtZHo8VHtIDnZw=", + "dev": true, + "requires": { + "defined": "0.0.0", + "duplexer": "~0.1.1", + "minimist": "0.0.5", + "sprintf": "~0.1.3", + "tap-parser": "~0.4.0", + "tape": "~2.3.2", + "through2": "~0.2.3" + }, + "dependencies": { + "deep-equal": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.1.2.tgz", + "integrity": "sha1-skbCuApXCkfBG+HZvRBw7IeLh84=", + "dev": true + }, + "defined": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-0.0.0.tgz", + "integrity": "sha1-817qfXBekzuvE7LwOz+D2SFAOz4=", + "dev": true + }, + "minimist": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", + "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=", + "dev": true + }, + "tape": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-2.3.3.tgz", + "integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=", + "dev": true, + "requires": { + "deep-equal": "~0.1.0", + "defined": "~0.0.0", + "inherits": "~2.0.1", + "jsonify": "~0.0.0", + "resumer": "~0.0.0", + "through": "~2.3.4" + } + } + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true, + "optional": true + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "optional": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "optional": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "optional": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "globals": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz", + "integrity": "sha1-/iI5t1dJcuZ+QfgIgj+b+kqZHjc=", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "integrity": "sha1-bLtFIevVHODsCpNr/XZX736bFyo=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true, + "optional": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "optional": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "optional": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true, + "optional": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true, + "optional": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "optional": true, + "requires": { + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true, + "optional": true + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true, + "optional": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "number-is-nan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz", + "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "optional": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-homedir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz", + "integrity": "sha1-DWK99EuRb9O73PLKsZGUj7CU8Ac=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "output-file-sync": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", + "integrity": "sha1-0KM+7+YaIF+suQCS6CZZjVJFznY=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "optional": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true, + "optional": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz", + "integrity": "sha1-D5awAc6pCxJZLOVm7bl+wR5pvQU=", + "dev": true, + "optional": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "randomatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", + "dev": true, + "optional": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.5.tgz", + "integrity": "sha1-okJvjc1FUcd6M/lu3yiGojyClmk=", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "optional": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regexp.prototype.flags": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", + "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2" + } + }, + "regexpp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", + "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", + "dev": true + }, + "regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz", + "integrity": "sha1-IQZfcHJ60FOg3V6VesngDHVg2Qo=", + "dev": true, + "optional": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + } + } + }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "dev": true, + "requires": { + "through": "~2.3.4" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "5.5.11", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", + "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true, + "optional": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "sprintf": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/sprintf/-/sprintf-0.1.5.tgz", + "integrity": "sha1-j4PjmpMXwaUCy324BQ5Rxnn27c8=", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", + "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.10.0", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "regexp.prototype.flags": "^1.2.0" + } + }, + "string.prototype.trim": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", + "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz", + "integrity": "sha1-dRC2ZVZ8qRTMtdfgcnY6yWi+NyQ=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true + }, + "table": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", + "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "dev": true, + "requires": { + "ajv": "^6.0.1", + "ajv-keywords": "^3.0.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "tap-parser": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-0.4.3.tgz", + "integrity": "sha1-pOrhkMENdsehEZIf84u+TVjwnuo=", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.11" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", + "integrity": "sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "tape": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", + "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "defined": "~1.0.0", + "for-each": "~0.3.3", + "function-bind": "~1.1.1", + "glob": "~7.1.2", + "has": "~1.0.3", + "inherits": "~2.0.3", + "minimist": "~1.2.0", + "object-inspect": "~1.6.0", + "resolve": "~1.7.1", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.1.2", + "through": "~2.3.8" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "tape-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tape-promise/-/tape-promise-1.1.0.tgz", + "integrity": "sha1-5XVNU3GwtkoN74WllQtPwCZNoCk=", + "dev": true, + "requires": { + "onetime": "^1.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9", + "xtend": "~2.1.1" + }, + "dependencies": { + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "readable-stream": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", + "integrity": "sha1-9u73ZPUUyJ4rniMUanW6EGdW0j4=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "~0.4.0" + } + } + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "optional": true + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "^1.1.1" + }, + "dependencies": { + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", + "integrity": "sha1-HmWWmWXMvC20VIxrhKbyxa7dRzk=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xmlhttprequest": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", + "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", + "dev": true + } + } +} From 6061d130fa6e8247bf0c0639879e43647458a784 Mon Sep 17 00:00:00 2001 From: "J.M" Date: Thu, 16 Aug 2018 02:51:23 +0200 Subject: [PATCH 48/48] Update Node versions --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e6b67c..44e72cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,9 @@ node_js: - "0.10" - "0.12" - iojs - - "4" - - "5" + - "8" + - "9" + - "10" sudo: false