├── .gitignore ├── LICENSE ├── README.md ├── docs └── img │ └── how-meteorrider-works.png ├── meteor └── startup.js └── www ├── index.html └── js └── meteor-rider.js /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | !empty 3 | .cordova 4 | 5 | *.un~ 6 | *.swp 7 | *.swo 8 | *.sublime-project 9 | *.sublime-workspace 10 | 11 | # OS files to ignore 12 | *.7z 13 | *.dmg 14 | *.gz 15 | *.iso 16 | *.rar 17 | *.tar 18 | *.zip 19 | *.com 20 | *.class 21 | *.dll 22 | *.exe 23 | *.o 24 | *.so 25 | *.log 26 | *.sql 27 | *.sqlite 28 | .DS_Store 29 | ._.DS_Store 30 | .DS_Store? 31 | ehthumbs.db 32 | Icon? 33 | Thumbs.db 34 | .svn 35 | .AppleDouble 36 | ._* 37 | 38 | *.tmproj 39 | /nbproject 40 | /nbproject/private/ 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 alan bount 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATION NOTICE 2 | 3 | Meteor core now supports a better Meteor + Cordova integration approach. 4 | 5 | This project was useful while that didn't exist, but Meteor's integration is great, use it. 6 | 7 | * https://www.meteor.com/try/7 8 | * http://docs.meteor.com/#/full/Cordova-depends 9 | * http://docs.meteor.com/#/full/mobileconfigjs 10 | 11 | ---- 12 | 13 | # MeteorRider 14 | 15 | An approach for integrating [PhoneGap/Cordova](http://phonegap.com/) + [Meteor](https://www.meteor.com/). 16 | 17 | *Your Meteor web app (real time everything), inside a Cordova shell (native APIs) = awesome!* 18 | 19 | # How it works 20 | 21 | * Cordova loads it's normal, compiled `www/index.html` 22 | * All normal, compiled Cordova JS loads *(this ensures Cordova API versions are maintained, decoupled from Meteor)* 23 | * MeteorRider 24 | * **Step 1) loading** 25 | * MeteorRider looks in localStorage to see if we have cached the last requested HTML 26 | * **Step 2) requesting** 27 | * MeteorRider does an AJAX request to your Meteor Server 28 | * MeteorRider replaces paths in the HTML response to be full URLs 29 | * **(Step 3) replacing** 30 | * MeteorRider **replaces** the DOM *("hijacking the DOM")* 31 | * MeteorRider stores the HTML for next time's loading screen 32 | * The DOM loads all the Meteor JS/CSS 33 | * *NOTE that all of Cordova's JS remains in the DOM (from before)* 34 | * *future-feature: planning on caching and inlining this* 35 | * Meteor connects via DDP to the Meteor Server 36 | 37 | ![overview](./docs/img/how-meteorrider-works.png) 38 | 39 | ## It's Easy 40 | 41 | It might sound a bit complex, but really it's pretty simple. 42 | 43 | This approach is good for the following reasons 44 | 45 | * The JS needed for whatever version of Cordova is always bundled with Cordova, 46 | updating is easy. 47 | * All Cordova API and Plugins' remain available, because the Cordova JS is 48 | loaded first, we just add to it. 49 | * Once setup you can mostly ignore Cordova and only update your Meteor app, all 50 | updates get to the client FAST (no app updating needed) 51 | * iframes are the devil! 52 | 53 | If you want an alternative, without the extra AJAX request try out 54 | 55 | * [Pack Meteor](https://github.com/SpaceCapsule/packmeteor) is a great way to 56 | compile all of what you need to run meteor (client) on [Chrome Packaged Apps for Mobile](https://github.com/MobileChromeApps/mobile-chrome-apps) (etc) 57 | * upside: super-fast, all Meteor assets live local in the Mobile App 58 | * upside: build script gets all Meteor JS & assets *(well managed)* 59 | * upside: chrome APIs available if you do [Chrome Packaged Apps for Mobile](https://github.com/MobileChromeApps/mobile-chrome-apps) 60 | * upside: decent "offline" support 61 | * downside: slightly more complicated release proccess 62 | * downside: you have to re-release the mobile application for every update to your Meteor App 63 | * downside: you have coordinate your entire user base's apps for releases if your app changes (since they have an old version installed) 64 | * [Meteor Cordova Loader](https://github.com/andrewreedy/meteor-cordova-loader) is another great alternative, basically a super-lazy-loader option 65 | * upside: faster initial load, no Cordova initial load screen 66 | * upside: load script gets all Cordova core & plugin JS from Meteor *(well managed)* 67 | * downside: you can [basically never update versions of Cordova after release](https://github.com/andrewreedy/meteor-cordova-loader/issues/16) because client versions are unknown 68 | * downside: like above, you can never add or upgrade a plugin for the same reasons, the client "version" is lost 69 | * downside: no "offline" support *(though, MeteorRider doesn't offer much for that either)* 70 | 71 | For more info, [a comparison of approaches](http://zeroasterisk.com/2013/08/22/meteor-phonegapcordova-roundup-fall-2013/) 72 | 73 | ## Example Projects 74 | 75 | * https://github.com/zeroasterisk/MeteorRiderExample0 Cordova 3.5 Android (2 commits) 76 | * This is a very basic example, showing how easy it is to implement 77 | * You can edit the URL to the Meteor App and this will work as a totally functional shell for your app 78 | * You can add platforms like *iOS* in the normal Cordova way and be up and working on them in seconds 79 | * https://github.com/zeroasterisk/MeteorRiderExample-CrossWalk CrossWalk + Cordova 3.5 ~ Android (1 commit) 80 | * **Did you know?** [CrossWalk](https://crosswalk-project.org/#documentation/cordova) is the project behind 81 | [Chrome Packaged Apps for Mobile](https://github.com/MobileChromeApps/mobile-chrome-apps), 82 | taking **Chromium** to the mobile device *(so you aren't stuck with old-ass-WebKit on Android)* 83 | * this is definitely my recommendation for performance on Android 84 | * You can edit the URL to the Meteor App and this will work as a totally functional shell for your app 85 | * Only Android :( 86 | 87 | ## Installation / Usage 88 | 89 | > NPM installer package under consideration see 90 | > [this npm package](https://github.com/poetic/meteor-rider) and 91 | > [this discussion](https://github.com/zeroasterisk/MeteorRider/pull/20) 92 | 93 | There are only a couple of files, and you can choose to manage them however you like... 94 | 95 | ### Get the Code 96 | 97 | ``` 98 | cd tmp 99 | git clone https://github.com/zeroasterisk/MeteorRider.git MeteorRider 100 | ``` 101 | 102 | ---- 103 | 104 | ## On PhoneGap/Cordova - Setup 105 | 106 | 107 | ### Option 1) Replace the whole index.html file 108 | 109 | You do not have to replace the whole `index.html` file, but it's a reasonable "fast start". 110 | 111 | * `www/js/meteor-rider.js` is our tool for getting the Meteor HTML and taking over the DOM 112 | * `www/js/phonegapapp.js` is where you setup your application and initializer for MeteorRider 113 | * has a basic structure for handling events and firing up MeteorRider 114 | * has a stub for a test switcher, to bypass loading MeteorRider for testing on device only 115 | * *you can override any of this* 116 | 117 | ``` 118 | cd pathtoyourphonegap/assets/www/ 119 | cp index.html index_old.html 120 | cp /tmp/MeteorRider/www/index.html index.html 121 | cp /tmp/MeteorRider/www/js/meteor-rider.js js/ 122 | ``` 123 | 124 | Then edit `index.html` with the appropriate **config** (see Configuration) 125 | 126 | ### Option 2) Edit the index.html file 127 | 128 | There is very little that is "required" to fire up MeteorRider. 129 | 130 | This is the minimum you want in your `index.html` 131 | 132 | 133 | 138 | 139 | You just need to call `MeteorRider.init()` when the `deviceready` Event is triggered. 140 | 141 | ### MeteorRider.config 142 | 143 | Here is the default config 144 | 145 | config: { 146 | meteorUrl: '', 147 | currentPath: '', 148 | localStorage: true, 149 | // step 1) loading text 150 | doLoading: true, 151 | // step 2) AJAX request 152 | doRequest: true, 153 | // step 3) AJAX response (or cache) replacing DOM 154 | doReplace: true 155 | }, 156 | 157 | If this is global variable is found, it sets the default config in MeteorRider 158 | 159 | var __MeteorRiderConfig__ = { 160 | meteorUrl: "http://leaderboard.meteor.com/", 161 | currentPath: "/", 162 | localStorage: true 163 | }; 164 | 165 | You can pass any part of the `config` into `MeteorRider.init()` like so: 166 | 167 | MeteorRider.init({ meteorUrl: "http://leaderboard.meteor.com/", localStorage: false }); 168 | 169 | You can also just pass in a string, and it will be treated like the meteorUrl *(simplest config)* 170 | 171 | MeteorRider.init("http://leaderboard.meteor.com/"); 172 | 173 | #### MeteorRider.config.meteorUrl (required) 174 | 175 | Set the `meteorUrl` property, it should be the full URL to your meteor app. 176 | 177 | > NOTE: full public URLs work best. 178 | > Localhost or internal IPs probably wont work. 179 | > If Cordova can't load it, it won't work. 180 | 181 | ---- 182 | 183 | ## On Meteor 184 | 185 | You do not have to put anything in Meteor, but if you copy in this `startup.js` file, it will handle *hot code pushes* and reload inside PhoneGap/Cordova, without losing the phonegap context. 186 | 187 | ``` 188 | cd pathtoyourmeteorapp 189 | cp /tmp/MeteorRider/meteor/startup.js startup.js 190 | ``` 191 | 192 | Obviously, the best bet is to look for the the Cordova APIs directly 193 | 194 | ``` 195 | if (_.isObject(device)) { 196 | console.log(device.cordova); 197 | } 198 | ``` 199 | 200 | You can also look for the `MeteorRider` JS object inside your Meteor app and use it as a means of basic knowledge about the client, and status. 201 | 202 | You can also force the `localStorage` to be the "loading" screen on the next 203 | pageload... (it should be the full HTML you want rendered) 204 | 205 | ``` 206 | MeteorRider.meteorHtml = '' + 207 | '' + 208 | '' + 209 | '' + 210 | 'My Cool Loading Content Here :)' + 211 | ''; 212 | 213 | MeteorRider.replaceStoreHtml(); 214 | ``` 215 | 216 | ---- 217 | 218 | ## Meteor Packages for PhoneGap/Cordova 219 | 220 | There are probably many more than this list, [let me know about `em](https://twitter.com/zeroasterisk). 221 | 222 | ### OAuth for Meteor + MeteorRider + PhoneGap/Cordova 223 | 224 | Sadly this should "just work" out-of-the-box, but as of now, it is 225 | [frought](https://github.com/AdamBrodzinski/meteor-phonegap-oauth/pull/5) 226 | with 227 | [peril](https://github.com/zeroasterisk/MeteorRider/issues/16). 228 | 229 | Luckily there is an excellent project alive which is a very easy `mrt add phonegap-oauth` away. 230 | 231 | Check it out here [meteor-phonegap-oauth](https://github.com/AdamBrodzinski/meteor-phonegap-oauth) 232 | 233 | 234 | ---- 235 | 236 | ## Common Problems / Tips 237 | 238 | **PhoneGap/Cordova Issues? Plugin Issues?** 239 | 240 | 1. Stop `MeteorRider` (comment it out, or setup testing conditional bypass) 241 | 2. Run whatever you're doing with just `index.html` and "on device" JS (without Meteor) 242 | 3. Working locally but not with MeteorRider? Enable MeteorRider again and look for a namespace collision... 243 | 244 | **Not Loading Meteor?** 245 | 246 | 1. Check the URL, can Cordova get to it? 247 | 2. Try loading a static file from your Meteor URL (from the `/public/` folder) as the `meteorUrl`. 248 | 3. Check the console from Cordova (Android, iOS, etc) 249 | 250 | MeteorRider requesting 251 | MeteorRider url: http://example.com 252 | MeteorRider response.status = 404 253 | 254 | You can uncomment the lines in MeteorRider where it logs the `meteorHtml` 255 | (the HTML content from Meteor). 256 | 257 | 258 | ### PhoneGap/Cordova needs to be setup to allow access to Meteor 259 | 260 | In older PhoneGap installs or if you've restrictred ``, may have to allow access to the Meteor app url/domain. Refer to the configuration documentation for your version of PhoneGap. 261 | 262 | http://docs.phonegap.com/en/edge/guide_appdev_security_index.md.html#Security%20Guide 263 | 264 | ``` 265 | 266 | ``` 267 | 268 | 269 | In the Wild 270 | -------------- 271 | 272 | * http://blonk.co/ 273 | * https://www.discovermeteor.com/blog/blonk-building-tinder-for-jobs-with-meteor-for-mobile/ 274 | * http://grigio.org/meteor_and_phonegap_cordova_3_x_build_native_app_android_and_ios 275 | * http://meteorpedia.org/read/Mobile_support 276 | * ?? 277 | 278 | Roadmap / Goals 279 | -------------- 280 | 281 | The main goal is to provide a simple, fast, and standardized way to connect 282 | PhoneGap to Meteor. 283 | 284 | The combination is very powerful, and I have high hope for the future. 285 | 286 | Goals: 287 | 288 | * PhoneGap version agnostic *(done)* 289 | * Meteor version agnostic *(mostly done)* 290 | * Device agnostic *(maybe done ? Android and iOS only ones experiemented with)* 291 | * Minimal configuration / setup *(done)* 292 | * Package installer 293 | 294 | Tasks: 295 | 296 | * invesigate loading just the HEAD data from the AJAX reques 297 | * invesigate loading the JS files (from Meteor) via AJAX so that we know when completed and could trigger callbacks 298 | * implement a warning/alerting system for device/connection state (PhoneGap version dependancies?) 299 | * we may provide a means of setting up an "offline" page 300 | 301 | Authors / Acknowledgements 302 | -------------- 303 | 304 | This is the "Option 3" approach I've been thinking about for a while. 305 | 306 | Inspiration and collaboration from: 307 | 308 | * [Abigail](https://github.com/awatson1978): https://github.com/awatson1978/cordova-phonegap 309 | * [Morten](https://github.com/raix): https://github.com/raix/Meteor-Cordova 310 | * And [pull requests](https://github.com/zeroasterisk/MeteorRider/pulls?direction=desc&page=1&sort=created&state=closed) from 311 | * [Daniel](https://github.com/DanyHunter) 312 | * [@cdoe](https://github.com/cdoe) 313 | * [Guillaume](https://github.com/silently) 314 | * [Marc](https://github.com/marbemac) 315 | 316 | I'd like to thank all of them for communicating with me while figuring out what my 317 | options were and for collaboration on this project. 318 | 319 | Background: 320 | 321 | http://prezi.com/ig9gjm11mwsi/from-zero-to-mobile-web-app-in-sixty-minutes/ 322 | 323 | http://zeroasterisk.com/2013/08/22/meteor-phonegapcordova-roundup-fall-2013/ 324 | -------------------------------------------------------------------------------- /docs/img/how-meteorrider-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeroasterisk/MeteorRider/c2394e42d1d7e8d46eda70a7b73495b3e1673b6e/docs/img/how-meteorrider-works.png -------------------------------------------------------------------------------- /meteor/startup.js: -------------------------------------------------------------------------------- 1 | if (Meteor.isClient) { 2 | Meteor.startup(function () { 3 | // verify if we are within a cordova wrapper 4 | if (typeof cordova === 'undefined') { 5 | return; 6 | } 7 | // verify if we have a MeteorRider object too 8 | if (typeof MeteorRider === 'undefined') { 9 | return; 10 | } 11 | // attempt to setup a callback handler for hot reloads 12 | if (typeof Reload !== 'undefined' && typeof Reload._onMigrate === 'function') { 13 | // this is the newer method, internal? 14 | Reload._onMigrate('cordovaapp', function () { 15 | MeteorRider.init(); 16 | return [false]; 17 | }); 18 | return; 19 | } 20 | 21 | if (typeof Meteor._reload.onMigrate === 'function') { 22 | // this is the older method, deprecated? 23 | // it does not seem to be deprecated in meteor 0.8.2 24 | Meteor._reload.onMigrate('cordovaapp', function () { 25 | MeteorRider.init(); 26 | return [false]; 27 | }); 28 | return; 29 | } 30 | 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Meteor Rider Example 9 | 10 | 11 |
12 | 13 |

