Skip to content
This repository has been archived by the owner on Jan 14, 2022. It is now read-only.

Commit

Permalink
Merge pull request #54 from manifoldjs/v0.2.0
Browse files Browse the repository at this point in the history
v0.2.0
  • Loading branch information
msrodri committed Nov 26, 2015
2 parents 423fd3d + 6ee0a11 commit a31b045
Show file tree
Hide file tree
Showing 16 changed files with 1,002 additions and 193 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-plugin-hostedwebapp",
"version": "0.1.5",
"version": "0.2.0",
"description": "Hosted Web App Plugin",
"cordova": {
"id": "cordova-plugin-hostedwebapp",
Expand Down
8 changes: 5 additions & 3 deletions plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova-plugin-hostedwebapp"
version="0.1.4">
version="0.2.0">
<name>HostedWebApp</name>
<description>Hosted Web App Plugin</description>
<license>MIT License</license>
Expand All @@ -20,13 +20,15 @@
<js-module src="www/hostedWebApp.js" name="hostedwebapp">
<clobbers target="hostedwebapp" />
</js-module>

<asset src="www/hostedapp-bridge.js" target="hostedapp-bridge.js" />

<engines>
<engine name="cordova-windows" version="<=4.1.0" />
<engine name="cordova-ios" version="<=3.9.1" />
<engine name="cordova-ios" version="<=3.9.2" />
<engine name="cordova-android" version="<=4.1.1" />
</engines>

<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
Expand Down
89 changes: 75 additions & 14 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ When the application is launched, the plugin automatically handles navigation to

> **Note:** Although the W3C specs for the Web App manifest consider absolute and relative URLs valid for the _start_url_ value (e.g. _http://www.racer2k.net/racer/start.html_ and _/start.html_ are both valid), the plugin requires this URL **to be an absolute URL**. Otherwise, the installed applications won't be able to navigate to the web site.
The plugin enables the injection of additional Cordova plugins and app-specific scripts that consume them allowing you to take advantage of native features in your hosted web apps.

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
Expand Down Expand Up @@ -82,6 +84,68 @@ The plugin enables using content hosted in a web site inside a Cordova applicati
> **Note:** The plugin updates the Cordova configuration file (config.xml) with the information in the W3C manifest. If the information in the manifest changes, you can reapply the updated manifest settings at any time by executing prepare. For example:
`cordova prepare`

### Using Cordova Plugins in Hosted Web Apps
The plugin supports the injection of Cordova and the plugin interface scripts into the pages of a hosted site. There are two different plugin modes: '_server_' and '_client_'. In '_client_' mode, the **cordova.js** file and the plugin interface script files are retrieved from the app package. In '_server_' mode, these files are downloaded from the server along with the rest of the app's content. The plugin also provides a mechanism for injecting scripts that can be used, among other things, to consume the plugins added to the app. Imported scripts can be retrieved from the app package or downloaded from a remote source.

Very briefly, these are the steps that are needed to use plugins:

- Add one or more Cordova plugins to the app.

- Enable API access in any pages where Cordova and the plugins will be used. This injects the Cordova runtime environment and is configured via a custom extension in the W3C manifest. The **match** and **platform** attributes specifies the pages and platforms where you will use Cordova.

```
{
...
"mjs_api_access": [
{ "match": "http://yoursite.com/path1/*", "platform": "android, ios, windows", "access": "cordova" },
...
]
}
```
- Optionally, choose a plugin mode. The default mode is _client_.

**Client mode**
```
{
...
"mjs_cordova": {
"plugin_mode": "client"
}
}
```
**Server mode**
```
{
...
"mjs_cordova": {
"plugin_mode": "server",
"base_url": "js/cordova"
}
}
```
(In '_server_' mode, the Cordova files and plugin interface scripts must be deployed to the site to the path specified in **base_url**. Also, the **cordova.js** and **cordova_plugins.js** files for each platform need to be renamed to specify the platform in their names so that **cordova.js** and **cordova_plugins.js** become, in the case of Android for example, **cordova-android.js** and **cordova_plugins-android.js** respectively.)
To inject scripts into the hosted web content:
- Update the app's manifest to list the imported scripts in a custom **mjs_import_scripts** section.
```
{
...
"mjs_import_scripts": [
{ "src": "js/alerts.js" },
{ "src": "http://yoursite.com/js/app/contacts.js" },
{ "src": "js/camera.js", "match": "http://yoursite.com/profile/*" },
...
]
}
```
- For app-hosted scripts, copy the script files to the Cordova project. The path in **mjs_import_scripts** must be specified relative to the '_www_' folder of the project. Server-hosted scripts must be deployed to the site.
The following [wiki article](https://github.com/manifoldjs/ManifoldJS/wiki/Using-Cordova-Plugins-in-Hosted-Web-Apps) provides additional information about these features.
### Offline Feature
The plugin implements a basic offline feature that will show an offline page whenever network connectivity is lost. By default, the page shows a suitable message alerting the user about the loss of connectivity. To customize the offline experience, a page named **offline.html** can be placed in the **www** folder of the application and it will be used instead.
Expand Down Expand Up @@ -130,20 +194,18 @@ For example, the following manifest references icons from the _/resources_ path
}
</pre>
### 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_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.
### Navigation Scope
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_extended_scope** that defines an array of scope rules each one indicating 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.

To configure the security policy, the plugin hook maps the scope and URL access rules in the W3C manifest (**manifest.json**) to suitable access elements in the Cordova configuration file (**config.xml**). For example:
Typically, Cordova applications define scope rules to implement a security policy that controls access to external domains. To configure the security policy, the plugin hook maps the scope rules in the W3C manifest (**manifest.json**) to suitable `<allow-navigation>` elements in the Cordova configuration file (**config.xml**). For example:
**Manifest.json**
<pre>
...
"start_url": "http://www.xyz.com/",
"scope": "/",
"mjs_access_whitelist": [
{ "url": "http//googleapis.com/*" },
"mjs_extended_scope": [
{ "url": "http//otherdomain.com/*" },
{ "url": "http//login.anotherdomain.com/" }
]
...
Expand All @@ -152,9 +214,9 @@ To configure the security policy, the plugin hook maps the scope and URL access
**Config.xml**
<pre>
...
&lt;access origin="http://www.xyz.com/*" /&gt;
&lt;access origin="http://googleapis.com/*" /&gt;
&lt;access origin="http://login.anotherdomain.com/" /&gt;
&lt;allow-navigation href="http://www.xyz.com/*" /&gt;
&lt;allow-navigation href="http://otherdomain.com/*" /&gt;
&lt;allow-navigation href="http://login.anotherdomain.com/" /&gt;
...
</pre>
Expand Down Expand Up @@ -198,13 +260,12 @@ Windows Phone 8.1
iOS
Android
### Windows 8.1 and Windows Phone 8.1 Quirks
### Windows and Windows Phone Quirks
Cordova for Android and iOS platforms provide a security policy to control which network requests triggered by the page (css, js, images, XHRs, etc.) are allowed to be made; this means that they will be blocked if they are outside the scope and do not match any of the access rules defined in the manifest.
Cordova for Android and iOS platforms provide a security policy to control which network requests triggered by the page (css, js, images, XHRs, etc.) are allowed to be made; this means that they will be blocked if they don't match the `origin` attribute of any of the `<access>` elements defined in the Cordova configuration file (**config.xml**).
The Windows and Windows Phone platforms do not provide control for these kind of requests, and they will be allowed.

## Changelog
Releases are documented in [GitHub](https://github.com/manifoldjs/ManifoldCordova/releases).
Releases are documented in [GitHub](https://github.com/manifoldjs/ManifoldCordova/releases).
2 changes: 1 addition & 1 deletion scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"builder": "node ./node_modules/mocha/bin/mocha --reporter mocha-teamcity-reporter"
},
"devDependencies": {
"cordova-lib": "^5.0.0",
"cordova-lib": "^5.4.0",
"mocha": "^2.2.1",
"mocha-teamcity-reporter": "0.0.4",
"q": "^1.2.0"
Expand Down
11 changes: 9 additions & 2 deletions scripts/rollbackWindowsWrapperFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ function deleteFile(path) {

// Configure Cordova configuration parser
function configureParser(context) {
var cordova_util = context.requireCordovaModule('cordova-lib/src/cordova/util'),
ConfigParser = context.requireCordovaModule('cordova-lib/src/configparser/ConfigParser');
var cordova_util = context.requireCordovaModule('cordova-lib/src/cordova/util');
var ConfigParser;
try {
ConfigParser = context.requireCordovaModule('cordova-lib/node_modules/cordova-common').ConfigParser;
} catch (err) {
// Fallback to old location of config parser (old versions of cordova-lib)
ConfigParser = context.requireCordovaModule('cordova-lib/src/configparser/ConfigParser');
}

etree = context.requireCordovaModule('cordova-lib/node_modules/elementtree');

var xml = cordova_util.projectConfig(context.opts.projectRoot);
Expand Down
4 changes: 2 additions & 2 deletions scripts/test/assets/fullAccessRules/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"orientation": "landscape",
"display": "fullscreen",
"scope": "http://wat-docs.azurewebsites.net/*",
"mjs_access_whitelist": [
{ "url": "http://wat.codeplex.com", "external": true }
"mjs_extended_scope": [
"http://wat.codeplex.com"
]
}
8 changes: 4 additions & 4 deletions scripts/test/assets/xmlEmptyWidget/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"orientation": "landscape",
"display": "fullscreen",
"scope": "/scope-path/",
"mjs_access_whitelist": [
{ "url": "whitelist-rule-1" },
{ "url": "whitelist-rule-2" }
]
"mjs_extended_scope": [
"whitelist-rule-1",
"whitelist-rule-2"
]
}
81 changes: 34 additions & 47 deletions scripts/test/updateConfigurationBeforePrepare.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ function initializeContext(testDir) {
return require('cordova-lib/src/cordova/util');
}

if (moduleName === 'cordova-lib/node_modules/cordova-common') {
return require('cordova-lib/node_modules/cordova-common');
}

if (moduleName === 'cordova-lib/src/configparser/ConfigParser') {
return require('cordova-lib/src/configparser/ConfigParser');
}
Expand Down Expand Up @@ -228,119 +232,102 @@ describe('updateConfigurationBeforePrepare.js', function (){
});
});

it('Should remove "root" full access rules from config.xml', function (done){
it('Should keep generic network access rules from config.xml', function (done){
var testDir = path.join(workingDirectory, 'fullAccessRules');
var configXML = path.join(testDir, 'config.xml');
var ctx = initializeContext(testDir);

updateConfiguration(ctx).then(function () {
var content = fs.readFileSync(configXML).toString();
assert(content.indexOf('<allow-navigation href="http://*/*\" />') === -1);
assert(content.indexOf('<allow-navigation href="https://*/*\" />') === -1);
assert(content.indexOf('<allow-navigation href="*" />') === -1);
assert(content.indexOf('<allow-intent href="https://*/*\" />') === -1);
assert(content.indexOf('<allow-intent href="http://*/*\" />') === -1);
assert(content.indexOf('<access origin="https://*/*" />') > 0);
assert(content.indexOf('<access origin="http://*/*" />') > 0);

done();
});
});

it('Should remove generic allow-intent rules from config.xml', function (done){
var testDir = path.join(workingDirectory, 'fullAccessRules');
var configXML = path.join(testDir, 'config.xml');
var ctx = initializeContext(testDir);

updateConfiguration(ctx).then(function () {
var content = fs.readFileSync(configXML).toString();
assert(content.indexOf('<allow-intent href="https://*/*" />') === -1);
assert(content.indexOf('<allow-intent href="http://*/*" />') === -1);
assert(content.indexOf('<allow-intent href="*" />') === -1);
assert(content.indexOf('<access origin="https://*/*\" />') === -1);
assert(content.indexOf('<access origin="http://*/*\" />') === -1);

done();
});
});

it('Should add access rules for web site domain in config.xml if scope is missing', function (done){
it('Should add allow-navigation rule 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 = initializeContext(testDir);

updateConfiguration(ctx).then(function () {
var content = fs.readFileSync(configXML).toString();

// rules for android
assert(content.match(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="http:\/\/wat-docs.azurewebsites.net\/\*" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="http:\/\/wat-docs.azurewebsites.net\/\*" \/>[\s\S]*<\/platform>/));

// rules for ios
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="http:\/\/wat-docs.azurewebsites.net\/\*" \/>[\s\S]*<\/platform>/));
assert(content.indexOf('<allow-navigation hap-rule="yes" href="http://wat-docs.azurewebsites.net/*" />') > 0);

done();
});
});

it('Should add access rules for scope in config.xml if scope is a relative URL', function (done){
it('Should add allow-navigation rule for scope in config.xml if scope is a relative URL', 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(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="http:\/\/wat-docs.azurewebsites.net\/scope-path\/\*" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="http:\/\/wat-docs.azurewebsites.net\/scope-path\/\*" \/>[\s\S]*<\/platform>/));

// rules for ios
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="http:\/\/wat-docs.azurewebsites.net\/scope-path\/\*" \/>[\s\S]*<\/platform>/));
assert(content.indexOf('<allow-navigation hap-rule="yes" href="http://wat-docs.azurewebsites.net/scope-path/*" />') > 0);

done();
});
});

it('Should add access rules for scope in config.xml if scope is a full URL', function (done){
it('Should add allow-navigation rules for scope in config.xml if scope is a full URL', function (done){
var testDir = path.join(workingDirectory, 'fullUrlForScope');
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(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="http:\/\/www.domain.com\/\*" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="http:\/\/www.domain.com\/\*" \/>[\s\S]*<\/platform>/));

// rules for ios
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="http:\/\/www.domain.com\/\*" \/>[\s\S]*<\/platform>/));
assert(content.indexOf('<allow-navigation hap-rule="yes" href="http://www.domain.com/*" />') > 0);

done();
});
});

