diff --git a/package.json b/package.json index 3ab1dd2..0ff9550 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,31 @@ { - "name": "ManifoldCordova", - "version": "0.0.5", - "description": "This package is to be used as a workaround to be consumed fromt he ManifestJS tool. Its a Cordova plugin.", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "name": "cordova-plugin-hostedwebapp", + "version": "0.1.0", + "description": "Hosted Web App Plugin", + "cordova": { + "id": "cordova-plugin-hostedwebapp", + "platforms": [ + "android", + "windows", + "ios" + ] }, "repository": { "type": "git", "url": "https://github.com/manifoldjs/ManifoldCordova.git" }, "keywords": [ - "Cordova", - "plugin", + "cordova", + "manifest", "hosted", "web", - "manifest" + "ecosystem:cordova", + "cordova-android", + "cordova-windows", + "cordova-ios" ], "author": "", - "license": "ISC", + "license": "MIT", "bugs": { "url": "https://github.com/manifoldjs/ManifoldCordova/issues" }, diff --git a/plugin.xml b/plugin.xml index 6564218..6276239 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,7 +1,7 @@ + id="cordova-plugin-hostedwebapp" + version="0.1.0"> HostedWebApp Hosted Web App Plugin MIT License @@ -9,8 +9,9 @@ https://github.com/manifoldjs/ManifoldCordova.git https://github.com/manifoldjs/ManifoldCordova/issues - - + + + diff --git a/readme.md b/readme.md index ef2d6a8..71a566a 100644 --- a/readme.md +++ b/readme.md @@ -40,7 +40,7 @@ When the application is launched, the plugin automatically handles navigation to Lastly, since network connectivity is essential to the operation of a hosted web application, the plugin implements a basic offline feature that will show an offline page whenever connectivity is lost and will prevent users from interacting with the application until the connection is restored. ## Installation -`cordova plugin add com.manifoldjs.hostedwebapp` +`cordova plugin add cordova-plugin-hostedwebapp` > **IMPORTANT:** Before using the plugin, make sure to copy the W3C manifest file to the **root** folder of the Cordova application, alongside **config.xml**, and name it **manifest.json**. @@ -68,7 +68,7 @@ The plugin enables using content hosted in a web site inside a Cordova applicati > **Note:** You can find a sample manifest file at the start of this document. 1. Add the **Hosted Web Application** plugin to the project. - `cordova plugin add com.manifoldjs.hostedwebapp` + `cordova plugin add cordova-plugin-hostedwebapp` 1. Add one or more platforms, for example, to support Android. `cordova platform add android` @@ -131,7 +131,7 @@ For example, the following manifest references icons from the _/resources_ path ### URL Access Rules -For a hosted web application, the W3C manifest defines a scope that restricts the URLs to which the application can navigate. Additionally, the manifest can include a proprietary setting named **mjs_urlAccess** that defines an array of access rules each one consisting of a _url_ attribute that identifies the target of the rule and indicates whether URLs matching the rule should be navigated to by the application. Non-matching URLs will be launched externally. +For a hosted web application, the W3C manifest defines a scope that restricts the URLs to which the application can navigate. Additionally, the manifest can include a proprietary setting named **mjs_access_whitelist** that defines an array of access rules each one consisting of a _url_ attribute that identifies the target of the rule and indicates whether URLs matching the rule should be navigated to by the application. Non-matching URLs will be launched externally. Typically, Cordova applications define access rules to implement a security policy that controls access to external domains. The access rules must not only allow access to the scope defined by the W3C manifest but also to external content used within the site, for example, to reference script files hosted by a CDN origin. @@ -140,9 +140,9 @@ To configure the security policy, the plugin hook maps the scope and URL access **Manifest.json**
 ...
-   "start_url": "http://www.xyz.com/"
+   "start_url": "http://www.xyz.com/",
    "scope":  "/", 
-   "mjs_urlAccess":  [ 
+   "mjs_access_whitelist": [
      { "url": "http//googleapis.com/*" },
      { "url": "http//login.anotherdomain.com/" }
    ]
diff --git a/scripts/package.json b/scripts/package.json
index eef1662..b693267 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -10,7 +10,7 @@
     "builder": "node ./node_modules/mocha/bin/mocha --reporter mocha-teamcity-reporter"
   },
   "devDependencies": {
-    "cordova-lib": "^4.3.0",
+    "cordova-lib": "^5.0.0",
     "mocha": "^2.2.1",
     "mocha-teamcity-reporter": "0.0.4",
     "q": "^1.2.0"
diff --git a/scripts/test/assets/fullAccessRules/config.xml b/scripts/test/assets/fullAccessRules/config.xml
new file mode 100644
index 0000000..9731b12
--- /dev/null
+++ b/scripts/test/assets/fullAccessRules/config.xml
@@ -0,0 +1,22 @@
+
+
+  HelloWorld
+  
+      A sample Apache Cordova application that responds to the deviceready event.
+  
+  
+      Apache Cordova Team
+  
+  
+  
+  
+  
+  
+    
+  
+  
+    
+  
+  
+  
+
diff --git a/scripts/test/assets/normalFlow/www/manifest.json b/scripts/test/assets/fullAccessRules/manifest.json
similarity index 76%
rename from scripts/test/assets/normalFlow/www/manifest.json
rename to scripts/test/assets/fullAccessRules/manifest.json
index d724089..0c6724e 100644
--- a/scripts/test/assets/normalFlow/www/manifest.json
+++ b/scripts/test/assets/fullAccessRules/manifest.json
@@ -4,8 +4,7 @@
 	"orientation": "landscape",
 	"display": "fullscreen",
 	"scope": "http://wat-docs.azurewebsites.net/*",
-	"mjs_urlAccess": [
-		{ "url": "http://ajax.googleapis.com/*" },
+	"mjs_access_whitelist": [
 		{ "url": "http://wat.codeplex.com", "external": true }
 	]
 }
diff --git a/scripts/test/assets/fullAccessRules/www/empty.txt b/scripts/test/assets/fullAccessRules/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/fullAccessRules/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/assets/jsonEmpty/config.xml b/scripts/test/assets/jsonEmpty/config.xml
index 154fa89..49b4d59 100644
--- a/scripts/test/assets/jsonEmpty/config.xml
+++ b/scripts/test/assets/jsonEmpty/config.xml
@@ -8,7 +8,14 @@
       Apache Cordova Team
   
   
-  
+  
+  
+  
+  
+  
+      
+  
+      
   
   
 
diff --git a/scripts/test/assets/jsonEmpty/www/manifest.json b/scripts/test/assets/jsonEmpty/manifest.json
similarity index 100%
rename from scripts/test/assets/jsonEmpty/www/manifest.json
rename to scripts/test/assets/jsonEmpty/manifest.json
diff --git a/scripts/test/assets/jsonEmpty/www/empty.txt b/scripts/test/assets/jsonEmpty/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/jsonEmpty/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/assets/noExternalRulesNorScope/config.xml b/scripts/test/assets/noExternalRulesNorScope/config.xml
deleted file mode 100644
index 9ba7de9..0000000
--- a/scripts/test/assets/noExternalRulesNorScope/config.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-  HelloWorld
-  
-      A sample Apache Cordova application that responds to the deviceready event.
-  
-  
-      Apache Cordova Team
-  
-  
-  
-
diff --git a/scripts/test/assets/noExternalRulesNorScope/www/manifest.json b/scripts/test/assets/noExternalRulesNorScope/www/manifest.json
deleted file mode 100644
index 7e39600..0000000
--- a/scripts/test/assets/noExternalRulesNorScope/www/manifest.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "start_url": "http://wat-docs.azurewebsites.net/",
-  "name": "WAT Documentation",
-	"orientation": "landscape",
-	"display": "fullscreen",
-	"mjs_urlAccess": [
-		{ "url": "http://ajax.googleapis.com/*" }
-  ]
-}
diff --git a/scripts/test/assets/normalFlow/config.xml b/scripts/test/assets/normalFlow/config.xml
index 0aa8ee5..455d6a3 100644
--- a/scripts/test/assets/normalFlow/config.xml
+++ b/scripts/test/assets/normalFlow/config.xml
@@ -2,13 +2,16 @@
 
   HelloWorld
   
-      A sample Apache Cordova application that responds to the deviceready event.
+    A sample Apache Cordova application that responds to the deviceready event.
   
   
-      Apache Cordova Team
+    Apache Cordova Team
   
   
   
   
-  
+    
+  
+  
+
 
diff --git a/scripts/test/assets/normalFlow/manifest.json b/scripts/test/assets/normalFlow/manifest.json
new file mode 100644
index 0000000..c420a31
--- /dev/null
+++ b/scripts/test/assets/normalFlow/manifest.json
@@ -0,0 +1,7 @@
+{
+  "start_url": "http://wat-docs.azurewebsites.net/",
+  "short_name": "WAT Docs",
+  "name": "WAT Documentation",
+  "orientation": "landscape",
+  "display": "fullscreen"
+}
diff --git a/scripts/test/assets/normalFlow/www/empty.txt b/scripts/test/assets/normalFlow/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/normalFlow/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/assets/shortNameMissing/config.xml b/scripts/test/assets/shortNameMissing/config.xml
new file mode 100644
index 0000000..455d6a3
--- /dev/null
+++ b/scripts/test/assets/shortNameMissing/config.xml
@@ -0,0 +1,17 @@
+
+
+  HelloWorld
+  
+    A sample Apache Cordova application that responds to the deviceready event.
+  
+  
+    Apache Cordova Team
+  
+  
+  
+  
+    
+  
+  
+
+
diff --git a/scripts/test/assets/shortNameMissing/manifest.json b/scripts/test/assets/shortNameMissing/manifest.json
new file mode 100644
index 0000000..1751279
--- /dev/null
+++ b/scripts/test/assets/shortNameMissing/manifest.json
@@ -0,0 +1,6 @@
+{
+  "start_url": "http://wat-docs.azurewebsites.net/",
+  "name": "WAT Documentation",
+  "orientation": "landscape",
+  "display": "fullscreen"
+}
diff --git a/scripts/test/assets/shortNameMissing/www/empty.txt b/scripts/test/assets/shortNameMissing/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/shortNameMissing/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/assets/shortNameWithSlashes/config.xml b/scripts/test/assets/shortNameWithSlashes/config.xml
new file mode 100644
index 0000000..455d6a3
--- /dev/null
+++ b/scripts/test/assets/shortNameWithSlashes/config.xml
@@ -0,0 +1,17 @@
+
+
+  HelloWorld
+  
+    A sample Apache Cordova application that responds to the deviceready event.
+  
+  
+    Apache Cordova Team
+  
+  
+  
+  
+    
+  
+  
+
+
diff --git a/scripts/test/assets/shortNameWithSlashes/manifest.json b/scripts/test/assets/shortNameWithSlashes/manifest.json
new file mode 100644
index 0000000..d9969b5
--- /dev/null
+++ b/scripts/test/assets/shortNameWithSlashes/manifest.json
@@ -0,0 +1,7 @@
+{
+  "start_url": "http://wat-docs.azurewebsites.net/",
+  "short_name": "WAT /Docs/",
+  "name": "WAT Documentation",
+  "orientation": "landscape",
+  "display": "fullscreen"
+}
diff --git a/scripts/test/assets/shortNameWithSlashes/www/empty.txt b/scripts/test/assets/shortNameWithSlashes/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/shortNameWithSlashes/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/assets/updateAccessRules/config.xml b/scripts/test/assets/updateAccessRules/config.xml
deleted file mode 100644
index e21b332..0000000
--- a/scripts/test/assets/updateAccessRules/config.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-  HelloWorld
-  
-      A sample Apache Cordova application that responds to the deviceready event.
-  
-  
-      Apache Cordova Team
-  
-  
-  
-  
-  
-      
-  
-
diff --git a/scripts/test/assets/updateAccessRules/www/manifest.json b/scripts/test/assets/updateAccessRules/www/manifest.json
deleted file mode 100644
index 2f11e14..0000000
--- a/scripts/test/assets/updateAccessRules/www/manifest.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "start_url": "http://wat-docs.azurewebsites.net/",
-  "name": "WAT Documentation",
-	"scope": "http://wat-docs.azurewebsites.net/*",
-	"mjs_urlAccess": [
-  		{ "url": "http://ajax.googleapis.com/*" },
-      { "url": "http://wat.codeplex.com", "external": true }
-	]
-}
diff --git a/scripts/test/assets/xmlEmptyWidget/www/manifest.json b/scripts/test/assets/xmlEmptyWidget/manifest.json
similarity index 53%
rename from scripts/test/assets/xmlEmptyWidget/www/manifest.json
rename to scripts/test/assets/xmlEmptyWidget/manifest.json
index 80514a8..71fe314 100644
--- a/scripts/test/assets/xmlEmptyWidget/www/manifest.json
+++ b/scripts/test/assets/xmlEmptyWidget/manifest.json
@@ -3,8 +3,9 @@
     "name": "WAT Documentation",
 	"orientation": "landscape",
 	"display": "fullscreen",