Meteor Rider, Loading

14 | Loading.... 15 | 16 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /www/js/meteor-rider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Meteor Rider 3 | * =================== 4 | * 5 | * This script is to facilitate an easy means of "taking over" or "hijacking" 6 | * a HTML page, with a Meteor app. 7 | * 8 | * 3 Steps 9 | * ---------------- 10 | * 1. Setup a "Loading" interface, get from localStorage if we can 11 | * 2. Request Meteor URL via AJAX 12 | * 2. Replace the DOM with Meteor's HTML 13 | * 14 | * Use Case 15 | * ---------------- 16 | * The Use Case for this, is for a PhoneGap application. On the device, you 17 | * load a basic HTML file, and it initializes the phone and device specific JS. 18 | * Then you use Meteor Rider to connect to your Meteor backend and "take over" 19 | * the HTML. 20 | * 21 | * Requirements 22 | * ---------------- 23 | * - depot.js is required as a simple API into localStorage 24 | * https://github.com/mkuklis/depot.js 25 | * 26 | * Gotchas 27 | * ---------------- 28 | * - Cross Origin Request Security doesn't allow this in a browser, 29 | * but PhoneGap does, and if we can inject CORS headers into Meteor it might 30 | * work in a browser sometime 31 | * 32 | * - Meteor Rider can not remove CSS... so anything loaded on the root page, remains. 33 | * 34 | * - Local development might be complicated by routing/URLs, be sure you can 35 | * request from Meteor server from device/emulator 36 | * 37 | * 38 | */ 39 | 40 | //define('MeteorRider', ['jquery'], function($) { 41 | var MeteorRider = { 42 | 43 | // config object, will be overwritten by setup() 44 | config: { 45 | meteorUrl: '', 46 | currentPath: '', 47 | localStorage: true, 48 | // step 1) loading text 49 | doLoading: true, 50 | // step 2) AJAX request 51 | doRequest: true, 52 | // step 3) AJAX response (or cache) replacing DOM 53 | doReplace: true 54 | }, 55 | 56 | // placeholder for localStorage API 57 | depot: {}, 58 | 59 | // placeholder for XMLHttpRequest 60 | XHR: {}, 61 | 62 | // placeholder for HTML response 63 | meteorHtml: '', 64 | 65 | // placeholder for localStorage response 66 | storedHtml: '', 67 | 68 | /** 69 | * This is the main initialize method - use this to "fire off" MeteorRider 70 | * -- all setup done here 71 | * -- all "steps" triggered here (unless depending on AJAX response callback) 72 | * 73 | * @param mixed 74 | * object config optional config overrides defaults & global 75 | * string meteorUrl optional string to define the MeteorUrl 76 | */ 77 | init: function(config) { 78 | if (typeof config == 'string') { 79 | this.config.meteorUrl = config; 80 | var config = {}; 81 | } 82 | this.setup(config); 83 | if (this.config.doLoading) { 84 | this.loading(); 85 | } 86 | if (this.config.doRequest) { 87 | this.request(); 88 | } 89 | }, 90 | 91 | /** 92 | * Basic setup, config setup and verification 93 | */ 94 | setup: function(config) { 95 | 96 | // support for global config variable if set 97 | if (typeof __MeteorRiderConfig__ == "object") { 98 | for (var k in __MeteorRiderConfig__) { 99 | this.config[k] = __MeteorRiderConfig__[k]; 100 | } 101 | } 102 | 103 | // support for passed in config variable if set 104 | if (typeof config == "object") { 105 | for (var k in config) { 106 | this.config[k] = config[k]; 107 | } 108 | } 109 | 110 | // verify the meteorUrl is set and is a string 111 | if (typeof this.config.meteorUrl != "string") { 112 | this.config.meteorUrl = ''; 113 | } 114 | 115 | // clean the meteorUrl, remove the trailing slash 116 | this.config.meteorUrl = this.config.meteorUrl.replace(/\/$/, ''); 117 | 118 | // setup depot for localStorage API 119 | if (this.config.localStorage && typeof window.localStorage.getItem != "function") { 120 | console.error('MeteorRider localStorage Not Available ' + typeof window.localStorage.getItem); 121 | this.config.localStorage = false; 122 | } 123 | 124 | // verify the currentPath is set and is a string 125 | if (typeof this.config.currentPath != "string") { 126 | this.config.currentPath = ''; 127 | } 128 | 129 | // clean the currentPath, if it isn't empty and doesn't start with a "/", prefix 130 | if (this.config.currentPath.indexOf('/') != 0) { 131 | this.config.currentPath = '/' + this.config.currentPath; 132 | } 133 | 134 | }, 135 | 136 | // Step 1) loading 137 | 138 | /** 139 | * !!! Step 1 !!! 140 | * This happens when first initialized 141 | * - shows the initial "Loading" interface 142 | * - looks for existing localStorage cached content and replaces with that, if we have it 143 | */ 144 | loading: function() { 145 | console.log("MeteorRider Step 1) Loading"); 146 | this.loadingStoredHtml(); 147 | }, 148 | 149 | 150 | /** 151 | * part of loading... before AJAX request made 152 | * - do we already have a cached response from last time the AJAX request happened? 153 | * - call replace() with it right now... 154 | */ 155 | loadingStoredHtml: function () { 156 | if (!this.config.localStorage) { 157 | return; 158 | } 159 | 160 | this.storedHtml = window.localStorage.getItem( 'MeteorRider' ); 161 | if (typeof this.storedHtml != "string" || this.storedHtml.length < 1) { 162 | return; 163 | } 164 | console.log("MeteorRider Stored HTML Found (Replace before Request)"); 165 | this.storedHtml = decodeURI(this.storedHtml); 166 | this.meteorHtml = this.storedHtml; 167 | // skip to step 3 168 | this.replace(); 169 | }, 170 | 171 | // Step 2) requesting 172 | 173 | /** 174 | * !!! Step 2 !!! 175 | * Does a basic AJAX request to your Meteor server 176 | */ 177 | request: function() { 178 | console.log("MeteorRider Step 2) Requesting"); 179 | console.log("MeteorRider url: " + this.config.meteorUrl); 180 | 181 | // verify that the meteorUrl exists in the config 182 | if (!this.config.meteorUrl.length > 0) { 183 | console.error('MeteorRider: error: unable to determine config.meteorUrl'); 184 | return; 185 | } 186 | 187 | // native javascript ajax request 188 | this.XHR = new XMLHttpRequest(); 189 | this.XHR.open('GET', this.config.meteorUrl, true); 190 | this.XHR.onload = this.onSuccess.bind(this); 191 | this.XHR.onerror = this.onError.bind(this); 192 | this.XHR.send(); 193 | }, 194 | 195 | 196 | /** 197 | * There was a connection error of some sort 198 | */ 199 | onError: function () { 200 | console.error("MeteorRider AJAX Error State"); 201 | this.requestBad(); 202 | 203 | if (this.config.localStorage) { 204 | // there is a chance, we still have our staticly loaded cache from before 205 | // we didn't trigger it's events, but we are going to now... 206 | this.replaceDoneTriggerEvents(); 207 | } 208 | 209 | }, 210 | 211 | /** 212 | * This callback happens when the MeteorRider AJAX request is successful 213 | * validates http status codes 214 | */ 215 | onSuccess: function () { 216 | if (!(this.XHR.status >= 200 && this.XHR.status < 400)) { 217 | return this.requestBad(); 218 | } 219 | this.meteorHtml = this.XHR.responseText; 220 | return this.requestGood(); 221 | }, 222 | 223 | /** 224 | * We reached our target server, but it returned an error 225 | * (status not between 200-399) 226 | */ 227 | requestBad: function () { 228 | console.error("MeteorRider Request Bad"); 229 | console.error("MeteorRider Request.status = " + this.XHR.status + ": " + this.XHR.statusText); 230 | console.error("MeteorRider Step 3) ABORT - can not continue"); 231 | this.triggerEvent('MeteorRiderResponseError'); 232 | // request stops here 233 | }, 234 | 235 | /** 236 | * We reached our target server, and we got valid HTML response back 237 | * (status is between 200-399) 238 | * 239 | * Will trigger Step 3) replace 240 | * 241 | * @return void 242 | */ 243 | requestGood: function () { 244 | console.log("MeteorRider Request Good"); 245 | this.triggerEvent('MeteorRiderResponseSuccess'); 246 | 247 | // Alter HTML (fix paths) 248 | // console.log("MeteorRider meteorHtml = " + this.meteorHtml); // show if you need to debug 249 | this.requestCleanHtml(); 250 | // console.log("MeteorRider meteorHtml = " + this.meteorHtml); // show if you need to debug 251 | 252 | if (this.storedHtml == this.meteorHtml) { 253 | console.log("MeteorRider Request SAME - no need to continue"); 254 | return; 255 | } 256 | 257 | // Trigger Step 3) replace 258 | this.replace(); 259 | }, 260 | 261 | /** 262 | * update URLs in the HTML to include domain prefix 263 | * so Cordova knows "where" to get the Meteor assets from once loaded 264 | */ 265 | requestCleanHtml: function() { 266 | this.meteorHtml = this.meteorHtml.replace(/(href|src|manifest)\=\"\//gm, '$1="' + this.config.meteorUrl + '/'); 267 | }, 268 | 269 | // Step 3) replacing 270 | 271 | /** 272 | * !!! Step 3 !!! 273 | * Does a full DOM replacement with the content from the AJAX request 274 | * - called in AJAX request handler 275 | * - called in loading handler (if local cache found) 276 | */ 277 | replace: function () { 278 | if (!this.config.doReplace) { 279 | return; 280 | } 281 | console.log("MeteorRider Step 3) doing replacement"); 282 | // update the History with the current URL/path 283 | this.replaceHistoryState(); 284 | // Hijack DOM & Load 285 | this.replaceDom(); 286 | // Trigger Events on the DOM 287 | this.replaceDoneTriggerEvents(); 288 | 289 | // Store this HTML for next time 290 | this.replaceStoreHtml(); 291 | 292 | // we are done 293 | console.log("MeteorRider COMPLETE"); 294 | }, 295 | 296 | 297 | /** 298 | * history.replaceState() 299 | * set the window.history state 300 | * so iron-router and other packages which depend on window.location work correctly 301 | * window.history.replaceState() not supported in all clients 302 | * 303 | * @return void 304 | */ 305 | replaceHistoryState: function() { 306 | if (typeof window === 'undefined' || typeof window.history === 'undefined') { 307 | console.log("MeteorRider replaceHistoryState SKIPPED no window.history"); 308 | return; 309 | } 310 | if (typeof window.history.replaceState === 'function') { 311 | window.history.replaceState({}, "", this.config.currentPath); 312 | return; 313 | } 314 | if (typeof window.history.pushState === 'function') { 315 | window.history.pushState({}, "", this.config.currentPath); 316 | return; 317 | } 318 | }, 319 | 320 | /** 321 | * replace the document with the new document/meteorHtml 322 | * this is the REAL hijacking... 323 | * all old JS remains (unless overwritten, name collision) 324 | * all HTML is replaced/overwritten 325 | * all new CSS/JS is loaded 326 | */ 327 | replaceDom: function() { 328 | console.log("MeteorRider replaceDom init"); 329 | if (this.meteorHtml.length < 20) { 330 | console.log("MeteorRider replaceDom ABORT - too short: [" + this.meteorHtml + "]"); 331 | return; 332 | } 333 | this.triggerEvent('MeteorRiderReplaceInit'); 334 | document.open(); 335 | document.write(this.meteorHtml); 336 | console.log("MeteorRider replaceDom wrote: [" + this.meteorHtml + "]"); 337 | document.close(); 338 | this.triggerEvent('MeteorRiderReplaceComplete'); 339 | console.log("MeteorRider replaceDom complete"); 340 | }, 341 | 342 | /** 343 | * trigger the "loaded" events 344 | * TODO: it'd be nice to do this AFTER Meteor's JS has loaded (?) 345 | */ 346 | replaceDoneTriggerEvents: function() { 347 | this.triggerEvent('DOMContentLoaded'); 348 | this.triggerEvent('load'); 349 | this.triggerEvent('complete'); 350 | }, 351 | 352 | /** 353 | * After we replace the DOM we should store this copy of the HTML for next time 354 | */ 355 | replaceStoreHtml: function() { 356 | if (!this.config.localStorage) { 357 | return; 358 | } 359 | if (this.meteorHtml.length == 0) { 360 | return; 361 | } 362 | 363 | if (this.storedHtml == this.meteorHtml) { 364 | console.log("MeteorRider replaceStoreHtml SAME - no need to replace"); 365 | return; 366 | } 367 | 368 | window.localStorage.removeItem('MeteorRider'); 369 | window.localStorage.setItem('MeteorRider', encodeURI(this.meteorHtml)); 370 | console.log("MeteorRider replaceStoreHtml complete"); 371 | }, 372 | 373 | 374 | // Misc... 375 | 376 | /** 377 | * Super Generic trigger event function 378 | */ 379 | triggerEvent: function(eventName) { 380 | var event = document.createEvent('Event'); 381 | event.initEvent(eventName); 382 | document.dispatchEvent(event); 383 | } 384 | 385 | }; 386 | //}); 387 | 388 | --------------------------------------------------------------------------------