├── .circleci └── config.yml ├── .gitignore ├── .standard.yml ├── Appraisals ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── _guard-core ├── appraisal ├── console ├── guard ├── rake ├── rubocop ├── setup └── standardrb ├── gemfiles ├── rails_5.0.gemfile ├── rails_5.0.gemfile.lock ├── rails_5.1.gemfile ├── rails_5.1.gemfile.lock ├── rails_5.2.gemfile └── rails_5.2.gemfile.lock ├── lib ├── assets │ └── images │ │ ├── convert.rb │ │ └── serviceworker-rails │ │ ├── heart-1200x1200.png │ │ ├── heart-120x120.png │ │ ├── heart-144x144.png │ │ ├── heart-152x152.png │ │ ├── heart-180x180.png │ │ ├── heart-192x192.png │ │ ├── heart-36x36.png │ │ ├── heart-384x384.png │ │ ├── heart-48x48.png │ │ ├── heart-512x512.png │ │ ├── heart-60x60.png │ │ ├── heart-72x72.png │ │ ├── heart-76x76.png │ │ └── heart-96x96.png ├── generators │ └── serviceworker │ │ ├── install_generator.rb │ │ └── templates │ │ ├── manifest.json │ │ ├── offline.html │ │ ├── serviceworker-companion.js │ │ ├── serviceworker.js │ │ └── serviceworker.rb ├── service_worker.rb ├── serviceworker-rails.rb ├── serviceworker.rb └── serviceworker │ ├── engine.rb │ ├── handlers.rb │ ├── handlers │ ├── rack_handler.rb │ ├── sprockets_handler.rb │ └── webpacker_handler.rb │ ├── middleware.rb │ ├── rails.rb │ ├── rails │ └── version.rb │ ├── route.rb │ └── router.rb ├── serviceworker-rails.gemspec └── test ├── sample ├── .babelrc ├── .gitignore ├── .postcssrc.yml ├── Rakefile ├── app │ ├── assets │ │ ├── images │ │ │ └── .keep │ │ ├── javascripts │ │ │ ├── another │ │ │ │ └── serviceworker.js │ │ │ ├── application.js │ │ │ ├── captures │ │ │ │ ├── bar-serviceworker.js │ │ │ │ └── foo-serviceworker.js │ │ │ ├── fallback │ │ │ │ └── serviceworker.js │ │ │ ├── headers │ │ │ │ └── serviceworker.js │ │ │ └── serviceworker.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ ├── application_controller.rb │ │ ├── concerns │ │ │ └── .keep │ │ └── welcome_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── javascript │ │ └── packs │ │ │ ├── application.js │ │ │ └── serviceworker.js │ ├── mailers │ │ └── .keep │ ├── models │ │ ├── .keep │ │ └── concerns │ │ │ └── .keep │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── welcome │ │ └── index.html.erb ├── bin │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ ├── webpack │ └── webpack-dev-server ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── serviceworker.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── routes.rb │ ├── secrets.yml │ ├── webpack │ │ ├── development.js │ │ ├── environment.js │ │ ├── production.js │ │ └── test.js │ └── webpacker.yml ├── lib │ └── assets │ │ └── .keep ├── log │ └── .keep ├── package.json ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── assets │ │ └── serviceworker.js │ └── favicon.ico └── yarn.lock ├── serviceworker ├── handlers_test.rb ├── install_generator_test.rb ├── rack_integration_test.rb ├── rails_integration_test.rb ├── rails_test.rb ├── route_test.rb └── router_test.rb ├── static └── assets │ └── serviceworker.js ├── support └── generator_test_helpers.rb └── test_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | ruby: circleci/ruby@1.1.0 5 | node: circleci/node@2 6 | 7 | commands: 8 | bundle-install: 9 | description: Install gem dependency matrix 10 | steps: 11 | - run: ruby -v 12 | - run: echo $RAILS_ENV 13 | - run: echo $RACK_ENV 14 | - run: 'shasum Gemfile.lock gemfiles/*.lock > checksum.tmp' 15 | - run: bundle config set path ~/vendor/bundle 16 | - restore_cache: 17 | keys: 18 | - gems-v1.0-{{ checksum "./checksum.tmp" }} 19 | - gems-v1.0- 20 | - run: bundle install 21 | - run: bundle exec appraisal install 22 | - save_cache: 23 | key: gems-v1.0-{{ checksum "./checksum.tmp" }} 24 | paths: 25 | - ~/vendor/bundle 26 | yarn-install: 27 | description: Install NPM dependencies 28 | steps: 29 | - restore_cache: 30 | keys: 31 | - npm-v2-{{ checksum "./test/sample/yarn.lock" }} 32 | - npm-v2- 33 | - run: bundle exec rake app:yarn_install_frozen 34 | - save_cache: 35 | key: npm-v2-{{ checksum "./test/sample/yarn.lock" }} 36 | paths: 37 | - ./test/sample/node_modules 38 | 39 | ruby-test: 40 | description: Run tests against various Rails versions 41 | steps: 42 | - run: bundle exec appraisal rake test 43 | 44 | jobs: 45 | test: 46 | working_directory: ~/serviceworker-rails 47 | docker: 48 | - image: cimg/ruby:<>-node 49 | parameters: 50 | version: 51 | description: 'version tag' 52 | default: '2.7' 53 | type: string 54 | environment: 55 | BUNDLE_JOBS: '3' 56 | BUNDLE_RETRY: '3' 57 | BUNDLE_PATH: vendor/bundle 58 | RAILS_ENV: test 59 | NODE_ENV: development 60 | steps: 61 | - checkout 62 | - bundle-install 63 | - yarn-install 64 | - ruby-test 65 | - run: bundle exec standardrb 66 | 67 | workflows: 68 | test: 69 | jobs: 70 | - test: 71 | matrix: 72 | parameters: 73 | version: ['2.5', '2.6', '2.7'] 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | **/tmp/ 9 | **/log/ 10 | **/node_modules/ 11 | .byebug_history 12 | .ruby-gemset 13 | .ruby-version 14 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | ignore: # default: [] 2 | - 'test/sample/node_modules/**/*' 3 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | appraise "rails-5.0" do 4 | gem "rails", "~> 5.0" 5 | gem "webpacker" 6 | end 7 | 8 | appraise "rails-5.1" do 9 | gem "rails", "~> 5.1" 10 | gem "webpacker" 11 | end 12 | 13 | appraise "rails-5.2" do 14 | gem "rails", "~> 5.2" 15 | gem "webpacker", ">= 4.1" 16 | end 17 | 18 | # appraise "rails-6.0" do 19 | # gem "rails", "~> 6.0" 20 | # gem "webpacker", ">= 4.1" 21 | # end 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 0.6.0 - 2019-04-11 2 | 3 | * enhancements 4 | * experimental support for Webpacker (@rossta, @pedantic-git) 5 | * allow cache override (@omnibs) 6 | * support recent rubies (@grzuy) 7 | 8 | ### 0.5.5 - 2017-05-01 9 | 10 | * bug fixes 11 | * Ensure middleware can handle assets when a separate asset host, e.g. a CDN, 12 | is configured 13 | 14 | ### 0.5.4 - 2017-02-02 15 | 16 | * bug fixes 17 | * Fix cache name for proper cache deletion in activation callback of serviceworker template 18 | 19 | ### 0.5.3 - 2017-01-11 20 | 21 | * bug fixes 22 | * Fix javascript variable name error in serviceworker template (@amelzer) 23 | 24 | ### 0.5.2 - 2017-01-07 25 | 26 | * enhancements 27 | * Enable generated serviceworker.js with improved fetch logic 28 | * bug fixes 29 | * Omit application.js from generated serviceworker.js to prevent circular 30 | sprockets requires 31 | 32 | ### 0.5.1 - 2016-11-17 33 | 34 | * enhancements 35 | * Add support for Rails 3 36 | * bug fixes 37 | * Fix syntax error in generated serviceworker.js 38 | * Remove duplicate line in generated manifest.json (@brandonhilkert) 39 | 40 | ### 0.5.0 - 2016-11-10 41 | 42 | * enhancements 43 | * Convert railtie to Rails engine 44 | * Extend install generator to add offline page and starter icons for generated web app manifest 45 | * Install serviceworker and manifest as .erb files 46 | 47 | ### 0.4.0 - 2016-11-09 48 | 49 | * enhancements 50 | * Add install generator to insert code snippets and add default serviceworker 51 | and companion scripts, web app manifest, and Rails initializer 52 | 53 | ### 0.3.1 - 2016-05-03 54 | 55 | * enhancements 56 | * add CHANGELOG 57 | * bug fixes 58 | * ensure railtie adds middleware to stack after config initializers are loaded 59 | 60 | ### 0.3.0 - 2016-05-02 61 | 62 | * enhancements 63 | * new route matching behavior; similar to Rails-routing style and Rack::Router 64 | * add to Travis-CI builds 65 | 66 | ### 0.2.0 - 2016-04-25 67 | 68 | * enhancements 69 | * routes are now configurable instead of a single-hardcoded path 70 | * support custom headers 71 | * extract Route and Router classes 72 | 73 | ### 0.1.0 - 2016-04-23 74 | 75 | * initial release 76 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at rosskaff@gmail.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in serviceworker-rails.gemspec 6 | gemspec 7 | 8 | group :development, :test do 9 | unless ENV["TRAVIS"] 10 | gem "guard", require: false 11 | gem "guard-minitest", require: false 12 | gem "pry" 13 | gem "pry-byebug", platforms: [:mri] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | serviceworker-rails (0.6.0) 5 | railties (>= 3.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (6.1.0) 11 | actionpack (= 6.1.0) 12 | activesupport (= 6.1.0) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailbox (6.1.0) 16 | actionpack (= 6.1.0) 17 | activejob (= 6.1.0) 18 | activerecord (= 6.1.0) 19 | activestorage (= 6.1.0) 20 | activesupport (= 6.1.0) 21 | mail (>= 2.7.1) 22 | actionmailer (6.1.0) 23 | actionpack (= 6.1.0) 24 | actionview (= 6.1.0) 25 | activejob (= 6.1.0) 26 | activesupport (= 6.1.0) 27 | mail (~> 2.5, >= 2.5.4) 28 | rails-dom-testing (~> 2.0) 29 | actionpack (6.1.0) 30 | actionview (= 6.1.0) 31 | activesupport (= 6.1.0) 32 | rack (~> 2.0, >= 2.0.9) 33 | rack-test (>= 0.6.3) 34 | rails-dom-testing (~> 2.0) 35 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 36 | actiontext (6.1.0) 37 | actionpack (= 6.1.0) 38 | activerecord (= 6.1.0) 39 | activestorage (= 6.1.0) 40 | activesupport (= 6.1.0) 41 | nokogiri (>= 1.8.5) 42 | actionview (6.1.0) 43 | activesupport (= 6.1.0) 44 | builder (~> 3.1) 45 | erubi (~> 1.4) 46 | rails-dom-testing (~> 2.0) 47 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 48 | activejob (6.1.0) 49 | activesupport (= 6.1.0) 50 | globalid (>= 0.3.6) 51 | activemodel (6.1.0) 52 | activesupport (= 6.1.0) 53 | activerecord (6.1.0) 54 | activemodel (= 6.1.0) 55 | activesupport (= 6.1.0) 56 | activestorage (6.1.0) 57 | actionpack (= 6.1.0) 58 | activejob (= 6.1.0) 59 | activerecord (= 6.1.0) 60 | activesupport (= 6.1.0) 61 | marcel (~> 0.3.1) 62 | mimemagic (~> 0.3.2) 63 | activesupport (6.1.0) 64 | concurrent-ruby (~> 1.0, >= 1.0.2) 65 | i18n (>= 1.6, < 2) 66 | minitest (>= 5.1) 67 | tzinfo (~> 2.0) 68 | zeitwerk (~> 2.3) 69 | appraisal (2.3.0) 70 | bundler 71 | rake 72 | thor (>= 0.14.0) 73 | ast (2.4.1) 74 | builder (3.2.4) 75 | byebug (11.1.3) 76 | coderay (1.1.3) 77 | concurrent-ruby (1.1.7) 78 | crass (1.0.6) 79 | docile (1.3.2) 80 | erubi (1.10.0) 81 | ffi (1.13.1) 82 | formatador (0.2.5) 83 | globalid (0.4.2) 84 | activesupport (>= 4.2.0) 85 | guard (2.16.2) 86 | formatador (>= 0.2.4) 87 | listen (>= 2.7, < 4.0) 88 | lumberjack (>= 1.0.12, < 2.0) 89 | nenv (~> 0.1) 90 | notiffany (~> 0.0) 91 | pry (>= 0.9.12) 92 | shellany (~> 0.0) 93 | thor (>= 0.18.1) 94 | guard-compat (1.2.1) 95 | guard-minitest (2.4.6) 96 | guard-compat (~> 1.2) 97 | minitest (>= 3.0) 98 | i18n (1.8.5) 99 | concurrent-ruby (~> 1.0) 100 | listen (3.3.3) 101 | rb-fsevent (~> 0.10, >= 0.10.3) 102 | rb-inotify (~> 0.9, >= 0.9.10) 103 | loofah (2.8.0) 104 | crass (~> 1.0.2) 105 | nokogiri (>= 1.5.9) 106 | lumberjack (1.2.8) 107 | mail (2.7.1) 108 | mini_mime (>= 0.1.1) 109 | marcel (0.3.3) 110 | mimemagic (~> 0.3.2) 111 | method_source (1.0.0) 112 | mimemagic (0.3.5) 113 | mini_mime (1.0.2) 114 | mini_portile2 (2.4.0) 115 | minitest (5.14.2) 116 | nenv (0.3.0) 117 | nio4r (2.5.4) 118 | nokogiri (1.10.10) 119 | mini_portile2 (~> 2.4.0) 120 | notiffany (0.1.3) 121 | nenv (~> 0.1) 122 | shellany (~> 0.0) 123 | parallel (1.20.1) 124 | parser (2.7.2.0) 125 | ast (~> 2.4.1) 126 | pry (0.13.1) 127 | coderay (~> 1.1) 128 | method_source (~> 1.0) 129 | pry-byebug (3.9.0) 130 | byebug (~> 11.0) 131 | pry (~> 0.13.0) 132 | rack (2.2.3) 133 | rack-test (1.1.0) 134 | rack (>= 1.0, < 3) 135 | rails (6.1.0) 136 | actioncable (= 6.1.0) 137 | actionmailbox (= 6.1.0) 138 | actionmailer (= 6.1.0) 139 | actionpack (= 6.1.0) 140 | actiontext (= 6.1.0) 141 | actionview (= 6.1.0) 142 | activejob (= 6.1.0) 143 | activemodel (= 6.1.0) 144 | activerecord (= 6.1.0) 145 | activestorage (= 6.1.0) 146 | activesupport (= 6.1.0) 147 | bundler (>= 1.15.0) 148 | railties (= 6.1.0) 149 | sprockets-rails (>= 2.0.0) 150 | rails-dom-testing (2.0.3) 151 | activesupport (>= 4.2.0) 152 | nokogiri (>= 1.6) 153 | rails-html-sanitizer (1.3.0) 154 | loofah (~> 2.3) 155 | railties (6.1.0) 156 | actionpack (= 6.1.0) 157 | activesupport (= 6.1.0) 158 | method_source 159 | rake (>= 0.8.7) 160 | thor (~> 1.0) 161 | rainbow (3.0.0) 162 | rake (13.0.1) 163 | rb-fsevent (0.10.4) 164 | rb-inotify (0.10.1) 165 | ffi (~> 1.0) 166 | regexp_parser (2.0.0) 167 | rexml (3.2.4) 168 | rubocop (1.4.2) 169 | parallel (~> 1.10) 170 | parser (>= 2.7.1.5) 171 | rainbow (>= 2.2.2, < 4.0) 172 | regexp_parser (>= 1.8) 173 | rexml 174 | rubocop-ast (>= 1.1.1) 175 | ruby-progressbar (~> 1.7) 176 | unicode-display_width (>= 1.4.0, < 2.0) 177 | rubocop-ast (1.3.0) 178 | parser (>= 2.7.1.5) 179 | rubocop-performance (1.9.1) 180 | rubocop (>= 0.90.0, < 2.0) 181 | rubocop-ast (>= 0.4.0) 182 | ruby-progressbar (1.10.1) 183 | shellany (0.0.1) 184 | simplecov (0.20.0) 185 | docile (~> 1.1) 186 | simplecov-html (~> 0.11) 187 | simplecov_json_formatter (~> 0.1) 188 | simplecov-html (0.12.3) 189 | simplecov_json_formatter (0.1.2) 190 | sprockets (3.7.2) 191 | concurrent-ruby (~> 1.0) 192 | rack (> 1, < 3) 193 | sprockets-rails (3.2.2) 194 | actionpack (>= 4.0) 195 | activesupport (>= 4.0) 196 | sprockets (>= 3.0.0) 197 | standard (0.10.2) 198 | rubocop (= 1.4.2) 199 | rubocop-performance (= 1.9.1) 200 | thor (1.0.1) 201 | tzinfo (2.0.3) 202 | concurrent-ruby (~> 1.0) 203 | unicode-display_width (1.7.0) 204 | websocket-driver (0.7.3) 205 | websocket-extensions (>= 0.1.0) 206 | websocket-extensions (0.1.5) 207 | zeitwerk (2.4.2) 208 | 209 | PLATFORMS 210 | ruby 211 | 212 | DEPENDENCIES 213 | appraisal (~> 2.3.0) 214 | guard 215 | guard-minitest 216 | minitest (~> 5.0) 217 | pry 218 | pry-byebug 219 | rack-test 220 | rails 221 | rake 222 | serviceworker-rails! 223 | simplecov 224 | sprockets (~> 3.0) 225 | sprockets-rails 226 | standard 227 | 228 | BUNDLED WITH 229 | 2.1.4 230 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A sample Guardfile 4 | # More info at https://github.com/guard/guard#readme 5 | 6 | ## Uncomment and set this to only include directories you want to watch 7 | # directories %w(app lib config test spec features) \ 8 | # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} 9 | 10 | ## Note: if you are using the `directories` clause above and you are not 11 | ## watching the project directory ('.'), then you will want to move 12 | ## the Guardfile to a watched dir and symlink it back, e.g. 13 | # 14 | # $ mkdir config 15 | # $ mv Guardfile config/ 16 | # $ ln -s config/Guardfile . 17 | # 18 | # and, you'll have to watch "config/Guardfile" instead of "Guardfile" 19 | 20 | guard :minitest do 21 | # with Minitest::Unit 22 | watch(%r{^test/(.*)/?(.*)_test\.rb$}) 23 | watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" } 24 | watch(%r{^test/test_helper\.rb$}) { "test" } 25 | 26 | # with Minitest::Spec 27 | # watch(%r{^spec/(.*)_spec\.rb$}) 28 | # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 29 | # watch(%r{^spec/spec_helper\.rb$}) { 'spec' } 30 | 31 | # Rails 4 32 | # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" } 33 | # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' } 34 | # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" } 35 | # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" } 36 | # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" } 37 | # watch(%r{^test/.+_test\.rb$}) 38 | # watch(%r{^test/test_helper\.rb$}) { 'test' } 39 | 40 | # Rails < 4 41 | # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" } 42 | # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" } 43 | # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" } 44 | end 45 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ross Kaffenberger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServiceWorker::Rails 2 | 3 | [![Build Status](https://travis-ci.org/rossta/serviceworker-rails.svg?branch=master)](https://travis-ci.org/rossta/serviceworker-rails) 4 | [![Code Climate](https://codeclimate.com/github/rossta/serviceworker-rails/badges/gpa.svg)](https://codeclimate.com/github/rossta/serviceworker-rails) 5 | 6 | Turn your Rails app into a Progressive Web App. Use [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) with the Rails [asset pipeline](https://github.com/rails/sprockets-rails) or [Webpacker](https://github.com/rails/webpacker) 7 | 8 | ## Why? 9 | 10 | The Rails asset pipeline makes a number of assumptions about what's best for deploying JavaScript, including asset digest fingerprints and long-lived cache headers - mostly to increase "cacheability". Rails also assumes a single parent directory, `/public/assets`, to make it easier to look up the file path for a given asset. 11 | 12 | Service worker assets must play by different rules. Consider these behaviors: 13 | 14 | * Service workers may only be active from within the scope from which they are 15 | served. So if you try to register a service worker from a Rails asset pipeline 16 | path, like `/assets/serviceworker-abcd1234.js`, it will only be able to interact 17 | with requests and responses within `/assets/`**. This is not what we want. 18 | 19 | * [MDN states](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API#Download_install_and_activate) browsers check for updated service worker scripts in the background every 24 hours (possibly less). Rails developers wouldn't be able to take advantage of this feature since the fingerprint strategy means assets at a given url are immutable. Beside fingerprintings, the `Cache-Control` headers used for static files served from Rails also work against browser's treatment of service workers. 20 | 21 | We want Sprockets or Webpacker to compile service worker JavaScript from ES6/7, CoffeeScript, ERB, etc. but must remove the caching and scoping mechanisms offered by Rails defaults. This is where `serviceworker-rails` comes in. 22 | 23 | *Check out the [blog post](https://rossta.net/blog/service-worker-on-rails.html) 24 | for more background.* 25 | 26 | ### Demo 27 | 28 | See various examples of using Service Workers in the demo Rails app, [Service Worker Rails Sandbox](https://serviceworker-rails.herokuapp.com/). The [source code](https://github.com/rossta/serviceworker-rails-sandbox) is also on GitHub. 29 | 30 | ## Features 31 | 32 | * Maps service worker endpoints to Rails assets 33 | * Adds appropriate response headers to service workers 34 | * Renders compiled source in production and development 35 | 36 | ## Installation 37 | 38 | Add this line to your application's Gemfile: 39 | 40 | ```ruby 41 | gem 'serviceworker-rails' 42 | ``` 43 | 44 | And then execute: 45 | 46 | $ bundle 47 | 48 | Or install it yourself as: 49 | 50 | $ gem install serviceworker-rails 51 | 52 | To set up your Rails project for use with a Service Worker, you either use the 53 | Rails generator and edit the generated files as needed, or you can follow the 54 | manual installation steps. 55 | 56 | ### Automated setup 57 | 58 | After bundling the gem in your Rails project, run the generator from the root of 59 | your Rails project. 60 | 61 | ``` 62 | $ rails g serviceworker:install 63 | ``` 64 | 65 | The generator will create the following files: 66 | 67 | * `config/initializers/serviceworker.rb` - for configuring your Rails app 68 | * `app/assets/javascripts/serviceworker.js.erb` - a blank Service Worker 69 | script with some example strategies 70 | * `app/assets/javascripts/serviceworker-companion.js` - a snippet of JavaScript 71 | necessary to register your Service Worker in the browser 72 | * `app/assets/javascripts/manifest.json.erb` - a starter web app manifest 73 | pointing to some default app icons provided by the gem 74 | * `public/offline.html` - a starter offline page 75 | 76 | It will also make the following modifications to existing files: 77 | 78 | * Adds a sprockets directive to `application.js` to require 79 | `serviceworker-companion.js` 80 | * Adds `serviceworker.js` and `manifest.json` to the list of compiled assets in 81 | `config/initializers/assets.rb` 82 | * Injects tags into the `head` of `app/views/layouts/application.html.erb` for 83 | linking to the web app manifest 84 | 85 | **NOTE** Given that Service Worker operates in a separate browser thread, outside the context of your web pages, you don't want to include `serviceworker.js` script in your `application.js`. So if you have a line like `require_tree .` in your `application.js` file, you'll either need to move your `serviceworker.js` to another location or replace `require_tree` with something more explicit. 86 | 87 | To learn more about each of the changes or to perform the set up yourself, check 88 | out the manual setup section below. 89 | 90 | ### Manual setup 91 | 92 | Let's add a `ServiceWorker` to cache some of your JavaScript and CSS assets. We'll assume you already have a Rails application using the asset pipeline built on Sprockets. 93 | 94 | #### Add a service worker script 95 | 96 | Create a JavaScript file called `app/assets/javascripts/serviceworker.js.erb`: 97 | 98 | ```javascript 99 | // app/assets/javascripts/serviceworker.js.erb 100 | console.log('[Service Worker] Hello world!'); 101 | 102 | var CACHE_NAME = 'v1-cached-assets' 103 | 104 | function onInstall(event) { 105 | event.waitUntil( 106 | caches.open(CACHE_NAME).then(function prefill(cache) { 107 | return cache.addAll([ 108 | '<%= asset_path "application.js" %>', 109 | '<%= asset_path "application.css" %>', 110 | '/offline.html', 111 | // you get the idea ... 112 | ]); 113 | }) 114 | ); 115 | } 116 | 117 | function onActivate(event) { 118 | console.log('[Serviceworker]', "Activating!", event); 119 | event.waitUntil( 120 | caches.keys().then(function(cacheNames) { 121 | return Promise.all( 122 | cacheNames.filter(function(cacheName) { 123 | // Return true if you want to remove this cache, 124 | // but remember that caches are shared across 125 | // the whole origin 126 | return cacheName.indexOf('v1') !== 0; 127 | }).map(function(cacheName) { 128 | return caches.delete(cacheName); 129 | }) 130 | ); 131 | }) 132 | ); 133 | } 134 | 135 | self.addEventListener('install', onInstall) 136 | self.addEventListener('activate', onActivate) 137 | ``` 138 | 139 | For use in production, instruct Sprockets to precompile service worker scripts separately from `application.js`, as in the following example: 140 | 141 | #### Register the service worker 142 | 143 | You'll need to register the service worker with a companion script in your main page JavaScript, like `application.js`. You can use the following: 144 | 145 | ```javascript 146 | // app/assets/javascripts/serviceworker-companion.js 147 | 148 | if (navigator.serviceWorker) { 149 | navigator.serviceWorker.register('/serviceworker.js', { scope: './' }) 150 | .then(function(reg) { 151 | console.log('[Page] Service worker registered!'); 152 | }); 153 | } 154 | 155 | // app/assets/javascripts/application.js 156 | 157 | // ... 158 | //= require serviceworker-companion 159 | ``` 160 | 161 | #### Add a manifest 162 | 163 | You may also want to create a `manifest.json` file to make your web app installable. 164 | 165 | ```javascript 166 | // app/assets/javascripts/manifest.json 167 | { 168 | "name": "My Progressive Rails App", 169 | "short_name": "Progressive", 170 | "start_url": "/" 171 | } 172 | ``` 173 | 174 | You'd then link to your manifest from the application layout: 175 | 176 | ```html 177 | 178 | ``` 179 | 180 | #### Configure the middleware 181 | 182 | Next, add a new initializer as show below to instruct the `serviceworker-rails` 183 | middleware how to route requests for assets by canonical url. 184 | 185 | ```ruby 186 | # config/initializers/serviceworker.rb 187 | 188 | Rails.application.configure do 189 | config.serviceworker.routes.draw do 190 | match "/serviceworker.js" 191 | match "/manifest.json" 192 | end 193 | end 194 | ``` 195 | 196 | #### Precompile the assets 197 | 198 | ```ruby 199 | # config/initializers/assets.rb 200 | 201 | Rails.application.configure do 202 | config.assets.precompile += %w[serviceworker.js manifest.json] 203 | end 204 | ``` 205 | 206 | #### Test the setup 207 | 208 | At this point, restart your Rails app and reload a page in your app in Chrome or Firefox. Using dev tools, you should be able to determine. 209 | 210 | 1. The page requests a service worker at `/serviceworker.js` 211 | 2. The Rails app responds to the request by compiling and rendering the file in `app/assets/javascripts/serviceworker.js.erb`. 212 | 3. The console displays messages from the page and the service worker 213 | 4. The application JavaScript and CSS assets are added to the browser's request/response [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache). 214 | 215 | #### Using the cache 216 | 217 | So far so good? At this point, all we've done is pre-fetched assets and added them to the cache, but we're not doing anything with them yet. 218 | 219 | Now, we can use the service worker to intercept requests and either serve them from the cache if they exist there or fallback to the network response otherwise. In most cases, we can expect responses coming from the local cache to be much faster than those coming from the network. 220 | 221 | ```javascript 222 | // app/assets/javascripts/serviceworker.js.erb 223 | 224 | function onFetch(event) { 225 | // Fetch from network, fallback to cached content, then offline.html for same-origin GET requests 226 | var request = event.request; 227 | 228 | if (!request.url.match(/^https?:\/\/example.com/) ) { return; } 229 | if (request.method !== 'GET') { return; } 230 | 231 | event.respondWith( 232 | fetch(request) // first, the network 233 | .catch(function fallback() { 234 | caches.match(request).then(function(response) { // then, the cache 235 | response || caches.match("/offline.html"); // then, /offline cache 236 | }) 237 | }) 238 | ); 239 | 240 | // See https://jakearchibald.com/2014/offline-cookbook/#on-network-response for more examples 241 | } 242 | 243 | self.addEventListener('fetch', onFetch); 244 | ``` 245 | 246 | ## Configuration 247 | 248 | When `serviceworker-rails` is required in your Gemfile, it will insert a middleware into the Rails 249 | middleware stack. You'll want to configure it by mapping serviceworker routes to 250 | Sprockets JavaScript assets in an initializer, like the example below. 251 | 252 | ```ruby 253 | # config/initializers/serviceworker.rb 254 | 255 | Rails.application.configure do 256 | config.serviceworker.routes.draw do 257 | # maps to asset named 'serviceworker.js' implicitly 258 | match "/serviceworker.js" 259 | 260 | # map to a named asset explicitly 261 | match "/proxied-serviceworker.js" => "nested/asset/serviceworker.js" 262 | match "/nested/serviceworker.js" => "another/serviceworker.js" 263 | 264 | # capture named path segments and interpolate to asset name 265 | match "/captures/*segments/serviceworker.js" => "%{segments}/serviceworker.js" 266 | 267 | # capture named parameter and interpolate to asset name 268 | match "/parameter/:id/serviceworker.js" => "project/%{id}/serviceworker.js" 269 | 270 | # insert custom headers 271 | match "/header-serviceworker.js" => "another/serviceworker.js", 272 | headers: { "X-Resource-Header" => "A resource" } 273 | 274 | # maps to serviceworker "pack" compiled by Webpacker 275 | match "/webpack-serviceworker.js" => "serviceworker.js", pack: true 276 | 277 | # anonymous glob exposes `paths` variable for interpolation 278 | match "/*/serviceworker.js" => "%{paths}/serviceworker.js" 279 | end 280 | end 281 | ``` 282 | 283 | `Serviceworker::Rails` will insert a `Cache-Control` header to instruct browsers 284 | not to cache your serviceworkers by default. You can customize the headers for all service worker routes if you'd like, 285 | such as adding the experimental [`Service-Worker-Allowed`](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-allowed) header to set the allowed scope. 286 | 287 | ```ruby 288 | config.serviceworker.headers["Service-Worker-Allowed"] = "/" 289 | config.serviceworker.headers["X-Custom-Header"] = "foobar" 290 | ``` 291 | 292 | ## Development 293 | 294 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 295 | 296 | ## Contributing 297 | 298 | Bug reports and pull requests are welcome on GitHub at https://github.com/rossta/serviceworker-rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 299 | 300 | ## License 301 | 302 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 303 | 304 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rubygems" 4 | require "bundler/setup" 5 | require "bundler/gem_tasks" 6 | require "rake/testtask" 7 | require "rubocop/rake_task" 8 | 9 | APP_RAKEFILE = File.expand_path("test/sample/Rakefile", __dir__) 10 | load "rails/tasks/engine.rake" 11 | 12 | RuboCop::RakeTask.new 13 | 14 | Rake::TestTask.new(:test) do |t| 15 | t.libs << "test" 16 | t.libs << "lib" 17 | t.test_files = FileList["test/**/*_test.rb"] 18 | end 19 | 20 | task default: %i[test rubocop] 21 | 22 | namespace :app do 23 | task :yarn_install do 24 | Dir.chdir("test/sample") do 25 | sh "yarn install" 26 | end 27 | end 28 | 29 | task :yarn_install_frozen do 30 | Dir.chdir("test/sample") do 31 | sh "yarn install --frozen-lockfile" 32 | end 33 | end 34 | end 35 | 36 | task :compile do 37 | if defined?(Webpacker) 38 | Dir.chdir("test/sample") do 39 | sh "yarn install --frozen-lockfile" 40 | sh "RAILS_ENV=test ./bin/rake webpacker:compile" 41 | end 42 | end 43 | end 44 | 45 | Rake::Task[:test].enhance [:compile] do 46 | if defined?(Webpacker) 47 | Dir.chdir("test/sample") do 48 | sh "RAILS_ENV=test ./bin/rake webpacker:clobber" 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /bin/_guard-core: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application '_guard-core' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | require "rubygems" 16 | require "bundler/setup" 17 | 18 | load Gem.bin_path("guard", "_guard-core") 19 | -------------------------------------------------------------------------------- /bin/appraisal: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'appraisal' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | require "rubygems" 16 | require "bundler/setup" 17 | 18 | load Gem.bin_path("appraisal", "appraisal") 19 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "serviceworker/rails" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start 16 | -------------------------------------------------------------------------------- /bin/guard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'guard' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | require "rubygems" 16 | require "bundler/setup" 17 | 18 | load Gem.bin_path("guard", "guard") 19 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rake' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | require "rubygems" 16 | require "bundler/setup" 17 | 18 | load Gem.bin_path("rake", "rake") 19 | -------------------------------------------------------------------------------- /bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rubocop' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | require "rubygems" 16 | require "bundler/setup" 17 | 18 | load Gem.bin_path("rubocop", "rubocop") 19 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /bin/standardrb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'standardrb' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("standard", "standardrb") 30 | -------------------------------------------------------------------------------- /gemfiles/rails_5.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.0" 6 | gem "webpacker" 7 | 8 | group :development, :test do 9 | gem "guard", require: false 10 | gem "guard-minitest", require: false 11 | gem "pry" 12 | gem "pry-byebug", platforms: [:mri] 13 | end 14 | 15 | gemspec path: "../" 16 | -------------------------------------------------------------------------------- /gemfiles/rails_5.0.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | serviceworker-rails (0.6.0) 5 | railties (>= 3.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.2.4.4) 11 | actionpack (= 5.2.4.4) 12 | nio4r (~> 2.0) 13 | websocket-driver (>= 0.6.1) 14 | actionmailer (5.2.4.4) 15 | actionpack (= 5.2.4.4) 16 | actionview (= 5.2.4.4) 17 | activejob (= 5.2.4.4) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.2.4.4) 21 | actionview (= 5.2.4.4) 22 | activesupport (= 5.2.4.4) 23 | rack (~> 2.0, >= 2.0.8) 24 | rack-test (>= 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.2.4.4) 28 | activesupport (= 5.2.4.4) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.2.4.4) 34 | activesupport (= 5.2.4.4) 35 | globalid (>= 0.3.6) 36 | activemodel (5.2.4.4) 37 | activesupport (= 5.2.4.4) 38 | activerecord (5.2.4.4) 39 | activemodel (= 5.2.4.4) 40 | activesupport (= 5.2.4.4) 41 | arel (>= 9.0) 42 | activestorage (5.2.4.4) 43 | actionpack (= 5.2.4.4) 44 | activerecord (= 5.2.4.4) 45 | marcel (~> 0.3.1) 46 | activesupport (5.2.4.4) 47 | concurrent-ruby (~> 1.0, >= 1.0.2) 48 | i18n (>= 0.7, < 2) 49 | minitest (~> 5.1) 50 | tzinfo (~> 1.1) 51 | appraisal (2.3.0) 52 | bundler 53 | rake 54 | thor (>= 0.14.0) 55 | arel (9.0.0) 56 | ast (2.4.1) 57 | builder (3.2.4) 58 | byebug (11.1.3) 59 | coderay (1.1.3) 60 | concurrent-ruby (1.1.7) 61 | crass (1.0.6) 62 | docile (1.3.2) 63 | erubi (1.10.0) 64 | ffi (1.13.1) 65 | formatador (0.2.5) 66 | globalid (0.4.2) 67 | activesupport (>= 4.2.0) 68 | guard (2.16.2) 69 | formatador (>= 0.2.4) 70 | listen (>= 2.7, < 4.0) 71 | lumberjack (>= 1.0.12, < 2.0) 72 | nenv (~> 0.1) 73 | notiffany (~> 0.0) 74 | pry (>= 0.9.12) 75 | shellany (~> 0.0) 76 | thor (>= 0.18.1) 77 | guard-compat (1.2.1) 78 | guard-minitest (2.4.6) 79 | guard-compat (~> 1.2) 80 | minitest (>= 3.0) 81 | i18n (1.8.5) 82 | concurrent-ruby (~> 1.0) 83 | listen (3.3.3) 84 | rb-fsevent (~> 0.10, >= 0.10.3) 85 | rb-inotify (~> 0.9, >= 0.9.10) 86 | loofah (2.8.0) 87 | crass (~> 1.0.2) 88 | nokogiri (>= 1.5.9) 89 | lumberjack (1.2.8) 90 | mail (2.7.1) 91 | mini_mime (>= 0.1.1) 92 | marcel (0.3.3) 93 | mimemagic (~> 0.3.2) 94 | method_source (1.0.0) 95 | mimemagic (0.3.5) 96 | mini_mime (1.0.2) 97 | mini_portile2 (2.4.0) 98 | minitest (5.14.2) 99 | nenv (0.3.0) 100 | nio4r (2.5.4) 101 | nokogiri (1.10.10) 102 | mini_portile2 (~> 2.4.0) 103 | notiffany (0.1.3) 104 | nenv (~> 0.1) 105 | shellany (~> 0.0) 106 | parallel (1.20.1) 107 | parser (2.7.2.0) 108 | ast (~> 2.4.1) 109 | pry (0.13.1) 110 | coderay (~> 1.1) 111 | method_source (~> 1.0) 112 | pry-byebug (3.9.0) 113 | byebug (~> 11.0) 114 | pry (~> 0.13.0) 115 | rack (2.2.3) 116 | rack-proxy (0.6.5) 117 | rack 118 | rack-test (1.1.0) 119 | rack (>= 1.0, < 3) 120 | rails (5.2.4.4) 121 | actioncable (= 5.2.4.4) 122 | actionmailer (= 5.2.4.4) 123 | actionpack (= 5.2.4.4) 124 | actionview (= 5.2.4.4) 125 | activejob (= 5.2.4.4) 126 | activemodel (= 5.2.4.4) 127 | activerecord (= 5.2.4.4) 128 | activestorage (= 5.2.4.4) 129 | activesupport (= 5.2.4.4) 130 | bundler (>= 1.3.0) 131 | railties (= 5.2.4.4) 132 | sprockets-rails (>= 2.0.0) 133 | rails-dom-testing (2.0.3) 134 | activesupport (>= 4.2.0) 135 | nokogiri (>= 1.6) 136 | rails-html-sanitizer (1.3.0) 137 | loofah (~> 2.3) 138 | railties (5.2.4.4) 139 | actionpack (= 5.2.4.4) 140 | activesupport (= 5.2.4.4) 141 | method_source 142 | rake (>= 0.8.7) 143 | thor (>= 0.19.0, < 2.0) 144 | rainbow (3.0.0) 145 | rake (13.0.1) 146 | rb-fsevent (0.10.4) 147 | rb-inotify (0.10.1) 148 | ffi (~> 1.0) 149 | regexp_parser (2.0.0) 150 | rexml (3.2.4) 151 | rubocop (1.4.2) 152 | parallel (~> 1.10) 153 | parser (>= 2.7.1.5) 154 | rainbow (>= 2.2.2, < 4.0) 155 | regexp_parser (>= 1.8) 156 | rexml 157 | rubocop-ast (>= 1.1.1) 158 | ruby-progressbar (~> 1.7) 159 | unicode-display_width (>= 1.4.0, < 2.0) 160 | rubocop-ast (1.3.0) 161 | parser (>= 2.7.1.5) 162 | rubocop-performance (1.9.1) 163 | rubocop (>= 0.90.0, < 2.0) 164 | rubocop-ast (>= 0.4.0) 165 | ruby-progressbar (1.10.1) 166 | semantic_range (2.3.1) 167 | shellany (0.0.1) 168 | simplecov (0.20.0) 169 | docile (~> 1.1) 170 | simplecov-html (~> 0.11) 171 | simplecov_json_formatter (~> 0.1) 172 | simplecov-html (0.12.3) 173 | simplecov_json_formatter (0.1.2) 174 | sprockets (3.7.2) 175 | concurrent-ruby (~> 1.0) 176 | rack (> 1, < 3) 177 | sprockets-rails (3.2.2) 178 | actionpack (>= 4.0) 179 | activesupport (>= 4.0) 180 | sprockets (>= 3.0.0) 181 | standard (0.10.2) 182 | rubocop (= 1.4.2) 183 | rubocop-performance (= 1.9.1) 184 | thor (1.0.1) 185 | thread_safe (0.3.6) 186 | tzinfo (1.2.8) 187 | thread_safe (~> 0.1) 188 | unicode-display_width (1.7.0) 189 | webpacker (5.2.1) 190 | activesupport (>= 5.2) 191 | rack-proxy (>= 0.6.1) 192 | railties (>= 5.2) 193 | semantic_range (>= 2.3.0) 194 | websocket-driver (0.7.3) 195 | websocket-extensions (>= 0.1.0) 196 | websocket-extensions (0.1.5) 197 | 198 | PLATFORMS 199 | ruby 200 | 201 | DEPENDENCIES 202 | appraisal (~> 2.3.0) 203 | guard 204 | guard-minitest 205 | minitest (~> 5.0) 206 | pry 207 | pry-byebug 208 | rack-test 209 | rails (~> 5.0) 210 | rake 211 | serviceworker-rails! 212 | simplecov 213 | sprockets (~> 3.0) 214 | sprockets-rails 215 | standard 216 | webpacker 217 | 218 | BUNDLED WITH 219 | 2.1.4 220 | -------------------------------------------------------------------------------- /gemfiles/rails_5.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.1" 6 | gem "webpacker" 7 | 8 | group :development, :test do 9 | gem "guard", require: false 10 | gem "guard-minitest", require: false 11 | gem "pry" 12 | gem "pry-byebug", platforms: [:mri] 13 | end 14 | 15 | gemspec path: "../" 16 | -------------------------------------------------------------------------------- /gemfiles/rails_5.1.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | serviceworker-rails (0.6.0) 5 | railties (>= 3.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.2.4.4) 11 | actionpack (= 5.2.4.4) 12 | nio4r (~> 2.0) 13 | websocket-driver (>= 0.6.1) 14 | actionmailer (5.2.4.4) 15 | actionpack (= 5.2.4.4) 16 | actionview (= 5.2.4.4) 17 | activejob (= 5.2.4.4) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.2.4.4) 21 | actionview (= 5.2.4.4) 22 | activesupport (= 5.2.4.4) 23 | rack (~> 2.0, >= 2.0.8) 24 | rack-test (>= 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.2.4.4) 28 | activesupport (= 5.2.4.4) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.2.4.4) 34 | activesupport (= 5.2.4.4) 35 | globalid (>= 0.3.6) 36 | activemodel (5.2.4.4) 37 | activesupport (= 5.2.4.4) 38 | activerecord (5.2.4.4) 39 | activemodel (= 5.2.4.4) 40 | activesupport (= 5.2.4.4) 41 | arel (>= 9.0) 42 | activestorage (5.2.4.4) 43 | actionpack (= 5.2.4.4) 44 | activerecord (= 5.2.4.4) 45 | marcel (~> 0.3.1) 46 | activesupport (5.2.4.4) 47 | concurrent-ruby (~> 1.0, >= 1.0.2) 48 | i18n (>= 0.7, < 2) 49 | minitest (~> 5.1) 50 | tzinfo (~> 1.1) 51 | appraisal (2.3.0) 52 | bundler 53 | rake 54 | thor (>= 0.14.0) 55 | arel (9.0.0) 56 | ast (2.4.1) 57 | builder (3.2.4) 58 | byebug (11.1.3) 59 | coderay (1.1.3) 60 | concurrent-ruby (1.1.7) 61 | crass (1.0.6) 62 | docile (1.3.2) 63 | erubi (1.10.0) 64 | ffi (1.13.1) 65 | formatador (0.2.5) 66 | globalid (0.4.2) 67 | activesupport (>= 4.2.0) 68 | guard (2.16.2) 69 | formatador (>= 0.2.4) 70 | listen (>= 2.7, < 4.0) 71 | lumberjack (>= 1.0.12, < 2.0) 72 | nenv (~> 0.1) 73 | notiffany (~> 0.0) 74 | pry (>= 0.9.12) 75 | shellany (~> 0.0) 76 | thor (>= 0.18.1) 77 | guard-compat (1.2.1) 78 | guard-minitest (2.4.6) 79 | guard-compat (~> 1.2) 80 | minitest (>= 3.0) 81 | i18n (1.8.5) 82 | concurrent-ruby (~> 1.0) 83 | listen (3.3.3) 84 | rb-fsevent (~> 0.10, >= 0.10.3) 85 | rb-inotify (~> 0.9, >= 0.9.10) 86 | loofah (2.8.0) 87 | crass (~> 1.0.2) 88 | nokogiri (>= 1.5.9) 89 | lumberjack (1.2.8) 90 | mail (2.7.1) 91 | mini_mime (>= 0.1.1) 92 | marcel (0.3.3) 93 | mimemagic (~> 0.3.2) 94 | method_source (1.0.0) 95 | mimemagic (0.3.5) 96 | mini_mime (1.0.2) 97 | mini_portile2 (2.4.0) 98 | minitest (5.14.2) 99 | nenv (0.3.0) 100 | nio4r (2.5.4) 101 | nokogiri (1.10.10) 102 | mini_portile2 (~> 2.4.0) 103 | notiffany (0.1.3) 104 | nenv (~> 0.1) 105 | shellany (~> 0.0) 106 | parallel (1.20.1) 107 | parser (2.7.2.0) 108 | ast (~> 2.4.1) 109 | pry (0.13.1) 110 | coderay (~> 1.1) 111 | method_source (~> 1.0) 112 | pry-byebug (3.9.0) 113 | byebug (~> 11.0) 114 | pry (~> 0.13.0) 115 | rack (2.2.3) 116 | rack-proxy (0.6.5) 117 | rack 118 | rack-test (1.1.0) 119 | rack (>= 1.0, < 3) 120 | rails (5.2.4.4) 121 | actioncable (= 5.2.4.4) 122 | actionmailer (= 5.2.4.4) 123 | actionpack (= 5.2.4.4) 124 | actionview (= 5.2.4.4) 125 | activejob (= 5.2.4.4) 126 | activemodel (= 5.2.4.4) 127 | activerecord (= 5.2.4.4) 128 | activestorage (= 5.2.4.4) 129 | activesupport (= 5.2.4.4) 130 | bundler (>= 1.3.0) 131 | railties (= 5.2.4.4) 132 | sprockets-rails (>= 2.0.0) 133 | rails-dom-testing (2.0.3) 134 | activesupport (>= 4.2.0) 135 | nokogiri (>= 1.6) 136 | rails-html-sanitizer (1.3.0) 137 | loofah (~> 2.3) 138 | railties (5.2.4.4) 139 | actionpack (= 5.2.4.4) 140 | activesupport (= 5.2.4.4) 141 | method_source 142 | rake (>= 0.8.7) 143 | thor (>= 0.19.0, < 2.0) 144 | rainbow (3.0.0) 145 | rake (13.0.1) 146 | rb-fsevent (0.10.4) 147 | rb-inotify (0.10.1) 148 | ffi (~> 1.0) 149 | regexp_parser (2.0.0) 150 | rexml (3.2.4) 151 | rubocop (1.4.2) 152 | parallel (~> 1.10) 153 | parser (>= 2.7.1.5) 154 | rainbow (>= 2.2.2, < 4.0) 155 | regexp_parser (>= 1.8) 156 | rexml 157 | rubocop-ast (>= 1.1.1) 158 | ruby-progressbar (~> 1.7) 159 | unicode-display_width (>= 1.4.0, < 2.0) 160 | rubocop-ast (1.3.0) 161 | parser (>= 2.7.1.5) 162 | rubocop-performance (1.9.1) 163 | rubocop (>= 0.90.0, < 2.0) 164 | rubocop-ast (>= 0.4.0) 165 | ruby-progressbar (1.10.1) 166 | semantic_range (2.3.1) 167 | shellany (0.0.1) 168 | simplecov (0.20.0) 169 | docile (~> 1.1) 170 | simplecov-html (~> 0.11) 171 | simplecov_json_formatter (~> 0.1) 172 | simplecov-html (0.12.3) 173 | simplecov_json_formatter (0.1.2) 174 | sprockets (3.7.2) 175 | concurrent-ruby (~> 1.0) 176 | rack (> 1, < 3) 177 | sprockets-rails (3.2.2) 178 | actionpack (>= 4.0) 179 | activesupport (>= 4.0) 180 | sprockets (>= 3.0.0) 181 | standard (0.10.2) 182 | rubocop (= 1.4.2) 183 | rubocop-performance (= 1.9.1) 184 | thor (1.0.1) 185 | thread_safe (0.3.6) 186 | tzinfo (1.2.8) 187 | thread_safe (~> 0.1) 188 | unicode-display_width (1.7.0) 189 | webpacker (5.2.1) 190 | activesupport (>= 5.2) 191 | rack-proxy (>= 0.6.1) 192 | railties (>= 5.2) 193 | semantic_range (>= 2.3.0) 194 | websocket-driver (0.7.3) 195 | websocket-extensions (>= 0.1.0) 196 | websocket-extensions (0.1.5) 197 | 198 | PLATFORMS 199 | ruby 200 | 201 | DEPENDENCIES 202 | appraisal (~> 2.3.0) 203 | guard 204 | guard-minitest 205 | minitest (~> 5.0) 206 | pry 207 | pry-byebug 208 | rack-test 209 | rails (~> 5.1) 210 | rake 211 | serviceworker-rails! 212 | simplecov 213 | sprockets (~> 3.0) 214 | sprockets-rails 215 | standard 216 | webpacker 217 | 218 | BUNDLED WITH 219 | 2.1.4 220 | -------------------------------------------------------------------------------- /gemfiles/rails_5.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rails", "~> 5.2" 6 | gem "webpacker", ">= 4.1" 7 | 8 | group :development, :test do 9 | gem "guard", require: false 10 | gem "guard-minitest", require: false 11 | gem "pry" 12 | gem "pry-byebug", platforms: [:mri] 13 | end 14 | 15 | gemspec path: "../" 16 | -------------------------------------------------------------------------------- /gemfiles/rails_5.2.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | serviceworker-rails (0.6.0) 5 | railties (>= 3.1) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (5.2.4.4) 11 | actionpack (= 5.2.4.4) 12 | nio4r (~> 2.0) 13 | websocket-driver (>= 0.6.1) 14 | actionmailer (5.2.4.4) 15 | actionpack (= 5.2.4.4) 16 | actionview (= 5.2.4.4) 17 | activejob (= 5.2.4.4) 18 | mail (~> 2.5, >= 2.5.4) 19 | rails-dom-testing (~> 2.0) 20 | actionpack (5.2.4.4) 21 | actionview (= 5.2.4.4) 22 | activesupport (= 5.2.4.4) 23 | rack (~> 2.0, >= 2.0.8) 24 | rack-test (>= 0.6.3) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 27 | actionview (5.2.4.4) 28 | activesupport (= 5.2.4.4) 29 | builder (~> 3.1) 30 | erubi (~> 1.4) 31 | rails-dom-testing (~> 2.0) 32 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 33 | activejob (5.2.4.4) 34 | activesupport (= 5.2.4.4) 35 | globalid (>= 0.3.6) 36 | activemodel (5.2.4.4) 37 | activesupport (= 5.2.4.4) 38 | activerecord (5.2.4.4) 39 | activemodel (= 5.2.4.4) 40 | activesupport (= 5.2.4.4) 41 | arel (>= 9.0) 42 | activestorage (5.2.4.4) 43 | actionpack (= 5.2.4.4) 44 | activerecord (= 5.2.4.4) 45 | marcel (~> 0.3.1) 46 | activesupport (5.2.4.4) 47 | concurrent-ruby (~> 1.0, >= 1.0.2) 48 | i18n (>= 0.7, < 2) 49 | minitest (~> 5.1) 50 | tzinfo (~> 1.1) 51 | appraisal (2.3.0) 52 | bundler 53 | rake 54 | thor (>= 0.14.0) 55 | arel (9.0.0) 56 | ast (2.4.1) 57 | builder (3.2.4) 58 | byebug (11.1.3) 59 | coderay (1.1.3) 60 | concurrent-ruby (1.1.7) 61 | crass (1.0.6) 62 | docile (1.3.2) 63 | erubi (1.10.0) 64 | ffi (1.13.1) 65 | formatador (0.2.5) 66 | globalid (0.4.2) 67 | activesupport (>= 4.2.0) 68 | guard (2.16.2) 69 | formatador (>= 0.2.4) 70 | listen (>= 2.7, < 4.0) 71 | lumberjack (>= 1.0.12, < 2.0) 72 | nenv (~> 0.1) 73 | notiffany (~> 0.0) 74 | pry (>= 0.9.12) 75 | shellany (~> 0.0) 76 | thor (>= 0.18.1) 77 | guard-compat (1.2.1) 78 | guard-minitest (2.4.6) 79 | guard-compat (~> 1.2) 80 | minitest (>= 3.0) 81 | i18n (1.8.5) 82 | concurrent-ruby (~> 1.0) 83 | listen (3.3.3) 84 | rb-fsevent (~> 0.10, >= 0.10.3) 85 | rb-inotify (~> 0.9, >= 0.9.10) 86 | loofah (2.8.0) 87 | crass (~> 1.0.2) 88 | nokogiri (>= 1.5.9) 89 | lumberjack (1.2.8) 90 | mail (2.7.1) 91 | mini_mime (>= 0.1.1) 92 | marcel (0.3.3) 93 | mimemagic (~> 0.3.2) 94 | method_source (1.0.0) 95 | mimemagic (0.3.5) 96 | mini_mime (1.0.2) 97 | mini_portile2 (2.4.0) 98 | minitest (5.14.2) 99 | nenv (0.3.0) 100 | nio4r (2.5.4) 101 | nokogiri (1.10.10) 102 | mini_portile2 (~> 2.4.0) 103 | notiffany (0.1.3) 104 | nenv (~> 0.1) 105 | shellany (~> 0.0) 106 | parallel (1.20.1) 107 | parser (2.7.2.0) 108 | ast (~> 2.4.1) 109 | pry (0.13.1) 110 | coderay (~> 1.1) 111 | method_source (~> 1.0) 112 | pry-byebug (3.9.0) 113 | byebug (~> 11.0) 114 | pry (~> 0.13.0) 115 | rack (2.2.3) 116 | rack-proxy (0.6.5) 117 | rack 118 | rack-test (1.1.0) 119 | rack (>= 1.0, < 3) 120 | rails (5.2.4.4) 121 | actioncable (= 5.2.4.4) 122 | actionmailer (= 5.2.4.4) 123 | actionpack (= 5.2.4.4) 124 | actionview (= 5.2.4.4) 125 | activejob (= 5.2.4.4) 126 | activemodel (= 5.2.4.4) 127 | activerecord (= 5.2.4.4) 128 | activestorage (= 5.2.4.4) 129 | activesupport (= 5.2.4.4) 130 | bundler (>= 1.3.0) 131 | railties (= 5.2.4.4) 132 | sprockets-rails (>= 2.0.0) 133 | rails-dom-testing (2.0.3) 134 | activesupport (>= 4.2.0) 135 | nokogiri (>= 1.6) 136 | rails-html-sanitizer (1.3.0) 137 | loofah (~> 2.3) 138 | railties (5.2.4.4) 139 | actionpack (= 5.2.4.4) 140 | activesupport (= 5.2.4.4) 141 | method_source 142 | rake (>= 0.8.7) 143 | thor (>= 0.19.0, < 2.0) 144 | rainbow (3.0.0) 145 | rake (13.0.1) 146 | rb-fsevent (0.10.4) 147 | rb-inotify (0.10.1) 148 | ffi (~> 1.0) 149 | regexp_parser (2.0.0) 150 | rexml (3.2.4) 151 | rubocop (1.4.2) 152 | parallel (~> 1.10) 153 | parser (>= 2.7.1.5) 154 | rainbow (>= 2.2.2, < 4.0) 155 | regexp_parser (>= 1.8) 156 | rexml 157 | rubocop-ast (>= 1.1.1) 158 | ruby-progressbar (~> 1.7) 159 | unicode-display_width (>= 1.4.0, < 2.0) 160 | rubocop-ast (1.3.0) 161 | parser (>= 2.7.1.5) 162 | rubocop-performance (1.9.1) 163 | rubocop (>= 0.90.0, < 2.0) 164 | rubocop-ast (>= 0.4.0) 165 | ruby-progressbar (1.10.1) 166 | semantic_range (2.3.1) 167 | shellany (0.0.1) 168 | simplecov (0.20.0) 169 | docile (~> 1.1) 170 | simplecov-html (~> 0.11) 171 | simplecov_json_formatter (~> 0.1) 172 | simplecov-html (0.12.3) 173 | simplecov_json_formatter (0.1.2) 174 | sprockets (3.7.2) 175 | concurrent-ruby (~> 1.0) 176 | rack (> 1, < 3) 177 | sprockets-rails (3.2.2) 178 | actionpack (>= 4.0) 179 | activesupport (>= 4.0) 180 | sprockets (>= 3.0.0) 181 | standard (0.10.2) 182 | rubocop (= 1.4.2) 183 | rubocop-performance (= 1.9.1) 184 | thor (1.0.1) 185 | thread_safe (0.3.6) 186 | tzinfo (1.2.8) 187 | thread_safe (~> 0.1) 188 | unicode-display_width (1.7.0) 189 | webpacker (5.2.1) 190 | activesupport (>= 5.2) 191 | rack-proxy (>= 0.6.1) 192 | railties (>= 5.2) 193 | semantic_range (>= 2.3.0) 194 | websocket-driver (0.7.3) 195 | websocket-extensions (>= 0.1.0) 196 | websocket-extensions (0.1.5) 197 | 198 | PLATFORMS 199 | ruby 200 | 201 | DEPENDENCIES 202 | appraisal (~> 2.3.0) 203 | guard 204 | guard-minitest 205 | minitest (~> 5.0) 206 | pry 207 | pry-byebug 208 | rack-test 209 | rails (~> 5.2) 210 | rake 211 | serviceworker-rails! 212 | simplecov 213 | sprockets (~> 3.0) 214 | sprockets-rails 215 | standard 216 | webpacker (>= 4.1) 217 | 218 | BUNDLED WITH 219 | 2.1.4 220 | -------------------------------------------------------------------------------- /lib/assets/images/convert.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Lint/Void 4 | %w[48 60 72 76 96 120 152 180 192 512].each do |dim| 5 | %(convert heart-1200x1200.png -resize #{dim}x heart-#{dim}x#{dim}.png) 6 | end 7 | # rubocop:enable Lint/Void 8 | -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-1200x1200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-1200x1200.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-120x120.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-144x144.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-152x152.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-180x180.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-192x192.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-36x36.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-384x384.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-48x48.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-512x512.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-60x60.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-72x72.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-76x76.png -------------------------------------------------------------------------------- /lib/assets/images/serviceworker-rails/heart-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/lib/assets/images/serviceworker-rails/heart-96x96.png -------------------------------------------------------------------------------- /lib/generators/serviceworker/install_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails/generators" 4 | require "fileutils" 5 | 6 | module Serviceworker 7 | module Generators 8 | class InstallGenerator < ::Rails::Generators::Base 9 | desc "Make your Rails app a progressive web app" 10 | source_root File.join(File.dirname(__FILE__), "templates") 11 | 12 | def create_assets 13 | template "manifest.json", javascripts_dir("manifest.json.erb") 14 | template "serviceworker.js", javascripts_dir("serviceworker.js.erb") 15 | template "serviceworker-companion.js", javascripts_dir("serviceworker-companion.js") 16 | end 17 | 18 | def create_initializer 19 | template "serviceworker.rb", initializers_dir("serviceworker.rb") 20 | end 21 | 22 | def update_application_js 23 | ext, directive = detect_js_format 24 | snippet = "#{directive} require serviceworker-companion\n" 25 | append_to_file application_js_path(ext), snippet 26 | end 27 | 28 | def update_precompiled_assets 29 | snippet = "Rails.configuration.assets.precompile += %w[serviceworker.js manifest.json]\n" 30 | file_path = initializers_dir("assets.rb") 31 | FileUtils.touch file_path 32 | append_to_file file_path, snippet 33 | end 34 | 35 | def update_application_layout 36 | layout = detect_layout 37 | snippet = %() 38 | snippet += %(\n) 39 | unless layout 40 | conditional_warn "Could not locate application layout. To insert manifest tags manually, use:\n\n#{snippet}\n" 41 | return 42 | end 43 | insert_into_file layout, snippet, before: "\n" 44 | end 45 | 46 | def add_offline_html 47 | template "offline.html", public_dir("offline.html") 48 | end 49 | 50 | private 51 | 52 | def application_js_path(ext) 53 | javascripts_dir("application#{ext}") 54 | end 55 | 56 | def detect_js_format 57 | %w[.js .js.erb .coffee .coffee.erb .js.coffee .js.coffee.erb].each do |ext| 58 | next unless File.exist?(javascripts_dir("application#{ext}")) 59 | return [ext, "#="] if ext.include?(".coffee") 60 | 61 | return [ext, "//="] 62 | end 63 | end 64 | 65 | def detect_layout 66 | layouts = %w[.html.erb .html.haml .html.slim .erb .haml .slim].map { |ext| 67 | layouts_dir("application#{ext}") 68 | } 69 | layouts.find { |layout| File.exist?(layout) } 70 | end 71 | 72 | def javascripts_dir(*paths) 73 | join("app", "assets", "javascripts", *paths) 74 | end 75 | 76 | def initializers_dir(*paths) 77 | join("config", "initializers", *paths) 78 | end 79 | 80 | def layouts_dir(*paths) 81 | join("app", "views", "layouts", *paths) 82 | end 83 | 84 | def public_dir(*paths) 85 | join("public", *paths) 86 | end 87 | 88 | def join(*paths) 89 | File.expand_path(File.join(*paths), destination_root) 90 | end 91 | 92 | def conditional_warn(warning) 93 | silenced? || warn(warning) 94 | end 95 | 96 | def silenced? 97 | ENV["RAILS_ENV"] == "test" 98 | end 99 | end 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/generators/serviceworker/templates/manifest.json: -------------------------------------------------------------------------------- 1 | <%% icon_sizes = Rails.configuration.serviceworker.icon_sizes %> 2 | { 3 | "name": "My Progressive Rails App", 4 | "short_name": "Progressive", 5 | "start_url": "/", 6 | "icons": [ 7 | <%% icon_sizes.map { |s| "#{s}x#{s}" }.each.with_index do |dim, i| %> 8 | { 9 | "src": "<%%= image_path "serviceworker-rails/heart-#{dim}.png" %>", 10 | "sizes": "<%%= dim %>", 11 | "type": "image/png" 12 | }<%%= i == (icon_sizes.length - 1) ? '' : ',' %> 13 | <%% end %> 14 | ], 15 | "theme_color": "#000000", 16 | "background_color": "#FFFFFF", 17 | "display": "fullscreen", 18 | "orientation": "portrait" 19 | } 20 | -------------------------------------------------------------------------------- /lib/generators/serviceworker/templates/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | You are not connected to the Internet 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

