cordova-app-loader
Remote update your Cordova App
- Write a manifest.json to describe files of your app.
- Use the manifest.json to bootstrap your app.
- Build and deploy your app.
A little later...
- Upload an update to your server (manifest.json + files)
- Use
CordovaAppLoader
to
- Check the new manifest
- Download files
- Update your app!
Based on cordova-promise-fs and cordova-file-cache.
Installation
Get javascript
Download and include CordovaPromiseFS.js, CordovaAppLoader.js and bootstrap.js.
With npm or bower:
bower install cordova-app-loader cordova-promise-fs
npm install cordova-app-loader cordova-promise-fs
Setup Cordova
cordova platform add ios@3.7.0
cordova plugin add org.apache.cordova.file
cordova plugin add org.apache.cordova.file-transfer
IMPORTANT: For iOS, use Cordova 3.7.0 or higher (due to a bug that affects requestFileSystem).
Demo Time!
Check out Cordova App Loader in Chrome for a demo! (Chrome only!)
Or run on your own computer:
git clone git@github.com:markmarijnissen/cordova-app-loader.git
cd cordova-app-loader
cordova platform add ios@3.7.0
cordova plugin add org.apache.cordova.file
cordova plugin add org.apache.cordova.file-transfer
cordova run ios
Note: Want to run your own server? Modify serverRoot
in www/app.js
!
Usage
Overview
- Write a manifest.json
- Add bootstrap.js script to your index.html
- Instantiate a
CordovaAppLoader
- Check for updates
- Download new files
- Apply update
Step 1: Write a manifest.json
Describe which files to download and which files to load during bootstrap.
{
"files": {
"jquery": {
"version": "afb90752e0a90c24b7f724faca86c5f3d15d1178",
"filename": "lib/jquery.min.js"
},
"bluebird": {
"version": "f37ff9832449594d1cefe98260cae9fdc13e0749",
"filename": "lib/bluebird.js"
},
"CordovaPromiseFS": {
"version": "635bd29385fe6664b1cf86dc16fb3d801aa9461a",
"filename": "lib/CordovaPromiseFS.js"
},
"CordovaAppLoader": {
"version": "76f1eecd3887e69d7b08c60be4f14f90069ca8b8",
"filename": "lib/CordovaAppLoader.js"
},
"template": {
"version": "3e70f2873de3d9c91e31271c1a59b32e8002ac23",
"filename": "template.html"
},
"app": {
"version": "8c99369a825644e68e21433d78ed8b396351cc7d",
"filename": "app.js"
},
"style": {
"version": "6e76f36f27bf29402a70c8adfee0f84b8a595973",
"filename": "style.css"
}
},
"load": [
"lib/jquery.min.js",
"lib/bluebird.js",
"lib/CordovaPromiseFS.js",
"lib/CordovaAppLoader.js",
"app.js",
"style.css"
],
"root":"./",
}
Workflow tip:
You can update your existing manifest like this:
node bin/update-manifest www www/manifest.json
node bin/update-manifest [root-directory] [manifest.json]
It will update the version of only changed files (with a hash of the content).
Retrieves manifest.json and dynamically inserts JS/CSS to the current page.
<script type="text/javascript" timeout="5000" manifest="manifest.json" src="bootstrap.js"></script>
On the second run, the manifest.json is retrieved from localStorage.
If after timeout
milliseconds window.BOOTSTRAP_OK
is not true, the (corrupt?) manifest in localStorage is destroyed, and the page will reload. So make sure you set window.BOOTSTRAP_OK = true
when your app has succesfully loaded!
Tip:
Bundle a manifest.json with your app. This way, your app will also launch when not connected to the internet. When your app is updated, it will write a new manifest.json to localStorage. If this update is corrupt, it can safely revert to the bundled manifest.json
Step 3: Intialize CordovaAppLoader
var fs = new CordovaPromiseFS({});
var loader = window.loader = new CordovaAppLoader({
fs: fs,
serverRoot: 'http://data.madebymark.nl/cordova-app-loader/',
localRoot: 'app',
mode: 'mirror',
cacheBuster: true
checkTimeout: 10000
});
Step 4: Check for updates
loader.check().then(function(updateAvailable) { ... })
loader.check('http://yourserver.com/manifest.json').then( ... )
loader.check({ files: { ... } }).then( ... )
Implementation Note: Only file versions are compared! If you, for example, update manifest.load
then the promise will return false
!
Step 5: Download update
loader.download(onprogress)
.then(function(manifest){ ... },function(failedDownloadUrlArray){ ... });
Note: When downloading, invalid files are deleted first. This invalidates the current manifest. Therefore, the current manifest is removed from localStorage. The app is reverted to "factory settings" (the manifest.json that comes bundled with the app).
Step 6: Apply update (reload page to bootstrap new files)
This writes the new manifest to localStorage and reloads the page to bootstrap the updated app.
loader.update()
loader.update(false)
Implementation Note: CordovaAppLoader changes the manifest.root
to point to your file cache - otherwise the bootstrap script can't find the downloaded files!
Design Decisions
I want CordovaAppLoader to be fast, responsive, reliable and safe. In order to do this, I've made the following decisions:
Loading JS/CSS dynamically using bootstrap.js
First, I wanted to download 'index.html' to storage, then redirect the app to this new index.html.
This has a few problems:
cordova.js
and plugin javascript cannot be found.- It is hard to include
cordova.js
in the manifest because it is platform specific. - It is hard to find all plugin javascript - it is buried in Cordova internals.
- Reloading a page costs more time, CPU and memory because cordova plugins are reset.
Dynamically inserting CSS and JS allows you for almost the same freedom in updates, without all these problems.
Fast, reliable and performant downloads:
- To save bandwidth and time, only files that have changed are downloaded.
- CordovaPromiseFS limits concurrency (3) to avoid trashing your app.
- CordovaFileCache will retry the download up to 3 times - each with an increasing timeout.
- When executing
loader.download()
for the second time, old downloads are aborted. - "onprogress" event is called explicitly on every download.
Responsive app: Avoid never-resolving promises
check
and download
return a promise. These promises should always resolve - i.e. don't wait forever for a "deviceready" or for a "manifest.json" AJAX call to return.
I am assuming the following promises resolve or reject sometime:
As you see, most methods rely on the succes/error callbacks of native/Cordova methods.
Only for deviceready
and the XHR-request I've added timeouts to ensure a timely response.
Offline - when you loose connection.
When using check
: The XHR will timeout.
When using download
: I am assuming Cordova will invoke the error callback. The download has a few retry-attempts. If the connetion isn't restored before the last retry-attemt, the download will fail.
Crashes
The only critical moment is during a download. Old files are removed while new files aren't fully downloaded yet. This makes the current manifest point to missing or corrupt files. Therefore, before downloading, the current manifest is destroyed.
If the app crashes during a download, it will restart using the original manifest.
Bugs in the update
- When
BOOTSTRAP_OK
is not set to true
after a timeout, the app will destroy the current manifest and revert back to the original manifest.
More to be considered?
Let me know if you find bugs. Report an issue!
Changelog
0.5.0 (15/11/2014)
- Reject XHR-request when checking.
0.4.0 (13/11/2014)
- Changed manifest.json format.
0.3.0 (13/11/2014)
0.2.0 (09/11/2014)
- Improved app layout
- Added test-cases to the app (slow, broken app, broken download)
- Several bugfixes
0.1.0 (07/11/2014)
Contribute
Convert CommonJS to a browser-version:
npm install webpack -g
npm run-script prepublish
Feel free to contribute to this project in any way. The easiest way to support this project is by giving it a star.
Contact
© 2014 - Mark Marijnissen