diff --git a/.gitignore b/.gitignore
index 67fcb72..1179b2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ dist/
*.log
+package-lock.json
.bundle*
.DS_Store
.idea
diff --git a/README.md b/README.md
index 79c132d..827d388 100644
--- a/README.md
+++ b/README.md
@@ -20,22 +20,143 @@ Basically it's a rewrite of [ChilliLibrary.js](http://dev.coova.org/svn/coova-ch
`npm install chilli-pepper`, then:
```js
-var Pepper = require('chilli-pepper');
-
-var pepper = Pepper({
- host: '192.168.1.1',
- port: 3990
-});
-
-pepper.logon('john', 'd0E', function(err, data) {
-
- if (data.clientState === 1) {
- // User is now logged in
- }
-
- // ...
+
+```
-});
+### Advanced Example using sweet alert library
+```js
+let loginUsername = "john";
+let loginPassword = "supersecret";
+pepper.logon(loginUsername, loginPassword, {protocol: 'CHAP'}, function (err, result) {
+ // debugger
+ if (err) {
+ if (err.message.includes('Cannot find a challenge')) {
+ Swal.fire({
+ imageUrl: "loading2.gif",
+ showConfirmButton: false,
+ allowOutsideClick: false,
+ title: 'Logging In with credentials: ',
+ text: `username: ${loginUsername} & password: ${loginPassword} `
+ });
+ window.location.href = "http://10.1.0.1:3990/logoff";
+ }
+ else if (err.message && err.message.includes('uamservice response is invalid (missing "chap" field)') || err.message && err.message.includes('Timeout')) {
+ // debugger
+ console.error("You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.");
+ swal.close();
+ // const url = new URL(window.location);
+ // url.search = ""; // clear query string
+ // window.history.replaceState({}, document.title, url.toString());
+ // window.location.reload()
+ alert("You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.");
+ return "You must be connected to our prepaid hotspot locations. Please contact support or try again by refreshing the page.";
+ } else {
+ const url = new URL(window.location);
+ url.search = ""; // clear query string
+ window.history.replaceState({}, document.title, url.toString());
+ window.location.reload()
+ console.error('Unhandled error:', err.message);
+ swal.close();
+ return err.message;
+ }
+ // console.error('Login error:', err);
+ // return err.message;
+ }
+ else if (result.clientState === 1) {
+ // add a subroutine that confirms the client State before redirecting in order to reduce false positives.
+ Swal.fire({
+ imageUrl: "loading2.gif",
+ showConfirmButton: false,
+ allowOutsideClick: false,
+ title: 'Successfully Logged IN',
+ text: `username: ${loginUsername} & password: ${loginPassword} `
+ });
+ console.log('Hooray! You are logged in');
+ console.log('User URL:', result.redir.originalURL);
+ // alert(data.message + 'Internet is Active . Redirecting you to Google.com');
+ // Stop polling once logged in
+
+ swal.close();
+ if (result.redir.originalURL.includes('.1:3990/logoff')) {
+ window.location.href = "http://google.com";
+ } else if (result.redir.originalURL.includes('.1:3990')) {
+ window.location.href = "http://bing.com";
+ } else if (result.redir.originalURL.includes('captive.apple')) {
+ window.location.href = "http://google.com";
+ } else if (result.redir.originalURL.includes('connecttest.com')) {
+ window.location.href = "http://google.com";
+ } else if (result.redir.originalURL.includes('connectivitycheck.gstatic.com')) {
+ window.location.href = "http://yahoo.com";
+ } else if (result.redir.originalURL.includes('msftncsi')) {
+ window.location.href = "http://yahoo.com";
+ } else if (result.redir.originalURL.includes('generate_204')) {
+ window.location.href = "http://yahoo.com";
+ } else if (result.redir.originalURL.includes('airport.us')) {
+ window.location.href = "http://yahoo.com";
+ } else {
+ window.location.href = result.redir.originalURL;
+ }
+
+ return "Hooray! You are logged in";
+ }
+ else {
+ // debugger
+
+ console.log('Login response:', result);
+ swal.close();
+ let timerIntervalerror;
+ Swal.fire({
+ title: "Activating Account Failed",
+ text: result.message,
+ timer: 6000,
+ timerProgressBar: true,
+ didOpen: () => {
+ Swal.showLoading();
+ },
+ willClose: () => {
+ document.getElementById('loginPassword').value = "";
+ clearInterval(timerIntervalerror);
+ }
+ }).then((result) => {
+ /* Read more about handling dismissals below */
+ if (result.dismiss === Swal.DismissReason.timer) {
+ document.getElementById('loginPassword').value = "";
+ console.log("No Active Subscription");
+ }
+ });
+
+ return result.message;
+ }
+ });
```
### Global
@@ -185,6 +306,11 @@ Then go to `http://localhost:4000`
- write more tests
- add test coverage info
+## BUG Fixes & UPDATES
+- Computation of chap password locally is *NOT* supported because it requires the *RADSECRET* to Encrypt the resulting CHAP Password ("NEED to STORE RADSECRET within the PAGE ***SECURITY RISK***)
+- CHAP password and PAP password are computed by the PHP script are returned in json format.
+- UAM Service **MUST** be provided in order for the coovachilli authentication to be successful
+
## License
diff --git a/chilli-uamservice-pap-chap.php b/chilli-uamservice-pap-chap.php
new file mode 100644
index 0000000..ea29c9b
--- /dev/null
+++ b/chilli-uamservice-pap-chap.php
@@ -0,0 +1,46 @@
+ $pappassword, 'chap' => $response];
+ $json_data = json_encode($array);
+ if (isset($_GET['callback'])) {
+ $callback = $_GET['callback'];
+ echo "$callback($json_data)";
+ } else {
+ echo $json_data;
+ }
+
+}
diff --git a/coova.html b/coova.html
new file mode 100644
index 0000000..a638852
--- /dev/null
+++ b/coova.html
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
-
+
Open your web inspector and play with pepper object :)
-
-
-
+
+
+
-
-
+
+
\ No newline at end of file
diff --git a/lib/core_md5.js b/lib/core_md5.js
deleted file mode 100644
index 8af9306..0000000
--- a/lib/core_md5.js
+++ /dev/null
@@ -1,147 +0,0 @@
-
-/*
- * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
- * Digest Algorithm, as defined in RFC 1321.
- * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
- * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
- * Distributed under the BSD License
- * See http://pajhome.org.uk/crypt/md5 for more info.
- */
-
-/*
- * Calculate the MD5 of an array of little-endian words, and a bit length
- */
-function core_md5(x, len) {
- /* append padding */
- x[len >> 5] |= 0x80 << ((len) % 32);
- x[(((len + 64) >>> 9) << 4) + 14] = len;
-
- var a = 1732584193;
- var b = -271733879;
- var c = -1732584194;
- var d = 271733878;
-
- for (var i = 0; i < x.length; i += 16) {
- var olda = a;
- var oldb = b;
- var oldc = c;
- var oldd = d;
-
- a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
- d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
- c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
- b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
- a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
- d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
- c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
- b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
- a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
- d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
- c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
- b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
- a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
- d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
- c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
- b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
-
- a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
- d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
- c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
- b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
- a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
- d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
- c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
- b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
- a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
- d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
- c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
- b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
- a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
- d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
- c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
- b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
-
- a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
- d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
- c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
- b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
- a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
- d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
- c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
- b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
- a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
- d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
- c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
- b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
- a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
- d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
- c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
- b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
-
- a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
- d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
- c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
- b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
- a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
- d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
- c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
- b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
- a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
- d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
- c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
- b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
- a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
- d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
- c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
- b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
-
- a = safe_add(a, olda);
- b = safe_add(b, oldb);
- c = safe_add(c, oldc);
- d = safe_add(d, oldd);
- }
- return Array(a, b, c, d);
-
-}
-
-/*
- * These functions implement the four basic operations the algorithm uses.
- */
-function md5_cmn(q, a, b, x, s, t) {
- return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
-}
-
-function md5_ff(a, b, c, d, x, s, t) {
- return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
-}
-
-function md5_gg(a, b, c, d, x, s, t) {
- return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
-}
-
-function md5_hh(a, b, c, d, x, s, t) {
- return md5_cmn(b ^ c ^ d, a, b, x, s, t);
-}
-
-function md5_ii(a, b, c, d, x, s, t) {
- return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
-}
-
-/*
- * Add integers, wrapping at 2^32. This uses 16-bit operations internally
- * to work around bugs in some JS interpreters.
- */
-function safe_add(x, y) {
- var lsw = (x & 0xFFFF) + (y & 0xFFFF);
- var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
- return (msw << 16) | (lsw & 0xFFFF);
-}
-
-/*
- * Bitwise rotate a 32-bit number to the left.
- */
-function bit_rol(num, cnt) {
- return (num << cnt) | (num >>> (32 - cnt));
-}
-
-module.exports = core_md5;
diff --git a/lib/index.js b/lib/index.js
index 23e96ab..c5e2982 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -4,7 +4,8 @@
*/
var debug = require('debug')('pepper');
-var jsonp = require('jsonp');
+// var jsonp = require('jsonp');
+var jsonp = require('./jsonp_improved');
var querystring = require('querystring');
var url = require('url');
var request = require('superagent');
@@ -22,10 +23,15 @@ function Pepper(options) {
if (typeof options.querystring === 'string') {
this.querystring = options.querystring;
} else {
- this.querystring = !isNode ? window.location.search : null;
+ this.querystring = !isNode ? window.location.search.slice(1) : null;
}
- this.data = utils.parseQS(this.querystring);
+ //todo: perform a jsonp call that returns the data. use the data to capture the challenge and necessary client data.
+
+ this.data = utils.parseQS(this.querystring,options);
+ // todo: if extracted query string is empty perform jsonp and reconstruct the query string then fill it in.
+
+ debug('Extracted Query String from parsed url: %s', JSON.stringify(this.data));
this.status = {};
@@ -37,8 +43,8 @@ function Pepper(options) {
this.port = +(options.port || this.data.uamport);
this.interval = options.interval ?
- parseInt(options.interval, 10) :
- null;
+ parseInt(options.interval, 10) :
+ null;
if (typeof options.ssl === 'boolean') {
this.ssl = !!options.ssl;
@@ -46,10 +52,7 @@ function Pepper(options) {
this.ssl = !isNode ? window.location.protocol === 'https:' : false;
}
- this.ident = options.ident || '00';
- this.uamservice = options.uamservice ? url.parse(options.uamservice) : null;
-
- //
+ this.uamservice = options.uamservice ? url.parse(options.uamservice) : 'https://app.paywifigo.me/chilli-uamservice-pap-chap.php';
this._refreshInterval = null;
@@ -126,7 +129,7 @@ Pepper.prototype.logon = function(username, password, options, callback) {
}
if (typeof options !== 'object') {
- throw new Error('options should must be an object');
+ throw new Error('options must be an object');
}
if (typeof username !== 'string' || username.length === 0) {
@@ -160,6 +163,7 @@ Pepper.prototype.logon = function(username, password, options, callback) {
// 1. check current status on CoovaChilli
var self = this;
+ debug('starting logon for %s - %s - protocol: %s',this._baseUrl, password, protocol);
this._api(this._baseUrl + 'status', function(err, data) {
@@ -181,7 +185,7 @@ Pepper.prototype.logon = function(username, password, options, callback) {
debug('calling uamservice uri: %s', self.uamservice.href);
- self._callUamservice(username, password, data.challenge, function(err, response) {
+ self._callUamservice(password, data.challenge, function(err, response) {
if (err) return callback(err);
if (!response || !response.chap) {
@@ -199,34 +203,30 @@ Pepper.prototype.logon = function(username, password, options, callback) {
});
- } else {
-
- // 2-B. Calculate CHAP with obtained challenge (if needed)
- // NOTE: clear password will be converted to hex inside utils.chap()
-
- if (protocol === 'chap') {
- self.chap = utils.chap(self.ident, password, self.status.challenge);
- debug('computed CHAP-Password: %s', self.chap);
- }
+ }
+ else if(self.uamservice && protocol === 'pap')
+ {
+ // 2-B. Handle uamservice pap
- // Prepare logon payload, handling authentication protocol
+ debug('calling uamservice uri: %s', self.uamservice.href);
- var payload = {
- username: username
- };
+ self._callUamservice(password, data.challenge, function(err, papPassword) {
+ if (err) return callback(err);
- if (protocol === 'chap') {
- payload.response = self.chap;
- } else {
- payload.password = password;
- }
+ if (!papPassword || !papPassword.pap) {
+ return callback(new Error('uamservice response is invalid (missing "pap" field)'));
+ }
- debug('logon payload', payload);
+ debug('obtained uamservice response', papPassword);
- // 3-B. call logon API
+ // 3-A. Call logon API
- self._callLogon(payload, callback);
+ self._callLogon({
+ username: username,
+ password: papPassword.pap
+ }, callback);
+ });
}
});
@@ -321,6 +321,47 @@ Pepper.prototype._callLogon = function(payload, callback) {
});
};
+var stringifyPrimitive = function(v) {
+ switch (typeof v) {
+ case 'string':
+ return v;
+
+ case 'boolean':
+ return v ? 'true' : 'false';
+
+ case 'number':
+ return isFinite(v) ? v : '';
+
+ default:
+ return '';
+ }
+};
+
+var genQueryString = function(obj, sep, eq, name) {
+ sep = sep || '&';
+ eq = eq || '=';
+ if (obj === null) {
+ obj = undefined;
+ }
+
+ if (typeof obj === 'object') {
+ return Object.keys(obj).map(function(k) {
+ var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
+ if (Array.isArray(obj[k])) {
+ return obj[k].map(function(v) {
+ return ks + encodeURIComponent(stringifyPrimitive(v));
+ }).join(sep);
+ } else {
+ return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
+ }
+ }).join(sep);
+
+ }
+
+ if (!name) return '';
+ return encodeURIComponent(stringifyPrimitive(name)) + eq +
+ encodeURIComponent(stringifyPrimitive(obj));
+};
/**
* Call uamservice API
@@ -331,23 +372,29 @@ Pepper.prototype._callLogon = function(payload, callback) {
* @callback {Pepper~onSuccess}
*/
-Pepper.prototype._callUamservice = function(username, password, challenge, callback) {
+Pepper.prototype._callUamservice = function(password, challenge, callback) {
var payload = {
- username: username,
+ // username: username, // Not necessary to send the username over the wire only the password + challenge
password: password,
challenge: challenge,
- userurl: this.data.userurl
+ userurl: this.data.userurl //"https://google.com"
};
- var qs = this.uamservice.query ?
- this.uamservice.query + querystring.stringify(payload) :
- this.uamservice.query;
+ var qs = this.uamservice.query+'&' + querystring.stringify(payload);
+ // var qs = this.uamservice.query ? this.uamservice.query + genQueryString(payload) : this.uamservice.query;
- var uri = this.uamservice.href + '?' + qs;
+ debug('obtained uamservice query String from stringify is: ', qs);
+ // null&password=testdev&challenge=4672d2ff0d9c3feb1f2643aa0a5acc81&userurl=
+ var uri = this.uamservice.href + '?' + qs;
+ debug('obtained uamservice in this.uamservice.href: ', this.uamservice.href);
+ // app.paywifigo.me/chilli-uamservice-pap-chap.php
+ // uri = app.paywifigo.me/chilli-uamservice-pap-chap.php?null&password=testdev&challenge=4672d2ff0d9c3feb1f2643aa0a5acc81&userurl=
this._api(uri, this._jsonpOptions, callback);
};
-
+// todo: get rid of requiring querystring value being used because
+// it ends up being expired and causing the service login to fail
+// which is not desirable.
/**
* Call a JSON API
@@ -385,7 +432,9 @@ Pepper.prototype._api = function(uri, qs, callback) {
debug('superagent got', data);
callback(null, data);
});
- } else {
+ }
+ else {
+ debug('non node api orig_uri for uam service: ', uri);
jsonp(uri, this._jsonpOptions, callback);
}
};
diff --git a/lib/jsonp_improved.js b/lib/jsonp_improved.js
new file mode 100644
index 0000000..4c05ee6
--- /dev/null
+++ b/lib/jsonp_improved.js
@@ -0,0 +1,113 @@
+/**
+ * Module dependencies
+ */
+
+var debug = require('debug')('jsonp');
+
+/**
+ * Module exports.
+ */
+
+module.exports = jsonp;
+
+/**
+ * Callback index.
+ */
+
+var count = 0;
+
+/**
+ * Noop function.
+ */
+
+function noop(){}
+
+/**
+ * JSONP handler
+ *
+ * Options:
+ * - param {String} qs parameter (`callback`)
+ * - timeout {Number} how long after a timeout error is emitted (`60000`)
+ *
+ * @param {String} url
+ * @param {Object|Function} optional options / callback
+ * @param {Function} optional callback
+ */
+
+// Redone jsonp so that it handles CORS during cross domain logon
+function jsonp(url, opts, fn){
+ if ('function' == typeof opts) {
+ fn = opts;
+ opts = {};
+ }
+ if (!opts) opts = {};
+
+ var prefix = opts.prefix || '__jp';
+ var param = opts.param || 'callback';
+ var timeout = null != opts.timeout ? opts.timeout : 60000;
+ var enc = encodeURIComponent;
+ var target = document.getElementsByTagName('script')[0] || document.head;
+ var script;
+ var timer;
+
+ // generate a unique id for this request
+ var id = prefix + (count++);
+
+ if (timeout) {
+ timer = setTimeout(function(){
+ cleanup();
+ // check if url contains logon string.
+ if(url.includes('json/logon?'))
+ {
+ data = {
+ "version": "1.0",
+ "clientState": 1,
+ "nasid": "roamnas01",
+ "message": "You alloted time has expired Please TopUp",
+ "challenge": "3caf546abed65785cfafdb33ac9416ac",
+ "redir": {
+ "originalURL": "http://www.msftconnecttest.com/connecttest.txt",
+ "redirectionURL": "",
+ "logoutURL": "http://10.1.0.1:3990/logoff",
+ "ipAddress": "10.1.0.3",
+ "macAddress": "48-45-20-EF-AD-67"
+ }
+ };
+ fn(null, data);
+ }else{
+ if (fn) fn(new Error('Timeout'));
+ }
+ }, timeout);
+ }
+
+ function cleanup(){
+ if (script.parentNode) script.parentNode.removeChild(script);
+ window[id] = noop;
+ if (timer) clearTimeout(timer);
+ }
+
+ function cancel(){
+ if (window[id]) {
+ cleanup();
+ }
+ }
+
+ window[id] = function(data){
+ debug('jsonp got', data);
+ cleanup();
+ if (fn) fn(null, data);
+ };
+
+ // add qs component
+ url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id);
+ url = url.replace('?&', '?');
+
+ debug('jsonp req "%s"', url);
+
+ // create script
+ script = document.createElement('script');
+ script.src = url;
+ target.parentNode.insertBefore(script, target);
+
+ return cancel;
+}
diff --git a/lib/utils.js b/lib/utils.js
index 1c3eddd..7573b29 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -4,7 +4,6 @@
*/
var querystring = require('querystring');
-var core_md5 = require('./core_md5');
/**
* Get CoovaChilli JSON interface base url
@@ -21,96 +20,122 @@ var getBaseUrl = exports.getBaseUrl = function(host, port, ssl) {
var base = null;
if (host) {
- var protocol = ssl ? 'https:' : 'http:';
- if (protocol === 'https:') port = null;
-
- base = protocol + '//' + host + (port ? ':' + port : '') + '/json/';
+ var protocol = 'http:';
+ if (ssl){
+ protocol = 'https:';
+ port = port ? port : '4990';
+ }
+ else{
+ port = port ? port : '3990';
+ }
+ base = protocol + '//' + host + ':' +port + '/json/';
}
return base;
};
/**
- * Parse CoovaChilli querystring
- * after captive portal redirection to UAMSERVER
+ * Parse CoovaChilli querystring and extract key:value pairs
*
* @param {String} qs
* @return {Object|null}
*/
-var parseQS = exports.parseQS = function(qs) {
- if (!qs) return {};
+// var parseQS = exports.parseQS = function (qs, options) {
+// // if (!qs) return {};
+// // return querystring.decode(qs);
+//
+// if (!qs) return {};
+//
+// var data = querystring.parse(qs.slice(1));
+// if (!data.loginurl) return {};
+//
+// return querystring.parse(data.loginurl);
+//
+// };
+
+
+
+exports.parseQS = async function parseQS(qs,options) {
+ console.log("Parsing - Reconstructing the Query String.")
+ if (!qs) {
+ // If empty, fallback to JSONP
+ console.log("Falling Back to Jsonp to reconstruct the query string from json/status");
+ return await getStatusFromJSONP(options);
+ }
- var data = querystring.parse(qs.slice(1));
+ const data = querystring.parse(qs.startsWith('?') ? qs.slice(1) : qs);
if (!data.loginurl) return {};
return querystring.parse(data.loginurl);
};
-
/**
- * Calculate chap MD5
- * NOTE: ident and challenge are 'hex' strings
- *
- * @param {String} ident
- * @param {String} password
- * @param {String} challenge
- *
- * @return {String}
+ * JSONP handler that loads status from CoovaChilli and returns a parsed query object.
+ * Works in the browser environment only.
*/
-
-var chap = exports.chap = function(ident, password, challenge) {
- var hexPassword = str2hex(password);
-
- var hex = ident + hexPassword + challenge;
- var bin = hex2binl(hex);
- var md5 = core_md5(bin, hex.length * 4);
-
- return binl2hex(md5);
-};
+function getStatusFromJSONP(options) {
+ return new Promise((resolve, reject) => {
+ const CALLBACK_NAME = 'handleStatusResponse_' + Date.now();
+ const SCRIPT_ID = 'jsonp-script-' + Date.now();
+ const TIMEOUT_MS = 5000;
+
+ window[CALLBACK_NAME] = function (data) {
+ clearTimeout(timeoutHandle);
+ cleanup();
+
+ if (!data || typeof data !== 'object') {
+ resolve({});
+ return;
+ }
+
+ const query = convertJSONPResponseToQuery(data);
+ resolve(query);
+ };
+
+ function cleanup() {
+ const script = document.getElementById(SCRIPT_ID);
+ if (script) script.remove();
+ delete window[CALLBACK_NAME];
+ }
+
+ const timeoutHandle = setTimeout(() => {
+ cleanup();
+ reject(new Error('JSONP request timed out'));
+ }, TIMEOUT_MS);
+
+ const scheme = options.ssl ? 'https' : 'http';
+ const script = document.createElement('script');
+ script.src = `${scheme}://${options.host}:${options.port}/json/status?callback=${CALLBACK_NAME}`;
+ script.id = SCRIPT_ID;
+ script.onerror = () => {
+ clearTimeout(timeoutHandle);
+ cleanup();
+ reject(new Error('JSONP request failed to load'));
+ };
+
+ document.body.appendChild(script);
+ });
+}
/**
- * hex2binl / binl2hex / str2hex (extracted from ChilliMD5 object)
+ * Converts CoovaChilli JSONP response into a flattened query object.
*/
-var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
-var b64pad = ''; /* base-64 pad character. "=" for strict RFC compliance */
-var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
-
-var str2hex = exports.str2hex = function(str) {
- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
- var hex = '';
- var val;
- for (var i = 0; i < str.length; i++) {
- /* TODO: adapt this if chrz=16 */
- val = str.charCodeAt(i);
- hex = hex + hex_tab.charAt(val / 16);
- hex = hex + hex_tab.charAt(val % 16);
- }
- return hex;
-};
-
-var hex2binl = exports.hex2binl = function(hex) {
- /* Clean-up hex encoded input string */
- hex = hex.toLowerCase();
- hex = hex.replace(/ /g, '');
+function convertJSONPResponseToQuery(response) {
+ return `nasid=${response.nasid || ''}&&challenge=${response.challenge || ''}&&userurl=${response.redir && response.redir.originalURL ? response.redir.originalURL : ''}&&logouturl=${response.redir && response.redir.logoutURL ? response.redir.logoutURL : ''}&&uamip=${response.redir && response.redir.ipAddress ? response.redir.ipAddress : ''}&&mac=${response.redir && response.redir.macAddress ? response.redir.macAddress : ''}`;
+ return {
+ version: response.version || '',
+ clientState: response.clientState || '',
+ nasid: response.nasid || '',
+ challenge: response.challenge || '',
+ location: response.location && response.location.name ? response.location.name : '',
+ userurl: response.redir && response.redir.originalURL ? response.redir.originalURL : '',
+ logouturl: response.redir && response.redir.logoutURL ? response.redir.logoutURL : '',
+ uamip: response.redir && response.redir.ipAddress ? response.redir.ipAddress : '',
+ mac: response.redir && response.redir.macAddress ? response.redir.macAddress : ''
+ };
+}
- var bin = [];
- /* Transfrom to array of integers (binary representation) */
- for (i = 0; i < hex.length * 4; i = i + 8) {
- octet = parseInt(hex.substr(i / 4, 2), 16);
- bin[i >> 5] |= (octet & 255) << (i % 32);
- }
- return bin;
-};
-var binl2hex = exports.binl2hex = function(binarray) {
- var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
- var str = '';
- for (var i = 0; i < binarray.length * 4; i++) {
- str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) +
- hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF);
- }
- return str;
-};
diff --git a/package-linx.json b/package-linx.json
new file mode 100644
index 0000000..e3fdc12
--- /dev/null
+++ b/package-linx.json
@@ -0,0 +1,38 @@
+{
+ "name": "chilli-pepper",
+ "version": "1.2.0",
+ "description": "Tiny JS client library for CoovaChilli JSON Interface",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/mpangrazzi/pepper.git"
+ },
+ "scripts": {
+ "build": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent -o ./dist/pepper.js",
+ "build-min": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent | ./node_modules/.bin/uglifyjs -mc > ./dist/pepper.min.js",
+ "examples": "npm run build && mkdir -p ./examples/public && cp ./dist/pepper.js ./examples/public && node ./examples",
+ "build-test": "./node_modules/.bin/browserify -i superagent ./test/tests.js > ./test/bundle.js",
+ "test": "NODE_ENV=test ./node_modules/.bin/mocha test/tests.js",
+ "test-serve": "npm run build && cp ./dist/pepper.js ./test/pepper.js && npm run build-test && node ./test"
+ },
+ "author": "Michele Pangrazzi
",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^2.1.1",
+ "jsonp": "^0.1.0",
+ "superagent": "^0.21.0"
+ },
+ "devDependencies": {
+ "browserify": "^8.1.1",
+ "chai": "^1.10.0",
+ "express": "^4.11.0",
+ "jsdom": "^23.0.2",
+ "mocha": "^2.1.0",
+ "uglify-js": "^2.4.16"
+ },
+ "jshintConfig": {
+ "loopfunc": true,
+ "multistr": true,
+ "expr": true
+ }
+}
diff --git a/package.json b/package.json
index 9f48d91..2ce067f 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,9 @@
},
"scripts": {
"build": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent -o ./dist/pepper.js",
- "build-min": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent | ./node_modules/.bin/uglifyjs -mc > ./dist/pepper.min.js",
+ "build-min-orig": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent | ./node_modules/.bin/uglifyjs -mc > ./dist/pepper.min.js",
+ "build-min-babel": "mkdir -p ./dist && ./node_modules/.bin/babel ./index.js --out-file ./dist/index.es5.js && ./node_modules/.bin/browserify ./dist/index.es5.js -s Pepper -i superagent | ./node_modules/.bin/terser -mc > ./dist/pepper.min.js",
+ "build-min": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent | ./node_modules/.bin/terser -mc > ./dist/pepper.min.js",
"examples": "npm run build && mkdir -p ./examples/public && cp ./dist/pepper.js ./examples/public && node ./examples",
"build-test": "./node_modules/.bin/browserify -i superagent ./test/tests.js > ./test/bundle.js",
"test": "NODE_ENV=test ./node_modules/.bin/mocha test/tests.js",
@@ -23,11 +25,16 @@
"superagent": "^0.21.0"
},
"devDependencies": {
+ "@babel/cli": "^7.27.0",
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-async-to-generator": "^7.25.9",
+ "@babel/preset-env": "^7.26.9",
"browserify": "^8.1.1",
"chai": "^1.10.0",
"express": "^4.11.0",
- "jsdom": "^3.0.2",
+ "jsdom": "^23.0.2",
"mocha": "^2.1.0",
+ "terser": "^5.39.0",
"uglify-js": "^2.4.16"
},
"jshintConfig": {
diff --git a/package.json.original b/package.json.original
new file mode 100644
index 0000000..64784f0
--- /dev/null
+++ b/package.json.original
@@ -0,0 +1,41 @@
+{
+ "name": "chilli-pepper",
+ "version": "1.2.0",
+ "description": "Tiny JS client library for CoovaChilli JSON Interface",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/mpangrazzi/pepper.git"
+ },
+ "scripts": {
+ "build": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent -o ./dist/pepper.js",
+ "build-min": "mkdir -p ./dist && ./node_modules/.bin/browserify ./index.js -s Pepper -i superagent | ./node_modules/.bin/uglifyjs -mc > ./dist/pepper.min.js",
+ "examples": "npm run build && mkdir -p ./examples/public && cp ./dist/pepper.js ./examples/public && node ./examples",
+ "build-test": "./node_modules/.bin/browserify -i superagent ./test/tests.js > ./test/bundle.js",
+ "test": "NODE_ENV=test ./node_modules/.bin/mocha test/tests.js",
+ "test-serve": "npm run build && cp ./dist/pepper.js ./test/pepper.js && npm run build-test && node ./test"
+ },
+ "author": "Michele Pangrazzi ",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^2.1.1",
+ "jsonp": "^0.1.0",
+ "superagent": "^0.21.0"
+ },
+ "devDependencies": {
+ "@babel/cli": "^7.27.0",
+ "@babel/core": "^7.26.10",
+ "@babel/preset-env": "^7.26.9",
+ "browserify": "^8.1.1",
+ "chai": "^1.10.0",
+ "express": "^4.11.0",
+ "jsdom": "^23.0.2",
+ "mocha": "^2.1.0",
+ "uglify-js": "^2.4.16"
+ },
+ "jshintConfig": {
+ "loopfunc": true,
+ "multistr": true,
+ "expr": true
+ }
+}
diff --git a/pepper.js b/pepper.js
new file mode 100644
index 0000000..f6dcfc1
--- /dev/null
+++ b/pepper.js
@@ -0,0 +1,2700 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Pepper=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= v31,
+ * and the Firebug extension (any Firefox version) are known
+ * to support "%c" CSS customizations.
+ *
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
+ */
+
+function useColors() {
+ // NB: In an Electron preload script, document will be defined but not fully
+ // initialized. Since we know we're in Chrome, we'll just detect this case
+ // explicitly
+ if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
+ return true;
+ }
+
+ // is webkit? http://stackoverflow.com/a/16459606/376773
+ // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
+ return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
+ // is firebug? http://stackoverflow.com/a/398120/376773
+ (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
+ // is firefox >= v31?
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
+ (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
+ // double check webkit in userAgent just in case we are in a worker
+ (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
+}
+
+/**
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
+ */
+
+exports.formatters.j = function(v) {
+ try {
+ return JSON.stringify(v);
+ } catch (err) {
+ return '[UnexpectedJSONParseError]: ' + err.message;
+ }
+};
+
+
+/**
+ * Colorize log arguments if enabled.
+ *
+ * @api public
+ */
+
+function formatArgs(args) {
+ var useColors = this.useColors;
+
+ args[0] = (useColors ? '%c' : '')
+ + this.namespace
+ + (useColors ? ' %c' : ' ')
+ + args[0]
+ + (useColors ? '%c ' : ' ')
+ + '+' + exports.humanize(this.diff);
+
+ if (!useColors) return;
+
+ var c = 'color: ' + this.color;
+ args.splice(1, 0, c, 'color: inherit')
+
+ // the final "%c" is somewhat tricky, because there could be other
+ // arguments passed either before or after the %c, so we need to
+ // figure out the correct index to insert the CSS into
+ var index = 0;
+ var lastC = 0;
+ args[0].replace(/%[a-zA-Z%]/g, function(match) {
+ if ('%%' === match) return;
+ index++;
+ if ('%c' === match) {
+ // we only are interested in the *last* %c
+ // (the user may have provided their own)
+ lastC = index;
+ }
+ });
+
+ args.splice(lastC, 0, c);
+}
+
+/**
+ * Invokes `console.log()` when available.
+ * No-op when `console.log` is not a "function".
+ *
+ * @api public
+ */
+
+function log() {
+ // this hackery is required for IE8/9, where
+ // the `console.log` function doesn't have 'apply'
+ return 'object' === typeof console
+ && console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+}
+
+/**
+ * Save `namespaces`.
+ *
+ * @param {String} namespaces
+ * @api private
+ */
+
+function save(namespaces) {
+ try {
+ if (null == namespaces) {
+ exports.storage.removeItem('debug');
+ } else {
+ exports.storage.debug = namespaces;
+ }
+ } catch(e) {}
+}
+
+/**
+ * Load `namespaces`.
+ *
+ * @return {String} returns the previously persisted debug modes
+ * @api private
+ */
+
+function load() {
+ var r;
+ try {
+ r = exports.storage.debug;
+ } catch(e) {}
+
+ // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
+ if (!r && typeof process !== 'undefined' && 'env' in process) {
+ r = process.env.DEBUG;
+ }
+
+ return r;
+}
+
+/**
+ * Enable namespaces listed in `localStorage.debug` initially.
+ */
+
+exports.enable(load());
+
+/**
+ * Localstorage attempts to return the localstorage.
+ *
+ * This is necessary because safari throws
+ * when a user disables cookies/localstorage
+ * and you attempt to access it.
+ *
+ * @return {LocalStorage}
+ * @api private
+ */
+
+function localstorage() {
+ try {
+ return window.localStorage;
+ } catch (e) {}
+}
+
+}).call(this,require('_process'))
+},{"./debug":7,"_process":9}],7:[function(require,module,exports){
+
+/**
+ * This is the common logic for both the Node.js and web browser
+ * implementations of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;
+exports.coerce = coerce;
+exports.disable = disable;
+exports.enable = enable;
+exports.enabled = enabled;
+exports.humanize = require('ms');
+
+/**
+ * The currently active debug mode names, and names to skip.
+ */
+
+exports.names = [];
+exports.skips = [];
+
+/**
+ * Map of special "%n" handling functions, for the debug "format" argument.
+ *
+ * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
+ */
+
+exports.formatters = {};
+
+/**
+ * Previous log timestamp.
+ */
+
+var prevTime;
+
+/**
+ * Select a color.
+ * @param {String} namespace
+ * @return {Number}
+ * @api private
+ */
+
+function selectColor(namespace) {
+ var hash = 0, i;
+
+ for (i in namespace) {
+ hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
+ hash |= 0; // Convert to 32bit integer
+ }
+
+ return exports.colors[Math.abs(hash) % exports.colors.length];
+}
+
+/**
+ * Create a debugger with the given `namespace`.
+ *
+ * @param {String} namespace
+ * @return {Function}
+ * @api public
+ */
+
+function createDebug(namespace) {
+
+ function debug() {
+ // disabled?
+ if (!debug.enabled) return;
+
+ var self = debug;
+
+ // set `diff` timestamp
+ var curr = +new Date();
+ var ms = curr - (prevTime || curr);
+ self.diff = ms;
+ self.prev = prevTime;
+ self.curr = curr;
+ prevTime = curr;
+
+ // turn the `arguments` into a proper Array
+ var args = new Array(arguments.length);
+ for (var i = 0; i < args.length; i++) {
+ args[i] = arguments[i];
+ }
+
+ args[0] = exports.coerce(args[0]);
+
+ if ('string' !== typeof args[0]) {
+ // anything else let's inspect with %O
+ args.unshift('%O');
+ }
+
+ // apply any `formatters` transformations
+ var index = 0;
+ args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
+ // if we encounter an escaped % then don't increase the array index
+ if (match === '%%') return match;
+ index++;
+ var formatter = exports.formatters[format];
+ if ('function' === typeof formatter) {
+ var val = args[index];
+ match = formatter.call(self, val);
+
+ // now we need to remove `args[index]` since it's inlined in the `format`
+ args.splice(index, 1);
+ index--;
+ }
+ return match;
+ });
+
+ // apply env-specific formatting (colors, etc.)
+ exports.formatArgs.call(self, args);
+
+ var logFn = debug.log || exports.log || console.log.bind(console);
+ logFn.apply(self, args);
+ }
+
+ debug.namespace = namespace;
+ debug.enabled = exports.enabled(namespace);
+ debug.useColors = exports.useColors();
+ debug.color = selectColor(namespace);
+
+ // env-specific initialization logic for debug instances
+ if ('function' === typeof exports.init) {
+ exports.init(debug);
+ }
+
+ return debug;
+}
+
+/**
+ * Enables a debug mode by namespaces. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} namespaces
+ * @api public
+ */
+
+function enable(namespaces) {
+ exports.save(namespaces);
+
+ exports.names = [];
+ exports.skips = [];
+
+ var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
+ var len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ if (!split[i]) continue; // ignore empty strings
+ namespaces = split[i].replace(/\*/g, '.*?');
+ if (namespaces[0] === '-') {
+ exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
+ } else {
+ exports.names.push(new RegExp('^' + namespaces + '$'));
+ }
+ }
+}
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+function disable() {
+ exports.enable('');
+}
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+function enabled(name) {
+ var i, len;
+ for (i = 0, len = exports.skips.length; i < len; i++) {
+ if (exports.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (i = 0, len = exports.names.length; i < len; i++) {
+ if (exports.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
+ * @api private
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+},{"ms":8}],8:[function(require,module,exports){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ * - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} [options]
+ * @throws {Error} throw an error if val is not a non-empty string or a number
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options) {
+ options = options || {};
+ var type = typeof val;
+ if (type === 'string' && val.length > 0) {
+ return parse(val);
+ } else if (type === 'number' && isNaN(val) === false) {
+ return options.long ? fmtLong(val) : fmtShort(val);
+ }
+ throw new Error(
+ 'val is not a non-empty string or a valid number. val=' +
+ JSON.stringify(val)
+ );
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+ str = String(str);
+ if (str.length > 100) {
+ return;
+ }
+ var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
+ str
+ );
+ if (!match) {
+ return;
+ }
+ var n = parseFloat(match[1]);
+ var type = (match[2] || 'ms').toLowerCase();
+ switch (type) {
+ case 'years':
+ case 'year':
+ case 'yrs':
+ case 'yr':
+ case 'y':
+ return n * y;
+ case 'days':
+ case 'day':
+ case 'd':
+ return n * d;
+ case 'hours':
+ case 'hour':
+ case 'hrs':
+ case 'hr':
+ case 'h':
+ return n * h;
+ case 'minutes':
+ case 'minute':
+ case 'mins':
+ case 'min':
+ case 'm':
+ return n * m;
+ case 'seconds':
+ case 'second':
+ case 'secs':
+ case 'sec':
+ case 's':
+ return n * s;
+ case 'milliseconds':
+ case 'millisecond':
+ case 'msecs':
+ case 'msec':
+ case 'ms':
+ return n;
+ default:
+ return undefined;
+ }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function fmtShort(ms) {
+ if (ms >= d) {
+ return Math.round(ms / d) + 'd';
+ }
+ if (ms >= h) {
+ return Math.round(ms / h) + 'h';
+ }
+ if (ms >= m) {
+ return Math.round(ms / m) + 'm';
+ }
+ if (ms >= s) {
+ return Math.round(ms / s) + 's';
+ }
+ return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function fmtLong(ms) {
+ return plural(ms, d, 'day') ||
+ plural(ms, h, 'hour') ||
+ plural(ms, m, 'minute') ||
+ plural(ms, s, 'second') ||
+ ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+ if (ms < n) {
+ return;
+ }
+ if (ms < n * 1.5) {
+ return Math.floor(ms / n) + ' ' + name;
+ }
+ return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+},{}],9:[function(require,module,exports){
+// shim for using process in browser
+
+var process = module.exports = {};
+var queue = [];
+var draining = false;
+
+function drainQueue() {
+ if (draining) {
+ return;
+ }
+ draining = true;
+ var currentQueue;
+ var len = queue.length;
+ while(len) {
+ currentQueue = queue;
+ queue = [];
+ var i = -1;
+ while (++i < len) {
+ currentQueue[i]();
+ }
+ len = queue.length;
+ }
+ draining = false;
+}
+process.nextTick = function (fun) {
+ queue.push(fun);
+ if (!draining) {
+ setTimeout(drainQueue, 0);
+ }
+};
+
+process.title = 'browser';
+process.browser = true;
+process.env = {};
+process.argv = [];
+process.version = ''; // empty string to avoid regexp issues
+process.versions = {};
+
+function noop() {}
+
+process.on = noop;
+process.addListener = noop;
+process.once = noop;
+process.off = noop;
+process.removeListener = noop;
+process.removeAllListeners = noop;
+process.emit = noop;
+
+process.binding = function (name) {
+ throw new Error('process.binding is not supported');
+};
+
+// TODO(shtylman)
+process.cwd = function () { return '/' };
+process.chdir = function (dir) {
+ throw new Error('process.chdir is not supported');
+};
+process.umask = function() { return 0; };
+
+},{}],10:[function(require,module,exports){
+(function (global){
+/*! http://mths.be/punycode v1.2.4 by @mathias */
+;(function(root) {
+
+ /** Detect free variables */
+ var freeExports = typeof exports == 'object' && exports;
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /**
+ * The `punycode` object.
+ * @name punycode
+ * @type Object
+ */
+ var punycode,
+
+ /** Highest positive signed 32-bit float value */
+ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+ /** Bootstring parameters */
+ base = 36,
+ tMin = 1,
+ tMax = 26,
+ skew = 38,
+ damp = 700,
+ initialBias = 72,
+ initialN = 128, // 0x80
+ delimiter = '-', // '\x2D'
+
+ /** Regular expressions */
+ regexPunycode = /^xn--/,
+ regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
+ regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
+
+ /** Error messages */
+ errors = {
+ 'overflow': 'Overflow: input needs wider integers to process',
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ while (length--) {
+ array[length] = fn(array[length]);
+ }
+ return array;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings.
+ * @private
+ * @param {String} domain The domain name.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ return map(string.split(regexSeparators), fn).join('.');
+ }
+
+ /**
+ * Creates an array containing the numeric code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of numeric code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of numeric code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic numeric code point value.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ if (codePoint - 48 < 10) {
+ return codePoint - 22;
+ }
+ if (codePoint - 65 < 26) {
+ return codePoint - 65;
+ }
+ if (codePoint - 97 < 26) {
+ return codePoint - 97;
+ }
+ return base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if `flag` is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII-only symbols to a string of Unicode
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII-only symbols.
+ * @returns {String} The resulting string of Unicode symbols.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode symbols to a Punycode string of ASCII-only
+ * symbols.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode symbols.
+ * @returns {String} The resulting Punycode string of ASCII-only symbols.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's state to ,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name to Unicode. Only the
+ * Punycoded parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it on a string that has already been converted to
+ * Unicode.
+ * @memberOf punycode
+ * @param {String} domain The Punycode domain name to convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(domain) {
+ return mapDomain(domain, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name to Punycode. Only the
+ * non-ASCII parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it with a domain that's already in ASCII.
+ * @memberOf punycode
+ * @param {String} domain The domain name to convert, as a Unicode string.
+ * @returns {String} The Punycode representation of the given domain name.
+ */
+ function toASCII(domain) {
+ return mapDomain(domain, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.2.4',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to Unicode code points, and back.
+ * @see
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define('punycode', function() {
+ return punycode;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+}(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],11:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+'use strict';
+
+// If obj.hasOwnProperty has been overridden, then calling
+// obj.hasOwnProperty(prop) will break.
+// See: https://github.com/joyent/node/issues/1707
+function hasOwnProperty(obj, prop) {
+ return Object.prototype.hasOwnProperty.call(obj, prop);
+}
+
+module.exports = function(qs, sep, eq, options) {
+ sep = sep || '&';
+ eq = eq || '=';
+ var obj = {};
+
+ if (typeof qs !== 'string' || qs.length === 0) {
+ return obj;
+ }
+
+ var regexp = /\+/g;
+ qs = qs.split(sep);
+
+ var maxKeys = 1000;
+ if (options && typeof options.maxKeys === 'number') {
+ maxKeys = options.maxKeys;
+ }
+
+ var len = qs.length;
+ // maxKeys <= 0 means that we should not limit keys count
+ if (maxKeys > 0 && len > maxKeys) {
+ len = maxKeys;
+ }
+
+ for (var i = 0; i < len; ++i) {
+ var x = qs[i].replace(regexp, '%20'),
+ idx = x.indexOf(eq),
+ kstr, vstr, k, v;
+
+ if (idx >= 0) {
+ kstr = x.substr(0, idx);
+ vstr = x.substr(idx + 1);
+ } else {
+ kstr = x;
+ vstr = '';
+ }
+
+ k = decodeURIComponent(kstr);
+ v = decodeURIComponent(vstr);
+
+ if (!hasOwnProperty(obj, k)) {
+ obj[k] = v;
+ } else if (isArray(obj[k])) {
+ obj[k].push(v);
+ } else {
+ obj[k] = [obj[k], v];
+ }
+ }
+
+ return obj;
+};
+
+var isArray = Array.isArray || function (xs) {
+ return Object.prototype.toString.call(xs) === '[object Array]';
+};
+
+},{}],12:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+'use strict';
+
+var stringifyPrimitive = function(v) {
+ switch (typeof v) {
+ case 'string':
+ return v;
+
+ case 'boolean':
+ return v ? 'true' : 'false';
+
+ case 'number':
+ return isFinite(v) ? v : '';
+
+ default:
+ return '';
+ }
+};
+
+module.exports = function(obj, sep, eq, name) {
+ sep = sep || '&';
+ eq = eq || '=';
+ if (obj === null) {
+ obj = undefined;
+ }
+
+ if (typeof obj === 'object') {
+ return map(objectKeys(obj), function(k) {
+ var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
+ if (isArray(obj[k])) {
+ return map(obj[k], function(v) {
+ return ks + encodeURIComponent(stringifyPrimitive(v));
+ }).join(sep);
+ } else {
+ return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
+ }
+ }).join(sep);
+
+ }
+
+ if (!name) return '';
+ return encodeURIComponent(stringifyPrimitive(name)) + eq +
+ encodeURIComponent(stringifyPrimitive(obj));
+};
+
+var isArray = Array.isArray || function (xs) {
+ return Object.prototype.toString.call(xs) === '[object Array]';
+};
+
+function map (xs, f) {
+ if (xs.map) return xs.map(f);
+ var res = [];
+ for (var i = 0; i < xs.length; i++) {
+ res.push(f(xs[i], i));
+ }
+ return res;
+}
+
+var objectKeys = Object.keys || function (obj) {
+ var res = [];
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
+ }
+ return res;
+};
+
+},{}],13:[function(require,module,exports){
+'use strict';
+
+exports.decode = exports.parse = require('./decode');
+exports.encode = exports.stringify = require('./encode');
+
+},{"./decode":11,"./encode":12}],14:[function(require,module,exports){
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var punycode = require('punycode');
+
+exports.parse = urlParse;
+exports.resolve = urlResolve;
+exports.resolveObject = urlResolveObject;
+exports.format = urlFormat;
+
+exports.Url = Url;
+
+function Url() {
+ this.protocol = null;
+ this.slashes = null;
+ this.auth = null;
+ this.host = null;
+ this.port = null;
+ this.hostname = null;
+ this.hash = null;
+ this.search = null;
+ this.query = null;
+ this.pathname = null;
+ this.path = null;
+ this.href = null;
+}
+
+// Reference: RFC 3986, RFC 1808, RFC 2396
+
+// define these here so at least they only have to be
+// compiled once on the first module load.
+var protocolPattern = /^([a-z0-9.+-]+:)/i,
+ portPattern = /:[0-9]*$/,
+
+ // RFC 2396: characters reserved for delimiting URLs.
+ // We actually just auto-escape these.
+ delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'],
+
+ // RFC 2396: characters not allowed for various reasons.
+ unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims),
+
+ // Allowed by RFCs, but cause of XSS attacks. Always escape these.
+ autoEscape = ['\''].concat(unwise),
+ // Characters that are never ever allowed in a hostname.
+ // Note that any invalid chars are also handled, but these
+ // are the ones that are *expected* to be seen, so we fast-path
+ // them.
+ nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),
+ hostEndingChars = ['/', '?', '#'],
+ hostnameMaxLen = 255,
+ hostnamePartPattern = /^[a-z0-9A-Z_-]{0,63}$/,
+ hostnamePartStart = /^([a-z0-9A-Z_-]{0,63})(.*)$/,
+ // protocols that can allow "unsafe" and "unwise" chars.
+ unsafeProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that never have a hostname.
+ hostlessProtocol = {
+ 'javascript': true,
+ 'javascript:': true
+ },
+ // protocols that always contain a // bit.
+ slashedProtocol = {
+ 'http': true,
+ 'https': true,
+ 'ftp': true,
+ 'gopher': true,
+ 'file': true,
+ 'http:': true,
+ 'https:': true,
+ 'ftp:': true,
+ 'gopher:': true,
+ 'file:': true
+ },
+ querystring = require('querystring');
+
+function urlParse(url, parseQueryString, slashesDenoteHost) {
+ if (url && isObject(url) && url instanceof Url) return url;
+
+ var u = new Url;
+ u.parse(url, parseQueryString, slashesDenoteHost);
+ return u;
+}
+
+Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {
+ if (!isString(url)) {
+ throw new TypeError("Parameter 'url' must be a string, not " + typeof url);
+ }
+
+ var rest = url;
+
+ // trim before proceeding.
+ // This is to support parse stuff like " http://foo.com \n"
+ rest = rest.trim();
+
+ var proto = protocolPattern.exec(rest);
+ if (proto) {
+ proto = proto[0];
+ var lowerProto = proto.toLowerCase();
+ this.protocol = lowerProto;
+ rest = rest.substr(proto.length);
+ }
+
+ // figure out if it's got a host
+ // user@server is *always* interpreted as a hostname, and url
+ // resolution will treat //foo/bar as host=foo,path=bar because that's
+ // how the browser resolves relative URLs.
+ if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) {
+ var slashes = rest.substr(0, 2) === '//';
+ if (slashes && !(proto && hostlessProtocol[proto])) {
+ rest = rest.substr(2);
+ this.slashes = true;
+ }
+ }
+
+ if (!hostlessProtocol[proto] &&
+ (slashes || (proto && !slashedProtocol[proto]))) {
+
+ // there's a hostname.
+ // the first instance of /, ?, ;, or # ends the host.
+ //
+ // If there is an @ in the hostname, then non-host chars *are* allowed
+ // to the left of the last @ sign, unless some host-ending character
+ // comes *before* the @-sign.
+ // URLs are obnoxious.
+ //
+ // ex:
+ // http://a@b@c/ => user:a@b host:c
+ // http://a@b?@c => user:a host:c path:/?@c
+
+ // v0.12 TODO(isaacs): This is not quite how Chrome does things.
+ // Review our test case against browsers more comprehensively.
+
+ // find the first instance of any hostEndingChars
+ var hostEnd = -1;
+ for (var i = 0; i < hostEndingChars.length; i++) {
+ var hec = rest.indexOf(hostEndingChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+
+ // at this point, either we have an explicit point where the
+ // auth portion cannot go past, or the last @ char is the decider.
+ var auth, atSign;
+ if (hostEnd === -1) {
+ // atSign can be anywhere.
+ atSign = rest.lastIndexOf('@');
+ } else {
+ // atSign must be in auth portion.
+ // http://a@b/c@d => host:b auth:a path:/c@d
+ atSign = rest.lastIndexOf('@', hostEnd);
+ }
+
+ // Now we have a portion which is definitely the auth.
+ // Pull that off.
+ if (atSign !== -1) {
+ auth = rest.slice(0, atSign);
+ rest = rest.slice(atSign + 1);
+ this.auth = decodeURIComponent(auth);
+ }
+
+ // the host is the remaining to the left of the first non-host char
+ hostEnd = -1;
+ for (var i = 0; i < nonHostChars.length; i++) {
+ var hec = rest.indexOf(nonHostChars[i]);
+ if (hec !== -1 && (hostEnd === -1 || hec < hostEnd))
+ hostEnd = hec;
+ }
+ // if we still have not hit it, then the entire thing is a host.
+ if (hostEnd === -1)
+ hostEnd = rest.length;
+
+ this.host = rest.slice(0, hostEnd);
+ rest = rest.slice(hostEnd);
+
+ // pull out port.
+ this.parseHost();
+
+ // we've indicated that there is a hostname,
+ // so even if it's empty, it has to be present.
+ this.hostname = this.hostname || '';
+
+ // if hostname begins with [ and ends with ]
+ // assume that it's an IPv6 address.
+ var ipv6Hostname = this.hostname[0] === '[' &&
+ this.hostname[this.hostname.length - 1] === ']';
+
+ // validate a little.
+ if (!ipv6Hostname) {
+ var hostparts = this.hostname.split(/\./);
+ for (var i = 0, l = hostparts.length; i < l; i++) {
+ var part = hostparts[i];
+ if (!part) continue;
+ if (!part.match(hostnamePartPattern)) {
+ var newpart = '';
+ for (var j = 0, k = part.length; j < k; j++) {
+ if (part.charCodeAt(j) > 127) {
+ // we replace non-ASCII char with a temporary placeholder
+ // we need this to make sure size of hostname is not
+ // broken by replacing non-ASCII by nothing
+ newpart += 'x';
+ } else {
+ newpart += part[j];
+ }
+ }
+ // we test again with ASCII char only
+ if (!newpart.match(hostnamePartPattern)) {
+ var validParts = hostparts.slice(0, i);
+ var notHost = hostparts.slice(i + 1);
+ var bit = part.match(hostnamePartStart);
+ if (bit) {
+ validParts.push(bit[1]);
+ notHost.unshift(bit[2]);
+ }
+ if (notHost.length) {
+ rest = '/' + notHost.join('.') + rest;
+ }
+ this.hostname = validParts.join('.');
+ break;
+ }
+ }
+ }
+ }
+
+ if (this.hostname.length > hostnameMaxLen) {
+ this.hostname = '';
+ } else {
+ // hostnames are always lower case.
+ this.hostname = this.hostname.toLowerCase();
+ }
+
+ if (!ipv6Hostname) {
+ // IDNA Support: Returns a puny coded representation of "domain".
+ // It only converts the part of the domain name that
+ // has non ASCII characters. I.e. it dosent matter if
+ // you call it with a domain that already is in ASCII.
+ var domainArray = this.hostname.split('.');
+ var newOut = [];
+ for (var i = 0; i < domainArray.length; ++i) {
+ var s = domainArray[i];
+ newOut.push(s.match(/[^A-Za-z0-9_-]/) ?
+ 'xn--' + punycode.encode(s) : s);
+ }
+ this.hostname = newOut.join('.');
+ }
+
+ var p = this.port ? ':' + this.port : '';
+ var h = this.hostname || '';
+ this.host = h + p;
+ this.href += this.host;
+
+ // strip [ and ] from the hostname
+ // the host field still retains them, though
+ if (ipv6Hostname) {
+ this.hostname = this.hostname.substr(1, this.hostname.length - 2);
+ if (rest[0] !== '/') {
+ rest = '/' + rest;
+ }
+ }
+ }
+
+ // now rest is set to the post-host stuff.
+ // chop off any delim chars.
+ if (!unsafeProtocol[lowerProto]) {
+
+ // First, make 100% sure that any "autoEscape" chars get
+ // escaped, even if encodeURIComponent doesn't think they
+ // need to be.
+ for (var i = 0, l = autoEscape.length; i < l; i++) {
+ var ae = autoEscape[i];
+ var esc = encodeURIComponent(ae);
+ if (esc === ae) {
+ esc = escape(ae);
+ }
+ rest = rest.split(ae).join(esc);
+ }
+ }
+
+
+ // chop off from the tail first.
+ var hash = rest.indexOf('#');
+ if (hash !== -1) {
+ // got a fragment string.
+ this.hash = rest.substr(hash);
+ rest = rest.slice(0, hash);
+ }
+ var qm = rest.indexOf('?');
+ if (qm !== -1) {
+ this.search = rest.substr(qm);
+ this.query = rest.substr(qm + 1);
+ if (parseQueryString) {
+ this.query = querystring.parse(this.query);
+ }
+ rest = rest.slice(0, qm);
+ } else if (parseQueryString) {
+ // no query string, but parseQueryString still requested
+ this.search = '';
+ this.query = {};
+ }
+ if (rest) this.pathname = rest;
+ if (slashedProtocol[lowerProto] &&
+ this.hostname && !this.pathname) {
+ this.pathname = '/';
+ }
+
+ //to support http.request
+ if (this.pathname || this.search) {
+ var p = this.pathname || '';
+ var s = this.search || '';
+ this.path = p + s;
+ }
+
+ // finally, reconstruct the href based on what has been validated.
+ this.href = this.format();
+ return this;
+};
+
+// format a parsed object into a url string
+function urlFormat(obj) {
+ // ensure it's an object, and not a string url.
+ // If it's an obj, this is a no-op.
+ // this way, you can call url_format() on strings
+ // to clean up potentially wonky urls.
+ if (isString(obj)) obj = urlParse(obj);
+ if (!(obj instanceof Url)) return Url.prototype.format.call(obj);
+ return obj.format();
+}
+
+Url.prototype.format = function() {
+ var auth = this.auth || '';
+ if (auth) {
+ auth = encodeURIComponent(auth);
+ auth = auth.replace(/%3A/i, ':');
+ auth += '@';
+ }
+
+ var protocol = this.protocol || '',
+ pathname = this.pathname || '',
+ hash = this.hash || '',
+ host = false,
+ query = '';
+
+ if (this.host) {
+ host = auth + this.host;
+ } else if (this.hostname) {
+ host = auth + (this.hostname.indexOf(':') === -1 ?
+ this.hostname :
+ '[' + this.hostname + ']');
+ if (this.port) {
+ host += ':' + this.port;
+ }
+ }
+
+ if (this.query &&
+ isObject(this.query) &&
+ Object.keys(this.query).length) {
+ query = querystring.stringify(this.query);
+ }
+
+ var search = this.search || (query && ('?' + query)) || '';
+
+ if (protocol && protocol.substr(-1) !== ':') protocol += ':';
+
+ // only the slashedProtocols get the //. Not mailto:, xmpp:, etc.
+ // unless they had them to begin with.
+ if (this.slashes ||
+ (!protocol || slashedProtocol[protocol]) && host !== false) {
+ host = '//' + (host || '');
+ if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname;
+ } else if (!host) {
+ host = '';
+ }
+
+ if (hash && hash.charAt(0) !== '#') hash = '#' + hash;
+ if (search && search.charAt(0) !== '?') search = '?' + search;
+
+ pathname = pathname.replace(/[?#]/g, function(match) {
+ return encodeURIComponent(match);
+ });
+ search = search.replace('#', '%23');
+
+ return protocol + host + pathname + search + hash;
+};
+
+function urlResolve(source, relative) {
+ return urlParse(source, false, true).resolve(relative);
+}
+
+Url.prototype.resolve = function(relative) {
+ return this.resolveObject(urlParse(relative, false, true)).format();
+};
+
+function urlResolveObject(source, relative) {
+ if (!source) return relative;
+ return urlParse(source, false, true).resolveObject(relative);
+}
+
+Url.prototype.resolveObject = function(relative) {
+ if (isString(relative)) {
+ var rel = new Url();
+ rel.parse(relative, false, true);
+ relative = rel;
+ }
+
+ var result = new Url();
+ Object.keys(this).forEach(function(k) {
+ result[k] = this[k];
+ }, this);
+
+ // hash is always overridden, no matter what.
+ // even href="" will remove it.
+ result.hash = relative.hash;
+
+ // if the relative url is empty, then there's nothing left to do here.
+ if (relative.href === '') {
+ result.href = result.format();
+ return result;
+ }
+
+ // hrefs like //foo/bar always cut to the protocol.
+ if (relative.slashes && !relative.protocol) {
+ // take everything except the protocol from relative
+ Object.keys(relative).forEach(function(k) {
+ if (k !== 'protocol')
+ result[k] = relative[k];
+ });
+
+ //urlParse appends trailing / to urls like http://www.example.com
+ if (slashedProtocol[result.protocol] &&
+ result.hostname && !result.pathname) {
+ result.path = result.pathname = '/';
+ }
+
+ result.href = result.format();
+ return result;
+ }
+
+ if (relative.protocol && relative.protocol !== result.protocol) {
+ // if it's a known url protocol, then changing
+ // the protocol does weird things
+ // first, if it's not file:, then we MUST have a host,
+ // and if there was a path
+ // to begin with, then we MUST have a path.
+ // if it is file:, then the host is dropped,
+ // because that's known to be hostless.
+ // anything else is assumed to be absolute.
+ if (!slashedProtocol[relative.protocol]) {
+ Object.keys(relative).forEach(function(k) {
+ result[k] = relative[k];
+ });
+ result.href = result.format();
+ return result;
+ }
+
+ result.protocol = relative.protocol;
+ if (!relative.host && !hostlessProtocol[relative.protocol]) {
+ var relPath = (relative.pathname || '').split('/');
+ while (relPath.length && !(relative.host = relPath.shift()));
+ if (!relative.host) relative.host = '';
+ if (!relative.hostname) relative.hostname = '';
+ if (relPath[0] !== '') relPath.unshift('');
+ if (relPath.length < 2) relPath.unshift('');
+ result.pathname = relPath.join('/');
+ } else {
+ result.pathname = relative.pathname;
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ result.host = relative.host || '';
+ result.auth = relative.auth;
+ result.hostname = relative.hostname || relative.host;
+ result.port = relative.port;
+ // to support http.request
+ if (result.pathname || result.search) {
+ var p = result.pathname || '';
+ var s = result.search || '';
+ result.path = p + s;
+ }
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+ }
+
+ var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'),
+ isRelAbs = (
+ relative.host ||
+ relative.pathname && relative.pathname.charAt(0) === '/'
+ ),
+ mustEndAbs = (isRelAbs || isSourceAbs ||
+ (result.host && relative.pathname)),
+ removeAllDots = mustEndAbs,
+ srcPath = result.pathname && result.pathname.split('/') || [],
+ relPath = relative.pathname && relative.pathname.split('/') || [],
+ psychotic = result.protocol && !slashedProtocol[result.protocol];
+
+ // if the url is a non-slashed url, then relative
+ // links like ../.. should be able
+ // to crawl up to the hostname, as well. This is strange.
+ // result.protocol has already been set by now.
+ // Later on, put the first path part into the host field.
+ if (psychotic) {
+ result.hostname = '';
+ result.port = null;
+ if (result.host) {
+ if (srcPath[0] === '') srcPath[0] = result.host;
+ else srcPath.unshift(result.host);
+ }
+ result.host = '';
+ if (relative.protocol) {
+ relative.hostname = null;
+ relative.port = null;
+ if (relative.host) {
+ if (relPath[0] === '') relPath[0] = relative.host;
+ else relPath.unshift(relative.host);
+ }
+ relative.host = null;
+ }
+ mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === '');
+ }
+
+ if (isRelAbs) {
+ // it's absolute.
+ result.host = (relative.host || relative.host === '') ?
+ relative.host : result.host;
+ result.hostname = (relative.hostname || relative.hostname === '') ?
+ relative.hostname : result.hostname;
+ result.search = relative.search;
+ result.query = relative.query;
+ srcPath = relPath;
+ // fall through to the dot-handling below.
+ } else if (relPath.length) {
+ // it's relative
+ // throw away the existing file, and take the new path instead.
+ if (!srcPath) srcPath = [];
+ srcPath.pop();
+ srcPath = srcPath.concat(relPath);
+ result.search = relative.search;
+ result.query = relative.query;
+ } else if (!isNullOrUndefined(relative.search)) {
+ // just pull out the search.
+ // like href='?foo'.
+ // Put this after the other two cases because it simplifies the booleans
+ if (psychotic) {
+ result.hostname = result.host = srcPath.shift();
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+ result.search = relative.search;
+ result.query = relative.query;
+ //to support http.request
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ if (!srcPath.length) {
+ // no path at all. easy.
+ // we've already handled the other stuff above.
+ result.pathname = null;
+ //to support http.request
+ if (result.search) {
+ result.path = '/' + result.search;
+ } else {
+ result.path = null;
+ }
+ result.href = result.format();
+ return result;
+ }
+
+ // if a url ENDs in . or .., then it must get a trailing slash.
+ // however, if it ends in anything else non-slashy,
+ // then it must NOT get a trailing slash.
+ var last = srcPath.slice(-1)[0];
+ var hasTrailingSlash = (
+ (result.host || relative.host) && (last === '.' || last === '..') ||
+ last === '');
+
+ // strip single dots, resolve double dots to parent dir
+ // if the path tries to go above the root, `up` ends up > 0
+ var up = 0;
+ for (var i = srcPath.length; i >= 0; i--) {
+ last = srcPath[i];
+ if (last == '.') {
+ srcPath.splice(i, 1);
+ } else if (last === '..') {
+ srcPath.splice(i, 1);
+ up++;
+ } else if (up) {
+ srcPath.splice(i, 1);
+ up--;
+ }
+ }
+
+ // if the path is allowed to go above the root, restore leading ..s
+ if (!mustEndAbs && !removeAllDots) {
+ for (; up--; up) {
+ srcPath.unshift('..');
+ }
+ }
+
+ if (mustEndAbs && srcPath[0] !== '' &&
+ (!srcPath[0] || srcPath[0].charAt(0) !== '/')) {
+ srcPath.unshift('');
+ }
+
+ if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) {
+ srcPath.push('');
+ }
+
+ var isAbsolute = srcPath[0] === '' ||
+ (srcPath[0] && srcPath[0].charAt(0) === '/');
+
+ // put the host back
+ if (psychotic) {
+ result.hostname = result.host = isAbsolute ? '' :
+ srcPath.length ? srcPath.shift() : '';
+ //occationaly the auth can get stuck only in host
+ //this especialy happens in cases like
+ //url.resolveObject('mailto:local1@domain1', 'local2@domain2')
+ var authInHost = result.host && result.host.indexOf('@') > 0 ?
+ result.host.split('@') : false;
+ if (authInHost) {
+ result.auth = authInHost.shift();
+ result.host = result.hostname = authInHost.shift();
+ }
+ }
+
+ mustEndAbs = mustEndAbs || (result.host && srcPath.length);
+
+ if (mustEndAbs && !isAbsolute) {
+ srcPath.unshift('');
+ }
+
+ if (!srcPath.length) {
+ result.pathname = null;
+ result.path = null;
+ } else {
+ result.pathname = srcPath.join('/');
+ }
+
+ //to support request.http
+ if (!isNull(result.pathname) || !isNull(result.search)) {
+ result.path = (result.pathname ? result.pathname : '') +
+ (result.search ? result.search : '');
+ }
+ result.auth = relative.auth || result.auth;
+ result.slashes = result.slashes || relative.slashes;
+ result.href = result.format();
+ return result;
+};
+
+Url.prototype.parseHost = function() {
+ var host = this.host;
+ var port = portPattern.exec(host);
+ if (port) {
+ port = port[0];
+ if (port !== ':') {
+ this.port = port.substr(1);
+ }
+ host = host.substr(0, host.length - port.length);
+ }
+ if (host) this.hostname = host;
+};
+
+function isString(arg) {
+ return typeof arg === "string";
+}
+
+function isObject(arg) {
+ return typeof arg === 'object' && arg !== null;
+}
+
+function isNull(arg) {
+ return arg === null;
+}
+function isNullOrUndefined(arg) {
+ return arg == null;
+}
+
+},{"punycode":10,"querystring":13}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/test/fixtures/uamservice.js b/test/fixtures/uamservice.js
index ecf36c7..58e56d9 100644
--- a/test/fixtures/uamservice.js
+++ b/test/fixtures/uamservice.js
@@ -2,7 +2,8 @@
module.exports = function() {
return {
- chap: '1231b655a9c14dbcdea738991cd4a0c6'
+ chap: '1231b655a9c14dbcdea738991cd4a0c6',
+ pap: '1231b655a9c14dbcdea738991cd4a0c6'
};
};
diff --git a/test/tests.js b/test/tests.js
index 522c791..8118d9f 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -42,7 +42,6 @@ describe('Pepper', function() {
var pepper = Pepper({
querystring: sampleQs
});
-
expect(pepper.data).to.have.property('called');
expect(pepper.data).to.have.property('challenge');
expect(pepper.data).to.have.property('ip');
@@ -59,7 +58,8 @@ describe('Pepper', function() {
var pepper = Pepper({
host: '10.10.0.1',
port: 3990,
- ssl: false
+ ssl: false,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php'
});
expect(pepper._baseUrl).to.equal('http://10.10.0.1:3990/json/');
@@ -71,6 +71,7 @@ describe('Pepper', function() {
host: '10.10.0.1',
port: 3990,
ssl: false,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
querystring: sampleQs
});
@@ -82,6 +83,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -100,6 +102,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -116,6 +119,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -132,6 +136,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -146,6 +151,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -164,6 +170,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -183,6 +190,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -202,6 +210,7 @@ describe('Pepper', function() {
var pepper = Pepper({
host: 'localhost',
port: 5000,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
ssl: false
});
@@ -223,7 +232,7 @@ describe('Pepper', function() {
host: 'localhost',
port: 5000,
ssl: false,
- uamservice: 'https://uamservice.service.com'
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
});
pepper.logon('test', 'test', function(err, data) {
@@ -240,6 +249,7 @@ describe('Pepper', function() {
host: 'localhost',
port: 5000,
ssl: false,
+ uamservice: 'http://localhost/chilli-uamservice-pap-chap.php',
interval: 2500
});