-	"scope": "http://wat-docs.azurewebsites.net/*",
-	"mjs_urlAccess": [
-		{ "url": "http://ajax.googleapis.com/*" }
+	"scope": "/scope-path/",
+	"mjs_access_whitelist": [
+		{ "url": "whitelist-rule-1" },
+		{ "url": "whitelist-rule-2" }
   ]
 }
diff --git a/scripts/test/assets/xmlEmptyWidget/www/empty.txt b/scripts/test/assets/xmlEmptyWidget/www/empty.txt
new file mode 100644
index 0000000..50c5405
--- /dev/null
+++ b/scripts/test/assets/xmlEmptyWidget/www/empty.txt
@@ -0,0 +1 @@
+empty folder
\ No newline at end of file
diff --git a/scripts/test/updateConfiguration.js b/scripts/test/updateConfiguration.js
index 2cd51a3..4cac3fc 100644
--- a/scripts/test/updateConfiguration.js
+++ b/scripts/test/updateConfiguration.js
@@ -12,11 +12,17 @@ var fs = require('fs');
 var assetsDirectory = path.join(__dirname, 'assets');
 var workingDirectory = path.join(__dirname, 'tmp');
 
-function initializeContext(ctx) {
-  if (!ctx) {
-    ctx = {};
-  }
-
+function initializeContext(testDir) {
+  
+  var ctx = {
+    opts : {
+      plugin: {
+        id: 'com-manifoldjs-hostedwebapp'
+      },
+      projectRoot : testDir
+    }
+  };
+            
   var requireCordovaModule = ctx.requireCordovaModule;
 
   ctx.requireCordovaModule = function(moduleName) {
@@ -58,286 +64,249 @@ describe('updateConfiguration.js', function (){
     tu.copyRecursiveSync(assetsDirectory, workingDirectory);
   });
 
-  it('Should update name with value from manifest.json', function (){
+  it('Should update name with short_name value (without spaces) from manifest.json', function (done){
     var testDir = path.join(workingDirectory, 'normalFlow');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('WAT Documentation') > -1);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function() {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('WATDocs') > -1);
+      
+      done();
+    });
   });
 
-  it('Should not update name if it is missing in manifest.json', function (){
-    var testDir = path.join(workingDirectory, 'jsonEmpty');
-    var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('HelloWorld') > -1);
-  });
-
-  it('Should add name if XML element is missing', function (){
-    var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
+  it('Should update name with name value (without spaces) from manifest.json if short_name is missing', function (done){
+    var testDir = path.join(workingDirectory, 'shortNameMissing');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('WAT Documentation') > content.indexOf(''));
-    assert(content.indexOf('WAT Documentation') < content.indexOf(''));
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function() {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('WATDocumentation') > -1);
+      
+      done();
+    });
   });
