diff --git a/.gitignore b/.gitignore
index f935a41..02f1b08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
+node_modules/**
tags
-.zedstate
\ No newline at end of file
+.zedstate
+/www/umd-tough-cookie.js
diff --git a/README.md b/README.md
index df55aee..06b2e3a 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,10 @@ You can choose one of these two:
Caution: `urlencoded` does not support serializing deep structures whereas `json` does.
+### clearCookies
+Clear the cookie store.
+
+ cordovaHTTP.clearCookies();
## Asynchronous Functions
These functions all take success and error callbacks as their last 2 arguments.
@@ -206,12 +210,6 @@ This plugin utilizes some awesome open source networking libraries. These are b
We made a few modifications to http-request. They can be found in a separate repo here: https://github.com/wymsee/http-request
-## Cookies
-
-- a cookie set by a request isn't sent in subsequent requests
-
-Take this into account when using this plugin in your application.
-
## License
The MIT License
diff --git a/package.json b/package.json
index 37ec21e..1a5a563 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,9 @@
"name": "cordova-plugin-advanced-http",
"version": "1.4.0",
"description": "Cordova / Phonegap plugin for communicating with HTTP servers using SSL pinning",
+ "scripts": {
+ "build": "cp node_modules/umd-tough-cookie/lib/umd-tough-cookie.js www/umd-tough-cookie.js"
+ },
"cordova": {
"id": "cordova-plugin-advanced-http",
"platforms": [
@@ -44,5 +47,8 @@
"bugs": {
"url": "https://github.com/silkimen/cordova-plugin-advanced-http/issues"
},
- "homepage": "https://github.com/silkimen/cordova-plugin-advanced-http#readme"
+ "homepage": "https://github.com/silkimen/cordova-plugin-advanced-http#readme",
+ "devDependencies": {
+ "umd-tough-cookie": "2.3.2"
+ }
}
diff --git a/plugin.xml b/plugin.xml
index e21afb7..d9e0d89 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -16,7 +16,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/www/cordovaHttp.js b/www/advanced-http.js
similarity index 54%
rename from www/cordovaHttp.js
rename to www/advanced-http.js
index ac16312..54e5026 100644
--- a/www/cordovaHttp.js
+++ b/www/advanced-http.js
@@ -29,9 +29,13 @@
* An HTTP Plugin for PhoneGap.
*/
-var exec = require('cordova/exec');
+var pluginId = module.id.slice(0, module.id.indexOf('.'));
var validSerializers = ['urlencoded', 'json'];
+var exec = require('cordova/exec');
+var angularIntegration = require(pluginId +'.angular-integration');
+var cookieHandler = require(pluginId + '.cookie-handler');
+
// Thanks Mozilla: https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_.22Unicode_Problem.22
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
@@ -42,24 +46,50 @@ function b64EncodeUnicode(str) {
function mergeHeaders(globalHeaders, localHeaders) {
var globalKeys = Object.keys(globalHeaders);
var key;
+
for (var i = 0; i < globalKeys.length; i++) {
key = globalKeys[i];
+
if (!localHeaders.hasOwnProperty(key)) {
localHeaders[key] = globalHeaders[key];
}
}
+
return localHeaders;
}
function checkSerializer(serializer) {
- serializer = serializer || '';
- serializer = serializer.trim().toLowerCase();
+ serializer = serializer || '';
+ serializer = serializer.trim().toLowerCase();
- if (validSerializers.indexOf(serializer) > -1) {
- return serializer;
- }
+ if (validSerializers.indexOf(serializer) > -1) {
+ return serializer;
+ }
- return serializer[0];
+ return serializer[0];
+}
+
+function resolveCookieString(headers) {
+ var keys = Object.keys(headers);
+
+ for (var i = 0; i < keys.length; ++i) {
+ if (keys[i].match(/^set-cookie$/i)) {
+ return headers[keys[i]];
+ }
+ }
+
+ return null;
+}
+
+function getSuccessHandler(url, cb) {
+ return function(response) {
+ cookieHandler.setCookie(url, resolveCookieString(response.headers));
+ cb(response);
+ }
+}
+
+function getCookieHeader(url) {
+ return { Cookie: cookieHandler.getCookie(url) };
}
var http = {
@@ -76,7 +106,10 @@ var http = {
this.headers[header] = value;
},
setDataSerializer: function (serializer) {
- this.dataSerializer = checkSerializer(serializer);
+ this.dataSerializer = checkSerializer(serializer);
+ },
+ clearCookies: function () {
+ return cookieHandler.clearCookies();
},
enableSSLPinning: function (enable, success, failure) {
return exec(success, failure, 'CordovaHttpPlugin', 'enableSSLPinning', [enable]);
@@ -91,36 +124,50 @@ var http = {
data = data || {};
headers = headers || {};
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'post', [url, data, this.dataSerializer, headers]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'post', [url, data, this.dataSerializer, headers]);
},
get: function (url, params, headers, success, failure) {
params = params || {};
headers = headers || {};
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'get', [url, params, headers]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'get', [url, params, headers]);
},
put: function (url, data, headers, success, failure) {
data = data || {};
headers = headers || {};
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'put', [url, data, this.dataSerializer, headers]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'put', [url, data, this.dataSerializer, headers]);
},
delete: function (url, params, headers, success, failure) {
params = params || {};
headers = headers || {};
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'delete', [url, params, headers]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'delete', [url, params, headers]);
},
head: function (url, params, headers, success, failure) {
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'head', [url, params, headers]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'head', [url, params, headers]);
},
uploadFile: function (url, params, headers, filePath, name, success, failure) {
headers = mergeHeaders(this.headers, headers);
- return exec(success, failure, 'CordovaHttpPlugin', 'uploadFile', [url, params, headers, filePath, name]);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
+ return exec(getSuccessHandler(url, success), failure, 'CordovaHttpPlugin', 'uploadFile', [url, params, headers, filePath, name]);
},
downloadFile: function (url, params, headers, filePath, success, failure) {
headers = mergeHeaders(this.headers, headers);
+ headers = mergeHeaders(getCookieHeader(url), headers);
+
var win = function (result) {
var entry = new (require('cordova-plugin-file.FileEntry'))();
entry.isDirectory = false;
@@ -131,87 +178,10 @@ var http = {
entry.nativeURL = result.file.nativeURL;
success(entry);
};
+
return exec(win, failure, 'CordovaHttpPlugin', 'downloadFile', [url, params, headers, filePath]);
}
};
-if (typeof angular !== 'undefined') {
- angular.module('cordovaHTTP', []).factory('cordovaHTTP', function ($timeout, $q) {
- function makePromise(fn, args, async) {
- var deferred = $q.defer();
-
- var success = function (response) {
- if (async) {
- $timeout(function () {
- deferred.resolve(response);
- });
- } else {
- deferred.resolve(response);
- }
- };
-
- var fail = function (response) {
- if (async) {
- $timeout(function () {
- deferred.reject(response);
- });
- } else {
- deferred.reject(response);
- }
- };
-
- args.push(success);
- args.push(fail);
-
- fn.apply(http, args);
-
- return deferred.promise;
- }
-
- var cordovaHTTP = {
- getBasicAuthHeader: http.getBasicAuthHeader,
- useBasicAuth: function (username, password) {
- return http.useBasicAuth(username, password);
- },
- setHeader: function (header, value) {
- return http.setHeader(header, value);
- },
- setDataSerializer: function (serializer) {
- return http.setParamSerializer(serializer);
- },
- enableSSLPinning: function (enable) {
- return makePromise(http.enableSSLPinning, [enable]);
- },
- acceptAllCerts: function (allow) {
- return makePromise(http.acceptAllCerts, [allow]);
- },
- validateDomainName: function (validate) {
- return makePromise(http.validateDomainName, [validate]);
- },
- post: function (url, data, headers) {
- return makePromise(http.post, [url, data, headers], true);
- },
- get: function (url, params, headers) {
- return makePromise(http.get, [url, params, headers], true);
- },
- put: function (url, data, headers) {
- return makePromise(http.put, [url, data, headers], true);
- },
- delete: function (url, params, headers) {
- return makePromise(http.delete, [url, params, headers], true);
- },
- head: function (url, params, headers) {
- return makePromise(http.head, [url, params, headers], true);
- },
- uploadFile: function (url, params, headers, filePath, name) {
- return makePromise(http.uploadFile, [url, params, headers, filePath, name], true);
- },
- downloadFile: function (url, params, headers, filePath) {
- return makePromise(http.downloadFile, [url, params, headers, filePath], true);
- }
- };
- return cordovaHTTP;
- });
-}
-
+angularIntegration.registerService(http);
module.exports = http;
diff --git a/www/angular-integration.js b/www/angular-integration.js
new file mode 100644
index 0000000..c903b91
--- /dev/null
+++ b/www/angular-integration.js
@@ -0,0 +1,87 @@
+function registerService(http) {
+ if (typeof angular === 'undefined') return;
+
+ angular.module('cordovaHTTP', []).factory('cordovaHTTP', function ($timeout, $q) {
+ function makePromise(fn, args, async) {
+ var deferred = $q.defer();
+
+ var success = function (response) {
+ if (async) {
+ $timeout(function () {
+ deferred.resolve(response);
+ });
+ } else {
+ deferred.resolve(response);
+ }
+ };
+
+ var fail = function (response) {
+ if (async) {
+ $timeout(function () {
+ deferred.reject(response);
+ });
+ } else {
+ deferred.reject(response);
+ }
+ };
+
+ args.push(success);
+ args.push(fail);
+
+ fn.apply(http, args);
+
+ return deferred.promise;
+ }
+
+ var cordovaHTTP = {
+ getBasicAuthHeader: http.getBasicAuthHeader,
+ useBasicAuth: function (username, password) {
+ return http.useBasicAuth(username, password);
+ },
+ setHeader: function (header, value) {
+ return http.setHeader(header, value);
+ },
+ setDataSerializer: function (serializer) {
+ return http.setParamSerializer(serializer);
+ },
+ clearCookies: function () {
+ return http.clearCookies();
+ },
+ enableSSLPinning: function (enable) {
+ return makePromise(http.enableSSLPinning, [enable]);
+ },
+ acceptAllCerts: function (allow) {
+ return makePromise(http.acceptAllCerts, [allow]);
+ },
+ validateDomainName: function (validate) {
+ return makePromise(http.validateDomainName, [validate]);
+ },
+ post: function (url, data, headers) {
+ return makePromise(http.post, [url, data, headers], true);
+ },
+ get: function (url, params, headers) {
+ return makePromise(http.get, [url, params, headers], true);
+ },
+ put: function (url, data, headers) {
+ return makePromise(http.put, [url, data, headers], true);
+ },
+ delete: function (url, params, headers) {
+ return makePromise(http.delete, [url, params, headers], true);
+ },
+ head: function (url, params, headers) {
+ return makePromise(http.head, [url, params, headers], true);
+ },
+ uploadFile: function (url, params, headers, filePath, name) {
+ return makePromise(http.uploadFile, [url, params, headers, filePath, name], true);
+ },
+ downloadFile: function (url, params, headers, filePath) {
+ return makePromise(http.downloadFile, [url, params, headers, filePath], true);
+ }
+ };
+ return cordovaHTTP;
+ });
+}
+
+module.exports = {
+ registerService: registerService
+};
diff --git a/www/cookie-handler.js b/www/cookie-handler.js
new file mode 100644
index 0000000..5aa0dc1
--- /dev/null
+++ b/www/cookie-handler.js
@@ -0,0 +1,28 @@
+var pluginId = module.id.slice(0, module.id.indexOf('.'));
+var ToughCookie = require(pluginId + '.tough-cookie');
+var WebStorageCookieStore = require(pluginId + '.local-storage-store');
+
+var storage = window.localStorage;
+var storeKey = '__advancedHttpCookieStore__';
+
+var store = new WebStorageCookieStore(storage, storeKey);
+var cookieJar = new ToughCookie.CookieJar(store);
+
+module.exports = {
+ setCookie: setCookie,
+ getCookie: getCookie,
+ clearCookies: clearCookies
+}
+
+function setCookie(url, cookieStr) {
+ if (!cookieStr) return;
+ cookieJar.setCookieSync(cookieStr, url);
+}
+
+function getCookie(url) {
+ return cookieJar.getCookieStringSync(url);
+}
+
+function clearCookies() {
+ window.localStorage.removeItem(storeKey);
+}
diff --git a/www/local-storage-store.js b/www/local-storage-store.js
new file mode 100644
index 0000000..353d820
--- /dev/null
+++ b/www/local-storage-store.js
@@ -0,0 +1,183 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Exponent
+ *
+ * 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.
+ *
+ * Based on "tough-cookie-web-storage-store" v1.0.0
+ * Thanks James Ide: https://github.com/exponentjs/tough-cookie-web-storage-store
+ *
+ * Modified by Sefa Ilkimen for cordova plugin integration
+ *
+ */
+
+'use strict';
+
+var pluginId = module.id.slice(0, module.id.indexOf('.'));
+var ToughCookie = require(pluginId + '.tough-cookie');
+var _ = require(pluginId + '.lodash');
+
+function WebStorageCookieStore(storage, storeKey) {
+ ToughCookie.Store.call(this);
+ this._storage = storage || window.localStorage;
+ this._storeKey = storeKey || '__cookieStore__';
+ this.synchronous = true;
+}
+
+WebStorageCookieStore.prototype = Object.create(ToughCookie.Store);
+
+WebStorageCookieStore.prototype.findCookie = function(domain, path, key, callback) {
+ var store = this._readStore();
+ var cookie = _.get(store, [domain, path, key], null);
+
+ callback(null, ToughCookie.Cookie.fromJSON(cookie));
+};
+
+WebStorageCookieStore.prototype.findCookies = function(domain, path, callback) {
+ if (!domain) {
+ callback(null, []);
+ return;
+ }
+
+ var that = this;
+ var cookies = [];
+ var store = this._readStore();
+ var domains = ToughCookie.permuteDomain(domain) || [domain];
+
+ domains.forEach(function(domain) {
+ if (!store[domain]) {
+ return;
+ }
+
+ var matchingPaths = Object.keys(store[domain]);
+
+ if (path != null) {
+ matchingPaths = matchingPaths.filter(function(cookiePath) {
+ return that._isOnPath(cookiePath, path);
+ });
+ }
+
+ matchingPaths.forEach(function(path) {
+ Array.prototype.push.apply(cookies, _.values(store[domain][path]));
+ });
+ });
+
+ cookies = cookies.map(function(cookie) {
+ return ToughCookie.Cookie.fromJSON(cookie);
+ });
+
+ callback(null, cookies);
+};
+
+/**
+ * Returns whether `cookiePath` is on the given `urlPath`
+ */
+WebStorageCookieStore.prototype._isOnPath = function(cookiePath, urlPath) {
+ if (!cookiePath) {
+ return false;
+ }
+
+ if (cookiePath === urlPath) {
+ return true;
+ }
+
+ if (!urlPath.startsWith(cookiePath)) {
+ return false;
+ }
+
+ if (cookiePath[cookiePath.length - 1] !== '/' && urlPath[cookiePath.length] !== '/') {
+ return false;
+ }
+
+ return true;
+};
+
+WebStorageCookieStore.prototype.putCookie = function(cookie, callback) {
+ var store = this._readStore();
+
+ _.set(store, [cookie.domain, cookie.path, cookie.key], cookie);
+ this._writeStore(store);
+ callback(null);
+};
+
+WebStorageCookieStore.prototype.updateCookie = function(oldCookie, newCookie, callback) {
+ this.putCookie(newCookie, callback);
+};
+
+
+WebStorageCookieStore.prototype.removeCookie = function(domain, path, key, callback) {
+ var store = this._readStore();
+
+ _.unset(store, [domain, path, key]);
+ this._writeStore(store);
+ callback(null);
+};
+
+WebStorageCookieStore.prototype.removeCookies = function(domain, path, callback) {
+ var store = this._readStore();
+
+ if (path == null) {
+ _.unset(store, [domain]);
+ } else {
+ _.unset(store, [domain, path]);
+ }
+
+ this._writeStore(store);
+ callback(null);
+};
+
+WebStorageCookieStore.prototype.getAllCookies = function(callback) {
+ var cookies = [];
+ var store = this._readStore();
+
+ Object.keys(store).forEach(function(domain) {
+ Object.keys(store[domain]).forEach(function(path) {
+ Array.protype.push.apply(cookies, _.values(store[domain][path]));
+ });
+ });
+
+ cookies = cookies.map(function(cookie) {
+ return ToughCookie.Cookie.fromJSON(cookie);
+ });
+
+ cookies.sort(function(c1, c2) {
+ return (c1.creationIndex || 0) - (c2.creationIndex || 0);
+ });
+
+ callback(null, cookies);
+};
+
+WebStorageCookieStore.prototype._readStore = function() {
+ var json = this._storage.getItem(this._storeKey);
+
+ if (json !== null) {
+ try {
+ return JSON.parse(json);
+ } catch (e) { }
+ }
+
+ return {};
+};
+
+WebStorageCookieStore.prototype._writeStore = function(store) {
+ this._storage.setItem(this._storeKey, JSON.stringify(store));
+};
+
+module.exports = WebStorageCookieStore;
diff --git a/www/lodash.js b/www/lodash.js
new file mode 100644
index 0000000..7f48eec
--- /dev/null
+++ b/www/lodash.js
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Lodash (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
+ * Build: `lodash include="get,set,unset,values"`
+ */
+;(function(){function t(t,e){for(var n=-1,r=null==t?0:t.length,o=Array(r);++n=t}function S(t){var e=typeof t;
+return null!=t&&("object"==e||"function"==e)}function w(t){return null!=t&&typeof t=="object"}function x(t){return typeof t=="symbol"||w(t)&&"[object Symbol]"==l(t)}function F(t){return null==t?"":p(t)}function $(t){if(A(t)){var e=st(t),n=!e&&ft(t),r=!e&&!n&&pt(t),o=!e&&!n&&!r&&ht(t);if(e=e||n||r||o){for(var n=t.length,u=String,i=-1,c=Array(n);++it)&&(t==e.length-1?e.pop():nt.call(e,t,1),--this.size,true)},u.prototype.get=function(t){var e=this.__data__;return t=c(e,t),0>t?E:e[t][1]},u.prototype.has=function(t){
+return-1r?(++this.size,n.push([t,e])):n[r][1]=e,this},i.prototype.clear=function(){this.size=0,this.__data__={hash:new o,map:new(it||u),string:new o}},i.prototype.delete=function(t){return t=y(this,t).delete(t),this.size-=t?1:0,t},i.prototype.get=function(t){return y(this,t).get(t)},i.prototype.has=function(t){return y(this,t).has(t)},i.prototype.set=function(t,e){var n=y(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,
+this};var lt=function(t){t=v(t,function(t){return 500===e.size&&e.clear(),t});var e=t.cache;return t}(function(t){var e=[];return P.test(t)&&e.push(""),t.replace(U,function(t,n,r,o){e.push(r?o.replace(C,"$1"):n||t)}),e});v.Cache=i;var ft=f(function(){return arguments}())?f:function(t){return w(t)&&Q.call(t,"callee")&&!et.call(t,"callee")},st=Array.isArray,pt=D||k,ht=N?e(N):s;r.keys=$,r.memoize=v,r.set=function(t,e,n){if(null!=t&&S(t)){e=h(e,t);for(var r=-1,o=e.length,u=o-1,i=t;null!=i&&++ro.length)){var u=0,i=-1,c=-1,l=o.length;for(0>u&&(u=-u>l?0:l+u),i=i>l?l:i,0>i&&(i+=l),l=u>i?0:i-u>>>0,u>>>=0,i=Array(l);++c