It looks like you've lost your Internet connection

62 |

You may need to reconnect to Wi-Fi.

63 |
64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /lib/generators/serviceworker/templates/serviceworker-companion.js: -------------------------------------------------------------------------------- 1 | if (navigator.serviceWorker) { 2 | navigator.serviceWorker 3 | .register("/serviceworker.js", { scope: "./" }) 4 | .then(function() { 5 | console.log("[Companion]", "Rails Service worker registered!") 6 | }) 7 | .catch(function(error) { 8 | // registration failed :( 9 | console.log("[Companion]", "Rails Service worker registration failed: " + error) 10 | }) 11 | } -------------------------------------------------------------------------------- /lib/generators/serviceworker/templates/serviceworker.js: -------------------------------------------------------------------------------- 1 | var CACHE_VERSION = 'v1'; 2 | var CACHE_NAME = CACHE_VERSION + ':sw-cache-'; 3 | 4 | function onInstall(event) { 5 | console.log('[Serviceworker]', "Rails Service Worker Installing!", event); 6 | event.waitUntil( 7 | caches.open(CACHE_NAME).then(function prefill(cache) { 8 | return cache.addAll([ 9 | 10 | // make sure serviceworker.js is not required by application.js 11 | // if you want to reference application.js from here 12 | '<%%#= asset_path "application.js" %>', 13 | 14 | '<%%= asset_path "application.css" %>', 15 | 16 | '/offline.html', 17 | 18 | ]); 19 | }) 20 | ); 21 | } 22 | 23 | function onActivate(event) { 24 | console.log('[Serviceworker]', "Rails Service Worker Activating!", event); 25 | event.waitUntil( 26 | caches.keys().then(function(cacheNames) { 27 | return Promise.all( 28 | cacheNames.filter(function(cacheName) { 29 | // Return true if you want to remove this cache, 30 | // but remember that caches are shared across 31 | // the whole origin 32 | return cacheName.indexOf(CACHE_VERSION) !== 0; 33 | }).map(function(cacheName) { 34 | return caches.delete(cacheName); 35 | }) 36 | ); 37 | }) 38 | ); 39 | } 40 | 41 | // Borrowed from https://github.com/TalAter/UpUp 42 | function onFetch(event) { 43 | event.respondWith( 44 | // try to return untouched request from network first 45 | fetch(event.request).catch(function() { 46 | // if it fails, try to return request from the cache 47 | return caches.match(event.request).then(function(response) { 48 | if (response) { 49 | return response; 50 | } 51 | // if not found in cache, return default offline content for navigate requests 52 | if (event.request.mode === 'navigate' || 53 | (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) { 54 | console.log('[Serviceworker]', "Rails Service Worker Fetching offline content", event); 55 | return caches.match('/offline.html'); 56 | } 57 | }) 58 | }) 59 | ); 60 | } 61 | 62 | self.addEventListener('install', onInstall); 63 | self.addEventListener('activate', onActivate); 64 | self.addEventListener('fetch', onFetch); 65 | -------------------------------------------------------------------------------- /lib/generators/serviceworker/templates/serviceworker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | config.serviceworker.routes.draw do 5 | # map to assets implicitly 6 | match "/serviceworker.js" 7 | match "/manifest.json" 8 | 9 | # Examples 10 | # 11 | # map to a named asset explicitly 12 | # match "/proxied-serviceworker.js" => "nested/asset/serviceworker.js" 13 | # match "/nested/serviceworker.js" => "another/serviceworker.js" 14 | # 15 | # capture named path segments and interpolate to asset name 16 | # match "/captures/*segments/serviceworker.js" => "%{segments}/serviceworker.js" 17 | # 18 | # capture named parameter and interpolate to asset name 19 | # match "/parameter/:id/serviceworker.js" => "project/%{id}/serviceworker.js" 20 | # 21 | # insert custom headers 22 | # match "/header-serviceworker.js" => "another/serviceworker.js", 23 | # headers: { "X-Resource-Header" => "A resource" } 24 | # 25 | # anonymous glob exposes `paths` variable for interpolation 26 | # match "/*/serviceworker.js" => "%{paths}/serviceworker.js" 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/service_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "serviceworker" 4 | -------------------------------------------------------------------------------- /lib/serviceworker-rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "serviceworker" 4 | require "serviceworker/rails" 5 | -------------------------------------------------------------------------------- /lib/serviceworker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | Error = Class.new(StandardError) 5 | RouteError = Class.new(Error) 6 | end 7 | 8 | require "serviceworker/route" 9 | require "serviceworker/router" 10 | require "serviceworker/middleware" 11 | -------------------------------------------------------------------------------- /lib/serviceworker/engine.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails" 4 | require "rails/railtie" 5 | require "serviceworker" 6 | 7 | module ServiceWorker 8 | class Engine < ::Rails::Engine 9 | config.serviceworker = ActiveSupport::OrderedOptions.new 10 | config.serviceworker.headers = {} 11 | config.serviceworker.routes = ServiceWorker::Router.new 12 | config.serviceworker.handler = :sprockets 13 | config.serviceworker.icon_sizes = %w[36 48 60 72 76 96 120 144 152 180 192 384 512] 14 | 15 | initializer "serviceworker-rails.configure_rails_initialization", after: :load_config_initializers do 16 | config.serviceworker.logger ||= ::Rails.logger 17 | 18 | if config.respond_to?(:assets) 19 | config.assets.precompile += %w[serviceworker-rails/*.png] 20 | elsif app.config.respond_to?(:assets) 21 | app.config.assets.precompile += %w[serviceworker-rails/*.png] 22 | end 23 | 24 | app.middleware.use ServiceWorker::Middleware, config.serviceworker 25 | end 26 | 27 | def app 28 | ::Rails.application 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/serviceworker/handlers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "serviceworker/handlers/rack_handler" 4 | 5 | module ServiceWorker 6 | module Handlers 7 | extend self 8 | 9 | def build(handler) 10 | resolve_handler(handler) || default_handler 11 | end 12 | 13 | def handler_for_route_match(route_match) 14 | options = route_match.options 15 | return webpacker_handler if Route.webpacker?(options) 16 | return sprockets_handler if Route.sprockets?(options) 17 | 18 | nil 19 | end 20 | 21 | def ===(other) 22 | other.respond_to?(:call) 23 | end 24 | 25 | def handler_for_name(name) 26 | available_handlers = %w[sprockets webpacker rack] 27 | if available_handlers.include?(name.to_s) 28 | send("#{name}_handler") 29 | else 30 | raise ServiceWorker::Error, 31 | "Unknown handler #{name.inspect}. Please use one of #{available_handlers.inspect}" 32 | end 33 | end 34 | 35 | def resolve_handler(handler) 36 | case handler 37 | when Handlers 38 | handler 39 | when Symbol, String 40 | handler_for_name(handler) 41 | end 42 | end 43 | 44 | def webpacker_handler 45 | require "serviceworker/handlers/webpacker_handler" 46 | ServiceWorker::Handlers::WebpackerHandler.new 47 | end 48 | 49 | def sprockets_handler 50 | require "serviceworker/handlers/sprockets_handler" 51 | ServiceWorker::Handlers::SprocketsHandler.new 52 | end 53 | 54 | def rack_handler 55 | ServiceWorker::Handlers::RackHandler.new 56 | end 57 | 58 | def default_handler 59 | if sprockets? 60 | sprockets_handler 61 | else 62 | rack_handler 63 | end 64 | end 65 | 66 | def webpacker? 67 | defined?(::Webpacker) 68 | end 69 | 70 | def sprockets? 71 | defined?(::Rails) && ::Rails.configuration.assets 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/serviceworker/handlers/rack_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | module Handlers 5 | class RackHandler 6 | def initialize(root = Dir.getwd) 7 | @root = root 8 | end 9 | 10 | def call(env) 11 | path_info = env.fetch("serviceworker.asset_name") 12 | 13 | file_server.call(env.merge("PATH_INFO" => path_info)) 14 | end 15 | 16 | def file_path(path_info) 17 | @root.join(path_info) 18 | end 19 | 20 | def file_server 21 | @file_server ||= ::Rack::File.new(@root) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/serviceworker/handlers/sprockets_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rack/file" 4 | 5 | module ServiceWorker 6 | module Handlers 7 | class SprocketsHandler 8 | def call(env) 9 | path_info = env.fetch("serviceworker.asset_name") 10 | 11 | if config.compile 12 | sprockets_server.call(env.merge("PATH_INFO" => path_info)) 13 | else 14 | file_server.call(env.merge("PATH_INFO" => asset_path(path_info))) 15 | end 16 | end 17 | 18 | private 19 | 20 | def sprockets_server 21 | ::Rails.application.assets 22 | end 23 | 24 | def file_server 25 | @file_server ||= ::Rack::File.new(::Rails.public_path) 26 | end 27 | 28 | def config 29 | ::Rails.configuration.assets 30 | end 31 | 32 | def asset_path(path) 33 | if controller_helpers.respond_to?(:compute_asset_path) 34 | controller_helpers.compute_asset_path(path) 35 | else 36 | logical_asset_path(path) 37 | end 38 | end 39 | 40 | def controller_helpers 41 | ::ActionController::Base.helpers 42 | end 43 | 44 | def logical_asset_path(path) 45 | asset_path = controller_helpers.asset_path(path) 46 | uri = URI.parse(asset_path) 47 | uri.host = nil 48 | uri.scheme = nil 49 | uri.to_s 50 | rescue URI::InvalidURIError 51 | asset_path 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/serviceworker/handlers/webpacker_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rack/file" 4 | require "webpacker" 5 | 6 | module ServiceWorker 7 | module Handlers 8 | class WebpackerHandler 9 | def call(env) 10 | path_info = env.fetch("serviceworker.asset_name") 11 | 12 | path = Webpacker.manifest.lookup(path_info) 13 | 14 | if Webpacker.dev_server.running? 15 | proxy = Webpacker::DevServerProxy.new 16 | proxy.call(env.merge("PATH_INFO" => path)) 17 | else 18 | file_server.call(env.merge("PATH_INFO" => path)) 19 | end 20 | end 21 | 22 | private 23 | 24 | def file_server 25 | @file_server ||= ::Rack::File.new(::Rails.public_path) 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/serviceworker/middleware.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "serviceworker/handlers" 4 | 5 | module ServiceWorker 6 | class Middleware 7 | REQUEST_METHOD = "REQUEST_METHOD" 8 | GET = "GET" 9 | HEAD = "HEAD" 10 | 11 | # Initialize the Rack middleware for responding to serviceworker asset 12 | # requests 13 | # 14 | # @app [#call] middleware stack 15 | # @opts [Hash] options to inject 16 | # @param opts [#match_route] :routes matches routes on PATH_INFO 17 | # @param opts [Hash] :headers default headers to use for matched routes 18 | # @param opts [#call] :handler resolves response from matched asset name 19 | # @param opts [#info] :logger logs requests 20 | def initialize(app, opts = {}) 21 | @app = app 22 | @opts = opts 23 | @headers = default_headers.merge(opts.fetch(:headers, {})) 24 | @router = opts.fetch(:routes, ServiceWorker::Router.new) 25 | @handler = Handlers.build(@opts.fetch(:handler, nil)) 26 | end 27 | 28 | def call(env) 29 | case env[REQUEST_METHOD] 30 | when GET, HEAD 31 | route_match = @router.match_route(env) 32 | return respond_to_match(route_match, env) if route_match 33 | end 34 | 35 | @app.call(env) 36 | end 37 | 38 | private 39 | 40 | def default_headers 41 | { 42 | "Cache-Control" => "private, max-age=0, no-cache" 43 | } 44 | end 45 | 46 | def respond_to_match(route_match, env) 47 | env = env.merge("serviceworker.asset_name" => route_match.asset_name) 48 | 49 | status, headers, body = handler_for_route_match(route_match).call(env) 50 | 51 | [status, headers.merge(@headers).merge(route_match.headers), body] 52 | end 53 | 54 | def handler_for_route_match(route_match) 55 | Handlers.handler_for_route_match(route_match) || @handler 56 | end 57 | 58 | def info(msg) 59 | logger.info "[#{self.class}] - #{msg}" 60 | end 61 | 62 | def logger 63 | @logger ||= @opts.fetch(:logger, Logger.new($stdout)) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/serviceworker/rails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | module Rails 5 | end 6 | end 7 | 8 | require "serviceworker/rails/version" 9 | require "serviceworker/engine" 10 | -------------------------------------------------------------------------------- /lib/serviceworker/rails/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | module Rails 5 | VERSION = "0.6.0" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/serviceworker/route.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | class Route 5 | attr_reader :path_pattern, :asset_pattern, :options 6 | 7 | RouteMatch = Struct.new(:path, :asset_name, :headers, :options) { 8 | def to_s 9 | asset_name 10 | end 11 | } 12 | 13 | def self.webpacker?(options) 14 | options.key?(:pack) && Handlers.webpacker? 15 | end 16 | 17 | def self.sprockets?(options) 18 | options.key?(:asset) 19 | end 20 | 21 | def initialize(path_pattern, asset_pattern = nil, options = {}) 22 | if asset_pattern.is_a?(Hash) 23 | options = asset_pattern 24 | asset_pattern = nil 25 | end 26 | 27 | @path_pattern = path_pattern 28 | @asset_pattern = if self.class.webpacker?(options) 29 | asset_pattern || options.fetch(:pack, path_pattern) 30 | else 31 | asset_pattern || options.fetch(:asset, path_pattern) 32 | end 33 | @options = options 34 | end 35 | 36 | def match(path) 37 | raise ArgumentError, "path is required" if path.to_s.strip.empty? 38 | 39 | (asset = resolver.call(path)) || (return nil) 40 | 41 | RouteMatch.new(path, asset, headers, options) 42 | end 43 | 44 | def headers 45 | @options.fetch(:headers, {}) 46 | end 47 | 48 | private 49 | 50 | def resolver 51 | @resolver ||= AssetResolver.new(path_pattern, asset_pattern) 52 | end 53 | 54 | class AssetResolver 55 | PATH_INFO = "PATH_INFO" 56 | DEFAULT_WILDCARD_NAME = :paths 57 | WILDCARD_PATTERN = %r{/\*([^/]*)}.freeze 58 | NAMED_SEGMENTS_PATTERN = %r{/([^/]*):([^:$/]+)}.freeze 59 | LEADING_SLASH_PATTERN = %r{^/}.freeze 60 | INTERPOLATION_PATTERN = Regexp.union( 61 | /%%/, 62 | /%\{(\w+)\}/ # matches placeholders like "%{foo}" 63 | ) 64 | 65 | attr_reader :path_pattern, :asset_pattern 66 | 67 | def initialize(path_pattern, asset_pattern) 68 | @path_pattern = path_pattern 69 | @asset_pattern = asset_pattern 70 | end 71 | 72 | def call(path) 73 | raise ArgumentError, "path is required" if path.to_s.strip.empty? 74 | 75 | (captures = path_captures(regexp, path)) || (return nil) 76 | 77 | interpolate_captures(asset_pattern, captures) 78 | end 79 | 80 | private 81 | 82 | def regexp 83 | @regexp ||= compile_regexp(path_pattern) 84 | end 85 | 86 | def compile_regexp(pattern) 87 | Regexp.new("\\A#{compiled_source(pattern)}\\Z") 88 | end 89 | 90 | def compiled_source(pattern) 91 | @wildcard_name = nil 92 | pattern_match = pattern.match(WILDCARD_PATTERN) 93 | if pattern_match 94 | @wildcard_name = if pattern_match[1].to_s.strip.empty? 95 | DEFAULT_WILDCARD_NAME 96 | else 97 | pattern_match[1].to_sym 98 | end 99 | pattern.gsub(WILDCARD_PATTERN, "(?:/(.*)|)") 100 | else 101 | p = if pattern.match(NAMED_SEGMENTS_PATTERN) 102 | pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^.$/]+)') 103 | else 104 | pattern 105 | end 106 | p + '(?:\.(?.*))?' 107 | end 108 | end 109 | 110 | def path_captures(regexp, path) 111 | (path_match = path.match(regexp)) || (return nil) 112 | params = if @wildcard_name 113 | {@wildcard_name => path_match[1].to_s.split("/")} 114 | else 115 | Hash[path_match.names.map(&:to_sym).zip(path_match.captures)] 116 | end 117 | params.delete(:format) if params.key?(:format) && params[:format].nil? 118 | params 119 | end 120 | 121 | def interpolate_captures(string, captures) 122 | string.gsub(INTERPOLATION_PATTERN) { |match| 123 | if match == "%%" 124 | "%" 125 | else 126 | key = (Regexp.last_match(1) || Regexp.last_match(2)).to_sym 127 | value = captures.key?(key) ? Array(captures[key]).join("/") : key 128 | value = value.call(captures) if value.respond_to?(:call) 129 | Regexp.last_match(3) ? format("%#{Regexp.last_match(3)}", value) : value 130 | end 131 | }.gsub(LEADING_SLASH_PATTERN, "") 132 | end 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/serviceworker/router.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ServiceWorker 4 | class Router 5 | PATH_INFO = "PATH_INFO" 6 | 7 | def self.default 8 | new.draw_default 9 | end 10 | 11 | attr_reader :routes 12 | 13 | def initialize 14 | @routes = [] 15 | 16 | draw(&Proc.new) if block_given? 17 | end 18 | 19 | def draw(&block) 20 | return self unless block 21 | 22 | if block.arity == 1 23 | yield(self) 24 | else 25 | instance_eval(&block) 26 | end 27 | 28 | self 29 | end 30 | 31 | def draw_default 32 | draw { get "/serviceworker.js" } 33 | end 34 | 35 | def match(path, *args) 36 | if path.is_a?(Hash) 37 | opts = path.to_a 38 | path, asset = opts.shift 39 | args = [asset, opts.to_h] 40 | end 41 | 42 | Route.new(path, *args).tap do |route| 43 | @routes << route 44 | end 45 | end 46 | alias_method :get, :match 47 | 48 | def any? 49 | @routes.any? 50 | end 51 | 52 | def match_route(env) 53 | path = env[PATH_INFO] 54 | @routes.lazy.map { |route| route.match(path) }.detect(&:itself) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /serviceworker-rails.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "serviceworker/rails/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "serviceworker-rails" 9 | spec.version = ServiceWorker::Rails::VERSION 10 | spec.authors = ["Ross Kaffenberger"] 11 | spec.email = ["rosskaff@gmail.com"] 12 | 13 | spec.summary = "ServiceWorker for Rails 3+" 14 | spec.description = "Integrates ServiceWorker into the Rails asset pipeline." 15 | spec.homepage = "https://github.com/rossta/serviceworker-rails" 16 | spec.license = "MIT" 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | spec.bindir = "exe" 20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_dependency "railties", [">= 3.1"] 24 | 25 | spec.add_development_dependency "appraisal", "~> 2.3.0" 26 | spec.add_development_dependency "minitest", "~> 5.0" 27 | spec.add_development_dependency "rack-test" 28 | spec.add_development_dependency "rails" 29 | spec.add_development_dependency "sprockets", "~> 3.0" 30 | spec.add_development_dependency "rake" 31 | spec.add_development_dependency "standard" 32 | spec.add_development_dependency "simplecov" 33 | spec.add_development_dependency "sprockets-rails" 34 | end 35 | -------------------------------------------------------------------------------- /test/sample/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": "> 1%", 7 | "uglify": true 8 | }, 9 | "useBuiltIns": true 10 | }] 11 | ], 12 | 13 | "plugins": [ 14 | "syntax-dynamic-import", 15 | "transform-object-rest-spread", 16 | ["transform-class-properties", { "spec": true }] 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/sample/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | !/log/.keep 13 | /tmp 14 | /public/packs 15 | /public/packs-test 16 | /node_modules 17 | yarn-debug.log* 18 | .yarn-integrity 19 | -------------------------------------------------------------------------------- /test/sample/.postcssrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | postcss-import: {} 3 | postcss-cssnext: {} 4 | -------------------------------------------------------------------------------- /test/sample/Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("config/application", __dir__) 4 | 5 | Sample::Application.load_tasks 6 | -------------------------------------------------------------------------------- /test/sample/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/app/assets/images/.keep -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/another/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Another ServiceWorker!'); 2 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require_tree . 14 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/captures/bar-serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Bar ServiceWorker!'); 2 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/captures/foo-serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Foo ServiceWorker!'); 2 | 3 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/fallback/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Fallback ServiceWorker!'); 2 | 3 | self.addEventListener('fetch', function(event) { 4 | console.log('SW:', 'fetching', event.request.url); 5 | return; 6 | }); 7 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/headers/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Header ServiceWorker!'); 2 | -------------------------------------------------------------------------------- /test/sample/app/assets/javascripts/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from ServiceWorker!'); 2 | 3 | self.addEventListener('fetch', function(event) { 4 | console.log('SW:', 'fetching', event.request.url); 5 | return; 6 | }); 7 | -------------------------------------------------------------------------------- /test/sample/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /test/sample/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | # Prevent CSRF attacks by raising an exception. 5 | # For APIs, you may want to use :null_session instead. 6 | protect_from_forgery with: :exception 7 | end 8 | -------------------------------------------------------------------------------- /test/sample/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/sample/app/controllers/welcome_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class WelcomeController < ApplicationController 4 | def index 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/sample/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /test/sample/app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/javascript and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | console.log('Hello World from Webpacker') 11 | -------------------------------------------------------------------------------- /test/sample/app/javascript/packs/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Webpack ServiceWorker!'); 2 | -------------------------------------------------------------------------------- /test/sample/app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/app/mailers/.keep -------------------------------------------------------------------------------- /test/sample/app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/app/models/.keep -------------------------------------------------------------------------------- /test/sample/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/sample/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/sample/app/views/welcome/index.html.erb: -------------------------------------------------------------------------------- 1 |

Hello, World!

2 | -------------------------------------------------------------------------------- /test/sample/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 5 | load Gem.bin_path("bundler", "bundle") 6 | -------------------------------------------------------------------------------- /test/sample/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_PATH = File.expand_path("../config/application", __dir__) 5 | require_relative "../config/boot" 6 | require "rails/commands" 7 | -------------------------------------------------------------------------------- /test/sample/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require_relative "../config/boot" 5 | require "rake" 6 | Rake.application.run 7 | -------------------------------------------------------------------------------- /test/sample/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "pathname" 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path("..", __dir__) 8 | 9 | Dir.chdir APP_ROOT do 10 | # This script is a starting point to setup your application. 11 | # Add necessary setup steps to this file: 12 | 13 | puts "== Installing dependencies ==" 14 | system "gem install bundler --conservative" 15 | system "bundle check || bundle install" 16 | 17 | # puts "\n== Copying sample files ==" 18 | # unless File.exist?("config/database.yml") 19 | # system "cp config/database.yml.sample config/database.yml" 20 | # end 21 | 22 | puts "\n== Preparing database ==" 23 | system "bin/rake db:setup" 24 | 25 | puts "\n== Removing old logs and tempfiles ==" 26 | system "rm -f log/*" 27 | system "rm -rf tmp/cache" 28 | 29 | puts "\n== Restarting application server ==" 30 | system "touch tmp/restart.txt" 31 | end 32 | -------------------------------------------------------------------------------- /test/sample/bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 5 | ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" 6 | 7 | require "pathname" 8 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 9 | Pathname.new(__FILE__).realpath) 10 | 11 | require "rubygems" 12 | require "bundler/setup" 13 | 14 | require "webpacker" 15 | require "webpacker/webpack_runner" 16 | Webpacker::WebpackRunner.run(ARGV) 17 | -------------------------------------------------------------------------------- /test/sample/bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 5 | ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" 6 | 7 | require "pathname" 8 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 9 | Pathname.new(__FILE__).realpath) 10 | 11 | require "rubygems" 12 | require "bundler/setup" 13 | 14 | require "webpacker" 15 | require "webpacker/dev_server_runner" 16 | Webpacker::DevServerRunner.run(ARGV) 17 | -------------------------------------------------------------------------------- /test/sample/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require ::File.expand_path("../config/environment", __FILE__) 6 | run Rails.application 7 | -------------------------------------------------------------------------------- /test/sample/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path("boot", __dir__) 4 | 5 | require "rails" 6 | # Pick the frameworks you want: 7 | require "action_controller/railtie" 8 | require "action_view/railtie" 9 | # require "sprockets/railtie" 10 | require "rails/test_unit/railtie" 11 | require "sprockets/railtie" 12 | 13 | Bundler.require(*Rails.groups) 14 | 15 | require "serviceworker/rails" 16 | 17 | module Sample 18 | class Application < Rails::Application 19 | # Settings in config/environments/* take precedence over those specified here. 20 | # Application configuration should go into files in config/initializers 21 | # -- all .rb files in that directory are automatically loaded. 22 | 23 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 24 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 25 | # config.time_zone = 'Central Time (US & Canada)' 26 | 27 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 28 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 29 | # config.i18n.default_locale = :de 30 | 31 | # Do not swallow errors in after_commit/after_rollback callbacks. 32 | # config.active_record.raise_in_transactional_callbacks = true 33 | 34 | config.serviceworker.headers["X-Custom-Header"] = "foobar" 35 | 36 | config.assets.enabled = true unless config.assets.enabled? 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/sample/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 5 | 6 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 7 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 8 | -------------------------------------------------------------------------------- /test/sample/config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: 5 23 | 24 | development: 25 | <<: *default 26 | database: sample_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: sample 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: sample_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: sample_production 84 | username: sample 85 | password: <%= ENV['SAMPLE_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /test/sample/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require File.expand_path("application", __dir__) 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /test/sample/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Sample::Application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports and disable caching. 15 | config.consider_all_requests_local = true 16 | config.action_controller.perform_caching = false 17 | 18 | # Don't care if the mailer can't send. 19 | # config.action_mailer.raise_delivery_errors = false 20 | 21 | # Print deprecation notices to the Rails logger. 22 | config.active_support.deprecation = :log 23 | 24 | # Raise an error on page load if there are pending migrations. 25 | # config.active_record.migration_error = :page_load 26 | 27 | # Debug mode disables concatenation and preprocessing of assets. 28 | # This option may cause significant delays in view rendering with a large 29 | # number of complex assets. 30 | config.assets.debug = true 31 | 32 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 33 | # yet still be able to expire them through the digest params. 34 | config.assets.digest = true 35 | 36 | # Adds additional error checking when serving assets at runtime. 37 | # Checks for improperly declared sprockets dependencies. 38 | # Raises helpful error messages. 39 | config.assets.raise_runtime_errors = true 40 | 41 | # Raises error for missing translations 42 | # config.action_view.raise_on_missing_translations = true 43 | end 44 | -------------------------------------------------------------------------------- /test/sample/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Sample::Application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 20 | # Add `rack-cache` to your Gemfile before enabling this. 21 | # For large-scale production use, consider using a caching reverse proxy like 22 | # NGINX, varnish or squid. 23 | # config.action_dispatch.rack_cache = true 24 | 25 | # Disable serving static files from the `/public` folder by default since 26 | # Apache or NGINX already handles this. 27 | config.serve_static_files = ENV["RAILS_SERVE_STATIC_FILES"].present? 28 | 29 | # Compress JavaScripts and CSS. 30 | config.assets.js_compressor = :uglifier 31 | # config.assets.css_compressor = :sass 32 | 33 | # Do not fallback to assets pipeline if a precompiled asset is missed. 34 | config.assets.compile = false 35 | 36 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 37 | # yet still be able to expire them through the digest params. 38 | config.assets.digest = true 39 | 40 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 41 | 42 | # Specifies the header that your server uses for sending files. 43 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 44 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 45 | 46 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 47 | # config.force_ssl = true 48 | 49 | # Use the lowest log level to ensure availability of diagnostic information 50 | # when problems arise. 51 | config.log_level = :debug 52 | 53 | # Prepend all log lines with the following tags. 54 | # config.log_tags = [ :subdomain, :uuid ] 55 | 56 | # Use a different logger for distributed setups. 57 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 58 | 59 | # Use a different cache store in production. 60 | # config.cache_store = :mem_cache_store 61 | 62 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 63 | # config.action_controller.asset_host = 'http://assets.example.com' 64 | 65 | # Ignore bad email addresses and do not raise email delivery errors. 66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 67 | # config.action_mailer.raise_delivery_errors = false 68 | 69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 70 | # the I18n.default_locale when a translation cannot be found). 71 | config.i18n.fallbacks = true 72 | 73 | # Send deprecation notices to registered listeners. 74 | config.active_support.deprecation = :notify 75 | 76 | # Use default logging formatter so that PID and timestamp are not suppressed. 77 | config.log_formatter = ::Logger::Formatter.new 78 | 79 | # Do not dump schema after migrations. 80 | # config.active_record.dump_schema_after_migration = false 81 | end 82 | -------------------------------------------------------------------------------- /test/sample/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TestAssetHost 4 | cattr_accessor :host 5 | 6 | def self.proc_method 7 | proc { TestAssetHost.host } 8 | end 9 | end 10 | 11 | Sample::Application.configure do 12 | # Settings specified here will take precedence over those in config/application.rb. 13 | 14 | # The test environment is used exclusively to run your application's 15 | # test suite. You never need to work with it otherwise. Remember that 16 | # your test database is "scratch space" for the test suite and is wiped 17 | # and recreated between test runs. Don't rely on the data there! 18 | config.cache_classes = true 19 | 20 | # Do not eager load code on boot. This avoids loading your whole application 21 | # just for the purpose of running a single test. If you are using a tool that 22 | # preloads Rails for running tests, you may have to set it to true. 23 | config.eager_load = false 24 | 25 | # Configure static file server for tests with Cache-Control for performance. 26 | if config.respond_to?(:public_file_server) 27 | config.public_file_server.enabled = true 28 | config.public_file_server.headers = {"Cache-Control" => "public, max-age=3600"} 29 | else 30 | config.serve_static_files = true 31 | config.static_cache_control = "public, max-age=3600" 32 | end 33 | 34 | # Show full error reports and disable caching. 35 | config.consider_all_requests_local = true 36 | config.action_controller.perform_caching = false 37 | 38 | # Raise exceptions instead of rendering exception templates. 39 | config.action_dispatch.show_exceptions = false 40 | 41 | # Disable request forgery protection in test environment. 42 | config.action_controller.allow_forgery_protection = false 43 | 44 | # Tell Action Mailer not to deliver emails to the real world. 45 | # The :test delivery method accumulates sent emails in the 46 | # ActionMailer::Base.deliveries array. 47 | # config.action_mailer.delivery_method = :test 48 | 49 | # Randomize the order test cases are executed. 50 | config.active_support.test_order = :random 51 | 52 | # Print deprecation notices to the stderr. 53 | config.active_support.deprecation = :stderr 54 | 55 | # Raises error for missing translations 56 | # config.action_view.raise_on_missing_translations = true 57 | 58 | # Debug mode disables concatenation and preprocessing of assets. 59 | # This option may cause significant delays in view rendering with a large 60 | # number of complex assets. 61 | config.assets.debug = true 62 | 63 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 64 | # yet still be able to expire them through the digest params. 65 | config.assets.digest = false 66 | 67 | # Adds additional error checking when serving assets at runtime. 68 | # Checks for improperly declared sprockets dependencies. 69 | # Raises helpful error messages. 70 | config.assets.raise_runtime_errors = true 71 | 72 | config.action_controller.asset_host = TestAssetHost.proc_method 73 | end 74 | -------------------------------------------------------------------------------- /test/sample/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = "1.0" 7 | 8 | # Add additional assets to the asset load path 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 13 | # Rails.application.config.assets.precompile += %w( search.js ) 14 | Rails.application.config.assets.precompile += %w[serviceworker.js] 15 | -------------------------------------------------------------------------------- /test/sample/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 7 | 8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 9 | # Rails.backtrace_cleaner.remove_silencers! 10 | -------------------------------------------------------------------------------- /test/sample/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /test/sample/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += [:password] 7 | -------------------------------------------------------------------------------- /test/sample/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new inflection rules using the following format. Inflections 6 | # are locale specific, and you may define rules for as many different 7 | # locales as you wish. All of these examples are active by default: 8 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 9 | # inflect.plural /^(ox)$/i, '\1en' 10 | # inflect.singular /^(ox)en/i, '\1' 11 | # inflect.irregular 'person', 'people' 12 | # inflect.uncountable %w( fish sheep ) 13 | # end 14 | 15 | # These inflection rules are supported but not enabled by default: 16 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 17 | # inflect.acronym 'RESTful' 18 | # end 19 | -------------------------------------------------------------------------------- /test/sample/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Add new mime types for use in respond_to blocks: 6 | # Mime::Type.register "text/richtext", :rtf 7 | -------------------------------------------------------------------------------- /test/sample/config/initializers/serviceworker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Sample::Application.configure do 4 | config.serviceworker.routes.draw_default 5 | 6 | config.serviceworker.routes.draw do 7 | match "/header-serviceworker.js" => "another/serviceworker.js", 8 | :headers => {"X-Resource-Header" => "A resource"} 9 | 10 | match "/nested/serviceworker.js", asset: "another/serviceworker.js" 11 | 12 | match "/captures/*named/serviceworker.js" => "captures/%{named}-serviceworker.js" 13 | 14 | get "/*/serviceworker.js" => "fallback/serviceworker.js" 15 | 16 | get "/webpack-serviceworker.js", pack: "serviceworker.js" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/sample/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Rails.application.config.session_store :cookie_store, key: "_sample_session" 6 | Rails.application.config.secret_token = "some secret phrase of at least 30 characters" 7 | -------------------------------------------------------------------------------- /test/sample/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /test/sample/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /test/sample/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | root to: "welcome#index" 5 | end 6 | -------------------------------------------------------------------------------- /test/sample/config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: d3a686fc3efe730cdf330be8f1aef2c43a5e78054e54bbd4e938356db8666134d2e751fbbd036ad5a5aa0c255634496fc167a7f2bc73d5c7c39e115403a282c7 15 | 16 | test: 17 | secret_key_base: 7ef584ccca145f6b559e63ba1203e499a15a6d30a0cfecb1faf9ea17bad79c95c01d90238fc2cebe2fdb0251a6719b003e57948567c86afe4b2b72acaefe057b 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /test/sample/config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /test/sample/config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | module.exports = environment 4 | -------------------------------------------------------------------------------- /test/sample/config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /test/sample/config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /test/sample/config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_output_path: packs 7 | cache_path: tmp/cache/webpacker 8 | 9 | # Additional paths webpack should lookup modules 10 | # ['app/assets', 'engine/foo/app/assets'] 11 | resolved_paths: [] 12 | 13 | # Reload manifest.json on all requests so we reload latest compiled packs 14 | cache_manifest: false 15 | 16 | extensions: 17 | - .js 18 | - .sass 19 | - .scss 20 | - .css 21 | - .module.sass 22 | - .module.scss 23 | - .module.css 24 | - .png 25 | - .svg 26 | - .gif 27 | - .jpeg 28 | - .jpg 29 | 30 | development: 31 | <<: *default 32 | compile: true 33 | 34 | # Reference: https://webpack.js.org/configuration/dev-server/ 35 | dev_server: 36 | https: false 37 | host: localhost 38 | port: 3035 39 | public: localhost:3035 40 | hmr: false 41 | # Inline should be set to true if using HMR 42 | inline: true 43 | overlay: true 44 | compress: true 45 | disable_host_check: true 46 | use_local_ip: false 47 | quiet: false 48 | headers: 49 | 'Access-Control-Allow-Origin': '*' 50 | watch_options: 51 | ignored: /node_modules/ 52 | 53 | 54 | test: 55 | <<: *default 56 | compile: true 57 | 58 | # Compile test packs to a separate directory 59 | public_output_path: packs-test 60 | 61 | production: 62 | <<: *default 63 | 64 | # Production depends on precompilation of packs prior to booting for performance. 65 | compile: false 66 | 67 | # Cache manifest.json for performance 68 | cache_manifest: true 69 | -------------------------------------------------------------------------------- /test/sample/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/lib/assets/.keep -------------------------------------------------------------------------------- /test/sample/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/log/.keep -------------------------------------------------------------------------------- /test/sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@rails/webpacker": "3.5.5" 4 | }, 5 | "devDependencies": { 6 | "webpack-dev-server": "3.7.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/sample/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/sample/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/sample/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/sample/public/assets/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from ServiceWorker!'); 2 | 3 | self.addEventListener('fetch', function(event) { 4 | console.log('SW:', 'fetching', event.request.url); 5 | return; 6 | }); 7 | -------------------------------------------------------------------------------- /test/sample/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rossta/serviceworker-rails/a81aa1f138716e4eda8c1241c8b8152c1b1bda9c/test/sample/public/favicon.ico -------------------------------------------------------------------------------- /test/serviceworker/handlers_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::HandlersTest < Minitest::Test 6 | def test_build_handler 7 | handler_given = -> { [200, {}, "console.log('Foobar!');"] } 8 | handler = ServiceWorker::Handlers.build(handler_given) 9 | 10 | assert_equal handler_given, handler 11 | end 12 | 13 | def test_build_sprockets_handler 14 | handler = ServiceWorker::Handlers.build(:sprockets) 15 | 16 | assert handler.is_a?(ServiceWorker::Handlers::SprocketsHandler) 17 | end 18 | 19 | def test_build_webpacker_handler 20 | return true unless defined?(::Webpacker) 21 | 22 | handler = ServiceWorker::Handlers.build(:webpacker) 23 | 24 | assert handler.is_a?(ServiceWorker::Handlers::WebpackerHandler) 25 | end 26 | 27 | def test_build_rack_handler 28 | handler = ServiceWorker::Handlers.build(:rack) 29 | 30 | assert handler.is_a?(ServiceWorker::Handlers::RackHandler) 31 | end 32 | 33 | def test_build_unknown_handler 34 | assert_raises ServiceWorker::Error do 35 | ServiceWorker::Handlers.build(:unknown) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/serviceworker/install_generator_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | require "generators/serviceworker/install_generator" 5 | 6 | class ServiceWorker::InstallGeneratorTest < ::Rails::Generators::TestCase 7 | include GeneratorTestHelpers 8 | 9 | class_attribute :install_destination 10 | 11 | tests Serviceworker::Generators::InstallGenerator 12 | destination File.expand_path("../tmp", File.dirname(__FILE__)) 13 | 14 | remove_generator_sample_app 15 | create_generator_sample_app 16 | 17 | Minitest.after_run do 18 | remove_generator_sample_app 19 | end 20 | 21 | setup do 22 | run_generator 23 | end 24 | 25 | test "generates serviceworker" do 26 | assert_file "app/assets/javascripts/serviceworker.js.erb" do |content| 27 | assert_match(/self.addEventListener\('install', onInstall\)/, content) 28 | end 29 | end 30 | 31 | test "generates web app manifest" do 32 | assert_file "app/assets/javascripts/manifest.json.erb" do |content| 33 | assert_match(/"name": "My Progressive Rails App"/, content) 34 | 35 | json = JSON.parse(evaluate_erb_asset_template(content)) 36 | 37 | assert_equal json["name"], "My Progressive Rails App" 38 | assert_equal json["icons"].length, ::Rails.configuration.serviceworker.icon_sizes.length 39 | end 40 | end 41 | 42 | test "generates companion javascript" do 43 | assert_file "app/assets/javascripts/serviceworker-companion.js" do |content| 44 | assert_match(/navigator.serviceWorker./, content) 45 | end 46 | 47 | assert_file "app/assets/javascripts/application.js" do |content| 48 | assert_match(%r{\n//= require serviceworker-companion}, content) 49 | end 50 | end 51 | 52 | test "generates initializer and precompiles assets" do 53 | assert_file "config/initializers/serviceworker.rb" do |content| 54 | assert_match(/config.serviceworker.routes.draw/, content) 55 | end 56 | 57 | assert_file "config/initializers/assets.rb" do |content| 58 | matcher = /Rails.configuration.assets.precompile \+= %w\[serviceworker.js manifest.json\]/ 59 | assert_match(matcher, content) 60 | end 61 | end 62 | 63 | test "appends manifest link" do 64 | assert_file "app/views/layouts/application.html.erb" do |content| 65 | assert_match(%r{}, content) 66 | assert_match(//, content) 67 | end 68 | end 69 | 70 | test "generates offline html" do 71 | assert_file "public/offline.html" 72 | end 73 | 74 | test "missing application layout does not error" do 75 | dir = File.expand_path("../tmp", File.dirname(__FILE__)) 76 | system "mv #{dir}/app/views/layouts/application.html.erb #{dir}/app/views/layouts/application.tmp" 77 | run_generator 78 | system "mv #{dir}/app/views/layouts/application.tmp #{dir}/app/views/layouts/application.html.erb" 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/serviceworker/rack_integration_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::RackIntegrationTest < Minitest::Test 6 | include Rack::Test::Methods 7 | 8 | def handler 9 | ServiceWorker::Handlers::RackHandler.new(Pathname.new(Dir.pwd).join("test/static")) 10 | end 11 | 12 | def router 13 | ServiceWorker::Router.new do 14 | match "/serviceworker.js" => "assets/serviceworker.js" 15 | match "/cacheable-serviceworker.js" => "assets/serviceworker.js", 16 | :headers => {"Cache-Control" => "public, max-age=12345"} 17 | end 18 | end 19 | 20 | def app 21 | config = { 22 | routes: router, 23 | handler: handler 24 | } 25 | Rack::Builder.new do 26 | map "/" do 27 | use ServiceWorker::Middleware, config 28 | run ->(_env) { [200, {"Content-Type" => "text/plain"}, ["OK"]] } 29 | end 30 | end 31 | end 32 | 33 | def test_serviceworker_route 34 | get "/serviceworker.js" 35 | 36 | assert last_response.ok?, "Expected a 200 response but got a #{last_response.status}" 37 | assert_equal "application/javascript", last_response.headers["Content-Type"] 38 | assert_equal "private, max-age=0, no-cache", last_response.headers["Cache-Control"] 39 | assert_match(/console.log\(.*'Hello from Rack ServiceWorker!'.*\);/, last_response.body) 40 | end 41 | 42 | def test_cacheable_route 43 | get "/cacheable-serviceworker.js" 44 | 45 | assert_equal "public, max-age=12345", last_response.headers["Cache-Control"] 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/serviceworker/rails_integration_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::RailsIntegrationTest < Minitest::Test 6 | include Rack::Test::Methods 7 | 8 | def app 9 | ::Rails.application 10 | end 11 | 12 | def setup 13 | get "/" 14 | end 15 | 16 | def teardown 17 | TestAssetHost.host = nil 18 | end 19 | 20 | def test_homepage 21 | assert last_response.ok? 22 | assert_match(/Hello, World/, last_response.body) 23 | end 24 | 25 | def test_serviceworker_route 26 | get "/serviceworker.js" 27 | 28 | assert last_response.ok? 29 | assert_equal "application/javascript", last_response.headers["Content-Type"] 30 | assert_equal "private, max-age=0, no-cache", last_response.headers["Cache-Control"] 31 | assert_match(/console.log\(.*'Hello from ServiceWorker!'.*\);/, last_response.body) 32 | end 33 | 34 | def test_custom_header 35 | get "/serviceworker.js" 36 | 37 | assert_equal "foobar", last_response.headers["X-Custom-Header"] 38 | end 39 | 40 | def test_nested_serviceworker_route 41 | get "/nested/serviceworker.js" 42 | 43 | assert last_response.ok? 44 | assert_match(/console.log\(.*'Hello from Another ServiceWorker!'.*\);/, last_response.body) 45 | end 46 | 47 | def test_inline_header_serviceworker_route 48 | get "/header-serviceworker.js" 49 | 50 | assert last_response.ok? 51 | assert_match(/console.log\(.*'Hello from Another ServiceWorker!'.*\);/, last_response.body) 52 | end 53 | 54 | def test_captured_serviceworker 55 | get "/captures/foo/serviceworker.js" 56 | 57 | assert last_response.ok? 58 | assert_match(/console.log\(.*'Hello from Foo ServiceWorker!'.*\);/, last_response.body) 59 | 60 | get "/captures/bar/serviceworker.js" 61 | 62 | assert last_response.ok? 63 | assert_match(/console.log\(.*'Hello from Bar ServiceWorker!'.*\);/, last_response.body) 64 | 65 | assert_raises ActionController::RoutingError do 66 | get "/captures/foobar/service/worker.js" 67 | end 68 | end 69 | 70 | def test_globbed_serviceworker_proxy 71 | get "/catchall/serviceworker.js" 72 | 73 | assert last_response.ok? 74 | assert_match(/console.log\(.*'Hello from Fallback ServiceWorker!'.*\);/, last_response.body) 75 | end 76 | 77 | def test_not_found_serviceworker_proxy 78 | assert_raises ActionController::RoutingError do 79 | get "/not/found/service/worker.js" 80 | end 81 | end 82 | 83 | def test_precompiled_serviceworker_request 84 | Rails.application.config.assets.stub(:compile, false) do 85 | get "/serviceworker.js" 86 | 87 | assert last_response.ok? 88 | assert_equal "application/javascript", last_response.headers["Content-Type"] 89 | assert_equal "private, max-age=0, no-cache", last_response.headers["Cache-Control"] 90 | assert_match(/console.log\(.*'Hello from ServiceWorker!'.*\);/, last_response.body) 91 | end 92 | end 93 | 94 | def test_cdn_asset_host 95 | TestAssetHost.host = "https://assets.example.com" 96 | Rails.application.config.assets.stub(:compile, false) do 97 | get "/serviceworker.js" 98 | 99 | assert last_response.ok? 100 | end 101 | end 102 | 103 | def test_webpacker_serviceworker 104 | if defined?(Webpacker) 105 | get "/webpack-serviceworker.js" 106 | assert last_response.ok? 107 | assert_match(/console.log\(.*Hello from Webpack ServiceWorker!.*\);/, last_response.body) 108 | else 109 | assert_raises ActionController::RoutingError do 110 | get "/webpack-serviceworker.js" 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /test/serviceworker/rails_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::RailsTest < Minitest::Test 6 | def test_that_it_has_a_version_number 7 | assert ::ServiceWorker::Rails::VERSION 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/serviceworker/route_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::RouteTest < Minitest::Test 6 | def test_initialize_route 7 | route = new_route("/path", foo: "bar") 8 | 9 | assert_equal "/path", route.path_pattern 10 | assert_equal({foo: "bar"}, route.options) 11 | end 12 | 13 | def test_initialize_route_with_asset 14 | route = new_route("/path", "asset", foo: "bar") 15 | 16 | assert_equal "/path", route.path_pattern 17 | assert_equal "asset", route.asset_pattern 18 | assert_equal({foo: "bar"}, route.options) 19 | end 20 | 21 | def test_match 22 | match "/*", "foo", "/", "foo" 23 | match "/*", "%{paths}", "/", "" 24 | match "/*", "foo", "/foo", "foo" 25 | match "/*", "%{paths}", "/foo", "foo" 26 | match "/*", "foo", "/foo/bar/baz", "foo" 27 | match "/*", "%{paths}", "/foo/bar/baz", "foo/bar/baz" 28 | match "/*/foobar.js", "foobar.js", "/not/found/foo/bar.js", nil 29 | match "/*/foobar.js", "foobar.js", "/is/found/foobar.js", "foobar.js" 30 | 31 | match "/*stuff", "foo", "/", "foo" 32 | match "/*stuff", "%{stuff}/foo", "/", "foo" 33 | match "/*stuff", "%{stuff}/bar", "/foo", "foo/bar" 34 | match "/*stuff", "%{stuff}/bar", "/foo/", "foo/bar" 35 | match "/*stuff", "%{stuff}/boo", "/foo/bar/baz", "foo/bar/baz/boo" 36 | 37 | match "/foo/*", "%{paths}", "/foo", "" 38 | match "/foo/*", "%{paths}", "/foo/bar", "bar" 39 | match "/foo/*", "%{paths}", "/foo/bar/baz", "bar/baz" 40 | match "/foo/*stuff", "%{stuff}", "/", nil 41 | match "/foo/*stuff", "%{stuff}", "/foo", "" 42 | match "/foo/*stuff", "%{stuff}", "/foo/bar/baz", "bar/baz" 43 | match "/", "", "/", "" 44 | match "/", "", "/foo", nil 45 | match "/foo", "", "/", nil 46 | match "/foo", "", "/foo", "" 47 | match "/:id", nil, "/42", ":id" 48 | match "/:id", nil, "/", nil 49 | match "/posts/:id", "%{id}", "/posts/42", "42" 50 | match "/posts/:id", "%{id}", "/posts", nil 51 | match "/:x/:y", "%{x}/%{y}", "/a/b", "a/b" 52 | match "/posts/:id", "%{id}.js", "/posts/42.js", "42.js" 53 | match "/api/v:version", "%{version}", "/api/v2", "2" 54 | match "/api/v:version/things", "%{version}", "/api/v2/things", "2" 55 | end 56 | 57 | def test_match_route_pack_true 58 | ServiceWorker::Handlers.stub(:webpacker?, true) do 59 | route = new_route("foo", "bar", pack: true) 60 | 61 | assert_equal "bar", route.match("foo").to_s 62 | end 63 | end 64 | 65 | def test_match_route_pack_asset_name 66 | ServiceWorker::Handlers.stub(:webpacker?, true) do 67 | route = new_route("foo", pack: "bar") 68 | 69 | assert_equal "bar", route.match("foo").to_s 70 | end 71 | end 72 | 73 | def match(path_pattern, asset_pattern, path_name, asset_name) 74 | msg = "#{caller(1..1).first} expected route #{path_pattern} to " 75 | 76 | route = new_route(path_pattern, asset_pattern) 77 | 78 | if asset_name 79 | msg += "match #{path_name} and return #{asset_name}" 80 | assert_equal(asset_name, route.match(path_name).to_s, msg) 81 | else 82 | msg += "no match #{path_name}" 83 | assert_nil(route.match(path_name), msg) 84 | end 85 | end 86 | 87 | def new_route(*args) 88 | ServiceWorker::Route.new(*args) 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /test/serviceworker/router_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class ServiceWorker::RouterTest < Minitest::Test 6 | def setup 7 | @router = ServiceWorker::Router.new 8 | end 9 | 10 | def test_router_empty_routes 11 | assert_equal @router.routes, [] 12 | refute @router.any? 13 | end 14 | 15 | def test_match_adds_route 16 | route = @router.match("/path", foo: "bar") 17 | 18 | assert_equal "/path", route.path_pattern 19 | assert_equal({foo: "bar"}, route.options) 20 | end 21 | 22 | def test_get_aliased_to_match 23 | route = @router.get("/path", foo: "bar") 24 | 25 | assert_equal "/path", route.path_pattern 26 | assert_equal({foo: "bar"}, route.options) 27 | end 28 | 29 | def test_match_adds_route_with_asset_mapping 30 | route = @router.match("/path" => "foo.js", :foo => "bar") 31 | 32 | assert_equal "/path", route.path_pattern 33 | assert_equal "foo.js", route.asset_pattern 34 | assert_equal({foo: "bar"}, route.options) 35 | end 36 | 37 | def test_draw_adds_given_routes 38 | @router.draw do 39 | match "/foo" 40 | match "/bar" 41 | end 42 | paths = @router.routes.map(&:path_pattern) 43 | 44 | assert_equal paths, ["/foo", "/bar"] 45 | end 46 | 47 | def test_draw_default 48 | @router.draw_default 49 | paths = @router.routes.map(&:path_pattern) 50 | 51 | assert_equal paths, ["/serviceworker.js"] 52 | end 53 | 54 | def test_match_route_matches 55 | @router.draw do 56 | match "/foo" 57 | match "/bar" 58 | end 59 | 60 | path, asset_name, headers, _ = @router.match_route("PATH_INFO" => "/foo").to_a 61 | assert_equal [path, asset_name, headers], ["/foo", "foo", {}] 62 | path, asset_name, headers, _ = @router.match_route("PATH_INFO" => "/bar").to_a 63 | assert_equal [path, asset_name, headers], ["/bar", "bar", {}] 64 | end 65 | 66 | def test_match_route_doesnt_match 67 | @router.draw do 68 | match "/foo" 69 | match "/bar" 70 | end 71 | 72 | refute @router.match_route("PATH_INFO" => "/not/found") 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /test/static/assets/serviceworker.js: -------------------------------------------------------------------------------- 1 | console.log('SW:', 'Hello from Rack ServiceWorker!'); 2 | -------------------------------------------------------------------------------- /test/support/generator_test_helpers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module GeneratorTestHelpers 4 | def self.included(base) 5 | base.extend ClassMethods 6 | end 7 | 8 | def evaluate_erb_asset_template(template) 9 | engine = ::ERB.new(template) 10 | asset_binding = asset_context_class.new.context_binding 11 | engine.result(asset_binding) 12 | end 13 | 14 | def asset_context_class 15 | Class.new do 16 | def image_path(name) 17 | "/assets/#{name}" 18 | end 19 | 20 | def context_binding 21 | binding 22 | end 23 | end 24 | end 25 | 26 | module ClassMethods 27 | def test_path 28 | File.join(File.dirname(__FILE__), "..") 29 | end 30 | 31 | def create_generator_sample_app 32 | FileUtils.cd(test_path) do 33 | system "rails new tmp --skip-active-record --skip-test-unit --skip-spring --skip-bundle --quiet" 34 | end 35 | end 36 | 37 | def remove_generator_sample_app 38 | FileUtils.rm_rf(destination_root) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "test" 4 | 5 | require "simplecov" 6 | SimpleCov.minimum_coverage 75 7 | SimpleCov.maximum_coverage_drop 25 8 | 9 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 10 | 11 | require File.expand_path("../test/sample/config/environment.rb", __dir__) 12 | 13 | require "rack/test" 14 | require "rails/test_help" 15 | require "minitest/autorun" 16 | require "minitest/pride" 17 | 18 | require "serviceworker-rails" 19 | 20 | # Load support files 21 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].sort.each { |f| require f } 22 | 23 | # Filter out Minitest backtrace while allowing backtrace from other libraries 24 | # to be shown. 25 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new 26 | --------------------------------------------------------------------------------