-
-  it('Should update orientation with value from manifest.json', function (){
-    var testDir = path.join(workingDirectory, 'normalFlow');
+  
+  it('Should ignore slashes when updating name from manifest.json', function (done) {
+    var testDir = path.join(workingDirectory, 'shortNameWithSlashes');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('WATDocs') > -1);
+      
+      done();
+    });
   });
 
-  it('Should not update orientation if it is missing in manifest.json', function (){
+  it('Should not update name if it is missing in manifest.json', function (done) {
     var testDir = path.join(workingDirectory, 'jsonEmpty');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > content.indexOf(''));
-    assert(content.indexOf('') < content.indexOf(''));
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('HelloWorld') > -1);
+      
+      done();
+    });
   });
 
-  it('Should add orientation if XML element element is missing', function (){
+  it('Should add name if XML element is missing', function (done){
     var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > content.indexOf(''));
-    assert(content.indexOf('') < content.indexOf(''));
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('WATDocumentation') > content.indexOf(''));
+      assert(content.indexOf('WATDocumentation') < content.indexOf(''));
+      done();
+    });
   });
 
-  it('Should update fullscreen with value from manifest.json', function (){
+ 
+  it('Should update orientation with value from manifest.json', function (done){
     var testDir = path.join(workingDirectory, 'normalFlow');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > -1);
+      
+      done();
+    });
 
-    updateConfiguration(ctx);
 
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
   });
 
