├── .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 | 
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 |
17 |
18 |
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 |
--------------------------------------------------------------------------------