it('Should add access rules for scope in config.xml if scope is a full URL with wildcard as subdomain', function (done){
it('Should add allow-navigation rule for scope in config.xml if scope is a full URL with wildcard as subdomain', function (done){
var testDir = path.join(workingDirectory, 'wildcardSubdomainForScope');
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(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="http:\/\/\*.domain.com" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="http:\/\/\*.domain.com" \/>[\s\S]*<\/platform>/));

// rules for ios
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="http:\/\/\*.domain.com" \/>[\s\S]*<\/platform>/));

assert(content.indexOf('<allow-navigation hap-rule="yes" href="http://*.domain.com" />') > 0);

done();
});
});

it('Should add access rules from mjs_access_whitelist list', function (done){
it('Should add allow-navigation 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(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="whitelist-rule-1" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="whitelist-rule-1" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<access hap-rule="yes" origin="whitelist-rule-2" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="android">[\s\S]*<allow-navigation hap-rule="yes" href="whitelist-rule-2" \/>[\s\S]*<\/platform>/));

// rules for ios
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="whitelist-rule-1" \/>[\s\S]*<\/platform>/));
assert(content.match(/<platform name="ios">[\s\S]*<access hap-rule="yes" origin="whitelist-rule-2" \/>[\s\S]*<\/platform>/));

assert(content.indexOf('<allow-navigation hap-rule="yes" href="whitelist-rule-1" />') > 0);
assert(content.indexOf('<allow-navigation hap-rule="yes" href="whitelist-rule-2" />') > 0);

done();
});
});
Expand Down
Loading

0 comments on commit a31b045

Please sign in to comment.