-  it('Should not update fullscreen if it is missing in manifest.json', function (){
+  it('Should not update orientation if it is missing in manifest.json', function (done){
     var testDir = path.join(workingDirectory, 'jsonEmpty');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > content.indexOf(''));
+      assert(content.indexOf('') < content.indexOf(''));
+      
+      done();
+    });
   });
 
-  it('Should add fullscreen if XML element is missing', function (){
+  it('Should add orientation if XML element element is missing', function (done){
     var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > content.indexOf(''));
-    assert(content.indexOf('') < content.indexOf(''));
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > content.indexOf(''));
+      assert(content.indexOf('') < content.indexOf(''));
+      
+      done();
+    });
   });
 
-  it('Should remove wildcard access rule from config.xml', function (){
+  it('Should update fullscreen with value from manifest.json', function (done){
     var testDir = path.join(workingDirectory, 'normalFlow');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
+    var ctx = initializeContext(testDir);
 
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') == -1);
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > -1);    
+    
+      done();
+    });
   });
 
-  it('Should keep wildcard access rule if scope and external rules not present', function (){
-    var testDir = path.join(workingDirectory, 'noExternalRulesNorScope');
+  it('Should not update fullscreen if it is missing in manifest.json', function (done){
+    var testDir = path.join(workingDirectory, 'jsonEmpty');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
+    var ctx = initializeContext(testDir);
 
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > -1);
+    
+      done();
+    });
   });
 
