js_application_reloader 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MjE0MWUzNWUzMjEyMjE4N2E5N2U4ODZiZTk5ZTA3Y2FmM2YxMGVlZg==
5
+ data.tar.gz: !binary |-
6
+ Yjk3YzUwYjZkMzRiOWU4YzM2NWZhMjU1OTgxMTQ0MjU0MTE4NTk3ZQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NWRlNGY3YmQwMmYyMjUzN2JjNWQ0MDhiNTk2NGMwMTZmY2I2NTMzYjdhZDNj
10
+ MDUwMGM4MTc2NmJmZTE5NWVhZjk2NjAxNzJjZmNhM2FlN2NkMTgwYWFlYmE1
11
+ ZTM2MDE5MjA0ZGIzZDlmOGIyMzUwYjk0ZDA4ZmY1NjIxYThmMzQ=
12
+ data.tar.gz: !binary |-
13
+ ZTgxMDc0MzEwYjc2OTE1OWM2YzMxMDk1NGIxMmE4NjVkZTdjN2MyNWY4OGI4
14
+ NjgyZWQwYmI1MjhiZjhlYzc0MmY4YWI4NDFiOWMxOTUyMmQ0MzQ4OWZmMDRj
15
+ ODQxMjhmMjM2YTUwM2E5MzI2YTRkZjNlZWFiNGNiNTQ5OWFiODU=
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in application_reloader.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Clavis Insight
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # JsApplicationReloader
2
+
3
+ # tl;dr
4
+ Timestamps on your JS, CSS and other assets get bumped to ensure that your
5
+ Javascript application is always running against the latest front-end code
6
+ exposed by the server. But what happens if the user doesn't browse off your
7
+ Single Page Application...
8
+
9
+ # Goal
10
+ JsApplicationReloader is a gem for enabling you to force the reload of a Single
11
+ Page Applications Javascript (SPA) in production. It is useful for Rails
12
+ applications which write an initial fragment of JSON into the Javascript to
13
+ configure the application. Traditionally it has been hard to expire this part of
14
+ the application's JS, along with the timestamps used in asset URLs such as
15
+ Javascript and CSS includes.
16
+
17
+ # How does it work?
18
+
19
+ Every time the rails server is restarted, a token (usually a timestamp) is
20
+ recorded on the server. This token is also written into the JSON fragment that
21
+ is rendered when the SPA is first loaded. Subsequent AJAX requests from the
22
+ front end send this token as an application specific header, which is compared
23
+ with the token recorded on the server. If these don't match it means the
24
+ server's Javascript has changed. This tells the server to send a token expired
25
+ header back to the client, along with some HTML. The client detects the token
26
+ expired header and displays the HTML to the user. This HTML contains a link
27
+ that the user can user to refresh the application. Simples!
28
+
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ ```ruby
35
+ gem 'js_application_reloader'
36
+ ```
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install js_application_reloader
45
+
46
+ ## Usage
47
+
48
+ After you install JsApplicationReloader and add it to your Gemfile, you need to
49
+ run the generator:
50
+
51
+ $ rails generate js_application_reloader:install
52
+
53
+ This will generate a file under
54
+ config/initializers/js_application_reloader.rb which you can customise.
55
+
56
+ Now is a good time to go and read that file for further details.
57
+
58
+ Then in your ApplicationController include the following line
59
+
60
+ include JsApplicationReloader::ControllerExtensions
61
+
62
+ ... this will add a before filter to your controllers which checks
63
+ whether the token is stale. It is recommended to put this line just
64
+ after any authentication/authorisation before filters.
65
+
66
+ In your app/views/layouts/application.html.erb file (or equivalent) include the
67
+ line
68
+
69
+ <%=raw JsApplicationReloader::inject_script %>
70
+
71
+ This will include the token and some Javascript code on your page.
72
+
73
+ Optionally you can use JsApplicationReloader to manage the expiration of
74
+ your stylesheet and javascript includes, eg.
75
+
76
+ <%= stylesheet_link_tag "/dist/stylesheets/application.css?version=#{JsApplicationReloader.token}" %>
77
+ <%= javascript_include_tag "/dist/javascriptions/main.css?version=#{JsApplicationReloader.token}" %>
78
+
79
+ **Note: If you choose to use JsApplicationReloader to manage the expiration
80
+ of your stylesheet and javascript includes, then ensure you include the
81
+ ```<%=raw JsApplicationReloader::inject_script %>``` line before the stylesheet
82
+ and JS includes.**
83
+
84
+ ## Basic configuration for simple jQuery projects (ie. synchronous Javascript)
85
+
86
+ You need to manually call the token expiration code in your callbacks, eg.
87
+
88
+ $.get(url, function(data, status, xhr) {
89
+ // You need to add these 3 lines
90
+ if (JsApplicationReloader.isTokenExpired(xhr)) {
91
+ return JsApplicationReloader.handleTokenExpiration(xhr);
92
+ }
93
+ // your normal callback handler code goes here...
94
+ });
95
+
96
+
97
+ ## Basic configuration for AMD/RequireJS projects (ie. asynchronous Javascript)
98
+
99
+ This requires 3 steps.
100
+
101
+ 1) In config/initializers/js_application_reloader.rb you need to set
102
+ ```config.async_js_project = true```.
103
+
104
+ 2) Configure application requests. You need to manually add some ajaxSend()
105
+ configuration to your application's JS codebase. Where you add this code depends
106
+ on what front-end MVC framework you are using, if any.
107
+
108
+ In BackboneJS you would insert this into the application's startup code, for example in application.js file. The only requirement is that Backbone is initialised.
109
+
110
+
111
+ // -- Wireup AJAX send calls --
112
+ $(document).ready(function() {
113
+ $(document).ajaxSend(function(event, request) {
114
+ if (JsApplicationReloader && JsApplicationReloader.token) {
115
+ request.setRequestHeader(JsApplicationReloader.tokenHeaderName, JsApplicationReloader.token);
116
+ }
117
+ });
118
+ });
119
+
120
+ 3) Configure application response handling. Again this depends on what front-end
121
+ MVC framework you are using.
122
+
123
+ In BackboneJS you would insert this into the application's startup code, for example in application.js file. The only requirement is that Backbone is initialised.
124
+
125
+ // -- Override Backbone sync() to handle expiration
126
+ // -- Do the equivalent for your chosen framework
127
+ var oldSyncMethod = Backbone.sync;
128
+ Backbone.sync = function(method, model, options) {
129
+ var oldError = options.error;
130
+ options.error = function(xhr, textStatus, errorThrown) {
131
+ // These are the important 3 lines
132
+ if (JsApplicationReloader.isTokenExpired(xhr)) {
133
+ return JsApplicationReloader.handleTokenExpiration(xhr);
134
+ }
135
+ if (oldError) { oldError(xhr, textStatus, errorThrown); }
136
+ };
137
+ return oldSyncMethod(method, model, options);
138
+ };
139
+
140
+ **What about AJAX requests where I bypass the front-end MVC framework (eg. plain old jQuery AJAX requests)?**
141
+
142
+ As these don't go through the MVC framework's machinary you need to manually
143
+ call the token expiration code in your request callbacks. This has to be done
144
+ everywhere that you skip going through your MVC framework. eg.
145
+
146
+ $.get(url, function(data, status, xhr) {
147
+ // You need to add these 3 lines
148
+ if (JsApplicationReloader.isTokenExpired(xhr)) {
149
+ return JsApplicationReloader.handleTokenExpiration(xhr);
150
+ }
151
+ // your normal callback handler code goes here...
152
+ });
153
+
154
+ # How do I know it worked?
155
+ Assuming that you are using the default strategy of reloading your application
156
+ when the server is restarted (ie. ```config.token = Time.now.to_i```)
157
+ then the following steps with verify it's working.
158
+
159
+ 1. Start your server
160
+ 2. Browse to a page that makes an AJAX request
161
+ 3. Optional - if you inspect the request headers (eg. in Chrome's Network tab) you
162
+ should see a header that looks like "X-Js-Application-Reloader-Current-Token".
163
+ If not, then something went wrong when installing or configuring.
164
+ 4. Stay on the same page in your Single Page Application
165
+ 5. Restart the server and wait for it to be fully up and running
166
+ 6. Without reloading the page, carry out an action that causing another AJAX
167
+ request
168
+ 7. If everything is working you should see "A new version of this application is
169
+ available. Please click here to load it."
170
+
171
+ # Customising
172
+ In addition to the config/initializers/js_application_reloader.rb settings you
173
+ can also carry out the following customisations.
174
+
175
+ ## Disable JsApplicationReloader for certain controllers
176
+ You can skip the before filter that JsApplicationReloader uses via
177
+
178
+ skip_before_filter :handle_js_application_reloader_token_expiration
179
+
180
+ ## Override what the server sends back to the client on token expiration
181
+
182
+ # Override this in your ApplicationController or on a per controller basis.
183
+ # The reload_required_http_status, application_name, redirect_url attributes
184
+ # are available to you on the JsApplicationReloader object.
185
+ def render_js_application_reloader_expiration
186
+ render :text => "A new version of #{JsApplicationReloader.application_name} is available. Please click <a href='#{JsApplicationReloader.redirect_url}'>here</a> to load it.", :status => JsApplicationReloader.reload_required_http_status
187
+ end
188
+
189
+ ## Override how the client handles the reloading of the application
190
+ You can put this at the bottom of your
191
+ config/initializers/js_application_reloader.rb (outside of the 'config' block).
192
+
193
+ def JsApplicationReloader.handle_reloader_token_expiration_on_client
194
+ <<-EOF
195
+ JsApplicationReloader.handleTokenExpiration = function(xhr) {
196
+ // $('body').html(xhr.responseText); // Original code
197
+ alert(xhr.responseText); // We change it to this
198
+ return false;
199
+ };
200
+ EOF
201
+ end
202
+
203
+ ## Future
204
+ There's lots to improve
205
+ * Usability: currently you are required to add a lot of code explicitly to your
206
+ application. It would be good if this could be avoided. For example, instead
207
+ of wiring up ajaxSend() calls perhaps we could do something with the
208
+ XMLHttpRequest object (thanks for the idea Stefan!)
209
+ * Flexibility: cater for non-Rails applications and other JS frameworks
210
+ * Tests: this gem has been tested manually; it needs some spec love
211
+
212
+ So please fork and send a Pull Request.
213
+
214
+ ## Contributing
215
+
216
+ 1. Fork it ( https://github.com/theirishpenguin/js_application_reloader/fork )
217
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
218
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
219
+ 4. Push to the branch (`git push origin my-new-feature`)
220
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'js_application_reloader/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "js_application_reloader"
8
+ spec.version = JsApplicationReloader::VERSION
9
+ spec.authors = ["Declan McGrath"]
10
+ spec.email = ["declan.mcgrath@clavisinsight.com"]
11
+ spec.summary = %q{Force a full reload of your Single Page Application when a new version of it is available}
12
+ spec.description = %q{Force a full reload of your Single Page Application when a new version of it is available}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
@@ -0,0 +1,15 @@
1
+ module JsApplicationReloader
2
+ class InstallGenerator < Rails::Generators::Base
3
+
4
+ TEMPLATE_FILENAME = 'js_application_reloader.rb'
5
+ DESTINATION_FILEPATH = 'config/initializers/js_application_reloader.rb'
6
+
7
+ desc "This generator creates an initializer file at #{DESTINATION_FILEPATH}"
8
+
9
+ source_root File.expand_path("../../templates", __FILE__)
10
+
11
+ def copy_initializer
12
+ copy_file TEMPLATE_FILENAME, DESTINATION_FILEPATH
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,31 @@
1
+ JsApplicationReloader.configure do |config|
2
+
3
+ # Leave async_js_project is false for projects that don't asynchronously load their Javascript
4
+ # Set it to true if you use libraries such as RequireJS which do load asynchronously
5
+ config.async_js_project = false
6
+
7
+ # Here we determine the value of the token, which is set every time the Rails server is (re)started.
8
+ # A change in value of this token indicates that the application needs to be reloaded.
9
+ #
10
+ # Examples
11
+ # * Time.now.to_i - The JS will be reloaded every time the Rails server is restarted
12
+ # * File.stat("./dist").mtime.to_i - The JS will be reloaded every time the Rails
13
+ # server is restarted AND the modified time of the public/dist directory is updated
14
+ config.token = Time.now.to_i # eg. File.stat("./public/dist").mtime.to_i
15
+
16
+ # Name of the header that holds the current value of the token sent from the client to the server
17
+ config.token_header_name = "X-Js-Application-Reloader-Current-Token"
18
+
19
+ # The http status code that the server sends back to the client with when a reload is required
20
+ config.reload_required_http_status = 200;
21
+
22
+ # Name of the header that the server sends back to the client with the response which indicates if a reload is required
23
+ config.status_header_name = "X-Js-Application-Reloader-Status"
24
+
25
+ # Let's you customize the expiration message with your application name
26
+ config.application_name = 'this application'
27
+
28
+ # Let's you customize the expiration message with a link to go to in order to reload the front end of the application
29
+ config.redirect_url = '/users/sign_in'
30
+
31
+ end
@@ -0,0 +1,23 @@
1
+ module JsApplicationReloader
2
+ module ControllerExtensions
3
+
4
+ def self.included(base)
5
+ base.before_filter :handle_js_application_reloader_token_expiration
6
+ end
7
+
8
+ def handle_js_application_reloader_token_expiration
9
+ # Note: we do a string comparision to avoid 232323 not matching "232323" mistakes
10
+ if request.headers[JsApplicationReloader.token_header_name] && (request.headers[JsApplicationReloader.token_header_name].to_s != JsApplicationReloader.token.to_s)
11
+ response.headers[JsApplicationReloader.status_header_name] = 'token_expired'
12
+ render_js_application_reloader_expiration
13
+ end
14
+ end
15
+
16
+ # override me in your ApplicationController to customize what gets
17
+ # sent back to the client on token expiration
18
+ def render_js_application_reloader_expiration
19
+ render :text => "A new version of #{JsApplicationReloader.application_name} is available. Please click <a href='#{JsApplicationReloader.redirect_url}'>here</a> to load it.", :status => JsApplicationReloader.reload_required_http_status
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module JsApplicationReloader
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,79 @@
1
+ require "js_application_reloader/version"
2
+ require "js_application_reloader/controller_extensions"
3
+
4
+ module JsApplicationReloader
5
+
6
+ # Can override these via their setters in config/initializers/js_application_reloader.rb
7
+ @async_js_project = false
8
+ @token = nil
9
+ @token_header_name = "X-Js-Application-Reloader-Current-Token"
10
+ @status_header_name = "X-Js-Application-Reloader-Status"
11
+ @reload_required_http_status = 200
12
+ @application_name = 'this application'
13
+ @redirect_url = '/users/sign_in'
14
+
15
+ class << self
16
+ attr_accessor :async_js_project
17
+ attr_accessor :token
18
+ attr_accessor :token_header_name
19
+ attr_accessor :status_header_name
20
+ attr_accessor :reload_required_http_status
21
+ attr_accessor :application_name
22
+ attr_accessor :redirect_url
23
+ end
24
+
25
+ # Nice configuration is a small twist on http://robots.thoughtbot.com/mygem-configure-block. Thanks!
26
+
27
+ def self.configure
28
+ yield(self)
29
+ end
30
+
31
+ # Must be injected into ERB template using raw
32
+ # TODO: Option to limit fields sent to the client for lightness
33
+ def self.inject_script
34
+ <<-EOF
35
+ <script type="text/javascript">
36
+ JsApplicationReloader = {
37
+ token: '#{JsApplicationReloader.token}',
38
+ tokenHeaderName: '#{JsApplicationReloader.token_header_name}',
39
+ statusHeaderName: '#{JsApplicationReloader.status_header_name}',
40
+ reloadRequiredHttpStatus: '#{JsApplicationReloader.reload_required_http_status}',
41
+ applicationName: '#{JsApplicationReloader.application_name}',
42
+ redirectUrl: '#{JsApplicationReloader.redirect_url}'
43
+ };
44
+
45
+ // -- Define Expiration check
46
+ JsApplicationReloader.isTokenExpired = function(xhr) {
47
+ return (xhr.status == JsApplicationReloader.reloadRequiredHttpStatus && (xhr.getResponseHeader(JsApplicationReloader.statusHeaderName) === 'token_expired'));
48
+ };
49
+
50
+ #{handle_reloader_token_expiration_on_client}
51
+
52
+ #{JsApplicationReloader.non_async_js_request_script unless JsApplicationReloader.async_js_project}
53
+ </script>
54
+ EOF
55
+ end
56
+
57
+ # Expiration handler (you can customize this)
58
+ def self.handle_reloader_token_expiration_on_client
59
+ <<-EOF
60
+ JsApplicationReloader.handleTokenExpiration = function(xhr) {
61
+ $('body').html(xhr.responseText); // Display the HTML returned by xhr.responseText as you see fit
62
+ return false;
63
+ };
64
+ EOF
65
+ end
66
+
67
+ # For non-async Javascript projects
68
+ def self.non_async_js_request_script
69
+ <<-EOF
70
+ $(document).ready(function() {
71
+ $(document).ajaxSend(function(event, request) {
72
+ if (JsApplicationReloader && JsApplicationReloader.token) {
73
+ request.setRequestHeader('#{JsApplicationReloader.token_header_name}', JsApplicationReloader.token);
74
+ }
75
+ });
76
+ });
77
+ EOF
78
+ end
79
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: js_application_reloader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Declan McGrath
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: Force a full reload of your Single Page Application when a new version
42
+ of it is available
43
+ email:
44
+ - declan.mcgrath@clavisinsight.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - js_application_reloader.gemspec
55
+ - lib/generators/js_application_reloader/install_generator.rb
56
+ - lib/generators/templates/js_application_reloader.rb
57
+ - lib/js_application_reloader.rb
58
+ - lib/js_application_reloader/controller_extensions.rb
59
+ - lib/js_application_reloader/version.rb
60
+ homepage: ''
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.4.5
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Force a full reload of your Single Page Application when a new version of
84
+ it is available
85
+ test_files: []