-  it('Should add launch-external attribute to existing access rule', function (){
-    var testDir = path.join(workingDirectory, 'updateAccessRules');
+  it('Should add fullscreen if XML element is missing', function (done){
+    var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-      opts : {
-        projectRoot : testDir
-      }
-    };
-    initializeContext(ctx);
-    
-    updateConfiguration(ctx);
+    var ctx = initializeContext(testDir);
 
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
-    assert(content.indexOf('') == -1);
-  });
-  
-  it('Should remove launch-external attribute from existing access rule', function (){
-    var testDir = path.join(workingDirectory, 'updateAccessRules');
-    var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-      opts : {
-        projectRoot : testDir
-      }
-    };
-    initializeContext(ctx);
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > content.indexOf(''));
+      assert(content.indexOf('') < content.indexOf(''));
     
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
-    assert(content.indexOf('') == -1);
+      done();
+    });
   });
 
-  it('Should keep existing access rule unchanged in config.xml', function (){
+  it('Should keep existing access rules unchanged in config.xml', function (done){
     var testDir = path.join(workingDirectory, 'jsonEmpty');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') > -1);        
+      assert(content.indexOf('') > -1); 
+      assert(content.indexOf('') > -1); 
+               
+      done();
+    });
   });
 
-  it('Should add internal access rule from hap_access list', function (){
-    var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
+  it('Should remove "root" full access rules from config.xml', function (done){
+    var testDir = path.join(workingDirectory, 'fullAccessRules');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);  
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      assert(content.indexOf('') === -1);
+      
+      done();
+    });
   });
 
-  it('Should add internal access rule from scope property', function (){
+  it('Should add access rules for web site domain in config.xml if scope is missing', function (done){
     var testDir = path.join(workingDirectory, 'normalFlow');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
 
-    updateConfiguration(ctx);
+      // rules for android
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
 
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
+      // rules for ios
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      
+      done();
+    });
   });
 
-  it('Should add external access rule to android section', function (){
-    var testDir = path.join(workingDirectory, 'normalFlow');
+  it('Should add access rules for scope in config.xml', function (done){
+    var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
     var configXML = path.join(testDir, 'config.xml');
-    var ctx = {
-                opts : {
-                  projectRoot : testDir
-                }
-              };
-    initializeContext(ctx);
-
-    updateConfiguration(ctx);
-
-    var content = fs.readFileSync(configXML).toString();
-    assert(content.indexOf('') > -1);
-    assert(content.indexOf('') > content.indexOf(''));
-    assert(content.indexOf('') < content.indexOf(''));
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+
+      // rules for android
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      
+      // rules for ios
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      
+      done();
+    });
   });
-  
+
+  it('Should add access rules from mjs_access_whitelist list', function (done){
+    var testDir = path.join(workingDirectory, 'xmlEmptyWidget');
+    var configXML = path.join(testDir, 'config.xml');
+    var ctx = initializeContext(testDir);
+
+    updateConfiguration(ctx).then(function () {
+      var content = fs.readFileSync(configXML).toString();
+      // rules for android
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      
+      // rules for ios
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+      assert(content.match(/[\s\S]*[\s\S]*<\/platform>/));
+    
+      done();
+    });
+  });
+
   afterEach(function () {
     tu.deleteRecursiveSync(workingDirectory);
   });
diff --git a/scripts/updateConfiguration.js b/scripts/updateConfiguration.js
index 7037fdc..9e20cc1 100644
--- a/scripts/updateConfiguration.js
+++ b/scripts/updateConfiguration.js
@@ -120,58 +120,85 @@ function configureParser(context) {
 }
 
 function processAccessRules(manifest) {
-    // Remove the wildcard ('*') access rules
-    config.removeElements('.//access[@origin=\'*\']');
-
-    // Remove previous access rules
-    config.removeElements('.//access[@hap-rule=\'yes\']');
-
-    // get the android platform section and create it if it does not exist
-    var androidRoot = config.doc.find('platform[@name=\'android\']');
-    if (!androidRoot) {
-        androidRoot = etree.SubElement(config.doc.getroot(), 'platform');
-        androidRoot.set('name', 'android');
-    }
-
-    // get the ios platform section and create it if it does not exist
-    var iosRoot = config.doc.find('platform[@name=\'ios\']');
-    if (!iosRoot) {
-        iosRoot = etree.SubElement(config.doc.getroot(), 'platform');
-        iosRoot.set('name', 'ios');
-    }
-
-    // Add base access rule based on the start_url and the scope
-    var baseUrlPattern = manifest.start_url;
-    if (manifest.scope && manifest.scope.length) {
-        baseUrlPattern = url.resolve(baseUrlPattern, manifest.scope);
-    }
+    if (manifest && manifest.start_url) {
+  
+        // Remove previous rules
+        config.removeElements('.//allow-intent[@hap-rule=\'yes\']');
+        config.removeElements('.//allow-navigation[@hap-rule=\'yes\']');
+        config.removeElements('.//access[@hap-rule=\'yes\']');
     
-    baseUrlPattern = url.resolve(baseUrlPattern, '*');
+        // Remove "full access"" rules
+        config.removeElements('.//allow-intent[@href=\'http://*/*\']');
+        config.removeElements('.//allow-intent[@href=\'https://*/*\']');
+        config.removeElements('.//allow-intent[@href=\'*\']');
+        config.removeElements('.//allow-navigation[@href=\'http://*/*\']');
+        config.removeElements('.//allow-navigation[@href=\'https://*/*\']');
+        config.removeElements('.//allow-navigation[@href=\'*\']');
+        config.removeElements('.//access[@origin=\'http://*/*\']');
+        config.removeElements('.//access[@origin=\'https://*/*\']');
+        config.removeElements('.//access[@origin=\'*\']');
     
-    var androidbaseRule = new etree.SubElement(androidRoot, 'access');
-    androidbaseRule.set('hap-rule','yes');
-    androidbaseRule.set('origin', baseUrlPattern);
+        // get the android platform section and create it if it does not exist
+        var androidRoot = config.doc.find('platform[@name=\'android\']');
+        if (!androidRoot) {
+            androidRoot = etree.SubElement(config.doc.getroot(), 'platform');
+            androidRoot.set('name', 'android');
+        }
     
-    var iosBaseRule = new etree.SubElement(iosRoot, 'access');
-    iosBaseRule.set('hap-rule','yes');
-    iosBaseRule.set('origin', baseUrlPattern);
+        // get the ios platform section and create it if it does not exist
+        var iosRoot = config.doc.find('platform[@name=\'ios\']');
+        if (!iosRoot) {
+            iosRoot = etree.SubElement(config.doc.getroot(), 'platform');
+            iosRoot.set('name', 'ios');
+        }
     
-    var baseUrl = baseUrlPattern.substring(0, baseUrlPattern.length - 1);;
-
-    // add additional access rules
-    if (manifest.mjs_urlAccess && manifest.mjs_urlAccess instanceof Array) {
-        manifest.mjs_urlAccess.forEach(function (item) {
-            // To avoid duplicates, add the rule only if it does not have the base URL as a prefix
-            if (item.url.indexOf(baseUrl) !== 0 ) {
-                var androidAccessEl = new etree.SubElement(androidRoot, 'access');
-                androidAccessEl.set('hap-rule','yes');
-                androidAccessEl.set('origin', item.url);
-                
-                var iosAccessEl = new etree.SubElement(iosRoot, 'access');
-                iosAccessEl.set('hap-rule','yes');
-                iosAccessEl.set('origin', item.url);
-            }
-        });
+        // determine base rule based on the start_url and the scope
+        var baseUrlPattern = manifest.start_url;
+        if (manifest.scope && manifest.scope.length) {
+            baseUrlPattern = url.resolve(baseUrlPattern, manifest.scope);
+        }
+        
+        baseUrlPattern = url.resolve(baseUrlPattern, '*');
+        
+        // add base rule as an access rule for Android
+        var androidAccessBaseRule = new etree.SubElement(androidRoot, 'access');
+        androidAccessBaseRule.set('hap-rule','yes');
+        androidAccessBaseRule.set('origin', baseUrlPattern);
+        
+        // add base rule as a navigation rule for Android
+        var androidNavigationBaseRule = new etree.SubElement(androidRoot, 'allow-navigation');
+        androidNavigationBaseRule.set('hap-rule','yes');
+        androidNavigationBaseRule.set('href', baseUrlPattern);
+        
+        // add base rule as an svvrdd rule for iOS
+        var iosBaseAccessRule = new etree.SubElement(iosRoot, 'access');
+        iosBaseAccessRule.set('hap-rule','yes');
+        iosBaseAccessRule.set('origin', baseUrlPattern);
+        
+        var baseUrl = baseUrlPattern.substring(0, baseUrlPattern.length - 1);;
+    
+        // add additional access rules
+        if (manifest.mjs_access_whitelist && manifest.mjs_access_whitelist instanceof Array) {
+            manifest.mjs_access_whitelist.forEach(function (item) {
+                // To avoid duplicates, add the rule only if it does not have the base URL as a prefix
+                if (item.url.indexOf(baseUrl) !== 0 ) {
+                    // add as an access rule for Android
+                    var androidAccessEl = new etree.SubElement(androidRoot, 'access');
+                    androidAccessEl.set('hap-rule','yes');
+                    androidAccessEl.set('origin', item.url);
+    
+                    // add as a navigation rule for Android
+                    var androidNavigationEl = new etree.SubElement(androidRoot, 'allow-navigation');
+                    androidNavigationEl.set('hap-rule','yes');
+                    androidNavigationEl.set('href', item.url);  
+                    
+                    // add as an access rule for iOS
+                    var iosAccessEl = new etree.SubElement(iosRoot, 'access');
+                    iosAccessEl.set('hap-rule','yes');
+                    iosAccessEl.set('origin', item.url);
+                }
+            });
+        }
     }
 }
 
diff --git a/src/android/HostedWebApp.java b/src/android/HostedWebApp.java
index 11b9c94..cea8047 100644
--- a/src/android/HostedWebApp.java
+++ b/src/android/HostedWebApp.java
@@ -183,21 +183,23 @@ else if (id.equals("onPageFinished")) {
 
     @Override
     public boolean onOverrideUrlLoading(String url) {
-        if (!this.webView.getWhitelist().isUrlWhiteListed(url)) {
-            try {
-                // If the URL is not whitelisted, open the URL in the external browser
-                // (code extracted from CordovaLib/src/CordovaUriHelper.java)
+        CordovaPlugin whiteListPlugin = this.webView.getPluginManager().getPlugin("Whitelist");
 
+        if (whiteListPlugin != null && Boolean.TRUE != whiteListPlugin.shouldAllowNavigation(url)) {
+            // If the URL is not in the list URLs to allow navigation, open the URL in the external browser
+            // (code extracted from CordovaLib/src/org/apache/cordova/CordovaWebViewImpl.java)
+            try {
                 Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
                 intent.addCategory(Intent.CATEGORY_BROWSABLE);
-                intent.setComponent(null);
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
-                    intent.setSelector(null);
+                Uri uri = Uri.parse(url);
+                // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
+                // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
+                if ("file".equals(uri.getScheme())) {
+                    intent.setDataAndType(uri, this.webView.getResourceApi().getMimeType(uri));
+                } else {
+                    intent.setData(uri);
                 }
-
-                this.activity.startActivity(intent);
-                return true;
+                cordova.getActivity().startActivity(intent);
             } catch (android.content.ActivityNotFoundException e) {
                 e.printStackTrace();
             }
@@ -208,9 +210,9 @@ public boolean onOverrideUrlLoading(String url) {
         }
     }
 
-  public JSONObject getManifest() {
-    return this.manifestObject;
-  }
+    public JSONObject getManifest() {
+        return this.manifestObject;
+    }
 
     private boolean assetExists(String asset) {
         final AssetManager assetManager = this.activity.getResources().getAssets();
@@ -250,11 +252,19 @@ private LinearLayout createOfflineRootLayout() {
     }
 
     private void handleNetworkConnectionChange(String info) {
+        final HostedWebApp me = HostedWebApp.this;
         if (info.equals("none")) {
             this.showOfflineOverlay();
         } else {
             if (this.isConnectionError) {
-                this.webView.reload();
+
+                this.activity.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        String currentUrl = me.webView.getUrl();
+                        me.webView.loadUrlIntoView(currentUrl, false);
+                    }
+                });
             } else {
                 this.hideOfflineOverlay();
             }
diff --git a/src/ios/CDVHostedWebApp.h b/src/ios/CDVHostedWebApp.h
index 90e08e3..861c341 100644
--- a/src/ios/CDVHostedWebApp.h
+++ b/src/ios/CDVHostedWebApp.h
@@ -3,6 +3,7 @@
 #define kManifestLoadedNotification @"kManifestLoadedNotification"
 
 #define kCDVHostedWebAppWebViewDidStartLoad @"CDVHostedWebAppWebViewDidStartLoad"
+#define kCDVHostedWebAppWebViewShouldStartLoadWithRequest @"CDVHostedWebAppWebViewShouldStartLoadWithRequest"
 #define kCDVHostedWebAppWebViewDidFinishLoad @"CDVHostedWebAppWebViewDidFinishLoad"
 #define kCDVHostedWebAppWebViewDidFailLoadWithError @"CDVHostedWebAppWebViewDidFailLoadWithError"
 
diff --git a/src/ios/CDVHostedWebApp.m b/src/ios/CDVHostedWebApp.m
index 21ff595..b069dd3 100644
--- a/src/ios/CDVHostedWebApp.m
+++ b/src/ios/CDVHostedWebApp.m
@@ -21,6 +21,8 @@ - (void)webViewDidStartLoad:(UIWebView*)theWebView {
 }
 
 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
+    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:kCDVHostedWebAppWebViewShouldStartLoadWithRequest object:request]];
+
     return [self.wrappedDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
 }
 
@@ -214,13 +216,13 @@ - (void)createOfflineView
 // network connectivity is lost. It restores the original view once the network is up again.
 - (void)updateConnectivityStatus:(NSNotification*)notification
 {
-    CDVReachability* reachability = [notification object];
-
     if ([[notification name] isEqualToString:kReachabilityChangedNotification]) {
-        NSLog (@"Received a network connectivity change notification. The device is currently %@.", reachability.connectionRequired ? @"offline" : @"online");
-        if (self.enableOfflineSupport) {
-            if ((reachability != nil) && [reachability isKindOfClass:[CDVReachability class]]) {
-                if (reachability.connectionRequired) {
+        CDVReachability* reachability = [notification object];
+        if ((reachability != nil) && [reachability isKindOfClass:[CDVReachability class]]) {
+            BOOL isOffline = (reachability.currentReachabilityStatus == NotReachable);
+            NSLog (@"Received a network connectivity change notification. The device is currently %@.", isOffline ? @"offLine" : @"online");
+            if (self.enableOfflineSupport) {
+                if (isOffline) {
                     [self.offlineView setHidden:NO];
                 }
                 else {
diff --git a/src/windows/HostedWebAppPluginProxy.js b/src/windows/HostedWebAppPluginProxy.js
index e4eb5dd..2640361 100644
--- a/src/windows/HostedWebAppPluginProxy.js
+++ b/src/windows/HostedWebAppPluginProxy.js
@@ -158,8 +158,8 @@ function configureWhiteList(manifest) {
 
 
         // add additional access rules
-        if (manifest.mjs_urlAccess && manifest.mjs_urlAccess instanceof Array) {
-            manifest.mjs_urlAccess.forEach(function (rule) {
+        if (manifest.mjs_access_whitelist && manifest.mjs_access_whitelist instanceof Array) {
+            manifest.mjs_access_whitelist.forEach(function (rule) {
                 _whiteList.push(convertPatternToRegex(rule.url));
             });
         }